diff --git a/CHANGELOG.md b/CHANGELOG.md
index 088dd41..3bd6943 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,259 @@
+# v1.6.8
+## 04/23/2019
+
+1. [](#new)
+ * Added `FlexCollection::filterBy()` method
+1. [](#bugfix)
+ * Revert `Use Null Coalesce Operator` [#2466](https://github.com/getgrav/grav/pull/2466)
+ * Fixed `FormTrait::render()` not providing config variable
+ * Updated `bin/grav clean` to clear `cache/compiled` and `user/config/security.yaml`
+
+# v1.6.7
+## 04/22/2019
+
+1. [](#new)
+ * Added a new `bin/grav yamllinter` CLI command to find YAML Linting issues [#2468](https://github.com/getgrav/grav/issues/2468#issuecomment-485151681)
+1. [](#improved)
+ * Improve `FormTrait` backwards compatibility with existing forms
+ * Added a new `Utils::getSubnet()` function for IPv4/IPv6 parsing [#2465](https://github.com/getgrav/grav/pull/2465)
+1. [](#bugfix)
+ * Remove disabled fields from the form schema
+ * Fix issue when excluding `inlineJs` and `inlineCss` from Assets pipeline [#2468](https://github.com/getgrav/grav/issues/2468)
+ * Fix for manually set position on external URLs [#2470](https://github.com/getgrav/grav/issues/2470)
+
+# v1.6.6
+## 04/17/2019
+
+1. [](#new)
+ * `FormInterface` now implements `RenderInterface`
+ * Added new `FormInterface::getTask()` method which reads the task from `form.task` in the blueprint
+1. [](#improved)
+ * Updated vendor libraries to latest
+1. [](#bugfix)
+ * Rollback `redirect_default_route` logic as it has issues with multi-lang [#2459](https://github.com/getgrav/grav/issues/2459)
+ * Fix potential issue with `|contains` Twig filter on PHP 7.3
+ * Fixed bug in text field filtering: return empty string if value isn't a string or number [#2460](https://github.com/getgrav/grav/issues/2460)
+ * Force Asset `priority` to be an integer and not throw error if invalid string passed [#2461](https://github.com/getgrav/grav/issues/2461)
+ * Fixed bug in text field filtering: return empty string if value isn't a string or number
+ * Fixed `FlexForm` missing getter methods for defining form variables
+
+# v1.6.5
+## 04/15/2019
+
+1. [](#bugfix)
+ * Backwards compatiblity with old `Uri::__toString()` output
+
+# v1.6.4
+## 04/15/2019
+
+1. [](#bugfix)
+ * Improved `redirect_default_route` logic as well as `Uri::toArray()` to take into account `root_path` and `extension`
+ * Rework logic to pull out excluded files from pipeline more reliably [#2445](https://github.com/getgrav/grav/issues/2445)
+ * Better logic in `Utils::normalizePath` to handle externals properly [#2216](https://github.com/getgrav/grav/issues/2216)
+ * Fixed to force all `Page::taxonomy` to be treated as strings [#2446](https://github.com/getgrav/grav/issues/2446)
+ * Fixed issue with `Grav['user']` not being available [form#332](https://github.com/getgrav/grav-plugin-form/issues/332)
+ * Updated rounding logic for `Utils::parseSize()` [#2394](https://github.com/getgrav/grav/issues/2394)
+ * Fixed Flex simple storage not being properly initialized if used with caching
+
+# v1.6.3
+## 04/12/2019
+
+1. [](#new)
+ * Added `Blueprint::addDynamicHandler()` method to allow custom dynamic handlers, for example `custom-options@: getCustomOptions`
+1. [](#bugfix)
+ * Missed a `CacheCommand` reference in `bin/grav` [#2442](https://github.com/getgrav/grav/issues/2442)
+ * Fixed issue with `Utils::normalizePath` messing with external URLs [#2216](https://github.com/getgrav/grav/issues/2216)
+ * Fix for `vUndefined` versions when upgrading
+
+# v1.6.2
+## 04/11/2019
+
+1. [](#bugfix)
+ * Revert renaming of `ClearCacheCommand` to ensure CLI GPM upgrades go smoothly
+
+# v1.6.1
+## 04/11/2019
+
+1. [](#improved)
+ * Improved CSS for the bottom filter bar of DebugBar
+1. [](#bugfix)
+ * Fixed issue with `@import` not being added to top of pipelined css [#2440](https://github.com/getgrav/grav/issues/2440)
+
+# v1.6.0
+## 04/11/2019
+
+1. [](#new)
+ * Set minimum requirements to [PHP 7.1.3](https://getgrav.org/blog/raising-php-requirements-2018)
+ * New `Scheduler` functionality for periodic jobs
+ * New `Backup` functionality with multiple backup profiles and scheduler integration
+ * Refactored `Assets Manager` to be more powerful and flexible
+ * Updated Doctrine Collections to 1.6
+ * Updated Doctrine Cache to 1.8
+ * Updated Symfony Components to 4.2
+ * Added new Cache purge functionality old cache manually via CLI/Admin as well as scheduler integration
+ * Added new `{% throw 404 'Not Found' %}` twig tag (with custom code/message)
+ * Added `Grav\Framework\File` classes for handling YAML, Markdown, JSON, INI and PHP serialized files
+ * Added `Grav\Framework\Collection\AbstractIndexCollection` class
+ * Added `Grav\Framework\Object\ObjectIndex` class
+ * Added `Grav\Framework\Flex` classes
+ * Added support for hiding form fields in blueprints by using dynamic property like `security@: admin.foobar`, `scope@: object` or `scope-ignore@: object` to any field
+ * New experimental **FlexObjects** powered `Users` for increased performance and capability (**disabled** by default)
+ * Added PSR-7 and PSR-15 classes
+ * Added `Grav\Framework\DI\Container` class
+ * Added `Grav\Framework\RequestHandler\RequestHandler` class
+ * Added `Page::httpResponseCode()` and `Page::httpHeaders()` methods
+ * Added `Grav\Framework\Form\Interfaces\FormInterface`
+ * Added `Grav\Framework\Form\Interfaces\FormFactoryInterface`
+ * Added `Grav\Framework\Form\FormTrait`
+ * Added `Page::forms()` method to get normalized list of all form headers defined in the page
+ * Added `onPageAction`, `onPageTask`, `onPageAction.{$action}` and `onPageTask.{$task}` events
+ * Added `Blueprint::processForm()` method to filter form inputs
+ * Move `processMarkdown()` method from `TwigExtension` to more general `Utils` class
+ * Added support to include extra files into `Media` (such as uploaded files)
+ * Added form preview support for `FlexObject`, including a way to render newly uploaded files before saving them
+ * Added `FlexObject::getChanges()` to determine what fields change during an update
+ * Added `arrayDiffMultidimensional`, `arrayIsAssociative`, `arrayCombine` Util functions
+ * New `$grav['users']` service to allow custom user classes implementing `UserInterface`
+ * Added `LogViewer` helper class and CLI command: `bin/grav logviewer`
+ * Added `select()` and `unselect()` methods to `CollectionInterface` and its base classes
+ * Added `orderBy()` and `limit()` methods to `ObjectCollectionInterface` and its base classes
+ * Added `user-data://` which is a writable stream (`user://data` is not and should be avoided)
+ * Added support for `/action:{$action}` (like task but used without nonce when only receiving data)
+ * Added `onAction.{$action}` event
+ * Added `Grav\Framework\Form\FormFlash` class to contain AJAX uploaded files in more reliable way
+ * Added `Grav\Framework\Form\FormFlashFile` class which implements `UploadedFileInterface` from PSR-7
+ * Added `Grav\Framework\Filesystem\Filesystem` class with methods to manipulate stream URLs
+ * Added new `$grav['filesystem']` service using an instance of the new `Filesystem` object
+ * Added `{% render object layout: 'default' with { variable: true } %}` for Flex objects and collections
+ * Added `$grav->setup()` to simplify CLI and custom access points
+ * Added `CsvFormatter` and `CsvFile` classes
+ * Added new system config option to `pages.hide_empty_folders` if a folder has no valid `.md` file available. Default behavior is `false` for compatibility.
+ * Added new system config option for `languages.pages_fallback_only` forcing only 'fallback' to find page content through supported languages, default behavior is to display any language found if active language is missing
+ * Added `Utils::arrayFlattenDotNotation()` and `Utils::arrayUnflattenDotNotation()` helper methods
+1. [](#improved)
+ * Add the page to onMarkdownInitialized event [#2412](https://github.com/getgrav/grav/issues/2412)
+ * Doctrine filecache is now namespaced with prefix to support purging
+ * Register all page types into `blueprint://pages` stream
+ * Removed `apc` and `xcache` support, made `apc` alias of `apcu`
+ * Support admin and regular translations via the `|t` twig filter and `t()` twig function
+ * Improved Grav Core installer/updater to run installer script
+ * Updated vendor libraries including Symfony `4.2.3`
+ * Renamed old `User` class to `Grav\Common\User\DataUser\User` with multiple improvements and small fixes
+ * `User` class now acts as a compatibility layer to older versions of Grav
+ * Deprecated `new User()`, `User::load()`, `User::find()` and `User::delete()` in favor of `$grav['users']` service
+ * `Media` constructor has now support to not to initialize the media objects
+ * Cleanly handle session corruption due to changing Flex object types
+ * Added `FlexObjectInterface::getDefaultValue()` and `FormInterface::getDefaultValue()`
+ * Added new `onPageContent()` event for every call to `Page::content()`
+ * Added phpstan: PHP Static Analysis Tool [#2393](https://github.com/getgrav/grav/pull/2393)
+ * Added `composer test-plugins` to test plugin issues with the current version of Grav
+ * Added `Flex::getObjects()` and `Flex::getMixedCollection()` methods for co-mingled collections
+ * Added support to use single Flex key parameter in `Flex::getObject()` method
+ * Added `FlexObjectInterface::search()` and `FlexCollectionInterface::search()` methods
+ * Override `system.media.upload_limit` with PHP's `post_max_size` or `upload_max_filesize`
+ * Class `Grav\Common\Page\Medium\AbstractMedia` now use array traits instead of extending `Grav\Common\Getters`
+ * Implemented `Grav\Framework\Psr7` classes as `Nyholm/psr7` decorators
+ * Added a new `cache-clear` scheduled job to go along with `cache-purge`
+ * Renamed `Grav\Framework\File\Formatter\FormatterInterface` to `Grav\Framework\File\Interfaces\FileFormatterInterface`
+ * Improved `File::save()` to use a temporary file if file isn't locked
+ * Improved `|t` filter to better support admin `|tu` style filter if in admin
+ * Update all classes to rely on `PageInterface` instead of `Page` class
+ * Better error checking in `bin/plugin` for existence and enabled
+ * Removed `media.upload_limit` references
+ * Twig `nicenumber`: do not use 0 + string casting hack
+ * Converted Twig tags to use namespaced Twig classes
+ * Site shows error on page rather than hard-crash when page has invalid frontmatter [#2343](https://github.com/getgrav/grav/issues/2343)
+ * Added `languages.default_lang` option to override the default lang (usually first supported language)
+ * Added `Content-Type: application/json` body support for PSR-7 `ServerRequest`
+ * Remove PHP time limit in `ZipArchive`
+ * DebugBar: Resolve twig templates in deprecated backtraces in order to help locating Twig issues
+ * Added `$grav['cache']->getSimpleCache()` method for getting PSR-16 compatible cache
+ * MediaTrait: Use PSR-16 cache
+ * Improved `Utils::normalizePath()` to support non-protocol URLs
+ * Added ability to reset `Page::metadata` to allow rebuilding from automatically generated values
+ * Added back missing `page.types` field in system content configuration [admin#1612](https://github.com/getgrav/grav-plugin-admin/issues/1612)
+ * Console commands: add method for invalidating cache
+ * Updated languages
+ * Improved `$page->forms()` call, added `$page->addForms()`
+ * Updated languages from crowdin
+ * Fixed `ImageMedium` constructor warning when file does not exist
+ * Improved `Grav\Common\User` class; added `$user->update()` method
+ * Added trim support for text input fields `validate: trim: true`
+ * Improved `Grav\Framework\File\Formatter` classes to have abstract parent class and some useful methods
+ * Support negotiated content types set via the Request `Accept:` header
+ * Support negotiated language types set via the Request `Accept-Language:` header
+ * Cleaned up and sorted the Service `idMap`
+ * Updated `Grav` container object to implement PSR-11 `ContainerInterface`
+ * Updated Grav `Processor` classes to implement PSR-15 `MiddlewareInterface`
+ * Make `Data` class to extend `JsonSerializable`
+ * Modified debugger icon to use retina space-dude version
+ * Added missing `Video::preload()` method
+ * Set session name based on `security.salt` rather than `GRAV_ROOT` [#2242](https://github.com/getgrav/grav/issues/2242)
+ * Added option to configure list of `xss_invalid_protocols` in `Security` config [#2250](https://github.com/getgrav/grav/issues/2250)
+ * Smarter `security.salt` checking now we use `security.yaml` for other options
+ * Added apcu autoloader optimization
+ * Additional helper methods in `Language`, `Languages`, and `LanguageCodes` classes
+ * Call `onFatalException` event also on internal PHP errors
+ * Built-in PHP Webserver: log requests before handling them
+ * Added support for syslog and syslog facility logging (default: 'file')
+ * Improved usability of `System` configuration blueprint with side-tabs
+ 1. [](#bugfix)
+ * Fixed issue with `Truncator::truncateWords` and `Truncator::truncateLetters` when string not wrapped in tags [#2432](https://github.com/getgrav/grav/issues/2432)
+ * Fixed `Undefined method closure::fields()` when getting avatar for user, thanks @Romarain [#2422](https://github.com/getgrav/grav/issues/2422)
+ * Fixed cached images not being updated when source image is modified
+ * Fixed deleting last list item in the form
+ * Fixed issue with `Utils::url()` method would append extra `base_url` if URL already included it
+ * Fixed `mkdir(...)` race condition
+ * Fixed `Obtaining write lock failed on file...`
+ * Fixed potential undefined property in `onPageNotFound` event handling
+ * Fixed some potential issues/bugs found by phpstan
+ * Fixed regression in GPM packages casted to Array (ref, getgrav/grav-plugin-admin@e3fc4ce)
+ * Fixed session_start(): Setting option 'session.name' failed [#2408](https://github.com/getgrav/grav/issues/2408)
+ * Fixed validation for select field type with selectize
+ * Fixed validation for boolean toggles
+ * Fixed non-namespaced exceptions in scheduler
+ * Fixed trailing slash redirect in multlang environment [#2350](https://github.com/getgrav/grav/issues/2350)
+ * Fixed some issues related to Medium objects losing query string attributes
+ * Broke out Medium timestamp so it's not cleared on `reset()`s
+ * Fixed issue with `redirect_trailing_slash` losing query string [#2269](https://github.com/getgrav/grav/issues/2269)
+ * Fixed failed login if user attempts to log in with upper case non-english letters
+ * Removed extra authenticated/authorized fields when saving existing user from a form
+ * Fixed `Grav\Framework\Route::__toString()` returning relative URL, not relative route
+ * Fixed handling of `append_url_extension` inside of `Page::templateFormat()` [#2264](https://github.com/getgrav/grav/issues/2264)
+ * Fixed a broken language string [#2261](https://github.com/getgrav/grav/issues/2261)
+ * Fixed clearing cache having no effect on Doctrine cache
+ * Fixed `Medium::relativePath()` for streams
+ * Fixed `Object` serialization breaking if overriding `jsonSerialize()` method
+ * Fixed `YamlFormatter::decode()` when calling `init_set()` with integer
+ * Fixed session throwing error in CLI if initialized
+ * Fixed `Uri::hasStandardPort()` to support reverse proxy configurations [#1786](https://github.com/getgrav/grav/issues/1786)
+ * Use `append_url_extension` from page header to set template format if set [#2604](https://github.com/getgrav/grav/pull/2064)
+ * Fixed some bugs in Grav environment selection logic
+ * Use login provider User avatar if set
+ * Fixed `Folder::doDelete($folder, false)` removing symlink when it should not
+ * Fixed asset manager to not add empty assets when they don't exist in the filesystem
+ * Update `script` and `style` Twig tags to use the new `Assets` classes
+ * Fixed asset pipeline to rewrite remote URLs as well as local [#2216](https://github.com/getgrav/grav/issues/2216)
+
+# v1.5.10
+## 03/21/2019
+
+1. [](#new)
+ * Added new `deferred` Twig extension
+
+# v1.5.9
+## 03/20/2019
+
+1. [](#new)
+ * Added new `onPageContent()` event for every call to `Page::content()`
+1. [](#improved)
+ * Fixed phpdoc generation
+ * Updated vendor libraries
+ * Force Toolbox v1.4.2
+1. [](#bugfix)
+ * EXIF fix for streams
+ * Fix for User avatar not working due to uppercase or spaces in email [#2403](https://github.com/getgrav/grav/pull/2403)
+
# v1.5.8
## 02/07/2019
diff --git a/README.md b/README.md
index 6ed3198..bf3a482 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,27 @@
#  Grav
-[](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad) [](https://chat.getgrav.org) [](https://travis-ci.org/getgrav/grav) [](#backers) [](#sponsors)
+[](https://github.com/phpstan/phpstan)
+[](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad)
+[](https://chat.getgrav.org)
+ [](https://travis-ci.org/getgrav/grav) [](#backers) [](#sponsors)
Grav is a **Fast**, **Simple**, and **Flexible**, file-based Web-platform. There is **Zero** installation required. Just extract the ZIP archive, and you are already up and running. It follows similar principles to other flat-file CMS platforms, but has a different design philosophy than most. Grav comes with a powerful **Package Management System** to allow for simple installation and upgrading of plugins and themes, as well as simple updating of Grav itself.
The underlying architecture of Grav is designed to use well-established and _best-in-class_ technologies to ensure that Grav is simple to use and easy to extend. Some of these key technologies include:
-* [Twig Templating](http://twig.sensiolabs.org/): for powerful control of the user interface
-* [Markdown](http://en.wikipedia.org/wiki/Markdown): for easy content creation
-* [YAML](http://yaml.org): for simple configuration
-* [Parsedown](http://parsedown.org/): for fast Markdown and Markdown Extra support
-* [Doctrine Cache](http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/reference/caching.html): layer for performance
-* [Pimple Dependency Injection Container](http://pimple.sensiolabs.org/): for extensibility and maintainability
-* [Symfony Event Dispatcher](http://symfony.com/doc/current/components/event_dispatcher/introduction.html): for plugin event handling
-* [Symfony Console](http://symfony.com/doc/current/components/console/introduction.html): for CLI interface
+* [Twig Templating](https://twig.sensiolabs.org/): for powerful control of the user interface
+* [Markdown](https://en.wikipedia.org/wiki/Markdown): for easy content creation
+* [YAML](https://yaml.org): for simple configuration
+* [Parsedown](https://parsedown.org/): for fast Markdown and Markdown Extra support
+* [Doctrine Cache](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/caching.html): layer for performance
+* [Pimple Dependency Injection Container](https://pimple.sensiolabs.org/): for extensibility and maintainability
+* [Symfony Event Dispatcher](https://symfony.com/doc/current/components/event_dispatcher/introduction.html): for plugin event handling
+* [Symfony Console](https://symfony.com/doc/current/components/console/introduction.html): for CLI interface
* [Gregwar Image Library](https://github.com/Gregwar/Image): for dynamic image manipulation
# Requirements
-- PHP 5.6.4 or higher. Check the [required modules list](https://learn.getgrav.org/basics/requirements#php-requirements)
+- PHP 7.1.3 or higher. Check the [required modules list](https://learn.getgrav.org/basics/requirements#php-requirements)
- Check the [Apache](https://learn.getgrav.org/basics/requirements#apache-requirements) or [IIS](https://learn.getgrav.org/basics/requirements#iis-requirements) requirements
# QuickStart
@@ -103,6 +106,7 @@ If you discover a possible security issue related to Grav or one of its plugins,
* Dive into more [advanced](https://learn.getgrav.org/advanced) functions
* Learn about the [Grav CLI](https://learn.getgrav.org/cli-console/grav-cli)
* Review examples in the [Grav Cookbook](https://learn.getgrav.org/cookbook)
+* More [Awesome Grav Stuff](https://github.com/getgrav/awesome-grav)
# Backers
Support Grav with a monthly donation to help us continue development. [[Become a backer](https://opencollective.com/grav#backer)]
diff --git a/bin/composer.phar b/bin/composer.phar
index 041775a..b8ce13f 100755
Binary files a/bin/composer.phar and b/bin/composer.phar differ
diff --git a/bin/gpm b/bin/gpm
index d4932a2..7ad1e4a 100755
--- a/bin/gpm
+++ b/bin/gpm
@@ -1,26 +1,24 @@
#!/usr/bin/env php
arguments->add([
]
]);
$climate->arguments->parse();
-$environment = $climate->arguments->get('environment');
// Set up environment based on params.
-Setup::$environment = $environment;
+$environment = $climate->arguments->get('environment');
$grav = Grav::instance(array('loader' => $autoload));
-$grav['uri']->init();
+$grav->setup($environment);
+
$grav['config']->init();
-$grav['streams'];
+$grav['uri']->init();
+$grav['users'];
$app = new Application('Grav Package Manager', GRAV_VERSION);
$app->addCommands(array(
diff --git a/bin/grav b/bin/grav
index e36f0cc..7120218 100755
--- a/bin/grav
+++ b/bin/grav
@@ -1,34 +1,52 @@
#!/usr/bin/env php
arguments->add([
+ 'environment' => [
+ 'prefix' => 'e',
+ 'longPrefix' => 'env',
+ 'description' => 'Configuration Environment',
+ 'defaultValue' => 'localhost'
+ ]
+]);
+$climate->arguments->parse();
+
+// Set up environment based on params.
+$environment = $climate->arguments->get('environment');
+
+$grav = Grav::instance(array('loader' => $autoload));
+$grav->setup($environment);
+
if (!ini_get('date.timezone')) {
date_default_timezone_set('UTC');
}
-if (!file_exists(ROOT_DIR . 'index.php')) {
+if (!file_exists(GRAV_ROOT . '/index.php')) {
exit('FATAL: Must be run from ROOT directory of Grav!');
}
@@ -41,6 +59,9 @@ $app->addCommands(array(
new \Grav\Console\Cli\ClearCacheCommand(),
new \Grav\Console\Cli\BackupCommand(),
new \Grav\Console\Cli\NewProjectCommand(),
+ new \Grav\Console\Cli\SchedulerCommand(),
new \Grav\Console\Cli\SecurityCommand(),
+ new \Grav\Console\Cli\LogViewerCommand(),
+ new \Grav\Console\Cli\YamlLinterCommand(),
));
$app->run();
diff --git a/bin/plugin b/bin/plugin
index a8b089a..6f8466d 100755
--- a/bin/plugin
+++ b/bin/plugin
@@ -1,30 +1,28 @@
#!/usr/bin/env php
arguments->add([
]
]);
$climate->arguments->parse();
+
$environment = $climate->arguments->get('environment');
-// Set up environment based on params.
-Setup::$environment = $environment;
-
$grav = Grav::instance(array('loader' => $autoload));
-$grav['uri']->init();
+$grav->setup($environment);
+
$grav['config']->init();
-$grav['streams'];
+$grav['uri']->init();
+$grav['users'];
$grav['plugins']->init();
$grav['themes']->init();
-
$app = new Application('Grav Plugins Commands', GRAV_VERSION);
$pattern = '([A-Z]\w+Command\.php)';
@@ -75,21 +72,36 @@ $argv = array_merge([$bin], $argv);
$input = new ArgvInput($argv);
+/** @var \Grav\Common\Data\Data $plugin */
$plugin = $grav['plugins']->get($name);
$output = new ConsoleOutput();
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red', null, array('bold')));
$output->getFormatter()->setStyle('white', new OutputFormatterStyle('white', null, array('bold')));
+if (is_null($plugin)) {
+ $output->writeln('');
+ $output->writeln("$name plugin not found");
+ die;
+}
+
+if (!$plugin->enabled) {
+ $output->writeln('');
+ $output->writeln("$name not enabled");
+ die;
+}
+
+
if (!$name) {
$output->writeln('');
- $output->writeln("Usage:");
+ $output->writeln('Usage:');
$output->writeln(" {$bin} [slug] [command] [arguments]");
$output->writeln('');
- $output->writeln("Example:");
+ $output->writeln('Example:');
$output->writeln(" {$bin} error log -l 1 --trace");
$list = Folder::all('plugins://', ['compare' => 'Pathname', 'pattern' => '/\/cli\/' . $pattern . '$/usm', 'levels' => 2]);
+ $total = 0;
if (count($list)) {
$available = [];
$output->writeln('');
@@ -98,13 +110,15 @@ if (!$name) {
$split = explode('/', $entry);
$entry = array_shift($split);
$index = str_pad($index++ + 1, 2, '0', STR_PAD_LEFT);
-
- if (in_array($entry, $available)) {
+ $total = str_pad($total++ + 1, 2, '0', STR_PAD_LEFT);
+ if (\in_array($entry, $available, true)) {
+ $total--;
continue;
}
$available[] = $entry;
- $output->writeln(' ' . $index . ". " . str_pad($entry, 15) . " ${bin} ${entry} list");
+ $commands_count = $index - $total + 1;
+ $output->writeln(' ' . $total . '. ' . str_pad($entry, 15) . " {$bin} {$entry} list");
}
}
diff --git a/composer.json b/composer.json
index d56945e..9518146 100644
--- a/composer.json
+++ b/composer.json
@@ -6,47 +6,71 @@
"homepage": "https://getgrav.org",
"license": "MIT",
"require": {
- "php": ">=5.6.4",
- "twig/twig": "~1.24",
- "erusev/parsedown": "1.6.4",
- "erusev/parsedown-extra": "~0.7",
- "symfony/yaml": "~3.4",
- "symfony/console": "~3.4",
- "symfony/event-dispatcher": "~3.4",
- "symfony/var-dumper": "~3.4",
- "symfony/polyfill-iconv": "~1.0",
- "doctrine/cache": "^1.6",
- "doctrine/collections": "^1.4",
- "psr/simple-cache": "^1.0",
- "psr/http-message": "^1.0",
- "guzzlehttp/psr7": "^1.4",
- "filp/whoops": "~2.0",
- "matthiasmullie/minify": "^1.3",
- "monolog/monolog": "~1.0",
- "gregwar/image": "2.*",
- "donatj/phpuseragentparser": "~0.3",
- "pimple/pimple": "~3.2",
- "rockettheme/toolbox": "~1.4",
- "maximebf/debugbar": "~1.10",
+ "php": ">=7.1.3",
+ "ext-json": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"ext-curl": "*",
"ext-zip": "*",
- "ext-json": "*",
- "league/climate": "^3.2",
+ "ext-dom": "*",
+ "symfony/polyfill-iconv": "^1.9",
+ "symfony/polyfill-php72": "^1.9",
+ "symfony/polyfill-php73": "^1.9",
+
+ "psr/simple-cache": "^1.0",
+ "psr/http-message": "^1.0",
+ "psr/http-server-middleware": "^1.0",
+
+ "kodus/psr7-server": "*",
+ "nyholm/psr7": "^1.0",
+
+ "twig/twig": "~1.35",
+ "erusev/parsedown": "1.6.4",
+ "erusev/parsedown-extra": "~0.7",
+ "symfony/yaml": "~4.2",
+ "symfony/console": "~4.2",
+ "symfony/event-dispatcher": "~4.2",
+ "symfony/var-dumper": "~4.2",
+ "symfony/process": "~4.2",
+ "doctrine/cache": "^1.8",
+ "doctrine/collections": "^1.5",
+ "guzzlehttp/psr7": "^1.4",
+ "filp/whoops": "~2.2",
+
+ "matthiasmullie/minify": "^1.3",
+ "monolog/monolog": "~1.0",
+ "gregwar/image": "2.*",
+ "donatj/phpuseragentparser": "~0.10",
+ "pimple/pimple": "~3.2",
+ "rockettheme/toolbox": "~1.4",
+ "maximebf/debugbar": "~1.15",
+ "league/climate": "^3.4",
"antoligy/dom-string-iterators": "^1.0",
- "miljar/php-exif": "^0.6.3",
- "composer/ca-bundle": "^1.0"
+ "miljar/php-exif": "^0.6.4",
+ "composer/ca-bundle": "^1.0",
+ "dragonmantank/cron-expression": "^1.2",
+ "phive/twig-extensions-deferred": "^1.0",
+ "willdurand/negotiation": "^2.3"
},
"require-dev": {
- "codeception/codeception": "^2.1",
- "phpunit/php-code-coverage": "~2.0",
- "fzaninotto/faker": "^1.5",
+ "codeception/codeception": "^2.4",
+ "phpstan/phpstan": "^0.11",
+ "phpstan/phpstan-deprecation-rules": "^0.11.0",
+ "phpunit/php-code-coverage": "~6.0",
+ "fzaninotto/faker": "^1.8",
"victorjonsson/markdowndocs": "dev-master"
},
+ "suggest": {
+ "ext-zend-opcache": "Recommended for better performance",
+ "ext-intl": "Recommended for multi-language sites",
+ "ext-memcache": "Needed to support Memcache servers",
+ "ext-memcached": "Needed to support Memcached servers",
+ "ext-redis": "Needed to support Redis servers"
+ },
"config": {
+ "apcu-autoloader": true,
"platform": {
- "php": "5.6.4"
+ "php": "7.1.3"
}
},
"repositories": [
@@ -66,6 +90,9 @@
},
"scripts": {
"post-create-project-cmd": "bin/grav install",
+ "phpstan": "vendor/bin/phpstan analyse -l 2 -c ./tests/phpstan/phpstan.neon system/src --memory-limit=256M",
+ "phpstan-framework": "vendor/bin/phpstan analyse -l 5 -c ./tests/phpstan/phpstan.neon system/src/Grav/Framework --memory-limit=256M",
+ "test-plugins": "vendor/bin/phpstan analyse -l 0 -c ./tests/phpstan/plugins.neon user/plugins --memory-limit=256M",
"test": "vendor/bin/codecept run unit",
"test-windows": "vendor\\bin\\codecept run unit"
},
diff --git a/composer.lock b/composer.lock
index 75a232d..2e2f476 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "544658e69ae737e742e014c6c674cc70",
+ "content-hash": "a65d66d9ce6844a3743c992e74c1c55d",
"packages": [
{
"name": "antoligy/dom-string-iterators",
@@ -52,16 +52,16 @@
},
{
"name": "composer/ca-bundle",
- "version": "1.1.3",
+ "version": "1.1.4",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
- "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660"
+ "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/ca-bundle/zipball/8afa52cd417f4ec417b4bfe86b68106538a87660",
- "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660",
+ "url": "https://api.github.com/repos/composer/ca-bundle/zipball/558f321c52faeb4828c03e7dc0cfe39a09e09a2d",
+ "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d",
"shasum": ""
},
"require": {
@@ -104,37 +104,42 @@
"ssl",
"tls"
],
- "time": "2018-10-18T06:09:13+00:00"
+ "time": "2019-01-28T09:30:10+00:00"
},
{
"name": "doctrine/cache",
- "version": "v1.6.2",
+ "version": "v1.8.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/cache.git",
- "reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b"
+ "reference": "d768d58baee9a4862ca783840eca1b9add7a7f57"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/cache/zipball/eb152c5100571c7a45470ff2a35095ab3f3b900b",
- "reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b",
+ "url": "https://api.github.com/repos/doctrine/cache/zipball/d768d58baee9a4862ca783840eca1b9add7a7f57",
+ "reference": "d768d58baee9a4862ca783840eca1b9add7a7f57",
"shasum": ""
},
"require": {
- "php": "~5.5|~7.0"
+ "php": "~7.1"
},
"conflict": {
"doctrine/common": ">2.2,<2.4"
},
"require-dev": {
- "phpunit/phpunit": "~4.8|~5.0",
- "predis/predis": "~1.0",
- "satooshi/php-coveralls": "~0.6"
+ "alcaeus/mongo-php-adapter": "^1.1",
+ "doctrine/coding-standard": "^4.0",
+ "mongodb/mongodb": "^1.1",
+ "phpunit/phpunit": "^7.0",
+ "predis/predis": "~1.0"
+ },
+ "suggest": {
+ "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.6.x-dev"
+ "dev-master": "1.8.x-dev"
}
},
"autoload": {
@@ -169,43 +174,45 @@
}
],
"description": "Caching library offering an object-oriented API for many cache backends",
- "homepage": "http://www.doctrine-project.org",
+ "homepage": "https://www.doctrine-project.org",
"keywords": [
"cache",
"caching"
],
- "time": "2017-07-22T12:49:21+00:00"
+ "time": "2018-08-21T18:01:43+00:00"
},
{
"name": "doctrine/collections",
- "version": "v1.4.0",
+ "version": "v1.6.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/collections.git",
- "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba"
+ "reference": "d2ae4ef05e25197343b6a39bae1d3c427a2f6956"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba",
- "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba",
+ "url": "https://api.github.com/repos/doctrine/collections/zipball/d2ae4ef05e25197343b6a39bae1d3c427a2f6956",
+ "reference": "d2ae4ef05e25197343b6a39bae1d3c427a2f6956",
"shasum": ""
},
"require": {
- "php": "^5.6 || ^7.0"
+ "php": "^7.1.3"
},
"require-dev": {
- "doctrine/coding-standard": "~0.1@dev",
- "phpunit/phpunit": "^5.7"
+ "doctrine/coding-standard": "^6.0",
+ "phpstan/phpstan-shim": "^0.9.2",
+ "phpunit/phpunit": "^7.0",
+ "vimeo/psalm": "^3.2.2"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.3.x-dev"
+ "dev-master": "1.6.x-dev"
}
},
"autoload": {
- "psr-0": {
- "Doctrine\\Common\\Collections\\": "lib/"
+ "psr-4": {
+ "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -234,27 +241,28 @@
"email": "schmittjoh@gmail.com"
}
],
- "description": "Collections Abstraction library",
- "homepage": "http://www.doctrine-project.org",
+ "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.",
+ "homepage": "https://www.doctrine-project.org/projects/collections.html",
"keywords": [
"array",
"collections",
- "iterator"
+ "iterators",
+ "php"
],
- "time": "2017-01-03T10:49:41+00:00"
+ "time": "2019-03-25T19:03:48+00:00"
},
{
"name": "donatj/phpuseragentparser",
- "version": "v0.11.1",
+ "version": "v0.13.0",
"source": {
"type": "git",
"url": "https://github.com/donatj/PhpUserAgent.git",
- "reference": "0eab93a89a1a73a087e795abc96d5777c3d48b64"
+ "reference": "5f2da266d2a386f9b231d4344ae37baf7a467c2d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/donatj/PhpUserAgent/zipball/0eab93a89a1a73a087e795abc96d5777c3d48b64",
- "reference": "0eab93a89a1a73a087e795abc96d5777c3d48b64",
+ "url": "https://api.github.com/repos/donatj/PhpUserAgent/zipball/5f2da266d2a386f9b231d4344ae37baf7a467c2d",
+ "reference": "5f2da266d2a386f9b231d4344ae37baf7a467c2d",
"shasum": ""
},
"require": {
@@ -268,7 +276,7 @@
"type": "library",
"autoload": {
"files": [
- "Source/UserAgentParser.php"
+ "src/UserAgentParser.php"
]
},
"notification-url": "https://packagist.org/downloads/",
@@ -292,7 +300,51 @@
"user agent",
"useragent"
],
- "time": "2018-10-25T20:22:53+00:00"
+ "time": "2019-03-08T20:52:23+00:00"
+ },
+ {
+ "name": "dragonmantank/cron-expression",
+ "version": "v1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dragonmantank/cron-expression.git",
+ "reference": "9504fa9ea681b586028adaaa0877db4aecf32bad"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/9504fa9ea681b586028adaaa0877db4aecf32bad",
+ "reference": "9504fa9ea681b586028adaaa0877db4aecf32bad",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0|~5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Cron\\": "src/Cron/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
+ "keywords": [
+ "cron",
+ "schedule"
+ ],
+ "time": "2017-01-23T04:29:33+00:00"
},
{
"name": "erusev/parsedown",
@@ -489,17 +541,17 @@
},
{
"name": "gregwar/image",
- "version": "v2.0.23",
+ "version": "v2.0.25",
"target-dir": "Gregwar/Image",
"source": {
"type": "git",
"url": "https://github.com/Gregwar/Image.git",
- "reference": "25fbb91a95d41d44587ee7ca8923745b66221a7d"
+ "reference": "03534d5760cbea5c96e6292041ff81a3bb205c36"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Gregwar/Image/zipball/25fbb91a95d41d44587ee7ca8923745b66221a7d",
- "reference": "25fbb91a95d41d44587ee7ca8923745b66221a7d",
+ "url": "https://api.github.com/repos/Gregwar/Image/zipball/03534d5760cbea5c96e6292041ff81a3bb205c36",
+ "reference": "03534d5760cbea5c96e6292041ff81a3bb205c36",
"shasum": ""
},
"require": {
@@ -537,7 +589,7 @@
"gd",
"image"
],
- "time": "2018-10-25T17:12:13+00:00"
+ "time": "2019-03-01T15:55:29+00:00"
},
{
"name": "guzzlehttp/psr7",
@@ -607,21 +659,76 @@
"time": "2018-12-04T20:46:45+00:00"
},
{
- "name": "league/climate",
- "version": "3.4.1",
+ "name": "kodus/psr7-server",
+ "version": "1.0.0",
"source": {
"type": "git",
- "url": "https://github.com/thephpleague/climate.git",
- "reference": "d657a19837c1f79a891381fb128b755aa3386381"
+ "url": "https://github.com/kodus/psr7-server.git",
+ "reference": "7c0e9c72e6cb282bf58e9e73386e4ded26c6ae13"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/climate/zipball/d657a19837c1f79a891381fb128b755aa3386381",
- "reference": "d657a19837c1f79a891381fb128b755aa3386381",
+ "url": "https://api.github.com/repos/kodus/psr7-server/zipball/7c0e9c72e6cb282bf58e9e73386e4ded26c6ae13",
+ "reference": "7c0e9c72e6cb282bf58e9e73386e4ded26c6ae13",
"shasum": ""
},
"require": {
- "php": "^5.6|^7.0",
+ "php": "^7.1",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.0"
+ },
+ "replace": {
+ "nyholm/psr7-server": "^0.3"
+ },
+ "require-dev": {
+ "nyholm/nsa": "^1.1",
+ "nyholm/psr7": "^1.0",
+ "phpunit/phpunit": "^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Nyholm\\Psr7Server\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com"
+ },
+ {
+ "name": "Martijn van der Ven",
+ "email": "martijn@vanderven.se"
+ }
+ ],
+ "description": "Helper classes to handle PSR-7 server requests",
+ "homepage": "http://tnyholm.se",
+ "keywords": [
+ "psr-17",
+ "psr-7"
+ ],
+ "time": "2018-12-05T09:09:19+00:00"
+ },
+ {
+ "name": "league/climate",
+ "version": "3.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/climate.git",
+ "reference": "0d2fdbf8829f60f6ba6433df68d6f3fe1271b8e6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/climate/zipball/0d2fdbf8829f60f6ba6433df68d6f3fe1271b8e6",
+ "reference": "0d2fdbf8829f60f6ba6433df68d6f3fe1271b8e6",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1",
"psr/log": "^1.0",
"seld/cli-prompt": "^1.0"
},
@@ -665,7 +772,7 @@
"php",
"terminal"
],
- "time": "2018-04-29T16:43:54+00:00"
+ "time": "2019-02-10T18:25:19+00:00"
},
{
"name": "matthiasmullie/minify",
@@ -970,6 +1077,161 @@
],
"time": "2018-11-05T09:00:11+00:00"
},
+ {
+ "name": "nyholm/psr7",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Nyholm/psr7.git",
+ "reference": "701fe7ea8c12c07b985b156d589134d328160cf7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Nyholm/psr7/zipball/701fe7ea8c12c07b985b156d589134d328160cf7",
+ "reference": "701fe7ea8c12c07b985b156d589134d328160cf7",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1",
+ "php-http/message-factory": "^1.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.0"
+ },
+ "provide": {
+ "psr/http-factory-implementation": "1.0",
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "http-interop/http-factory-tests": "dev-master",
+ "php-http/psr7-integration-tests": "dev-master",
+ "phpunit/phpunit": "^7.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Nyholm\\Psr7\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com"
+ },
+ {
+ "name": "Martijn van der Ven",
+ "email": "martijn@vanderven.se"
+ }
+ ],
+ "description": "A fast PHP7 implementation of PSR-7",
+ "homepage": "http://tnyholm.se",
+ "keywords": [
+ "psr-17",
+ "psr-7"
+ ],
+ "time": "2019-02-16T17:20:43+00:00"
+ },
+ {
+ "name": "phive/twig-extensions-deferred",
+ "version": "v1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/rybakit/twig-deferred-extension.git",
+ "reference": "5a2426d622afa74034e754ca5ea1d1ff7887627f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/rybakit/twig-deferred-extension/zipball/5a2426d622afa74034e754ca5ea1d1ff7887627f",
+ "reference": "5a2426d622afa74034e754ca5ea1d1ff7887627f",
+ "shasum": ""
+ },
+ "require": {
+ "twig/twig": "~1.18"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Phive\\Twig\\Extensions\\Deferred\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Eugene Leonovich",
+ "email": "gen.work@gmail.com"
+ }
+ ],
+ "description": "An extension for Twig that allows to defer block rendering",
+ "homepage": "https://github.com/rybakit/twig-extensions-deferred",
+ "keywords": [
+ "defer",
+ "extension",
+ "lazy",
+ "twig"
+ ],
+ "time": "2017-03-17T21:39:21+00:00"
+ },
+ {
+ "name": "php-http/message-factory",
+ "version": "v1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-http/message-factory.git",
+ "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1",
+ "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4",
+ "psr/http-message": "^1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com"
+ }
+ ],
+ "description": "Factory interfaces for PSR-7 HTTP Message",
+ "homepage": "http://php-http.org",
+ "keywords": [
+ "factory",
+ "http",
+ "message",
+ "stream",
+ "uri"
+ ],
+ "time": "2015-12-19T14:08:53+00:00"
+ },
{
"name": "pimple/pimple",
"version": "v3.2.3",
@@ -1069,6 +1331,58 @@
],
"time": "2017-02-14T16:28:37+00:00"
},
+ {
+ "name": "psr/http-factory",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-factory.git",
+ "reference": "378bfe27931ecc54ff824a20d6f6bfc303bbd04c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-factory/zipball/378bfe27931ecc54ff824a20d6f6bfc303bbd04c",
+ "reference": "378bfe27931ecc54ff824a20d6f6bfc303bbd04c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0.0",
+ "psr/http-message": "^1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interfaces for PSR-7 HTTP message factories",
+ "keywords": [
+ "factory",
+ "http",
+ "message",
+ "psr",
+ "psr-17",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "time": "2018-07-30T21:54:04+00:00"
+ },
{
"name": "psr/http-message",
"version": "1.0.1",
@@ -1119,6 +1433,112 @@
],
"time": "2016-08-06T14:39:51+00:00"
},
+ {
+ "name": "psr/http-server-handler",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-server-handler.git",
+ "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7",
+ "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0",
+ "psr/http-message": "^1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Server\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP server-side request handler",
+ "keywords": [
+ "handler",
+ "http",
+ "http-interop",
+ "psr",
+ "psr-15",
+ "psr-7",
+ "request",
+ "response",
+ "server"
+ ],
+ "time": "2018-10-30T16:46:14+00:00"
+ },
+ {
+ "name": "psr/http-server-middleware",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-server-middleware.git",
+ "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5",
+ "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0",
+ "psr/http-message": "^1.0",
+ "psr/http-server-handler": "^1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Server\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP server-side middleware",
+ "keywords": [
+ "http",
+ "http-interop",
+ "middleware",
+ "psr",
+ "psr-15",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "time": "2018-10-30T17:12:04+00:00"
+ },
{
"name": "psr/log",
"version": "1.1.0",
@@ -1256,26 +1676,27 @@
},
{
"name": "rockettheme/toolbox",
- "version": "1.4.2",
+ "version": "1.4.6",
"source": {
"type": "git",
"url": "https://github.com/rockettheme/toolbox.git",
- "reference": "93f5c3d5e173cee7419df20eed52711471abbc3e"
+ "reference": "e7010c2a956aff241dfa100ab8aef5b6cf83892a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/rockettheme/toolbox/zipball/93f5c3d5e173cee7419df20eed52711471abbc3e",
- "reference": "93f5c3d5e173cee7419df20eed52711471abbc3e",
+ "url": "https://api.github.com/repos/rockettheme/toolbox/zipball/e7010c2a956aff241dfa100ab8aef5b6cf83892a",
+ "reference": "e7010c2a956aff241dfa100ab8aef5b6cf83892a",
"shasum": ""
},
"require": {
+ "ext-json": "*",
"php": ">=5.4.0",
"pimple/pimple": "~3.0",
"symfony/event-dispatcher": ">2.5",
"symfony/yaml": ">2.5"
},
"require-dev": {
- "phpunit/phpunit": "~5.1.5"
+ "phpunit/phpunit": "~6"
},
"type": "library",
"autoload": {
@@ -1301,7 +1722,7 @@
"php",
"rockettheme"
],
- "time": "2018-08-08T18:03:32+00:00"
+ "time": "2019-03-20T19:59:17+00:00"
},
{
"name": "seld/cli-prompt",
@@ -1353,37 +1774,40 @@
},
{
"name": "symfony/console",
- "version": "v3.4.21",
+ "version": "v4.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "a700b874d3692bc8342199adfb6d3b99f62cc61a"
+ "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/a700b874d3692bc8342199adfb6d3b99f62cc61a",
- "reference": "a700b874d3692bc8342199adfb6d3b99f62cc61a",
+ "url": "https://api.github.com/repos/symfony/console/zipball/e2840bb38bddad7a0feaf85931e38fdcffdb2f81",
+ "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8",
- "symfony/debug": "~2.8|~3.0|~4.0",
+ "php": "^7.1.3",
+ "symfony/contracts": "^1.0",
"symfony/polyfill-mbstring": "~1.0"
},
"conflict": {
"symfony/dependency-injection": "<3.4",
"symfony/process": "<3.3"
},
+ "provide": {
+ "psr/log-implementation": "1.0"
+ },
"require-dev": {
"psr/log": "~1.0",
- "symfony/config": "~3.3|~4.0",
+ "symfony/config": "~3.4|~4.0",
"symfony/dependency-injection": "~3.4|~4.0",
- "symfony/event-dispatcher": "~2.8|~3.0|~4.0",
+ "symfony/event-dispatcher": "~3.4|~4.0",
"symfony/lock": "~3.4|~4.0",
- "symfony/process": "~3.3|~4.0"
+ "symfony/process": "~3.4|~4.0"
},
"suggest": {
- "psr/log-implementation": "For using the console logger",
+ "psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/lock": "",
"symfony/process": ""
@@ -1391,7 +1815,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.4-dev"
+ "dev-master": "4.2-dev"
}
},
"autoload": {
@@ -1418,44 +1842,48 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2019-01-04T04:42:43+00:00"
+ "time": "2019-04-08T14:23:48+00:00"
},
{
- "name": "symfony/debug",
- "version": "v3.4.21",
+ "name": "symfony/contracts",
+ "version": "v1.0.2",
"source": {
"type": "git",
- "url": "https://github.com/symfony/debug.git",
- "reference": "26d7f23b9bd0b93bee5583e4d6ca5cb1ab31b186"
+ "url": "https://github.com/symfony/contracts.git",
+ "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/debug/zipball/26d7f23b9bd0b93bee5583e4d6ca5cb1ab31b186",
- "reference": "26d7f23b9bd0b93bee5583e4d6ca5cb1ab31b186",
+ "url": "https://api.github.com/repos/symfony/contracts/zipball/1aa7ab2429c3d594dd70689604b5cf7421254cdf",
+ "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8",
- "psr/log": "~1.0"
- },
- "conflict": {
- "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
+ "php": "^7.1.3"
},
"require-dev": {
- "symfony/http-kernel": "~2.8|~3.0|~4.0"
+ "psr/cache": "^1.0",
+ "psr/container": "^1.0"
+ },
+ "suggest": {
+ "psr/cache": "When using the Cache contracts",
+ "psr/container": "When using the Service contracts",
+ "symfony/cache-contracts-implementation": "",
+ "symfony/service-contracts-implementation": "",
+ "symfony/translation-contracts-implementation": ""
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.4-dev"
+ "dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
- "Symfony\\Component\\Debug\\": ""
+ "Symfony\\Contracts\\": ""
},
"exclude-from-classmap": [
- "/Tests/"
+ "**/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
@@ -1464,44 +1892,53 @@
],
"authors": [
{
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
- "description": "Symfony Debug Component",
+ "description": "A set of abstractions extracted out of the Symfony components",
"homepage": "https://symfony.com",
- "time": "2019-01-01T13:45:19+00:00"
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "time": "2018-12-05T08:06:11+00:00"
},
{
"name": "symfony/event-dispatcher",
- "version": "v3.4.21",
+ "version": "v4.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "d1cdd46c53c264a2bd42505bd0e8ce21423bd0e2"
+ "reference": "fbce53cd74ac509cbe74b6f227622650ab759b02"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d1cdd46c53c264a2bd42505bd0e8ce21423bd0e2",
- "reference": "d1cdd46c53c264a2bd42505bd0e8ce21423bd0e2",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/fbce53cd74ac509cbe74b6f227622650ab759b02",
+ "reference": "fbce53cd74ac509cbe74b6f227622650ab759b02",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8"
+ "php": "^7.1.3",
+ "symfony/contracts": "^1.0"
},
"conflict": {
- "symfony/dependency-injection": "<3.3"
+ "symfony/dependency-injection": "<3.4"
},
"require-dev": {
"psr/log": "~1.0",
- "symfony/config": "~2.8|~3.0|~4.0",
- "symfony/dependency-injection": "~3.3|~4.0",
- "symfony/expression-language": "~2.8|~3.0|~4.0",
- "symfony/stopwatch": "~2.8|~3.0|~4.0"
+ "symfony/config": "~3.4|~4.0",
+ "symfony/dependency-injection": "~3.4|~4.0",
+ "symfony/expression-language": "~3.4|~4.0",
+ "symfony/stopwatch": "~3.4|~4.0"
},
"suggest": {
"symfony/dependency-injection": "",
@@ -1510,7 +1947,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.4-dev"
+ "dev-master": "4.2-dev"
}
},
"autoload": {
@@ -1537,20 +1974,20 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
- "time": "2019-01-01T18:08:36+00:00"
+ "time": "2019-04-06T13:51:08+00:00"
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.10.0",
+ "version": "v1.11.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
+ "reference": "82ebae02209c21113908c229e9883c419720738a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
- "reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a",
+ "reference": "82ebae02209c21113908c229e9883c419720738a",
"shasum": ""
},
"require": {
@@ -1562,7 +1999,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.9-dev"
+ "dev-master": "1.11-dev"
}
},
"autoload": {
@@ -1584,7 +2021,7 @@
},
{
"name": "Gert de Pagter",
- "email": "BackEndTea@gmail.com"
+ "email": "backendtea@gmail.com"
}
],
"description": "Symfony polyfill for ctype functions",
@@ -1595,20 +2032,20 @@
"polyfill",
"portable"
],
- "time": "2018-08-06T14:22:27+00:00"
+ "time": "2019-02-06T07:57:58+00:00"
},
{
"name": "symfony/polyfill-iconv",
- "version": "v1.10.0",
+ "version": "v1.11.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-iconv.git",
- "reference": "97001cfc283484c9691769f51cdf25259037eba2"
+ "reference": "f037ea22acfaee983e271dd9c3b8bb4150bd8ad7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/97001cfc283484c9691769f51cdf25259037eba2",
- "reference": "97001cfc283484c9691769f51cdf25259037eba2",
+ "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/f037ea22acfaee983e271dd9c3b8bb4150bd8ad7",
+ "reference": "f037ea22acfaee983e271dd9c3b8bb4150bd8ad7",
"shasum": ""
},
"require": {
@@ -1620,7 +2057,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.9-dev"
+ "dev-master": "1.11-dev"
}
},
"autoload": {
@@ -1654,20 +2091,20 @@
"portable",
"shim"
],
- "time": "2018-09-21T06:26:08+00:00"
+ "time": "2019-02-06T07:57:58+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.10.0",
+ "version": "v1.11.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "c79c051f5b3a46be09205c73b80b346e4153e494"
+ "reference": "fe5e94c604826c35a32fa832f35bd036b6799609"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494",
- "reference": "c79c051f5b3a46be09205c73b80b346e4153e494",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609",
+ "reference": "fe5e94c604826c35a32fa832f35bd036b6799609",
"shasum": ""
},
"require": {
@@ -1679,7 +2116,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.9-dev"
+ "dev-master": "1.11-dev"
}
},
"autoload": {
@@ -1713,42 +2150,211 @@
"portable",
"shim"
],
- "time": "2018-09-21T13:07:52+00:00"
+ "time": "2019-02-06T07:57:58+00:00"
},
{
- "name": "symfony/var-dumper",
- "version": "v3.4.21",
+ "name": "symfony/polyfill-php72",
+ "version": "v1.11.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/var-dumper.git",
- "reference": "a5f39641bb62e8b74e343467b145331273f615a2"
+ "url": "https://github.com/symfony/polyfill-php72.git",
+ "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/a5f39641bb62e8b74e343467b145331273f615a2",
- "reference": "a5f39641bb62e8b74e343467b145331273f615a2",
+ "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/ab50dcf166d5f577978419edd37aa2bb8eabce0c",
+ "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8",
- "symfony/polyfill-mbstring": "~1.0"
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.11-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Php72\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2019-02-06T07:57:58+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php73",
+ "version": "v1.11.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php73.git",
+ "reference": "d1fb4abcc0c47be136208ad9d68bf59f1ee17abd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/d1fb4abcc0c47be136208ad9d68bf59f1ee17abd",
+ "reference": "d1fb4abcc0c47be136208ad9d68bf59f1ee17abd",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.11-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Php73\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ],
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2019-02-06T07:57:58+00:00"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v4.2.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "8cf39fb4ccff793340c258ee7760fd40bfe745fe"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/8cf39fb4ccff793340c258ee7760fd40bfe745fe",
+ "reference": "8cf39fb4ccff793340c258ee7760fd40bfe745fe",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Process Component",
+ "homepage": "https://symfony.com",
+ "time": "2019-04-10T16:20:36+00:00"
+ },
+ {
+ "name": "symfony/var-dumper",
+ "version": "v4.2.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/var-dumper.git",
+ "reference": "e760a38e12b15032325e64be63f7ffc1817af617"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e760a38e12b15032325e64be63f7ffc1817af617",
+ "reference": "e760a38e12b15032325e64be63f7ffc1817af617",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/polyfill-php72": "~1.5"
},
"conflict": {
- "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0"
+ "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
+ "symfony/console": "<3.4"
},
"require-dev": {
"ext-iconv": "*",
+ "symfony/console": "~3.4|~4.0",
+ "symfony/process": "~3.4|~4.0",
"twig/twig": "~1.34|~2.4"
},
"suggest": {
"ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
"ext-intl": "To show region name in time zone dump",
- "ext-symfony_debug": ""
+ "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
},
+ "bin": [
+ "Resources/bin/var-dump-server"
+ ],
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.4-dev"
+ "dev-master": "4.2-dev"
}
},
"autoload": {
@@ -1782,24 +2388,24 @@
"debug",
"dump"
],
- "time": "2019-01-01T13:45:19+00:00"
+ "time": "2019-04-17T14:57:01+00:00"
},
{
"name": "symfony/yaml",
- "version": "v3.4.21",
+ "version": "v4.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "554a59a1ccbaac238a89b19c8e551a556fd0e2ea"
+ "reference": "6712daf03ee25b53abb14e7e8e0ede1a770efdb1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/554a59a1ccbaac238a89b19c8e551a556fd0e2ea",
- "reference": "554a59a1ccbaac238a89b19c8e551a556fd0e2ea",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/6712daf03ee25b53abb14e7e8e0ede1a770efdb1",
+ "reference": "6712daf03ee25b53abb14e7e8e0ede1a770efdb1",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8",
+ "php": "^7.1.3",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
@@ -1814,7 +2420,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.4-dev"
+ "dev-master": "4.2-dev"
}
},
"autoload": {
@@ -1841,20 +2447,20 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2019-01-01T13:45:19+00:00"
+ "time": "2019-03-30T15:58:42+00:00"
},
{
"name": "twig/twig",
- "version": "v1.37.1",
+ "version": "v1.39.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
- "reference": "66be9366c76cbf23e82e7171d47cbfa54a057a62"
+ "reference": "23e7b6f0cfa1d7ba3de69f30d8e05cf957412fec"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/twigphp/Twig/zipball/66be9366c76cbf23e82e7171d47cbfa54a057a62",
- "reference": "66be9366c76cbf23e82e7171d47cbfa54a057a62",
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/23e7b6f0cfa1d7ba3de69f30d8e05cf957412fec",
+ "reference": "23e7b6f0cfa1d7ba3de69f30d8e05cf957412fec",
"shasum": ""
},
"require": {
@@ -1869,7 +2475,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.37-dev"
+ "dev-master": "1.39-dev"
}
},
"autoload": {
@@ -1907,7 +2513,59 @@
"keywords": [
"templating"
],
- "time": "2019-01-14T14:59:29+00:00"
+ "time": "2019-04-16T17:12:57+00:00"
+ },
+ {
+ "name": "willdurand/negotiation",
+ "version": "v2.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/willdurand/Negotiation.git",
+ "reference": "03436ededa67c6e83b9b12defac15384cb399dc9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/willdurand/Negotiation/zipball/03436ededa67c6e83b9b12defac15384cb399dc9",
+ "reference": "03436ededa67c6e83b9b12defac15384cb399dc9",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Negotiation\\": "src/Negotiation"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "William Durand",
+ "email": "will+git@drnd.me"
+ }
+ ],
+ "description": "Content Negotiation tools for PHP provided as a standalone library.",
+ "homepage": "http://williamdurand.fr/Negotiation/",
+ "keywords": [
+ "accept",
+ "content",
+ "format",
+ "header",
+ "negotiation"
+ ],
+ "time": "2017-05-14T17:21:12+00:00"
}
],
"packages-dev": [
@@ -1972,16 +2630,16 @@
},
{
"name": "codeception/codeception",
- "version": "2.5.2",
+ "version": "2.5.5",
"source": {
"type": "git",
"url": "https://github.com/Codeception/Codeception.git",
- "reference": "4f89de32929fef53ca6d83b159fe329b6d484c1c"
+ "reference": "547a64cb31edcf1902b296c511f5ca74101bcb4c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Codeception/Codeception/zipball/4f89de32929fef53ca6d83b159fe329b6d484c1c",
- "reference": "4f89de32929fef53ca6d83b159fe329b6d484c1c",
+ "url": "https://api.github.com/repos/Codeception/Codeception/zipball/547a64cb31edcf1902b296c511f5ca74101bcb4c",
+ "reference": "547a64cb31edcf1902b296c511f5ca74101bcb4c",
"shasum": ""
},
"require": {
@@ -2013,7 +2671,7 @@
"predis/predis": "^1.0",
"squizlabs/php_codesniffer": "~2.0",
"symfony/process": ">=2.7 <5.0",
- "vlucas/phpdotenv": "^2.4.0"
+ "vlucas/phpdotenv": "^3.0"
},
"suggest": {
"aws/aws-sdk-php": "For using AWS Auth in REST module and Queue module",
@@ -2060,34 +2718,31 @@
"functional testing",
"unit testing"
],
- "time": "2019-01-02T10:28:51+00:00"
+ "time": "2019-03-23T17:57:45+00:00"
},
{
"name": "codeception/phpunit-wrapper",
- "version": "6.0.10",
+ "version": "7.7.1",
"source": {
"type": "git",
"url": "https://github.com/Codeception/phpunit-wrapper.git",
- "reference": "7057e599d97b02b4efb009681a43b327dbce138a"
+ "reference": "ab04a956264291505ea84998f43cf91639b4575d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/7057e599d97b02b4efb009681a43b327dbce138a",
- "reference": "7057e599d97b02b4efb009681a43b327dbce138a",
+ "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/ab04a956264291505ea84998f43cf91639b4575d",
+ "reference": "ab04a956264291505ea84998f43cf91639b4575d",
"shasum": ""
},
"require": {
- "phpunit/php-code-coverage": ">=2.2.4 <6.0",
- "phpunit/phpunit": ">=4.8.28 <5.0.0 || >=5.6.3 <7.0",
- "sebastian/comparator": ">1.1 <3.0",
- "sebastian/diff": ">=1.4 <4.0"
- },
- "replace": {
- "codeception/phpunit-wrapper": "*"
+ "phpunit/php-code-coverage": "^6.0",
+ "phpunit/phpunit": "7.5.*",
+ "sebastian/comparator": "^3.0",
+ "sebastian/diff": "^3.0"
},
"require-dev": {
"codeception/specify": "*",
- "vlucas/phpdotenv": "^2.4"
+ "vlucas/phpdotenv": "^3.0"
},
"type": "library",
"autoload": {
@@ -2106,24 +2761,24 @@
}
],
"description": "PHPUnit classes used by Codeception",
- "time": "2018-06-20T20:08:14+00:00"
+ "time": "2019-02-26T20:35:32+00:00"
},
{
"name": "codeception/stub",
- "version": "2.0.4",
+ "version": "2.1.0",
"source": {
"type": "git",
"url": "https://github.com/Codeception/Stub.git",
- "reference": "f50bc271f392a2836ff80690ce0c058efe1ae03e"
+ "reference": "853657f988942f7afb69becf3fd0059f192c705a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Codeception/Stub/zipball/f50bc271f392a2836ff80690ce0c058efe1ae03e",
- "reference": "f50bc271f392a2836ff80690ce0c058efe1ae03e",
+ "url": "https://api.github.com/repos/Codeception/Stub/zipball/853657f988942f7afb69becf3fd0059f192c705a",
+ "reference": "853657f988942f7afb69becf3fd0059f192c705a",
"shasum": ""
},
"require": {
- "phpunit/phpunit": ">=4.8 <8.0"
+ "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.0.3"
},
"type": "library",
"autoload": {
@@ -2136,36 +2791,82 @@
"MIT"
],
"description": "Flexible Stub wrapper for PHPUnit's Mock Builder",
- "time": "2018-07-26T11:55:37+00:00"
+ "time": "2019-03-02T15:35:10+00:00"
},
{
- "name": "doctrine/instantiator",
- "version": "1.0.5",
+ "name": "composer/xdebug-handler",
+ "version": "1.3.2",
"source": {
"type": "git",
- "url": "https://github.com/doctrine/instantiator.git",
- "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
+ "url": "https://github.com/composer/xdebug-handler.git",
+ "reference": "d17708133b6c276d6e42ef887a877866b909d892"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
- "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
+ "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/d17708133b6c276d6e42ef887a877866b909d892",
+ "reference": "d17708133b6c276d6e42ef887a877866b909d892",
"shasum": ""
},
"require": {
- "php": ">=5.3,<8.0-DEV"
+ "php": "^5.3.2 || ^7.0",
+ "psr/log": "^1.0"
},
"require-dev": {
- "athletic/athletic": "~0.1.8",
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Composer\\XdebugHandler\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "John Stevenson",
+ "email": "john-stevenson@blueyonder.co.uk"
+ }
+ ],
+ "description": "Restarts a process without xdebug.",
+ "keywords": [
+ "Xdebug",
+ "performance"
+ ],
+ "time": "2019-01-28T20:25:53+00:00"
+ },
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "a2c590166b2133a4633738648b6b064edae0814a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a",
+ "reference": "a2c590166b2133a4633738648b6b064edae0814a",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^6.0",
"ext-pdo": "*",
"ext-phar": "*",
- "phpunit/phpunit": "~4.0",
- "squizlabs/php_codesniffer": "~2.0"
+ "phpbench/phpbench": "^0.13",
+ "phpstan/phpstan-phpunit": "^0.11",
+ "phpstan/phpstan-shim": "^0.11",
+ "phpunit/phpunit": "^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "1.2.x-dev"
}
},
"autoload": {
@@ -2185,12 +2886,12 @@
}
],
"description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
- "homepage": "https://github.com/doctrine/instantiator",
+ "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
"keywords": [
"constructor",
"instantiate"
],
- "time": "2015-06-14T21:17:01+00:00"
+ "time": "2019-03-17T17:37:11+00:00"
},
{
"name": "facebook/webdriver",
@@ -2418,6 +3119,831 @@
],
"time": "2016-12-20T10:07:11+00:00"
},
+ {
+ "name": "jean85/pretty-package-versions",
+ "version": "1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Jean85/pretty-package-versions.git",
+ "reference": "75c7effcf3f77501d0e0caa75111aff4daa0dd48"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/75c7effcf3f77501d0e0caa75111aff4daa0dd48",
+ "reference": "75c7effcf3f77501d0e0caa75111aff4daa0dd48",
+ "shasum": ""
+ },
+ "require": {
+ "ocramius/package-versions": "^1.2.0",
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Jean85\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Alessandro Lai",
+ "email": "alessandro.lai85@gmail.com"
+ }
+ ],
+ "description": "A wrapper for ocramius/package-versions to get pretty versions strings",
+ "keywords": [
+ "composer",
+ "package",
+ "release",
+ "versions"
+ ],
+ "time": "2018-06-13T13:22:40+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.9.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72",
+ "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "replace": {
+ "myclabs/deep-copy": "self.version"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.0",
+ "doctrine/common": "^2.6",
+ "phpunit/phpunit": "^7.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ },
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "time": "2019-04-07T13:18:21+00:00"
+ },
+ {
+ "name": "nette/bootstrap",
+ "version": "v3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/bootstrap.git",
+ "reference": "e1075af05c211915e03e0c86542f3ba5433df4a3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/bootstrap/zipball/e1075af05c211915e03e0c86542f3ba5433df4a3",
+ "reference": "e1075af05c211915e03e0c86542f3ba5433df4a3",
+ "shasum": ""
+ },
+ "require": {
+ "nette/di": "^3.0",
+ "nette/utils": "^3.0",
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "latte/latte": "^2.2",
+ "nette/application": "^3.0",
+ "nette/caching": "^3.0",
+ "nette/database": "^3.0",
+ "nette/forms": "^3.0",
+ "nette/http": "^3.0",
+ "nette/mail": "^3.0",
+ "nette/robot-loader": "^3.0",
+ "nette/safe-stream": "^2.2",
+ "nette/security": "^3.0",
+ "nette/tester": "^2.0",
+ "tracy/tracy": "^2.6"
+ },
+ "suggest": {
+ "nette/robot-loader": "to use Configurator::createRobotLoader()",
+ "tracy/tracy": "to use Configurator::enableTracy()"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0",
+ "GPL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "🅱 Nette Bootstrap: the simple way to configure and bootstrap your Nette application.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "bootstrapping",
+ "configurator",
+ "nette"
+ ],
+ "time": "2019-03-26T12:59:07+00:00"
+ },
+ {
+ "name": "nette/di",
+ "version": "v3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/di.git",
+ "reference": "19d83539245aaacb59470828919182411061841f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/di/zipball/19d83539245aaacb59470828919182411061841f",
+ "reference": "19d83539245aaacb59470828919182411061841f",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "nette/neon": "^3.0",
+ "nette/php-generator": "^3.2.2",
+ "nette/robot-loader": "^3.2",
+ "nette/schema": "^1.0",
+ "nette/utils": "^3.0",
+ "php": ">=7.1"
+ },
+ "conflict": {
+ "nette/bootstrap": "<3.0"
+ },
+ "require-dev": {
+ "nette/tester": "^2.2",
+ "tracy/tracy": "^2.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ],
+ "files": [
+ "src/compatibility.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0",
+ "GPL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "💎 Nette Dependency Injection Container: Flexible, compiled and full-featured DIC with perfectly usable autowiring and support for all new PHP 7.1 features.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "compiled",
+ "di",
+ "dic",
+ "factory",
+ "ioc",
+ "nette",
+ "static"
+ ],
+ "time": "2019-04-03T19:35:46+00:00"
+ },
+ {
+ "name": "nette/finder",
+ "version": "v2.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/finder.git",
+ "reference": "6be1b83ea68ac558aff189d640abe242e0306fe2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/finder/zipball/6be1b83ea68ac558aff189d640abe242e0306fe2",
+ "reference": "6be1b83ea68ac558aff189d640abe242e0306fe2",
+ "shasum": ""
+ },
+ "require": {
+ "nette/utils": "^2.4 || ~3.0.0",
+ "php": ">=7.1"
+ },
+ "conflict": {
+ "nette/nette": "<2.2"
+ },
+ "require-dev": {
+ "nette/tester": "^2.0",
+ "tracy/tracy": "^2.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.5-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0",
+ "GPL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "🔍 Nette Finder: find files and directories with an intuitive API.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "filesystem",
+ "glob",
+ "iterator",
+ "nette"
+ ],
+ "time": "2019-02-28T18:13:25+00:00"
+ },
+ {
+ "name": "nette/neon",
+ "version": "v3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/neon.git",
+ "reference": "cbff32059cbdd8720deccf9e9eace6ee516f02eb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/neon/zipball/cbff32059cbdd8720deccf9e9eace6ee516f02eb",
+ "reference": "cbff32059cbdd8720deccf9e9eace6ee516f02eb",
+ "shasum": ""
+ },
+ "require": {
+ "ext-iconv": "*",
+ "ext-json": "*",
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "nette/tester": "^2.0",
+ "tracy/tracy": "^2.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0",
+ "GPL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "🍸 Nette NEON: encodes and decodes NEON file format.",
+ "homepage": "http://ne-on.org",
+ "keywords": [
+ "export",
+ "import",
+ "neon",
+ "nette",
+ "yaml"
+ ],
+ "time": "2019-02-05T21:30:40+00:00"
+ },
+ {
+ "name": "nette/php-generator",
+ "version": "v3.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/php-generator.git",
+ "reference": "acff8b136fad84b860a626d133e791f95781f9f5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/php-generator/zipball/acff8b136fad84b860a626d133e791f95781f9f5",
+ "reference": "acff8b136fad84b860a626d133e791f95781f9f5",
+ "shasum": ""
+ },
+ "require": {
+ "nette/utils": "^2.4.2 || ~3.0.0",
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "nette/tester": "^2.0",
+ "tracy/tracy": "^2.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0",
+ "GPL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 7.3 features.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "code",
+ "nette",
+ "php",
+ "scaffolding"
+ ],
+ "time": "2019-03-15T03:41:13+00:00"
+ },
+ {
+ "name": "nette/robot-loader",
+ "version": "v3.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/robot-loader.git",
+ "reference": "0712a0e39ae7956d6a94c0ab6ad41aa842544b5c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/robot-loader/zipball/0712a0e39ae7956d6a94c0ab6ad41aa842544b5c",
+ "reference": "0712a0e39ae7956d6a94c0ab6ad41aa842544b5c",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "nette/finder": "^2.5",
+ "nette/utils": "^3.0",
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "nette/tester": "^2.0",
+ "tracy/tracy": "^2.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0",
+ "GPL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "? Nette RobotLoader: high performance and comfortable autoloader that will search and autoload classes within your application.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "autoload",
+ "class",
+ "interface",
+ "nette",
+ "trait"
+ ],
+ "time": "2019-03-08T21:57:24+00:00"
+ },
+ {
+ "name": "nette/schema",
+ "version": "v1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/schema.git",
+ "reference": "6241d8d4da39e825dd6cb5bfbe4242912f4d7e4d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/schema/zipball/6241d8d4da39e825dd6cb5bfbe4242912f4d7e4d",
+ "reference": "6241d8d4da39e825dd6cb5bfbe4242912f4d7e4d",
+ "shasum": ""
+ },
+ "require": {
+ "nette/utils": "^3.0.1",
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "nette/tester": "^2.2",
+ "tracy/tracy": "^2.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0",
+ "GPL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "📐 Nette Schema: validating data structures against a given Schema.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "config",
+ "nette"
+ ],
+ "time": "2019-04-03T15:53:25+00:00"
+ },
+ {
+ "name": "nette/utils",
+ "version": "v3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/utils.git",
+ "reference": "bd961f49b211997202bda1d0fbc410905be370d4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/utils/zipball/bd961f49b211997202bda1d0fbc410905be370d4",
+ "reference": "bd961f49b211997202bda1d0fbc410905be370d4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "nette/tester": "~2.0",
+ "tracy/tracy": "^2.3"
+ },
+ "suggest": {
+ "ext-gd": "to use Image",
+ "ext-iconv": "to use Strings::webalize() and toAscii()",
+ "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
+ "ext-json": "to use Nette\\Utils\\Json",
+ "ext-mbstring": "to use Strings::lower() etc...",
+ "ext-xml": "to use Strings::length() etc. when mbstring is not available"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0",
+ "GPL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
+ "homepage": "https://nette.org",
+ "keywords": [
+ "array",
+ "core",
+ "datetime",
+ "images",
+ "json",
+ "nette",
+ "paginator",
+ "password",
+ "slugify",
+ "string",
+ "unicode",
+ "utf-8",
+ "utility",
+ "validation"
+ ],
+ "time": "2019-03-22T01:00:30+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v4.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "5221f49a608808c1e4d436df32884cbc1b821ac0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/5221f49a608808c1e4d436df32884cbc1b821ac0",
+ "reference": "5221f49a608808c1e4d436df32884cbc1b821ac0",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.5 || ^7.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "time": "2019-02-16T20:54:15+00:00"
+ },
+ {
+ "name": "ocramius/package-versions",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Ocramius/PackageVersions.git",
+ "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/a4d4b60d0e60da2487bd21a2c6ac089f85570dbb",
+ "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0.0",
+ "php": "^7.1.0"
+ },
+ "require-dev": {
+ "composer/composer": "^1.6.3",
+ "doctrine/coding-standard": "^5.0.1",
+ "ext-zip": "*",
+ "infection/infection": "^0.7.1",
+ "phpunit/phpunit": "^7.0.0"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "PackageVersions\\Installer",
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PackageVersions\\": "src/PackageVersions"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com"
+ }
+ ],
+ "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
+ "time": "2019-02-21T12:16:21+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
+ "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-phar": "*",
+ "phar-io/version": "^2.0",
+ "php": "^5.6 || ^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "time": "2018-07-08T19:23:20+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6",
+ "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "time": "2018-07-08T19:19:57+00:00"
+ },
{
"name": "phpdocumentor/reflection-common",
"version": "1.0.1",
@@ -2474,29 +4000,35 @@
},
{
"name": "phpdocumentor/reflection-docblock",
- "version": "3.3.2",
+ "version": "4.3.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
- "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2"
+ "reference": "94fd0001232e47129dd3504189fa1c7225010d08"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bf329f6c1aadea3299f08ee804682b7c45b326a2",
- "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08",
+ "reference": "94fd0001232e47129dd3504189fa1c7225010d08",
"shasum": ""
},
"require": {
- "php": "^5.6 || ^7.0",
+ "php": "^7.0",
"phpdocumentor/reflection-common": "^1.0.0",
"phpdocumentor/type-resolver": "^0.4.0",
"webmozart/assert": "^1.0"
},
"require-dev": {
- "mockery/mockery": "^0.9.4",
- "phpunit/phpunit": "^4.4"
+ "doctrine/instantiator": "~1.0.5",
+ "mockery/mockery": "^1.0",
+ "phpunit/phpunit": "^6.4"
},
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.x-dev"
+ }
+ },
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": [
@@ -2515,7 +4047,7 @@
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
- "time": "2017-11-10T14:09:06+00:00"
+ "time": "2017-11-30T07:14:17+00:00"
},
{
"name": "phpdocumentor/type-resolver",
@@ -2628,40 +4160,206 @@
"time": "2018-08-05T17:53:17+00:00"
},
{
- "name": "phpunit/php-code-coverage",
- "version": "2.2.4",
+ "name": "phpstan/phpdoc-parser",
+ "version": "0.3.1",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
+ "url": "https://github.com/phpstan/phpdoc-parser.git",
+ "reference": "2cc49f47c69b023eaf05b48e6529389893b13d74"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
- "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/2cc49f47c69b023eaf05b48e6529389893b13d74",
+ "reference": "2cc49f47c69b023eaf05b48e6529389893b13d74",
"shasum": ""
},
"require": {
- "php": ">=5.3.3",
- "phpunit/php-file-iterator": "~1.3",
- "phpunit/php-text-template": "~1.2",
- "phpunit/php-token-stream": "~1.3",
- "sebastian/environment": "^1.3.2",
- "sebastian/version": "~1.0"
+ "php": "~7.1"
},
"require-dev": {
- "ext-xdebug": ">=2.1.4",
- "phpunit/phpunit": "~4"
- },
- "suggest": {
- "ext-dom": "*",
- "ext-xdebug": ">=2.2.1",
- "ext-xmlwriter": "*"
+ "consistence/coding-standard": "^2.0.0",
+ "jakub-onderka/php-parallel-lint": "^0.9.2",
+ "phing/phing": "^2.16.0",
+ "phpstan/phpstan": "^0.10",
+ "phpunit/phpunit": "^6.3",
+ "slevomat/coding-standard": "^3.3.0",
+ "symfony/process": "^3.4 || ^4.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.2.x-dev"
+ "dev-master": "0.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\PhpDocParser\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPDoc parser with support for nullable, intersection and generic types",
+ "time": "2019-01-14T12:26:23+00:00"
+ },
+ {
+ "name": "phpstan/phpstan",
+ "version": "0.11.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan.git",
+ "reference": "24ce5a566a798b81343138ed5d41d6877554cf9a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/24ce5a566a798b81343138ed5d41d6877554cf9a",
+ "reference": "24ce5a566a798b81343138ed5d41d6877554cf9a",
+ "shasum": ""
+ },
+ "require": {
+ "composer/xdebug-handler": "^1.3.0",
+ "jean85/pretty-package-versions": "^1.0.3",
+ "nette/bootstrap": "^2.4 || ^3.0",
+ "nette/di": "^2.4.7 || ^3.0",
+ "nette/robot-loader": "^3.0.1",
+ "nette/utils": "^2.4.5 || ^3.0",
+ "nikic/php-parser": "^4.0.2",
+ "php": "~7.1",
+ "phpstan/phpdoc-parser": "^0.3",
+ "symfony/console": "~3.2 || ~4.0",
+ "symfony/finder": "~3.2 || ~4.0"
+ },
+ "conflict": {
+ "symfony/console": "3.4.16 || 4.1.5"
+ },
+ "require-dev": {
+ "brianium/paratest": "^2.0",
+ "consistence/coding-standard": "^3.5",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4",
+ "ext-intl": "*",
+ "ext-mysqli": "*",
+ "ext-soap": "*",
+ "ext-zip": "*",
+ "jakub-onderka/php-parallel-lint": "^1.0",
+ "localheinz/composer-normalize": "^1.1.0",
+ "phing/phing": "^2.16.0",
+ "phpstan/phpstan-deprecation-rules": "^0.11",
+ "phpstan/phpstan-php-parser": "^0.11",
+ "phpstan/phpstan-phpunit": "^0.11",
+ "phpstan/phpstan-strict-rules": "^0.11",
+ "phpunit/phpunit": "^7.0",
+ "slevomat/coding-standard": "^4.7.2",
+ "squizlabs/php_codesniffer": "^3.3.2"
+ },
+ "bin": [
+ "bin/phpstan"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.11-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\": [
+ "src/",
+ "build/PHPStan"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPStan - PHP Static Analysis Tool",
+ "time": "2019-03-25T16:40:09+00:00"
+ },
+ {
+ "name": "phpstan/phpstan-deprecation-rules",
+ "version": "0.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan-deprecation-rules.git",
+ "reference": "449fee6223220b337760abca4444801ddcc8b38d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/449fee6223220b337760abca4444801ddcc8b38d",
+ "reference": "449fee6223220b337760abca4444801ddcc8b38d",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^4.0",
+ "php": "~7.1",
+ "phpstan/phpstan": "^0.11"
+ },
+ "require-dev": {
+ "consistence/coding-standard": "^3.0.1",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4",
+ "jakub-onderka/php-parallel-lint": "^1.0",
+ "phing/phing": "^2.16.0",
+ "phpstan/phpstan-phpunit": "^0.11",
+ "phpunit/phpunit": "^7.0",
+ "slevomat/coding-standard": "^4.5.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.11-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.",
+ "time": "2018-12-05T18:04:16+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "6.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d",
+ "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.1",
+ "phpunit/php-file-iterator": "^2.0",
+ "phpunit/php-text-template": "^1.2.1",
+ "phpunit/php-token-stream": "^3.0",
+ "sebastian/code-unit-reverse-lookup": "^1.0.1",
+ "sebastian/environment": "^3.1 || ^4.0",
+ "sebastian/version": "^2.0.1",
+ "theseer/tokenizer": "^1.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.0"
+ },
+ "suggest": {
+ "ext-xdebug": "^2.6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.1-dev"
}
},
"autoload": {
@@ -2676,7 +4374,7 @@
"authors": [
{
"name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
+ "email": "sebastian@phpunit.de",
"role": "lead"
}
],
@@ -2687,29 +4385,32 @@
"testing",
"xunit"
],
- "time": "2015-10-06T15:47:00+00:00"
+ "time": "2018-10-31T16:06:48+00:00"
},
{
"name": "phpunit/php-file-iterator",
- "version": "1.4.5",
+ "version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
- "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
+ "reference": "050bedf145a257b1ff02746c31894800e5122946"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
- "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946",
+ "reference": "050bedf145a257b1ff02746c31894800e5122946",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.1"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.4.x-dev"
+ "dev-master": "2.0.x-dev"
}
},
"autoload": {
@@ -2724,7 +4425,7 @@
"authors": [
{
"name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
+ "email": "sebastian@phpunit.de",
"role": "lead"
}
],
@@ -2734,7 +4435,7 @@
"filesystem",
"iterator"
],
- "time": "2017-11-27T13:52:08+00:00"
+ "time": "2018-09-13T20:33:42+00:00"
},
{
"name": "phpunit/php-text-template",
@@ -2779,28 +4480,28 @@
},
{
"name": "phpunit/php-timer",
- "version": "1.0.9",
+ "version": "2.1.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-timer.git",
- "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
+ "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
- "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b389aebe1b8b0578430bda0c7c95a829608e059",
+ "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059",
"shasum": ""
},
"require": {
- "php": "^5.3.3 || ^7.0"
+ "php": "^7.1"
},
"require-dev": {
- "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+ "phpunit/phpunit": "^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0-dev"
+ "dev-master": "2.1-dev"
}
},
"autoload": {
@@ -2815,7 +4516,7 @@
"authors": [
{
"name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
+ "email": "sebastian@phpunit.de",
"role": "lead"
}
],
@@ -2824,33 +4525,33 @@
"keywords": [
"timer"
],
- "time": "2017-02-26T11:10:40+00:00"
+ "time": "2019-02-20T10:12:59+00:00"
},
{
"name": "phpunit/php-token-stream",
- "version": "1.4.12",
+ "version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
- "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16"
+ "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16",
- "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c99e3be9d3e85f60646f152f9002d46ed7770d18",
+ "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
- "php": ">=5.3.3"
+ "php": "^7.1"
},
"require-dev": {
- "phpunit/phpunit": "~4.2"
+ "phpunit/phpunit": "^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.4-dev"
+ "dev-master": "3.0-dev"
}
},
"autoload": {
@@ -2873,45 +4574,57 @@
"keywords": [
"tokenizer"
],
- "time": "2017-12-04T08:55:13+00:00"
+ "time": "2018-10-30T05:52:18+00:00"
},
{
"name": "phpunit/phpunit",
- "version": "4.8.36",
+ "version": "7.5.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "46023de9a91eec7dfb06cc56cb4e260017298517"
+ "reference": "c29c0525cf4572c11efe1db49a8b8aee9dfac58a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517",
- "reference": "46023de9a91eec7dfb06cc56cb4e260017298517",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c29c0525cf4572c11efe1db49a8b8aee9dfac58a",
+ "reference": "c29c0525cf4572c11efe1db49a8b8aee9dfac58a",
"shasum": ""
},
"require": {
+ "doctrine/instantiator": "^1.1",
"ext-dom": "*",
"ext-json": "*",
- "ext-pcre": "*",
- "ext-reflection": "*",
- "ext-spl": "*",
- "php": ">=5.3.3",
- "phpspec/prophecy": "^1.3.1",
- "phpunit/php-code-coverage": "~2.1",
- "phpunit/php-file-iterator": "~1.4",
- "phpunit/php-text-template": "~1.2",
- "phpunit/php-timer": "^1.0.6",
- "phpunit/phpunit-mock-objects": "~2.3",
- "sebastian/comparator": "~1.2.2",
- "sebastian/diff": "~1.2",
- "sebastian/environment": "~1.3",
- "sebastian/exporter": "~1.2",
- "sebastian/global-state": "~1.0",
- "sebastian/version": "~1.0",
- "symfony/yaml": "~2.1|~3.0"
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "myclabs/deep-copy": "^1.7",
+ "phar-io/manifest": "^1.0.2",
+ "phar-io/version": "^2.0",
+ "php": "^7.1",
+ "phpspec/prophecy": "^1.7",
+ "phpunit/php-code-coverage": "^6.0.7",
+ "phpunit/php-file-iterator": "^2.0.1",
+ "phpunit/php-text-template": "^1.2.1",
+ "phpunit/php-timer": "^2.1",
+ "sebastian/comparator": "^3.0",
+ "sebastian/diff": "^3.0",
+ "sebastian/environment": "^4.0",
+ "sebastian/exporter": "^3.1",
+ "sebastian/global-state": "^2.0",
+ "sebastian/object-enumerator": "^3.0.3",
+ "sebastian/resource-operations": "^2.0",
+ "sebastian/version": "^2.0.1"
+ },
+ "conflict": {
+ "phpunit/phpunit-mock-objects": "*"
+ },
+ "require-dev": {
+ "ext-pdo": "*"
},
"suggest": {
- "phpunit/php-invoker": "~1.1"
+ "ext-soap": "*",
+ "ext-xdebug": "*",
+ "phpunit/php-invoker": "^2.0"
},
"bin": [
"phpunit"
@@ -2919,7 +4632,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.8.x-dev"
+ "dev-master": "7.5-dev"
}
},
"autoload": {
@@ -2945,38 +4658,32 @@
"testing",
"xunit"
],
- "time": "2017-06-21T08:07:12+00:00"
+ "time": "2019-03-26T13:23:54+00:00"
},
{
- "name": "phpunit/phpunit-mock-objects",
- "version": "2.3.8",
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "1.0.1",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
- "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
- "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
+ "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
"shasum": ""
},
"require": {
- "doctrine/instantiator": "^1.0.2",
- "php": ">=5.3.3",
- "phpunit/php-text-template": "~1.2",
- "sebastian/exporter": "~1.2"
+ "php": "^5.6 || ^7.0"
},
"require-dev": {
- "phpunit/phpunit": "~4.4"
- },
- "suggest": {
- "ext-soap": "*"
+ "phpunit/phpunit": "^5.7 || ^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.3.x-dev"
+ "dev-master": "1.0.x-dev"
}
},
"autoload": {
@@ -2991,44 +4698,39 @@
"authors": [
{
"name": "Sebastian Bergmann",
- "email": "sb@sebastian-bergmann.de",
- "role": "lead"
+ "email": "sebastian@phpunit.de"
}
],
- "description": "Mock Object library for PHPUnit",
- "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
- "keywords": [
- "mock",
- "xunit"
- ],
- "time": "2015-10-02T06:51:40+00:00"
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "time": "2017-03-04T06:30:41+00:00"
},
{
"name": "sebastian/comparator",
- "version": "1.2.4",
+ "version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
+ "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
- "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da",
+ "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da",
"shasum": ""
},
"require": {
- "php": ">=5.3.3",
- "sebastian/diff": "~1.2",
- "sebastian/exporter": "~1.2 || ~2.0"
+ "php": "^7.1",
+ "sebastian/diff": "^3.0",
+ "sebastian/exporter": "^3.1"
},
"require-dev": {
- "phpunit/phpunit": "~4.4"
+ "phpunit/phpunit": "^7.1"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.2.x-dev"
+ "dev-master": "3.0-dev"
}
},
"autoload": {
@@ -3059,38 +4761,39 @@
}
],
"description": "Provides the functionality to compare PHP values for equality",
- "homepage": "http://www.github.com/sebastianbergmann/comparator",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
"keywords": [
"comparator",
"compare",
"equality"
],
- "time": "2017-01-29T09:50:25+00:00"
+ "time": "2018-07-12T15:12:46+00:00"
},
{
"name": "sebastian/diff",
- "version": "1.4.3",
+ "version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
- "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4"
+ "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4",
- "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29",
+ "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29",
"shasum": ""
},
"require": {
- "php": "^5.3.3 || ^7.0"
+ "php": "^7.1"
},
"require-dev": {
- "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+ "phpunit/phpunit": "^7.5 || ^8.0",
+ "symfony/process": "^2 || ^3.3 || ^4"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.4-dev"
+ "dev-master": "3.0-dev"
}
},
"autoload": {
@@ -3115,34 +4818,40 @@
"description": "Diff implementation",
"homepage": "https://github.com/sebastianbergmann/diff",
"keywords": [
- "diff"
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
],
- "time": "2017-05-22T07:24:03+00:00"
+ "time": "2019-02-04T06:01:07+00:00"
},
{
"name": "sebastian/environment",
- "version": "1.3.8",
+ "version": "4.1.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea"
+ "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea",
- "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6fda8ce1974b62b14935adc02a9ed38252eca656",
+ "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656",
"shasum": ""
},
"require": {
- "php": "^5.3.3 || ^7.0"
+ "php": "^7.1"
},
"require-dev": {
- "phpunit/phpunit": "^4.8 || ^5.0"
+ "phpunit/phpunit": "^7.5"
+ },
+ "suggest": {
+ "ext-posix": "*"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.3.x-dev"
+ "dev-master": "4.1-dev"
}
},
"autoload": {
@@ -3167,34 +4876,34 @@
"environment",
"hhvm"
],
- "time": "2016-08-18T05:49:44+00:00"
+ "time": "2019-02-01T05:27:49+00:00"
},
{
"name": "sebastian/exporter",
- "version": "1.2.2",
+ "version": "3.1.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
+ "reference": "234199f4528de6d12aaa58b612e98f7d36adb937"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
- "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937",
+ "reference": "234199f4528de6d12aaa58b612e98f7d36adb937",
"shasum": ""
},
"require": {
- "php": ">=5.3.3",
- "sebastian/recursion-context": "~1.0"
+ "php": "^7.0",
+ "sebastian/recursion-context": "^3.0"
},
"require-dev": {
"ext-mbstring": "*",
- "phpunit/phpunit": "~4.4"
+ "phpunit/phpunit": "^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.3.x-dev"
+ "dev-master": "3.1.x-dev"
}
},
"autoload": {
@@ -3234,27 +4943,27 @@
"export",
"exporter"
],
- "time": "2016-06-17T09:04:28+00:00"
+ "time": "2017-04-03T13:19:02+00:00"
},
{
"name": "sebastian/global-state",
- "version": "1.1.1",
+ "version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
- "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
+ "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
- "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
+ "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": "^7.0"
},
"require-dev": {
- "phpunit/phpunit": "~4.2"
+ "phpunit/phpunit": "^6.0"
},
"suggest": {
"ext-uopz": "*"
@@ -3262,7 +4971,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0-dev"
+ "dev-master": "2.0-dev"
}
},
"autoload": {
@@ -3285,32 +4994,124 @@
"keywords": [
"global state"
],
- "time": "2015-10-12T03:26:01+00:00"
+ "time": "2017-04-27T15:39:26+00:00"
},
{
- "name": "sebastian/recursion-context",
- "version": "1.0.5",
+ "name": "sebastian/object-enumerator",
+ "version": "3.0.3",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/recursion-context.git",
- "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7"
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
- "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5",
+ "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": "^7.0",
+ "sebastian/object-reflector": "^1.1.1",
+ "sebastian/recursion-context": "^3.0"
},
"require-dev": {
- "phpunit/phpunit": "~4.4"
+ "phpunit/phpunit": "^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "time": "2017-08-03T12:35:26+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "773f97c67f28de00d397be301821b06708fca0be"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be",
+ "reference": "773f97c67f28de00d397be301821b06708fca0be",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "time": "2017-03-29T09:07:27+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
+ "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
}
},
"autoload": {
@@ -3338,23 +5139,73 @@
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
- "time": "2016-10-03T07:41:43+00:00"
+ "time": "2017-03-03T06:23:57+00:00"
},
{
- "name": "sebastian/version",
- "version": "1.0.6",
+ "name": "sebastian/resource-operations",
+ "version": "2.0.1",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/version.git",
- "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
+ "url": "https://github.com/sebastianbergmann/resource-operations.git",
+ "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
- "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9",
+ "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9",
"shasum": ""
},
+ "require": {
+ "php": "^7.1"
+ },
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides a list of PHP built-in functions that operate on resources",
+ "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+ "time": "2018-10-04T04:07:39+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
+ "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
"autoload": {
"classmap": [
"src/"
@@ -3373,29 +5224,29 @@
],
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
"homepage": "https://github.com/sebastianbergmann/version",
- "time": "2015-06-21T13:59:46+00:00"
+ "time": "2016-10-03T07:35:21+00:00"
},
{
"name": "symfony/browser-kit",
- "version": "v3.4.21",
+ "version": "v4.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
- "reference": "6d98fb221a263c66b1311203fe4eed154035f508"
+ "reference": "c09c18cca96d7067152f78956faf55346c338283"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/browser-kit/zipball/6d98fb221a263c66b1311203fe4eed154035f508",
- "reference": "6d98fb221a263c66b1311203fe4eed154035f508",
+ "url": "https://api.github.com/repos/symfony/browser-kit/zipball/c09c18cca96d7067152f78956faf55346c338283",
+ "reference": "c09c18cca96d7067152f78956faf55346c338283",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8",
- "symfony/dom-crawler": "~2.8|~3.0|~4.0"
+ "php": "^7.1.3",
+ "symfony/dom-crawler": "~3.4|~4.0"
},
"require-dev": {
- "symfony/css-selector": "~2.8|~3.0|~4.0",
- "symfony/process": "~2.8|~3.0|~4.0"
+ "symfony/css-selector": "~3.4|~4.0",
+ "symfony/process": "~3.4|~4.0"
},
"suggest": {
"symfony/process": ""
@@ -3403,7 +5254,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.4-dev"
+ "dev-master": "4.2-dev"
}
},
"autoload": {
@@ -3430,29 +5281,29 @@
],
"description": "Symfony BrowserKit Component",
"homepage": "https://symfony.com",
- "time": "2019-01-01T13:45:19+00:00"
+ "time": "2019-04-07T09:56:43+00:00"
},
{
"name": "symfony/css-selector",
- "version": "v3.4.21",
+ "version": "v4.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "12f86295c46c36af9896cf21db6b6b8a1465315d"
+ "reference": "48eddf66950fa57996e1be4a55916d65c10c604a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/12f86295c46c36af9896cf21db6b6b8a1465315d",
- "reference": "12f86295c46c36af9896cf21db6b6b8a1465315d",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/48eddf66950fa57996e1be4a55916d65c10c604a",
+ "reference": "48eddf66950fa57996e1be4a55916d65c10c604a",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8"
+ "php": "^7.1.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.4-dev"
+ "dev-master": "4.2-dev"
}
},
"autoload": {
@@ -3483,29 +5334,29 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
- "time": "2019-01-02T09:30:52+00:00"
+ "time": "2019-01-16T20:31:39+00:00"
},
{
"name": "symfony/dom-crawler",
- "version": "v3.4.21",
+ "version": "v4.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
- "reference": "311f666d85d1075b0a294ba1f3de4ae9307d8180"
+ "reference": "53c97769814c80a84a8403efcf3ae7ae966d53bb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/311f666d85d1075b0a294ba1f3de4ae9307d8180",
- "reference": "311f666d85d1075b0a294ba1f3de4ae9307d8180",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/53c97769814c80a84a8403efcf3ae7ae966d53bb",
+ "reference": "53c97769814c80a84a8403efcf3ae7ae966d53bb",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8",
+ "php": "^7.1.3",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.0"
},
"require-dev": {
- "symfony/css-selector": "~2.8|~3.0|~4.0"
+ "symfony/css-selector": "~3.4|~4.0"
},
"suggest": {
"symfony/css-selector": ""
@@ -3513,7 +5364,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.4-dev"
+ "dev-master": "4.2-dev"
}
},
"autoload": {
@@ -3540,29 +5391,29 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
- "time": "2019-01-01T13:45:19+00:00"
+ "time": "2019-02-23T15:17:42+00:00"
},
{
"name": "symfony/finder",
- "version": "v3.4.21",
+ "version": "v4.2.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "3f2a2ab6315dd7682d4c16dcae1e7b95c8b8555e"
+ "reference": "e45135658bd6c14b61850bf131c4f09a55133f69"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/3f2a2ab6315dd7682d4c16dcae1e7b95c8b8555e",
- "reference": "3f2a2ab6315dd7682d4c16dcae1e7b95c8b8555e",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/e45135658bd6c14b61850bf131c4f09a55133f69",
+ "reference": "e45135658bd6c14b61850bf131c4f09a55133f69",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8"
+ "php": "^7.1.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.4-dev"
+ "dev-master": "4.2-dev"
}
},
"autoload": {
@@ -3589,56 +5440,47 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
- "time": "2019-01-01T13:45:19+00:00"
+ "time": "2019-04-06T13:51:08+00:00"
},
{
- "name": "symfony/process",
- "version": "v3.4.21",
+ "name": "theseer/tokenizer",
+ "version": "1.1.2",
"source": {
"type": "git",
- "url": "https://github.com/symfony/process.git",
- "reference": "0d41dd7d95ed179aed6a13393b0f4f97bfa2d25c"
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/0d41dd7d95ed179aed6a13393b0f4f97bfa2d25c",
- "reference": "0d41dd7d95ed179aed6a13393b0f4f97bfa2d25c",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/1c42705be2b6c1de5904f8afacef5895cab44bf8",
+ "reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8"
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.0"
},
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
"autoload": {
- "psr-4": {
- "Symfony\\Component\\Process\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
+ "classmap": [
+ "src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
}
],
- "description": "Symfony Process Component",
- "homepage": "https://symfony.com",
- "time": "2019-01-02T21:24:08+00:00"
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "time": "2019-04-04T09:56:43+00:00"
},
{
"name": "victorjonsson/markdowndocs",
@@ -3746,15 +5588,16 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
- "php": ">=5.6.4",
+ "php": ">=7.1.3",
+ "ext-json": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"ext-curl": "*",
"ext-zip": "*",
- "ext-json": "*"
+ "ext-dom": "*"
},
"platform-dev": [],
"platform-overrides": {
- "php": "5.6.4"
+ "php": "7.1.3"
}
}
diff --git a/index.php b/index.php
index 75d0cfe..64154df 100644
--- a/index.php
+++ b/index.php
@@ -9,35 +9,34 @@
namespace Grav;
-define('GRAV_PHP_MIN', '5.6.4');
-
-// Ensure vendor libraries exist
-$autoload = __DIR__ . '/vendor/autoload.php';
-if (!is_file($autoload)) {
- die("Please run: bin/grav install");
-}
-
-if (PHP_SAPI === 'cli-server') {
- if (!isset($_SERVER['PHP_CLI_ROUTER'])) {
- die("PHP webserver requires a router to run Grav, please use:
php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php");
- }
-}
-
-use Grav\Common\Grav;
-use RocketTheme\Toolbox\Event\Event;
+\define('GRAV_REQUEST_TIME', microtime(true));
+\define('GRAV_PHP_MIN', '7.1.3');
if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) {
die(sprintf('You are running PHP %s, but Grav needs at least PHP %s to run.', $ver, $req));
}
+if (PHP_SAPI === 'cli-server' && !isset($_SERVER['PHP_CLI_ROUTER'])) {
+ die("PHP webserver requires a router to run Grav, please use: php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php");
+}
+
+// Ensure vendor libraries exist
+$autoload = __DIR__ . '/vendor/autoload.php';
+if (!is_file($autoload)) {
+ die('Please run: bin/grav install');
+}
+
// Register the auto-loader.
$loader = require $autoload;
+use Grav\Common\Grav;
+use RocketTheme\Toolbox\Event\Event;
+
// Set timezone to default, falls back to system if php.ini not set
date_default_timezone_set(@date_default_timezone_get());
// Set internal encoding if mbstring loaded
-if (!extension_loaded('mbstring')) {
+if (!\extension_loaded('mbstring')) {
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
}
mb_internal_encoding('UTF-8');
@@ -52,6 +51,9 @@ $grav = Grav::instance(
// Process the page
try {
$grav->process();
+} catch (\Error $e) {
+ $grav->fireEvent('onFatalException', new Event(array('exception' => $e)));
+ throw $e;
} catch (\Exception $e) {
$grav->fireEvent('onFatalException', new Event(array('exception' => $e)));
throw $e;
diff --git a/system/assets/debugger.css b/system/assets/debugger.css
index 556da6a..475dca9 100644
--- a/system/assets/debugger.css
+++ b/system/assets/debugger.css
@@ -30,9 +30,25 @@ div.phpdebugbar {
}
.phpdebugbar .phpdebugbar-widgets-toolbar {
+ border-top: 1px solid #ddd;
padding-left: 5px;
+ padding-right: 2px;
+ padding-top: 2px;
+ background-color: #fafafa !important;
+ width: auto !important;
+ left: 0;
+ right: 0;
}
+.phpdebugbar .phpdebugbar-widgets-toolbar input {
+ background: transparent !important;
+}
+
+.phpdebugbar .phpdebugbar-widgets-toolbar .phpdebugbar-widgets-filter {
+
+}
+
+
.phpdebugbar input[type=text] {
padding: 0;
display: inline;
diff --git a/system/assets/grav.png b/system/assets/grav.png
index 3379934..9ae6d97 100644
Binary files a/system/assets/grav.png and b/system/assets/grav.png differ
diff --git a/system/blueprints/config/backups.yaml b/system/blueprints/config/backups.yaml
new file mode 100644
index 0000000..03c7806
--- /dev/null
+++ b/system/blueprints/config/backups.yaml
@@ -0,0 +1,125 @@
+title: PLUGIN_ADMIN.BACKUPS
+
+form:
+ validation: loose
+
+ fields:
+ history_title:
+ type: section
+ title: PLUGIN_ADMIN.BACKUPS_HISTORY
+ underline: true
+
+ history:
+ type: backupshistory
+
+ config_title:
+ type: section
+ title: PLUGIN_ADMIN.BACKUPS_PURGE_CONFIG
+ underline: true
+
+ purge.trigger:
+ type: select
+ label: PLUGIN_ADMIN.BACKUPS_STORAGE_PURGE_TRIGGER
+ size: medium
+ default: space
+ options:
+ space: Maximum Backup Space
+ number: Maximum Number of Backups
+ time: maximum Retention Time
+ validate:
+ required: true
+
+ purge.max_backups_count:
+ type: number
+ label: PLUGIN_ADMIN.BACKUPS_MAX_COUNT
+ default: 25
+ size: x-small
+ help: PLUGIN_ADMIN.BACKUPS_MAX_COUNT
+ validate:
+ min: 0
+ type: number
+ required: true
+ message: Must be a number 0 or greater
+
+ purge.max_backups_space:
+ type: number
+ label: PLUGIN_ADMIN.BACKUPS_MAX_SPACE
+ append: in GB
+ size: x-small
+ default: 5
+ validate:
+ min: 1
+ type: number
+ required: true
+ message: Space must be 1GB or greater
+
+ purge.max_backups_time:
+ type: number
+ label: PLUGIN_ADMIN.BACKUPS_MAX_RETENTION_TIME
+ append: PLUGIN_ADMIN.BACKUPS_MAX_RETENTION_TIME_APPEND
+ size: x-small
+ default: 365
+ validate:
+ min: 7
+ type: number
+ required: true
+ message: Rentenion days must be 7 or greater
+
+ profiles_title:
+ type: section
+ title: PLUGIN_ADMIN.BACKUPS_PROFILES
+ underline: true
+
+ profiles:
+ type: list
+ style: vertical
+ label:
+ classes: backups-list compact
+ sort: false
+
+ fields:
+ .name:
+ type: text
+ label: PLUGIN_ADMIN.NAME
+ placeholder: PLUGIN_ADMIN.BACKUPS_PROFILE_NAME
+ validate:
+ max: 20
+ message: 'Name must be less than 20 characters'
+ required: true
+ .root:
+ type: text
+ label: PLUGIN_ADMIN.BACKUPS_PROFILE_ROOT_FOLDER
+ help: PLUGIN_ADMIN.BACKUPS_PROFILE_ROOT_FOLDER_HELP
+ placeholder: '/'
+ default: '/'
+ validate:
+ required: true
+ .exclude_paths:
+ type: textarea
+ label: PLUGIN_ADMIN.BACKUPS_PROFILE_EXCLUDE_PATHS
+ rows: 5
+ placeholder: "/backup\r/cache\r/images\r/logs\r/tmp"
+ help: PLUGIN_ADMIN.BACKUPS_PROFILE_EXCLUDE_PATHS_HELP
+ .exclude_files:
+ type: textarea
+ label: PLUGIN_ADMIN.BACKUPS_PROFILE_EXCLUDE_FILES
+ rows: 5
+ placeholder: ".DS_Store\r.git\r.svn\r.hg\r.idea\r.vscode\rnode_modules"
+ help: PLUGIN_ADMIN.BACKUPS_PROFILE_EXCLUDE_FILES_HELP
+ .schedule:
+ type: toggle
+ label: PLUGIN_ADMIN.BACKUPS_PROFILE_SCHEDULE
+ highlight: 1
+ default: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+ .schedule_at:
+ type: cron
+ label: PLUGIN_ADMIN.BACKUPS_PROFILE_SCHEDULE_AT
+ default: '* 3 * * *'
+ validate:
+ required: true
+
diff --git a/system/blueprints/config/scheduler.yaml b/system/blueprints/config/scheduler.yaml
new file mode 100644
index 0000000..c145045
--- /dev/null
+++ b/system/blueprints/config/scheduler.yaml
@@ -0,0 +1,76 @@
+title: PLUGIN_ADMIN.SCHEDULER
+
+form:
+ validation: loose
+
+ fields:
+
+ status_title:
+ type: section
+ title: PLUGIN_ADMIN.SCHEDULER_STATUS
+ underline: true
+
+ status:
+ type: cronstatus
+ validate:
+ type: commalist
+
+ jobs_title:
+ type: section
+ title: PLUGIN_ADMIN.SCHEDULER_JOBS
+ underline: true
+
+ custom_jobs:
+ type: list
+ style: vertical
+ label:
+ classes: cron-job-list compact
+ key: id
+ fields:
+ .id:
+ type: key
+ label: ID
+ placeholder: 'process-name'
+ validate:
+ required: true
+ pattern: '[a-zа-я0-9_\-]+'
+ max: 20
+ message: 'ID must be lowercase with dashes/underscores only and less than 20 characters'
+ .command:
+ type: text
+ label: PLUGIN_ADMIN.COMMAND
+ placeholder: 'cd ~;ls -lah;'
+ validate:
+ required: true
+ .args:
+ type: text
+ label: PLUGIN_ADMIN.EXTRA_ARGUMENTS
+ .at:
+ type: cron
+ label: PLUGIN_ADMIN.SCHEDULER_RUNAT
+ help: PLUGIN_ADMIN.SCHEDULER_RUNAT_HELP
+ placeholder: '* * * * *'
+ validate:
+ required: true
+ .output:
+ type: text
+ label: PLUGIN_ADMIN.SCHEDULER_OUTPUT
+ help: PLUGIN_ADMIN.SCHEDULER_OUTPUT_HELP
+ placeholder: 'logs/ls-cron.out'
+ .output_mode:
+ type: select
+ label: PLUGIN_ADMIN.SCHEDULER_OUTPUT_TYPE
+ help: PLUGIN_ADMIN.SCHEDULER_OUTPUT_TYPE_HELP
+ default: append
+ options:
+ append: Append
+ overwrite: Overwrite
+ .email:
+ type: text
+ label: PLUGIN_ADMIN.SCHEDULER_EMAIL
+ help: PLUGIN_ADMIN.SCHEDULER_EMAIL_HELP
+ placeholder: 'notifications@yoursite.com'
+
+
+
+
diff --git a/system/blueprints/config/security.yaml b/system/blueprints/config/security.yaml
index 9f72416..e0296be 100644
--- a/system/blueprints/config/security.yaml
+++ b/system/blueprints/config/security.yaml
@@ -41,6 +41,14 @@ form:
validate:
type: bool
+ xss_invalid_protocols:
+ type: selectize
+ size: large
+ label: PLUGIN_ADMIN.XSS_INVALID_PROTOCOLS_LIST
+ classes: fancy
+ validate:
+ type: commalist
+
xss_enabled.moz_binding:
type: toggle
label: PLUGIN_ADMIN.XSS_MOZ_BINDINGS
diff --git a/system/blueprints/config/site.yaml b/system/blueprints/config/site.yaml
index c96d264..57fd3c5 100644
--- a/system/blueprints/config/site.yaml
+++ b/system/blueprints/config/site.yaml
@@ -16,13 +16,6 @@ form:
placeholder: PLUGIN_ADMIN.SITE_TITLE_PLACEHOLDER
help: PLUGIN_ADMIN.SITE_TITLE_HELP
- header.status:
- type: text
- label: Status
- size: large
- placeholder: PLUGIN_ADMIN.SITE_TITLE_PLACEHOLDER
- help: PLUGIN_ADMIN.SITE_TITLE_HELP
-
default_lang:
type: text
label: PLUGIN_ADMIN.SITE_DEFAULT_LANG
diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml
index 5bc2e49..f67b92c 100644
--- a/system/blueprints/config/system.yaml
+++ b/system/blueprints/config/system.yaml
@@ -4,1241 +4,1407 @@ form:
validation: loose
fields:
- content:
- type: section
- title: PLUGIN_ADMIN.CONTENT
- underline: true
+ system_tabs:
+ type: tabs
+ classes: side-tabs
fields:
- home.alias:
- type: pages
- size: large
- classes: fancy
- label: PLUGIN_ADMIN.HOME_PAGE
- show_all: false
- show_modular: false
- show_root: false
- show_slug: true
- help: PLUGIN_ADMIN.HOME_PAGE_HELP
-
- home.hide_in_urls:
- type: toggle
- label: PLUGIN_ADMIN.HIDE_HOME_IN_URLS
- help: PLUGIN_ADMIN.HIDE_HOME_IN_URLS_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
-
- pages.theme:
- type: themeselect
- classes: fancy
- selectize: true
- size: medium
- label: PLUGIN_ADMIN.DEFAULT_THEME
- help: PLUGIN_ADMIN.DEFAULT_THEME_HELP
-
- pages.process:
- type: checkboxes
- label: PLUGIN_ADMIN.PROCESS
- help: PLUGIN_ADMIN.PROCESS_HELP
- default: [markdown: true, twig: true]
- options:
- markdown: Markdown
- twig: Twig
- use: keys
-
- timezone:
- type: select
- label: PLUGIN_ADMIN.TIMEZONE
- size: medium
- classes: fancy
- help: PLUGIN_ADMIN.TIMEZONE_HELP
- data-options@: '\Grav\Common\Utils::timezones'
- default: ''
- options:
- '': 'Default (Server Timezone)'
-
- pages.dateformat.default:
- type: select
- size: medium
- selectize:
- create: true
- label: PLUGIN_ADMIN.DEFAULT_DATE_FORMAT
- help: PLUGIN_ADMIN.DEFAULT_DATE_FORMAT_HELP
- placeholder: PLUGIN_ADMIN.DEFAULT_DATE_FORMAT_PLACEHOLDER
- data-options@: '\Grav\Common\Utils::dateFormats'
- validate:
- type: string
-
- pages.dateformat.short:
- type: dateformat
- size: medium
- classes: fancy
- label: PLUGIN_ADMIN.SHORT_DATE_FORMAT
- help: PLUGIN_ADMIN.SHORT_DATE_FORMAT_HELP
- default: "jS M Y"
- options:
- "F jS \\a\\t g:ia": Date1
- "l jS \\of F g:i A": Date2
- "D, d M Y G:i:s": Date3
- "d-m-y G:i": Date4
- "jS M Y": Date5
-
- pages.dateformat.long:
- type: dateformat
- size: medium
- classes: fancy
- label: PLUGIN_ADMIN.LONG_DATE_FORMAT
- help: PLUGIN_ADMIN.LONG_DATE_FORMAT_HELP
- options:
- "F jS \\a\\t g:ia": Date1
- "l jS \\of F g:i A": Date2
- "D, d M Y G:i:s": Date3
- "d-m-y G:i": Date4
- "jS M Y": Date5
-
- pages.order.by:
- type: select
- size: large
- classes: fancy
- label: PLUGIN_ADMIN.DEFAULT_ORDERING
- help: PLUGIN_ADMIN.DEFAULT_ORDERING_HELP
- options:
- default: PLUGIN_ADMIN.DEFAULT_ORDERING_DEFAULT
- folder: PLUGIN_ADMIN.DEFAULT_ORDERING_FOLDER
- title: PLUGIN_ADMIN.DEFAULT_ORDERING_TITLE
- date: PLUGIN_ADMIN.DEFAULT_ORDERING_DATE
-
- pages.order.dir:
- type: toggle
- label: PLUGIN_ADMIN.DEFAULT_ORDER_DIRECTION
- highlight: asc
- default: desc
- help: PLUGIN_ADMIN.DEFAULT_ORDER_DIRECTION_HELP
- options:
- asc: PLUGIN_ADMIN.ASCENDING
- desc: PLUGIN_ADMIN.DESCENDING
-
- pages.list.count:
- type: text
- size: x-small
- append: PLUGIN_ADMIN.PAGES
- label: PLUGIN_ADMIN.DEFAULT_PAGE_COUNT
- help: PLUGIN_ADMIN.DEFAULT_PAGE_COUNT_HELP
- validate:
- type: number
- min: 1
-
- pages.publish_dates:
- type: toggle
- label: PLUGIN_ADMIN.DATE_BASED_PUBLISHING
- help: PLUGIN_ADMIN.DATE_BASED_PUBLISHING_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- pages.events:
- type: checkboxes
- label: PLUGIN_ADMIN.EVENTS
- help: PLUGIN_ADMIN.EVENTS_HELP
- default: [page: true, twig: true]
- options:
- page: Page Events
- twig: Twig Events
- use: keys
-
- pages.append_url_extension:
- type: text
- size: x-small
- placeholder: "e.g. .html"
- label: PLUGIN_ADMIN.APPEND_URL_EXT
- help: PLUGIN_ADMIN.APPEND_URL_EXT_HELP
-
- pages.redirect_default_route:
- type: toggle
- label: PLUGIN_ADMIN.REDIRECT_DEFAULT_ROUTE
- help: PLUGIN_ADMIN.REDIRECT_DEFAULT_ROUTE_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- pages.redirect_default_code:
- type: select
- size: medium
- classes: fancy
- label: PLUGIN_ADMIN.REDIRECT_DEFAULT_CODE
- help: PLUGIN_ADMIN.REDIRECT_DEFAULT_CODE_HELP
- options:
- 301: 301 - Permanent
- 302: 302 - Found
- 303: 303 - Other
- 304: 304 - Not Modified
-
- pages.redirect_trailing_slash:
- type: toggle
- label: PLUGIN_ADMIN.REDIRECT_TRAILING_SLASH
- help: PLUGIN_ADMIN.REDIRECT_TRAILING_SLASH_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- pages.ignore_hidden:
- type: toggle
- label: PLUGIN_ADMIN.IGNORE_HIDDEN
- help: PLUGIN_ADMIN.IGNORE_HIDDEN_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- pages.ignore_files:
- type: selectize
- size: large
- label: PLUGIN_ADMIN.IGNORE_FILES
- help: PLUGIN_ADMIN.IGNORE_FILES_HELP
- classes: fancy
- validate:
- type: commalist
-
- pages.ignore_folders:
- type: selectize
- size: large
- label: PLUGIN_ADMIN.IGNORE_FOLDERS
- help: PLUGIN_ADMIN.IGNORE_FOLDERS_HELP
- classes: fancy
- validate:
- type: commalist
-
- pages.url_taxonomy_filters:
- type: toggle
- label: PLUGIN_ADMIN.ALLOW_URL_TAXONOMY_FILTERS
- help: PLUGIN_ADMIN.ALLOW_URL_TAXONOMY_FILTERS_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- pages.twig_first:
- type: toggle
- label: PLUGIN_ADMIN.TWIG_FIRST
- help: PLUGIN_ADMIN.TWIG_FIRST_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- pages.never_cache_twig:
- type: toggle
- label: PLUGIN_ADMIN.NEVER_CACHE_TWIG
- help: PLUGIN_ADMIN.NEVER_CACHE_TWIG_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- pages.frontmatter.process_twig:
- type: toggle
- label: PLUGIN_ADMIN.FRONTMATTER_PROCESS_TWIG
- help: PLUGIN_ADMIN.FRONTMATTER_PROCESS_TWIG_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- pages.frontmatter.ignore_fields:
- type: selectize
- size: large
- placeholder: "e.g. forms"
- label: PLUGIN_ADMIN.FRONTMATTER_IGNORE_FIELDS
- help: PLUGIN_ADMIN.FRONTMATTER_IGNORE_FIELDS_HELP
- classes: fancy
- validate:
- type: commalist
-
- languages:
- type: section
- title: PLUGIN_ADMIN.LANGUAGES
- underline: true
-
- fields:
-
- languages.supported:
- type: selectize
- size: large
- placeholder: "e.g. en, fr"
- label: PLUGIN_ADMIN.SUPPORTED
- help: PLUGIN_ADMIN.SUPPORTED_HELP
- classes: fancy
- validate:
- type: commalist
-
- languages.include_default_lang:
- type: toggle
- label: PLUGIN_ADMIN.INCLUDE_DEFAULT_LANG
- help: PLUGIN_ADMIN.INCLUDE_DEFAULT_LANG_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
-
- languages.translations:
- type: toggle
- label: PLUGIN_ADMIN.TRANSLATIONS_ENABLED
- help: PLUGIN_ADMIN.TRANSLATIONS_ENABLED_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- languages.translations_fallback:
- type: toggle
- label: PLUGIN_ADMIN.TRANSLATIONS_FALLBACK
- help: PLUGIN_ADMIN.TRANSLATIONS_FALLBACK_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- languages.session_store_active:
- type: toggle
- label: PLUGIN_ADMIN.ACTIVE_LANGUAGE_IN_SESSION
- help: PLUGIN_ADMIN.ACTIVE_LANGUAGE_IN_SESSION_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- languages.http_accept_language:
- type: toggle
- label: PLUGIN_ADMIN.HTTP_ACCEPT_LANGUAGE
- help: PLUGIN_ADMIN.HTTP_ACCEPT_LANGUAGE_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- languages.override_locale:
- type: toggle
- label: PLUGIN_ADMIN.OVERRIDE_LOCALE
- help: PLUGIN_ADMIN.OVERRIDE_LOCALE_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- http_headers:
- type: section
- title: PLUGIN_ADMIN.HTTP_HEADERS
- underline: true
-
- fields:
- pages.expires:
- type: text
- size: small
- append: NICETIME.SECOND_PLURAL
- label: PLUGIN_ADMIN.EXPIRES
- help: PLUGIN_ADMIN.EXPIRES_HELP
- validate:
- type: number
- min: 1
- pages.cache_control:
- type: text
- size: medium
- label: PLUGIN_ADMIN.CACHE_CONTROL
- help: PLUGIN_ADMIN.CACHE_CONTROL_HELP
- placeholder: 'e.g. public, max-age=31536000'
- pages.last_modified:
- type: toggle
- label: PLUGIN_ADMIN.LAST_MODIFIED
- help: PLUGIN_ADMIN.LAST_MODIFIED_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
- pages.etag:
- type: toggle
- label: PLUGIN_ADMIN.ETAG
- help: PLUGIN_ADMIN.ETAG_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
- pages.vary_accept_encoding:
- type: toggle
- label: PLUGIN_ADMIN.VARY_ACCEPT_ENCODING
- help: PLUGIN_ADMIN.VARY_ACCEPT_ENCODING_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- markdown:
- type: section
- title: Markdown
- underline: true
-
- fields:
- pages.markdown.extra:
- type: toggle
- label: Markdown extra
- help: PLUGIN_ADMIN.MARKDOWN_EXTRA_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
- pages.markdown.auto_line_breaks:
- type: toggle
- label: PLUGIN_ADMIN.AUTO_LINE_BREAKS
- help: PLUGIN_ADMIN.AUTO_LINE_BREAKS_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
- pages.markdown.auto_url_links:
- type: toggle
- label: PLUGIN_ADMIN.AUTO_URL_LINKS
- help: PLUGIN_ADMIN.AUTO_URL_LINKS_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
- pages.markdown.escape_markup:
- type: toggle
- label: PLUGIN_ADMIN.ESCAPE_MARKUP
- help: PLUGIN_ADMIN.ESCAPE_MARKUP_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- caching:
- type: section
- title: PLUGIN_ADMIN.CACHING
- underline: true
-
- fields:
- cache.enabled:
- type: toggle
- label: PLUGIN_ADMIN.CACHING
- help: PLUGIN_ADMIN.CACHING_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- cache.check.method:
- type: select
- size: medium
- classes: fancy
- label: PLUGIN_ADMIN.CACHE_CHECK_METHOD
- help: PLUGIN_ADMIN.CACHE_CHECK_METHOD_HELP
- options:
- file: Markdown + Yaml file timestamps
- folder: Folder timestamps
- hash: All files timestamps
- none: No timestamp checking
-
- cache.driver:
- type: select
- size: small
- classes: fancy
- label: PLUGIN_ADMIN.CACHE_DRIVER
- help: PLUGIN_ADMIN.CACHE_DRIVER_HELP
- options:
- auto: Auto detect
- file: File
- apc: APC
- apcu: APCu
- xcache: Xcache
- memcache: Memcache
- memcached: Memcached
- wincache: WinCache
- redis: Redis
-
- cache.prefix:
- type: text
- size: x-small
- label: PLUGIN_ADMIN.CACHE_PREFIX
- help: PLUGIN_ADMIN.CACHE_PREFIX_HELP
- placeholder: PLUGIN_ADMIN.CACHE_PREFIX_PLACEHOLDER
-
- cache.clear_images_by_default:
- type: toggle
- label: PLUGIN_ADMIN.CLEAR_IMAGES_BY_DEFAULT
- help: PLUGIN_ADMIN.CLEAR_IMAGES_BY_DEFAULT_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- cache.cli_compatibility:
- type: toggle
- label: PLUGIN_ADMIN.CLI_COMPATIBILITY
- help: PLUGIN_ADMIN.CLI_COMPATIBILITY_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- cache.lifetime:
- type: text
- size: small
- append: NICETIME.SECOND_PLURAL
- label: PLUGIN_ADMIN.LIFETIME
- help: PLUGIN_ADMIN.LIFETIME_HELP
- validate:
- type: number
-
- cache.gzip:
- type: toggle
- label: PLUGIN_ADMIN.GZIP_COMPRESSION
- help: PLUGIN_ADMIN.GZIP_COMPRESSION_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- cache.allow_webserver_gzip:
- type: toggle
- label: PLUGIN_ADMIN.ALLOW_WEBSERVER_GZIP
- help: PLUGIN_ADMIN.ALLOW_WEBSERVER_GZIP_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- cache.memcache.server:
- type: text
- size: medium
- label: PLUGIN_ADMIN.MEMCACHE_SERVER
- help: PLUGIN_ADMIN.MEMCACHE_SERVER_HELP
- placeholder: "localhost"
-
- cache.memcache.port:
- type: text
- size: small
- label: PLUGIN_ADMIN.MEMCACHE_PORT
- help: PLUGIN_ADMIN.MEMCACHE_PORT_HELP
- placeholder: "11211"
-
- cache.memcached.server:
- type: text
- size: medium
- label: PLUGIN_ADMIN.MEMCACHED_SERVER
- help: PLUGIN_ADMIN.MEMCACHED_SERVER_HELP
- placeholder: "localhost"
-
- cache.memcached.port:
- type: text
- size: small
- label: PLUGIN_ADMIN.MEMCACHED_PORT
- help: PLUGIN_ADMIN.MEMCACHED_PORT_HELP
- placeholder: "11211"
-
- cache.redis.socket:
- type: text
- size: medium
- label: PLUGIN_ADMIN.REDIS_SOCKET
- help: PLUGIN_ADMIN.REDIS_SOCKET_HELP
- placeholder: "/var/run/redis/redis.sock"
-
- cache.redis.server:
- type: text
- size: medium
- label: PLUGIN_ADMIN.REDIS_SERVER
- help: PLUGIN_ADMIN.REDIS_SERVER_HELP
- placeholder: "localhost"
-
- cache.redis.port:
- type: text
- size: small
- label: PLUGIN_ADMIN.REDIS_PORT
- help: PLUGIN_ADMIN.REDIS_PORT_HELP
- placeholder: "6379"
-
- cache.redis.password:
- type: text
- size: small
- label: PLUGIN_ADMIN.REDIS_PASSWORD
-
-
-
- twig:
- type: section
- title: PLUGIN_ADMIN.TWIG_TEMPLATING
- underline: true
-
- fields:
- twig.cache:
- type: toggle
- label: PLUGIN_ADMIN.TWIG_CACHING
- help: PLUGIN_ADMIN.TWIG_CACHING_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- twig.debug:
- type: toggle
- label: PLUGIN_ADMIN.TWIG_DEBUG
- help: PLUGIN_ADMIN.TWIG_DEBUG_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- twig.auto_reload:
- type: toggle
- label: PLUGIN_ADMIN.DETECT_CHANGES
- help: PLUGIN_ADMIN.DETECT_CHANGES_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- twig.autoescape:
- type: toggle
- label: PLUGIN_ADMIN.AUTOESCAPE_VARIABLES
- help: PLUGIN_ADMIN.AUTOESCAPE_VARIABLES_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- twig.umask_fix:
- type: toggle
- label: PLUGIN_ADMIN.TWIG_UMASK_FIX
- help: PLUGIN_ADMIN.TWIG_UMASK_FIX_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- assets:
- type: section
- title: PLUGIN_ADMIN.ASSETS
- underline: true
-
- fields:
- assets.css_pipeline:
- type: toggle
- label: PLUGIN_ADMIN.CSS_PIPELINE
- help: PLUGIN_ADMIN.CSS_PIPELINE_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- assets.css_pipeline_include_externals:
- type: toggle
- label: PLUGIN_ADMIN.CSS_PIPELINE_INCLUDE_EXTERNALS
- help: PLUGIN_ADMIN.CSS_PIPELINE_INCLUDE_EXTERNALS_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- assets.css_pipeline_before_excludes:
- type: toggle
- label: PLUGIN_ADMIN.CSS_PIPELINE_BEFORE_EXCLUDES
- help: PLUGIN_ADMIN.CSS_PIPELINE_BEFORE_EXCLUDES_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- assets.css_minify:
- type: toggle
- label: PLUGIN_ADMIN.CSS_MINIFY
- help: PLUGIN_ADMIN.CSS_MINIFY_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- assets.css_minify_windows:
- type: toggle
- label: PLUGIN_ADMIN.CSS_MINIFY_WINDOWS_OVERRIDE
- help: PLUGIN_ADMIN.CSS_MINIFY_WINDOWS_OVERRIDE_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- assets.css_rewrite:
- type: toggle
- label: PLUGIN_ADMIN.CSS_REWRITE
- help: PLUGIN_ADMIN.CSS_REWRITE_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- assets.js_pipeline:
- type: toggle
- label: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE
- help: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- assets.js_pipeline_include_externals:
- type: toggle
- label: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS
- help: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- assets.js_pipeline_before_excludes:
- type: toggle
- label: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES
- help: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- assets.js_minify:
- type: toggle
- label: PLUGIN_ADMIN.JAVASCRIPT_MINIFY
- help: PLUGIN_ADMIN.JAVASCRIPT_MINIFY_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- assets.enable_asset_timestamp:
- type: toggle
- label: PLUGIN_ADMIN.ENABLED_TIMESTAMPS_ON_ASSETS
- help: PLUGIN_ADMIN.ENABLED_TIMESTAMPS_ON_ASSETS_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- assets.collections:
- type: multilevel
- label: PLUGIN_ADMIN.COLLECTIONS
- placeholder_key: collection_name
- placeholder_value: collection_path
- validate:
- type: array
-
- errors:
- type: section
- title: PLUGIN_ADMIN.ERROR_HANDLER
- underline: true
-
- fields:
- errors.display:
- type: select
- label: PLUGIN_ADMIN.DISPLAY_ERRORS
- help: PLUGIN_ADMIN.DISPLAY_ERRORS_HELP
- size: medium
- highlight: 1
- options:
- -1: PLUGIN_ADMIN.ERROR_SYSTEM
- 0: PLUGIN_ADMIN.ERROR_SIMPLE
- 1: PLUGIN_ADMIN.ERROR_FULL_BACKTRACE
- validate:
- type: int
-
-
- errors.log:
- type: toggle
- label: PLUGIN_ADMIN.LOG_ERRORS
- help: PLUGIN_ADMIN.LOG_ERRORS_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- debugger:
- type: section
- title: PLUGIN_ADMIN.DEBUGGER
- underline: true
-
- fields:
- debugger.enabled:
- type: toggle
- label: PLUGIN_ADMIN.DEBUGGER
- help: PLUGIN_ADMIN.DEBUGGER_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- debugger.shutdown.close_connection:
- type: toggle
- label: PLUGIN_ADMIN.SHUTDOWN_CLOSE_CONNECTION
- help: PLUGIN_ADMIN.SHUTDOWN_CLOSE_CONNECTION_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- media:
- type: section
- title: PLUGIN_ADMIN.MEDIA
- underline: true
-
- fields:
- images.default_image_quality:
- type: range
- append: '%'
- label: PLUGIN_ADMIN.DEFAULT_IMAGE_QUALITY
- help: PLUGIN_ADMIN.DEFAULT_IMAGE_QUALITY_HELP
- validate:
- min: 1
- max: 100
-
- images.cache_all:
- type: toggle
- label: PLUGIN_ADMIN.CACHE_ALL
- help: PLUGIN_ADMIN.CACHE_ALL_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- images.cache_perms:
- type: select
- size: small
- label: PLUGIN_ADMIN.CACHE_PERMS
- help: PLUGIN_ADMIN.CACHE_PERMS_HELP
- highlight: '0755'
- options:
- '0755': '0755'
- '0775': '0775'
-
- images.debug:
- type: toggle
- label: PLUGIN_ADMIN.IMAGES_DEBUG
- help: PLUGIN_ADMIN.IMAGES_DEBUG_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- images.auto_fix_orientation:
- type: toggle
- label: PLUGIN_ADMIN.IMAGES_AUTO_FIX_ORIENTATION
- help: PLUGIN_ADMIN.IMAGES_AUTO_FIX_ORIENTATION_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- media.enable_media_timestamp:
- type: toggle
- label: PLUGIN_ADMIN.ENABLE_MEDIA_TIMESTAMP
- help: PLUGIN_ADMIN.ENABLE_MEDIA_TIMESTAMP_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- media.auto_metadata_exif:
- type: toggle
- label: PLUGIN_ADMIN.ENABLE_AUTO_METADATA
- help: PLUGIN_ADMIN.ENABLE_AUTO_METADATA_HELP
- highlight: 0
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
-
-
- media.allowed_fallback_types:
- type: selectize
- size: large
- label: PLUGIN_ADMIN.FALLBACK_TYPES
- help: PLUGIN_ADMIN.FALLBACK_TYPES_HELP
- classes: fancy
- validate:
- type: commalist
-
- media.unsupported_inline_types:
- type: selectize
- size: large
- label: PLUGIN_ADMIN.INLINE_TYPES
- help: PLUGIN_ADMIN.INLINE_TYPES_HELP
- classes: fancy
- validate:
- type: commalist
-
- session:
- type: section
- title: PLUGIN_ADMIN.SESSION
- underline: true
-
- fields:
- session.enabled:
- type: hidden
- label: PLUGIN_ADMIN.ENABLED
- help: PLUGIN_ADMIN.SESSION_ENABLED_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- default: true
- validate:
- type: bool
-
- session.initialize:
- type: toggle
- label: PLUGIN_ADMIN.SESSION_INITIALIZE
- help: PLUGIN_ADMIN.SESSION_INITIALIZE_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- default: true
- validate:
- type: bool
-
- session.timeout:
- type: text
- size: small
- append: NICETIME.SECOND_PLURAL
- label: PLUGIN_ADMIN.TIMEOUT
- help: PLUGIN_ADMIN.TIMEOUT_HELP
- validate:
- type: number
- min: 0
-
- session.name:
- type: text
- size: small
- label: PLUGIN_ADMIN.NAME
- help: PLUGIN_ADMIN.SESSION_NAME_HELP
-
- session.secure:
- type: toggle
- label: PLUGIN_ADMIN.SESSION_SECURE
- help: PLUGIN_ADMIN.SESSION_SECURE_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- default: false
- validate:
- type: bool
-
- session.httponly:
- type: toggle
- label: PLUGIN_ADMIN.SESSION_HTTPONLY
- help: PLUGIN_ADMIN.SESSION_HTTPONLY_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- default: true
- validate:
- type: bool
-
- session.path:
- type: text
- size: small
- label: PLUGIN_ADMIN.SESSION_PATH
- help: PLUGIN_ADMIN.SESSION_PATH_HELP
-
- session.split:
- type: toggle
- label: PLUGIN_ADMIN.SESSION_SPLIT
- help: PLUGIN_ADMIN.SESSION_SPLIT_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- default: true
- validate:
- type: bool
-
- advanced:
- type: section
- title: PLUGIN_ADMIN.ADVANCED
- underline: true
-
- fields:
- gpm.releases:
- type: toggle
- label: PLUGIN_ADMIN.GPM_RELEASES
- highlight: stable
- help: PLUGIN_ADMIN.GPM_RELEASES_HELP
- options:
- stable: PLUGIN_ADMIN.STABLE
- testing: PLUGIN_ADMIN.TESTING
-
- gpm.proxy_url:
- type: text
- size: medium
- placeholder: "e.g. 127.0.0.1:3128"
- label: PLUGIN_ADMIN.PROXY_URL
- help: PLUGIN_ADMIN.PROXY_URL_HELP
-
- gpm.method:
- type: toggle
- label: PLUGIN_ADMIN.GPM_METHOD
- highlight: auto
- help: PLUGIN_ADMIN.GPM_METHOD_HELP
- options:
- auto: PLUGIN_ADMIN.AUTO
- fopen: PLUGIN_ADMIN.FOPEN
- curl: PLUGIN_ADMIN.CURL
-
- gpm.official_gpm_only:
- type: toggle
- label: PLUGIN_ADMIN.GPM_OFFICIAL_ONLY
- highlight: auto
- help: PLUGIN_ADMIN.GPM_OFFICIAL_ONLY_HELP
- highlight: 1
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- default: true
- validate:
- type: bool
-
- gpm.verify_peer:
- type: toggle
- label: PLUGIN_ADMIN.GPM_VERIFY_PEER
- highlight: 1
- help: PLUGIN_ADMIN.GPM_VERIFY_PEER_HELP
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- reverse_proxy_setup:
- type: toggle
- label: PLUGIN_ADMIN.REVERSE_PROXY
- highlight: 0
- help: PLUGIN_ADMIN.REVERSE_PROXY_HELP
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- username_regex:
- type: text
- size: large
- label: PLUGIN_ADMIN.USERNAME_REGEX
- help: PLUGIN_ADMIN.USERNAME_REGEX_HELP
-
- pwd_regex:
- type: text
- size: large
- label: PLUGIN_ADMIN.PWD_REGEX
- help: PLUGIN_ADMIN.PWD_REGEX_HELP
-
- intl_enabled:
- type: toggle
- label: PLUGIN_ADMIN.INTL_ENABLED
- highlight: 1
- help: PLUGIN_ADMIN.INTL_ENABLED_HELP
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- wrapped_site:
- type: toggle
- label: PLUGIN_ADMIN.WRAPPED_SITE
- highlight: 0
- help: PLUGIN_ADMIN.WRAPPED_SITE_HELP
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- absolute_urls:
- type: toggle
- label: PLUGIN_ADMIN.ABSOLUTE_URLS
- highlight: 0
- help: PLUGIN_ADMIN.ABSOLUTE_URLS_HELP
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- param_sep:
- type: select
- size: medium
- label: PLUGIN_ADMIN.PARAMETER_SEPARATOR
- classes: fancy
- help: PLUGIN_ADMIN.PARAMETER_SEPARATOR_HELP
- default: ''
- options:
- ':': ': (default)'
- ';': '; (for Apache running on Windows)'
-
- force_ssl:
- type: toggle
- label: PLUGIN_ADMIN.FORCE_SSL
- highlight: 0
- help: PLUGIN_ADMIN.FORCE_SSL_HELP
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- force_lowercase_urls:
- type: toggle
- label: PLUGIN_ADMIN.FORCE_LOWERCASE_URLS
- highlight: 1
- default: 1
- help: PLUGIN_ADMIN.FORCE_LOWERCASE_URLS_HELP
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- custom_base_url:
- type: text
- size: medium
- placeholder: "e.g. http://yoursite.com/yourpath"
- label: PLUGIN_ADMIN.CUSTOM_BASE_URL
- help: PLUGIN_ADMIN.CUSTOM_BASE_URL_HELP
-
- strict_mode.yaml_compat:
- type: toggle
- label: PLUGIN_ADMIN.STRICT_YAML_COMPAT
- highlight: 1
- default: 1
- help: PLUGIN_ADMIN.STRICT_YAML_COMPAT_HELP
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
-
- strict_mode.twig_compat:
- type: toggle
- label: PLUGIN_ADMIN.STRICT_TWIG_COMPAT
- highlight: 1
- default: 1
- help: PLUGIN_ADMIN.STRICT_TWIG_COMPAT_HELP
- options:
- 1: PLUGIN_ADMIN.YES
- 0: PLUGIN_ADMIN.NO
- validate:
- type: bool
+ content:
+ type: tab
+ title: PLUGIN_ADMIN.CONTENT
+
+ fields:
+ content_section:
+ type: section
+ title: PLUGIN_ADMIN.CONTENT
+ underline: true
+
+ home.alias:
+ type: pages
+ size: large
+ classes: fancy
+ label: PLUGIN_ADMIN.HOME_PAGE
+ show_all: false
+ show_modular: false
+ show_root: false
+ show_slug: true
+ help: PLUGIN_ADMIN.HOME_PAGE_HELP
+
+ home.hide_in_urls:
+ type: toggle
+ label: PLUGIN_ADMIN.HIDE_HOME_IN_URLS
+ help: PLUGIN_ADMIN.HIDE_HOME_IN_URLS_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ pages.theme:
+ type: themeselect
+ classes: fancy
+ selectize: true
+ size: medium
+ label: PLUGIN_ADMIN.DEFAULT_THEME
+ help: PLUGIN_ADMIN.DEFAULT_THEME_HELP
+
+ pages.process:
+ type: checkboxes
+ label: PLUGIN_ADMIN.PROCESS
+ help: PLUGIN_ADMIN.PROCESS_HELP
+ default: [markdown: true, twig: true]
+ options:
+ markdown: Markdown
+ twig: Twig
+ use: keys
+
+ pages.types:
+ type: array
+ label: PLUGIN_ADMIN.PAGE_TYPES
+ help: PLUGIN_ADMIN.PAGE_TYPES_HELP
+ size: small
+ default: ['html','htm','json','xml','txt','rss','atom']
+ value_only: true
+
+ timezone:
+ type: select
+ label: PLUGIN_ADMIN.TIMEZONE
+ size: medium
+ classes: fancy
+ help: PLUGIN_ADMIN.TIMEZONE_HELP
+ data-options@: '\Grav\Common\Utils::timezones'
+ default: ''
+ options:
+ '': 'Default (Server Timezone)'
+
+ pages.dateformat.default:
+ type: select
+ size: medium
+ selectize:
+ create: true
+ label: PLUGIN_ADMIN.DEFAULT_DATE_FORMAT
+ help: PLUGIN_ADMIN.DEFAULT_DATE_FORMAT_HELP
+ placeholder: PLUGIN_ADMIN.DEFAULT_DATE_FORMAT_PLACEHOLDER
+ data-options@: '\Grav\Common\Utils::dateFormats'
+ validate:
+ type: string
+
+ pages.dateformat.short:
+ type: dateformat
+ size: medium
+ classes: fancy
+ label: PLUGIN_ADMIN.SHORT_DATE_FORMAT
+ help: PLUGIN_ADMIN.SHORT_DATE_FORMAT_HELP
+ default: "jS M Y"
+ options:
+ "F jS \\a\\t g:ia": Date1
+ "l jS \\of F g:i A": Date2
+ "D, d M Y G:i:s": Date3
+ "d-m-y G:i": Date4
+ "jS M Y": Date5
+
+ pages.dateformat.long:
+ type: dateformat
+ size: medium
+ classes: fancy
+ label: PLUGIN_ADMIN.LONG_DATE_FORMAT
+ help: PLUGIN_ADMIN.LONG_DATE_FORMAT_HELP
+ options:
+ "F jS \\a\\t g:ia": Date1
+ "l jS \\of F g:i A": Date2
+ "D, d M Y G:i:s": Date3
+ "d-m-y G:i": Date4
+ "jS M Y": Date5
+
+ pages.order.by:
+ type: select
+ size: large
+ classes: fancy
+ label: PLUGIN_ADMIN.DEFAULT_ORDERING
+ help: PLUGIN_ADMIN.DEFAULT_ORDERING_HELP
+ options:
+ default: PLUGIN_ADMIN.DEFAULT_ORDERING_DEFAULT
+ folder: PLUGIN_ADMIN.DEFAULT_ORDERING_FOLDER
+ title: PLUGIN_ADMIN.DEFAULT_ORDERING_TITLE
+ date: PLUGIN_ADMIN.DEFAULT_ORDERING_DATE
+
+ pages.order.dir:
+ type: toggle
+ label: PLUGIN_ADMIN.DEFAULT_ORDER_DIRECTION
+ highlight: asc
+ default: desc
+ help: PLUGIN_ADMIN.DEFAULT_ORDER_DIRECTION_HELP
+ options:
+ asc: PLUGIN_ADMIN.ASCENDING
+ desc: PLUGIN_ADMIN.DESCENDING
+
+ pages.list.count:
+ type: text
+ size: x-small
+ append: PLUGIN_ADMIN.PAGES
+ label: PLUGIN_ADMIN.DEFAULT_PAGE_COUNT
+ help: PLUGIN_ADMIN.DEFAULT_PAGE_COUNT_HELP
+ validate:
+ type: number
+ min: 1
+
+ pages.publish_dates:
+ type: toggle
+ label: PLUGIN_ADMIN.DATE_BASED_PUBLISHING
+ help: PLUGIN_ADMIN.DATE_BASED_PUBLISHING_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ pages.events:
+ type: checkboxes
+ label: PLUGIN_ADMIN.EVENTS
+ help: PLUGIN_ADMIN.EVENTS_HELP
+ default: [page: true, twig: true]
+ options:
+ page: Page Events
+ twig: Twig Events
+ use: keys
+
+ pages.append_url_extension:
+ type: text
+ size: x-small
+ placeholder: "e.g. .html"
+ label: PLUGIN_ADMIN.APPEND_URL_EXT
+ help: PLUGIN_ADMIN.APPEND_URL_EXT_HELP
+
+ pages.redirect_default_route:
+ type: toggle
+ label: PLUGIN_ADMIN.REDIRECT_DEFAULT_ROUTE
+ help: PLUGIN_ADMIN.REDIRECT_DEFAULT_ROUTE_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ pages.redirect_default_code:
+ type: select
+ size: medium
+ classes: fancy
+ label: PLUGIN_ADMIN.REDIRECT_DEFAULT_CODE
+ help: PLUGIN_ADMIN.REDIRECT_DEFAULT_CODE_HELP
+ options:
+ 301: 301 - Permanent
+ 302: 302 - Found
+ 303: 303 - Other
+ 304: 304 - Not Modified
+
+ pages.redirect_trailing_slash:
+ type: toggle
+ label: PLUGIN_ADMIN.REDIRECT_TRAILING_SLASH
+ help: PLUGIN_ADMIN.REDIRECT_TRAILING_SLASH_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ pages.ignore_hidden:
+ type: toggle
+ label: PLUGIN_ADMIN.IGNORE_HIDDEN
+ help: PLUGIN_ADMIN.IGNORE_HIDDEN_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ pages.ignore_files:
+ type: selectize
+ size: large
+ label: PLUGIN_ADMIN.IGNORE_FILES
+ help: PLUGIN_ADMIN.IGNORE_FILES_HELP
+ classes: fancy
+ validate:
+ type: commalist
+
+ pages.ignore_folders:
+ type: selectize
+ size: large
+ label: PLUGIN_ADMIN.IGNORE_FOLDERS
+ help: PLUGIN_ADMIN.IGNORE_FOLDERS_HELP
+ classes: fancy
+ validate:
+ type: commalist
+
+ pages.hide_empty_folders:
+ type: selectize
+ size: large
+ label: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS
+ help: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS_HELP
+ classes: fancy
+ validate:
+ type: commalist
+
+ pages.url_taxonomy_filters:
+ type: toggle
+ label: PLUGIN_ADMIN.ALLOW_URL_TAXONOMY_FILTERS
+ help: PLUGIN_ADMIN.ALLOW_URL_TAXONOMY_FILTERS_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ pages.twig_first:
+ type: toggle
+ label: PLUGIN_ADMIN.TWIG_FIRST
+ help: PLUGIN_ADMIN.TWIG_FIRST_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ pages.never_cache_twig:
+ type: toggle
+ label: PLUGIN_ADMIN.NEVER_CACHE_TWIG
+ help: PLUGIN_ADMIN.NEVER_CACHE_TWIG_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ pages.frontmatter.process_twig:
+ type: toggle
+ label: PLUGIN_ADMIN.FRONTMATTER_PROCESS_TWIG
+ help: PLUGIN_ADMIN.FRONTMATTER_PROCESS_TWIG_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ pages.frontmatter.ignore_fields:
+ type: selectize
+ size: large
+ placeholder: "e.g. forms"
+ label: PLUGIN_ADMIN.FRONTMATTER_IGNORE_FIELDS
+ help: PLUGIN_ADMIN.FRONTMATTER_IGNORE_FIELDS_HELP
+ classes: fancy
+ validate:
+ type: commalist
+
+ languages:
+ type: tab
+ title: PLUGIN_ADMIN.LANGUAGES
+
+ fields:
+ languages-section:
+ type: section
+ title: PLUGIN_ADMIN.LANGUAGES
+ underline: true
+
+ languages.supported:
+ type: selectize
+ size: large
+ placeholder: "e.g. en, fr"
+ label: PLUGIN_ADMIN.SUPPORTED
+ help: PLUGIN_ADMIN.SUPPORTED_HELP
+ classes: fancy
+ validate:
+ type: commalist
+
+ languages.default_lang:
+ type: text
+ size: x-small
+ label: PLUGIN_ADMIN.DEFAULT_LANG
+ help: PLUGIN_ADMIN.DEFAULT_LANG_HELP
+
+ languages.include_default_lang:
+ type: toggle
+ label: PLUGIN_ADMIN.INCLUDE_DEFAULT_LANG
+ help: PLUGIN_ADMIN.INCLUDE_DEFAULT_LANG_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ languages.pages_fallback_only:
+ type: toggle
+ label: PLUGIN_ADMIN.PAGES_FALLBACK_ONLY
+ help: PLUGIN_ADMIN.PAGES_FALLBACK_ONLY_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ languages.translations:
+ type: toggle
+ label: PLUGIN_ADMIN.TRANSLATIONS_ENABLED
+ help: PLUGIN_ADMIN.TRANSLATIONS_ENABLED_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ languages.translations_fallback:
+ type: toggle
+ label: PLUGIN_ADMIN.TRANSLATIONS_FALLBACK
+ help: PLUGIN_ADMIN.TRANSLATIONS_FALLBACK_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ languages.session_store_active:
+ type: toggle
+ label: PLUGIN_ADMIN.ACTIVE_LANGUAGE_IN_SESSION
+ help: PLUGIN_ADMIN.ACTIVE_LANGUAGE_IN_SESSION_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ languages.http_accept_language:
+ type: toggle
+ label: PLUGIN_ADMIN.HTTP_ACCEPT_LANGUAGE
+ help: PLUGIN_ADMIN.HTTP_ACCEPT_LANGUAGE_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ languages.override_locale:
+ type: toggle
+ label: PLUGIN_ADMIN.OVERRIDE_LOCALE
+ help: PLUGIN_ADMIN.OVERRIDE_LOCALE_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ http_headers:
+ type: tab
+ title: PLUGIN_ADMIN.HTTP_HEADERS
+
+ fields:
+ http_headers_section:
+ type: section
+ title: PLUGIN_ADMIN.HTTP_HEADERS
+ underline: true
+
+ pages.expires:
+ type: text
+ size: small
+ append: GRAV.NICETIME.SECOND_PLURAL
+ label: PLUGIN_ADMIN.EXPIRES
+ help: PLUGIN_ADMIN.EXPIRES_HELP
+ validate:
+ type: number
+ min: 1
+ pages.cache_control:
+ type: text
+ size: medium
+ label: PLUGIN_ADMIN.CACHE_CONTROL
+ help: PLUGIN_ADMIN.CACHE_CONTROL_HELP
+ placeholder: 'e.g. public, max-age=31536000'
+ pages.last_modified:
+ type: toggle
+ label: PLUGIN_ADMIN.LAST_MODIFIED
+ help: PLUGIN_ADMIN.LAST_MODIFIED_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+ pages.etag:
+ type: toggle
+ label: PLUGIN_ADMIN.ETAG
+ help: PLUGIN_ADMIN.ETAG_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+ pages.vary_accept_encoding:
+ type: toggle
+ label: PLUGIN_ADMIN.VARY_ACCEPT_ENCODING
+ help: PLUGIN_ADMIN.VARY_ACCEPT_ENCODING_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ markdown:
+ type: tab
+ title: PLUGIN_ADMIN.MARKDOWN
+
+ fields:
+ markdow_section:
+ type: section
+ title: PLUGIN_ADMIN.MARKDOWN
+ underline: true
+
+ pages.markdown.extra:
+ type: toggle
+ label: Markdown extra
+ help: PLUGIN_ADMIN.MARKDOWN_EXTRA_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+ pages.markdown.auto_line_breaks:
+ type: toggle
+ label: PLUGIN_ADMIN.AUTO_LINE_BREAKS
+ help: PLUGIN_ADMIN.AUTO_LINE_BREAKS_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+ pages.markdown.auto_url_links:
+ type: toggle
+ label: PLUGIN_ADMIN.AUTO_URL_LINKS
+ help: PLUGIN_ADMIN.AUTO_URL_LINKS_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+ pages.markdown.escape_markup:
+ type: toggle
+ label: PLUGIN_ADMIN.ESCAPE_MARKUP
+ help: PLUGIN_ADMIN.ESCAPE_MARKUP_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ caching:
+ type: tab
+ title: PLUGIN_ADMIN.CACHING
+
+ fields:
+ caching_section:
+ type: section
+ title: PLUGIN_ADMIN.CACHING
+ underline: true
+
+ cache.enabled:
+ type: toggle
+ label: PLUGIN_ADMIN.CACHING
+ help: PLUGIN_ADMIN.CACHING_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ cache.check.method:
+ type: select
+ size: medium
+ classes: fancy
+ label: PLUGIN_ADMIN.CACHE_CHECK_METHOD
+ help: PLUGIN_ADMIN.CACHE_CHECK_METHOD_HELP
+ options:
+ file: Markdown + Yaml file timestamps
+ folder: Folder timestamps
+ hash: All files timestamps
+ none: No timestamp checking
+
+ cache.driver:
+ type: select
+ size: small
+ classes: fancy
+ label: PLUGIN_ADMIN.CACHE_DRIVER
+ help: PLUGIN_ADMIN.CACHE_DRIVER_HELP
+ options:
+ auto: Auto detect
+ file: File
+ apc: APC
+ apcu: APCu
+ xcache: Xcache
+ memcache: Memcache
+ memcached: Memcached
+ wincache: WinCache
+ redis: Redis
+
+ cache.prefix:
+ type: text
+ size: x-small
+ label: PLUGIN_ADMIN.CACHE_PREFIX
+ help: PLUGIN_ADMIN.CACHE_PREFIX_HELP
+ placeholder: PLUGIN_ADMIN.CACHE_PREFIX_PLACEHOLDER
+
+ cache.purge_at:
+ type: cron
+ label: PLUGIN_ADMIN.CACHE_PURGE_JOB
+ help: PLUGIN_ADMIN.CACHE_PURGE_JOB_HELP
+ default: '* 4 * * *'
+
+ cache.clear_at:
+ type: cron
+ label: PLUGIN_ADMIN.CACHE_CLEAR_JOB
+ help: PLUGIN_ADMIN.CACHE_CLEAR_JOB_HELP
+ default: '* 3 * * *'
+
+ cache.clear_job_type:
+ type: select
+ size: medium
+ label: PLUGIN_ADMIN.CACHE_JOB_TYPE
+ help: PLUGIN_ADMIN.CACHE_JOB_TYPE_HELP
+ options:
+ standard: Standard Cache Folders
+ all: All Cache Folders
+
+ cache.clear_images_by_default:
+ type: toggle
+ label: PLUGIN_ADMIN.CLEAR_IMAGES_BY_DEFAULT
+ help: PLUGIN_ADMIN.CLEAR_IMAGES_BY_DEFAULT_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ cache.cli_compatibility:
+ type: toggle
+ label: PLUGIN_ADMIN.CLI_COMPATIBILITY
+ help: PLUGIN_ADMIN.CLI_COMPATIBILITY_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ cache.lifetime:
+ type: text
+ size: small
+ append: GRAV.NICETIME.SECOND_PLURAL
+ label: PLUGIN_ADMIN.LIFETIME
+ help: PLUGIN_ADMIN.LIFETIME_HELP
+ validate:
+ type: number
+
+ cache.gzip:
+ type: toggle
+ label: PLUGIN_ADMIN.GZIP_COMPRESSION
+ help: PLUGIN_ADMIN.GZIP_COMPRESSION_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ cache.allow_webserver_gzip:
+ type: toggle
+ label: PLUGIN_ADMIN.ALLOW_WEBSERVER_GZIP
+ help: PLUGIN_ADMIN.ALLOW_WEBSERVER_GZIP_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ cache.memcache.server:
+ type: text
+ size: medium
+ label: PLUGIN_ADMIN.MEMCACHE_SERVER
+ help: PLUGIN_ADMIN.MEMCACHE_SERVER_HELP
+ placeholder: "localhost"
+
+ cache.memcache.port:
+ type: text
+ size: small
+ label: PLUGIN_ADMIN.MEMCACHE_PORT
+ help: PLUGIN_ADMIN.MEMCACHE_PORT_HELP
+ placeholder: "11211"
+
+ cache.memcached.server:
+ type: text
+ size: medium
+ label: PLUGIN_ADMIN.MEMCACHED_SERVER
+ help: PLUGIN_ADMIN.MEMCACHED_SERVER_HELP
+ placeholder: "localhost"
+
+ cache.memcached.port:
+ type: text
+ size: small
+ label: PLUGIN_ADMIN.MEMCACHED_PORT
+ help: PLUGIN_ADMIN.MEMCACHED_PORT_HELP
+ placeholder: "11211"
+
+ cache.redis.socket:
+ type: text
+ size: medium
+ label: PLUGIN_ADMIN.REDIS_SOCKET
+ help: PLUGIN_ADMIN.REDIS_SOCKET_HELP
+ placeholder: "/var/run/redis/redis.sock"
+
+ cache.redis.server:
+ type: text
+ size: medium
+ label: PLUGIN_ADMIN.REDIS_SERVER
+ help: PLUGIN_ADMIN.REDIS_SERVER_HELP
+ placeholder: "localhost"
+
+ cache.redis.port:
+ type: text
+ size: small
+ label: PLUGIN_ADMIN.REDIS_PORT
+ help: PLUGIN_ADMIN.REDIS_PORT_HELP
+ placeholder: "6379"
+
+ cache.redis.password:
+ type: text
+ size: small
+ label: PLUGIN_ADMIN.REDIS_PASSWORD
+
+ twig:
+ type: tab
+ title: PLUGIN_ADMIN.TWIG_TEMPLATING
+
+ fields:
+ twig_section:
+ type: section
+ title: PLUGIN_ADMIN.TWIG_TEMPLATING
+ underline: true
+
+ twig.cache:
+ type: toggle
+ label: PLUGIN_ADMIN.TWIG_CACHING
+ help: PLUGIN_ADMIN.TWIG_CACHING_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ twig.debug:
+ type: toggle
+ label: PLUGIN_ADMIN.TWIG_DEBUG
+ help: PLUGIN_ADMIN.TWIG_DEBUG_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ twig.auto_reload:
+ type: toggle
+ label: PLUGIN_ADMIN.DETECT_CHANGES
+ help: PLUGIN_ADMIN.DETECT_CHANGES_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ twig.autoescape:
+ type: toggle
+ label: PLUGIN_ADMIN.AUTOESCAPE_VARIABLES
+ help: PLUGIN_ADMIN.AUTOESCAPE_VARIABLES_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ twig.umask_fix:
+ type: toggle
+ label: PLUGIN_ADMIN.TWIG_UMASK_FIX
+ help: PLUGIN_ADMIN.TWIG_UMASK_FIX_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ assets:
+ type: tab
+ title: PLUGIN_ADMIN.ASSETS
+
+ fields:
+ assets_section:
+ type: section
+ title: PLUGIN_ADMIN.ASSETS
+ underline: true
+
+ assets.css_pipeline:
+ type: toggle
+ label: PLUGIN_ADMIN.CSS_PIPELINE
+ help: PLUGIN_ADMIN.CSS_PIPELINE_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ assets.css_pipeline_include_externals:
+ type: toggle
+ label: PLUGIN_ADMIN.CSS_PIPELINE_INCLUDE_EXTERNALS
+ help: PLUGIN_ADMIN.CSS_PIPELINE_INCLUDE_EXTERNALS_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ assets.css_pipeline_before_excludes:
+ type: toggle
+ label: PLUGIN_ADMIN.CSS_PIPELINE_BEFORE_EXCLUDES
+ help: PLUGIN_ADMIN.CSS_PIPELINE_BEFORE_EXCLUDES_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ assets.css_minify:
+ type: toggle
+ label: PLUGIN_ADMIN.CSS_MINIFY
+ help: PLUGIN_ADMIN.CSS_MINIFY_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ assets.css_minify_windows:
+ type: toggle
+ label: PLUGIN_ADMIN.CSS_MINIFY_WINDOWS_OVERRIDE
+ help: PLUGIN_ADMIN.CSS_MINIFY_WINDOWS_OVERRIDE_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ assets.css_rewrite:
+ type: toggle
+ label: PLUGIN_ADMIN.CSS_REWRITE
+ help: PLUGIN_ADMIN.CSS_REWRITE_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ assets.js_pipeline:
+ type: toggle
+ label: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE
+ help: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ assets.js_pipeline_include_externals:
+ type: toggle
+ label: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS
+ help: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ assets.js_pipeline_before_excludes:
+ type: toggle
+ label: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES
+ help: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ assets.js_minify:
+ type: toggle
+ label: PLUGIN_ADMIN.JAVASCRIPT_MINIFY
+ help: PLUGIN_ADMIN.JAVASCRIPT_MINIFY_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ assets.enable_asset_timestamp:
+ type: toggle
+ label: PLUGIN_ADMIN.ENABLED_TIMESTAMPS_ON_ASSETS
+ help: PLUGIN_ADMIN.ENABLED_TIMESTAMPS_ON_ASSETS_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ assets.collections:
+ type: multilevel
+ label: PLUGIN_ADMIN.COLLECTIONS
+ placeholder_key: collection_name
+ placeholder_value: collection_path
+ validate:
+ type: array
+
+ errors:
+ type: tab
+ title: PLUGIN_ADMIN.ERROR_HANDLER
+
+ fields:
+ errors_section:
+ type: section
+ title: PLUGIN_ADMIN.ERROR_HANDLER
+ underline: true
+
+ errors.display:
+ type: select
+ label: PLUGIN_ADMIN.DISPLAY_ERRORS
+ help: PLUGIN_ADMIN.DISPLAY_ERRORS_HELP
+ size: medium
+ highlight: 1
+ options:
+ -1: PLUGIN_ADMIN.ERROR_SYSTEM
+ 0: PLUGIN_ADMIN.ERROR_SIMPLE
+ 1: PLUGIN_ADMIN.ERROR_FULL_BACKTRACE
+ validate:
+ type: int
+
+
+ errors.log:
+ type: toggle
+ label: PLUGIN_ADMIN.LOG_ERRORS
+ help: PLUGIN_ADMIN.LOG_ERRORS_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ log.handler:
+ type: select
+ size: small
+ label: PLUGIN_ADMIN.LOG_HANDLER
+ help: PLUGIN_ADMIN.LOG_HANDLER_HELP
+ default: 'file'
+ options:
+ 'file': 'File'
+ 'syslog': 'Syslog'
+
+ log.syslog.facility:
+ type: select
+ size: small
+ label: PLUGIN_ADMIN.SYSLOG_FACILITY
+ help: PLUGIN_ADMIN.SYSLOG_FACILITY_HELP
+ default: local6
+ options:
+ auth: auth
+ authpriv: authpriv
+ cron: cron
+ daemon: daemon
+ kern: kern
+ lpr: lpr
+ mail: mail
+ news: news
+ syslog: syslog
+ user: user
+ uucp: uucp
+ local0: local0
+ local1: local1
+ local2: local2
+ local3: local3
+ local4: local4
+ local5: local5
+ local6: local6
+ local7: local7
+
+ debugger:
+ type: tab
+ title: PLUGIN_ADMIN.DEBUGGER
+
+ fields:
+ debugger_section:
+ type: section
+ title: PLUGIN_ADMIN.DEBUGGER
+ underline: true
+
+ debugger.enabled:
+ type: toggle
+ label: PLUGIN_ADMIN.DEBUGGER
+ help: PLUGIN_ADMIN.DEBUGGER_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ debugger.shutdown.close_connection:
+ type: toggle
+ label: PLUGIN_ADMIN.SHUTDOWN_CLOSE_CONNECTION
+ help: PLUGIN_ADMIN.SHUTDOWN_CLOSE_CONNECTION_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ media:
+ type: tab
+ title: PLUGIN_ADMIN.MEDIA
+
+ fields:
+ media_section:
+ type: section
+ title: PLUGIN_ADMIN.MEDIA
+ underline: true
+
+ images.default_image_quality:
+ type: range
+ append: '%'
+ label: PLUGIN_ADMIN.DEFAULT_IMAGE_QUALITY
+ help: PLUGIN_ADMIN.DEFAULT_IMAGE_QUALITY_HELP
+ validate:
+ min: 1
+ max: 100
+
+ images.cache_all:
+ type: toggle
+ label: PLUGIN_ADMIN.CACHE_ALL
+ help: PLUGIN_ADMIN.CACHE_ALL_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ images.cache_perms:
+ type: select
+ size: small
+ label: PLUGIN_ADMIN.CACHE_PERMS
+ help: PLUGIN_ADMIN.CACHE_PERMS_HELP
+ highlight: '0755'
+ options:
+ '0755': '0755'
+ '0775': '0775'
+
+ images.debug:
+ type: toggle
+ label: PLUGIN_ADMIN.IMAGES_DEBUG
+ help: PLUGIN_ADMIN.IMAGES_DEBUG_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ images.auto_fix_orientation:
+ type: toggle
+ label: PLUGIN_ADMIN.IMAGES_AUTO_FIX_ORIENTATION
+ help: PLUGIN_ADMIN.IMAGES_AUTO_FIX_ORIENTATION_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ images.seofriendly:
+ type: toggle
+ label: PLUGIN_ADMIN.IMAGES_SEOFRIENDLY
+ help: PLUGIN_ADMIN.IMAGES_SEOFRIENDLY_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ media.enable_media_timestamp:
+ type: toggle
+ label: PLUGIN_ADMIN.ENABLE_MEDIA_TIMESTAMP
+ help: PLUGIN_ADMIN.ENABLE_MEDIA_TIMESTAMP_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ media.auto_metadata_exif:
+ type: toggle
+ label: PLUGIN_ADMIN.ENABLE_AUTO_METADATA
+ help: PLUGIN_ADMIN.ENABLE_AUTO_METADATA_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+
+ media.allowed_fallback_types:
+ type: selectize
+ size: large
+ label: PLUGIN_ADMIN.FALLBACK_TYPES
+ help: PLUGIN_ADMIN.FALLBACK_TYPES_HELP
+ classes: fancy
+ validate:
+ type: commalist
+
+ media.unsupported_inline_types:
+ type: selectize
+ size: large
+ label: PLUGIN_ADMIN.INLINE_TYPES
+ help: PLUGIN_ADMIN.INLINE_TYPES_HELP
+ classes: fancy
+ validate:
+ type: commalist
+
+ session:
+ type: tab
+ title: PLUGIN_ADMIN.SESSION
+
+ fields:
+ session_section:
+ type: section
+ title: PLUGIN_ADMIN.SESSION
+ underline: true
+
+ session.enabled:
+ type: hidden
+ label: PLUGIN_ADMIN.ENABLED
+ help: PLUGIN_ADMIN.SESSION_ENABLED_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ default: true
+ validate:
+ type: bool
+
+ session.initialize:
+ type: toggle
+ label: PLUGIN_ADMIN.SESSION_INITIALIZE
+ help: PLUGIN_ADMIN.SESSION_INITIALIZE_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ default: true
+ validate:
+ type: bool
+
+ session.timeout:
+ type: text
+ size: small
+ append: GRAV.NICETIME.SECOND_PLURAL
+ label: PLUGIN_ADMIN.TIMEOUT
+ help: PLUGIN_ADMIN.TIMEOUT_HELP
+ validate:
+ type: number
+ min: 0
+
+ session.name:
+ type: text
+ size: small
+ label: PLUGIN_ADMIN.NAME
+ help: PLUGIN_ADMIN.SESSION_NAME_HELP
+
+ session.uniqueness:
+ type: select
+ size: medium
+ label: PLUGIN_ADMIN.SESSION_UNIQUENESS
+ help: PLUGIN_ADMIN.SESSION_UNIQUENESS_HELP
+ highlight: path
+ default: path
+ options:
+ path: Grav's root file path
+ salt: Grav's random security salt
+
+ session.secure:
+ type: toggle
+ label: PLUGIN_ADMIN.SESSION_SECURE
+ help: PLUGIN_ADMIN.SESSION_SECURE_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ default: false
+ validate:
+ type: bool
+
+ session.httponly:
+ type: toggle
+ label: PLUGIN_ADMIN.SESSION_HTTPONLY
+ help: PLUGIN_ADMIN.SESSION_HTTPONLY_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ default: true
+ validate:
+ type: bool
+
+ session.path:
+ type: text
+ size: small
+ label: PLUGIN_ADMIN.SESSION_PATH
+ help: PLUGIN_ADMIN.SESSION_PATH_HELP
+
+ session.split:
+ type: toggle
+ label: PLUGIN_ADMIN.SESSION_SPLIT
+ help: PLUGIN_ADMIN.SESSION_SPLIT_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ default: true
+ validate:
+ type: bool
+
+ advanced:
+ type: tab
+ title: PLUGIN_ADMIN.ADVANCED
+
+ fields:
+ advanced_section:
+ type: section
+ title: PLUGIN_ADMIN.ADVANCED
+ underline: true
+
+ gpm.releases:
+ type: toggle
+ label: PLUGIN_ADMIN.GPM_RELEASES
+ highlight: stable
+ help: PLUGIN_ADMIN.GPM_RELEASES_HELP
+ options:
+ stable: PLUGIN_ADMIN.STABLE
+ testing: PLUGIN_ADMIN.TESTING
+
+ gpm.proxy_url:
+ type: text
+ size: medium
+ placeholder: "e.g. 127.0.0.1:3128"
+ label: PLUGIN_ADMIN.PROXY_URL
+ help: PLUGIN_ADMIN.PROXY_URL_HELP
+
+ gpm.method:
+ type: toggle
+ label: PLUGIN_ADMIN.GPM_METHOD
+ highlight: auto
+ help: PLUGIN_ADMIN.GPM_METHOD_HELP
+ options:
+ auto: PLUGIN_ADMIN.AUTO
+ fopen: PLUGIN_ADMIN.FOPEN
+ curl: PLUGIN_ADMIN.CURL
+
+ gpm.official_gpm_only:
+ type: toggle
+ label: PLUGIN_ADMIN.GPM_OFFICIAL_ONLY
+ highlight: 1
+ help: PLUGIN_ADMIN.GPM_OFFICIAL_ONLY_HELP
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ default: true
+ validate:
+ type: bool
+
+ gpm.verify_peer:
+ type: toggle
+ label: PLUGIN_ADMIN.GPM_VERIFY_PEER
+ highlight: 1
+ help: PLUGIN_ADMIN.GPM_VERIFY_PEER_HELP
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ reverse_proxy_setup:
+ type: toggle
+ label: PLUGIN_ADMIN.REVERSE_PROXY
+ highlight: 0
+ help: PLUGIN_ADMIN.REVERSE_PROXY_HELP
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ username_regex:
+ type: text
+ size: large
+ label: PLUGIN_ADMIN.USERNAME_REGEX
+ help: PLUGIN_ADMIN.USERNAME_REGEX_HELP
+
+ pwd_regex:
+ type: text
+ size: large
+ label: PLUGIN_ADMIN.PWD_REGEX
+ help: PLUGIN_ADMIN.PWD_REGEX_HELP
+
+ intl_enabled:
+ type: toggle
+ label: PLUGIN_ADMIN.INTL_ENABLED
+ highlight: 1
+ help: PLUGIN_ADMIN.INTL_ENABLED_HELP
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ wrapped_site:
+ type: toggle
+ label: PLUGIN_ADMIN.WRAPPED_SITE
+ highlight: 0
+ help: PLUGIN_ADMIN.WRAPPED_SITE_HELP
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ absolute_urls:
+ type: toggle
+ label: PLUGIN_ADMIN.ABSOLUTE_URLS
+ highlight: 0
+ help: PLUGIN_ADMIN.ABSOLUTE_URLS_HELP
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ param_sep:
+ type: select
+ size: medium
+ label: PLUGIN_ADMIN.PARAMETER_SEPARATOR
+ classes: fancy
+ help: PLUGIN_ADMIN.PARAMETER_SEPARATOR_HELP
+ default: ''
+ options:
+ ':': ': (default)'
+ ';': '; (for Apache running on Windows)'
+
+ force_ssl:
+ type: toggle
+ label: PLUGIN_ADMIN.FORCE_SSL
+ highlight: 0
+ help: PLUGIN_ADMIN.FORCE_SSL_HELP
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ force_lowercase_urls:
+ type: toggle
+ label: PLUGIN_ADMIN.FORCE_LOWERCASE_URLS
+ highlight: 1
+ default: 1
+ help: PLUGIN_ADMIN.FORCE_LOWERCASE_URLS_HELP
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ custom_base_url:
+ type: text
+ size: medium
+ placeholder: "e.g. http://yoursite.com/yourpath"
+ label: PLUGIN_ADMIN.CUSTOM_BASE_URL
+ help: PLUGIN_ADMIN.CUSTOM_BASE_URL_HELP
+
+ accounts.type:
+ type: hidden
+
+ accounts.storage:
+ type: hidden
+
+ strict_mode.yaml_compat:
+ type: toggle
+ label: PLUGIN_ADMIN.STRICT_YAML_COMPAT
+ highlight: 1
+ default: 1
+ help: PLUGIN_ADMIN.STRICT_YAML_COMPAT_HELP
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ strict_mode.twig_compat:
+ type: toggle
+ label: PLUGIN_ADMIN.STRICT_TWIG_COMPAT
+ highlight: 1
+ default: 1
+ help: PLUGIN_ADMIN.STRICT_TWIG_COMPAT_HELP
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
diff --git a/system/blueprints/user/account.yaml b/system/blueprints/user/account.yaml
index ac7717b..897cc98 100644
--- a/system/blueprints/user/account.yaml
+++ b/system/blueprints/user/account.yaml
@@ -4,122 +4,124 @@ form:
fields:
- info:
- type: userinfo
- size: large
+ info:
+ type: userinfo
+ size: large
- avatar:
- type: file
- size: large
- destination: 'user://accounts/avatars'
- multiple: false
- random_name: true
+ avatar:
+ type: file
+ size: large
+ destination: 'user://accounts/avatars'
+ multiple: false
+ random_name: true
- content:
- type: section
- title: PLUGIN_ADMIN.ACCOUNT
- underline: true
+ content:
+ type: section
+ title: PLUGIN_ADMIN.ACCOUNT
+ underline: true
- username:
- type: text
- size: large
- label: PLUGIN_ADMIN.USERNAME
- disabled: true
- readonly: true
+ username:
+ type: text
+ size: large
+ label: PLUGIN_ADMIN.USERNAME
+ disabled: true
+ readonly: true
- email:
- type: email
- size: large
- label: PLUGIN_ADMIN.EMAIL
- validate:
- type: email
- message: PLUGIN_ADMIN.EMAIL_VALIDATION_MESSAGE
- required: true
+ email:
+ type: email
+ size: large
+ label: PLUGIN_ADMIN.EMAIL
+ validate:
+ type: email
+ message: PLUGIN_ADMIN.EMAIL_VALIDATION_MESSAGE
+ required: true
- password:
- type: password
- size: large
- label: PLUGIN_ADMIN.PASSWORD
- autocomplete: new-password
- validate:
- required: false
- message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE
- config-pattern@: system.pwd_regex
+ password:
+ type: password
+ size: large
+ label: PLUGIN_ADMIN.PASSWORD
+ autocomplete: new-password
+ validate:
+ required: false
+ message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE
+ config-pattern@: system.pwd_regex
- fullname:
- type: text
- size: large
- label: PLUGIN_ADMIN.FULL_NAME
- validate:
- required: true
+ fullname:
+ type: text
+ size: large
+ label: PLUGIN_ADMIN.FULL_NAME
+ validate:
+ required: true
- title:
- type: text
- size: large
- label: PLUGIN_ADMIN.TITLE
+ title:
+ type: text
+ size: large
+ label: PLUGIN_ADMIN.TITLE
- language:
- type: select
- label: PLUGIN_ADMIN.LANGUAGE
- size: medium
- classes: fancy
- data-options@: '\Grav\Plugin\Admin\Admin::adminLanguages'
- default: 'en'
- help: PLUGIN_ADMIN.LANGUAGE_HELP
+ language:
+ type: select
+ label: PLUGIN_ADMIN.LANGUAGE
+ size: medium
+ classes: fancy
+ data-options@: '\Grav\Plugin\Admin\Admin::adminLanguages'
+ default: 'en'
+ help: PLUGIN_ADMIN.LANGUAGE_HELP
- twofa_check:
- type: conditional
- condition: config.plugins.admin.twofa_enabled
+ twofa_check:
+ type: conditional
+ condition: config.plugins.admin.twofa_enabled
- fields:
+ fields:
- twofa:
- title: PLUGIN_ADMIN.2FA_TITLE
- type: section
- underline: true
+ 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_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'
- markdown: true
- label: PLUGIN_ADMIN.2FA_SECRET
- sublabel: PLUGIN_ADMIN.2FA_SECRET_HELP
+ twofa_secret:
+ type: 2fa_secret
+ outerclasses: 'twofa-secret'
+ markdown: true
+ label: PLUGIN_ADMIN.2FA_SECRET
+ sublabel: PLUGIN_ADMIN.2FA_SECRET_HELP
- security:
- title: PLUGIN_ADMIN.ACCESS_LEVELS
- type: section
- security: admin.super
- underline: true
+ security:
+ security@: admin.super
+ title: PLUGIN_ADMIN.ACCESS_LEVELS
+ type: section
+ underline: true
- fields:
- groups:
- type: select
- multiple: true
- size: large
- label: PLUGIN_ADMIN.GROUPS
- data-options@: '\Grav\Common\User\Group::groupNames'
- classes: fancy
- help: PLUGIN_ADMIN.GROUPS_HELP
- validate:
- type: commalist
+ fields:
+ groups:
+ security@: admin.super
+ type: select
+ multiple: true
+ size: large
+ label: PLUGIN_ADMIN.GROUPS
+ data-options@: '\Grav\Common\User\Group::groupNames'
+ classes: fancy
+ help: PLUGIN_ADMIN.GROUPS_HELP
+ validate:
+ type: commalist
- access:
- type: permissions
- label: PLUGIN_ADMIN.PERMISSIONS
- ignore_empty: true
- validate:
- type: array
+ access:
+ security@: admin.super
+ type: permissions
+ label: PLUGIN_ADMIN.PERMISSIONS
+ ignore_empty: true
+ validate:
+ type: array
diff --git a/system/blueprints/user/accounts.yaml b/system/blueprints/user/accounts.yaml
new file mode 100644
index 0000000..892a14a
--- /dev/null
+++ b/system/blueprints/user/accounts.yaml
@@ -0,0 +1,29 @@
+title: User Accounts
+description: User Accounts
+type: flex-objects
+
+extends@: 'user/account'
+
+config:
+ admin:
+ list:
+ fields:
+ username:
+ link: edit
+ search: true
+ email:
+ search: true
+ fullname:
+ search: true
+ options:
+ per_page: 20
+ order:
+ by: username
+ dir: asc
+
+ menu:
+ list:
+ route: '/accounts'
+ title: Accounts
+ icon: fa-users
+ authorize: ['admin.users', 'admin.accounts', 'admin.super']
diff --git a/system/config/backups.yaml b/system/config/backups.yaml
new file mode 100644
index 0000000..0d3c41e
--- /dev/null
+++ b/system/config/backups.yaml
@@ -0,0 +1,15 @@
+purge:
+ trigger: space
+ max_backups_count: 25
+ max_backups_space: 5
+ max_backups_time: 365
+
+profiles:
+ -
+ name: 'Default Site Backup'
+ root: '/'
+ schedule: false
+ schedule_at: '0 3 * * *'
+ exclude_paths: "/backup\r\n/cache\r\n/images\r\n/logs\r\n/tmp"
+ exclude_files: ".DS_Store\r\n.git\r\n.svn\r\n.hg\r\n.idea\r\n.vscode\r\nnode_modules"
+
diff --git a/system/config/security.yaml b/system/config/security.yaml
index 77e5de0..964e682 100644
--- a/system/config/security.yaml
+++ b/system/config/security.yaml
@@ -5,6 +5,13 @@ xss_enabled:
moz_binding: true
html_inline_styles: true
dangerous_tags: true
+xss_invalid_protocols:
+ - javascript
+ - livescript
+ - vbscript
+ - mocha
+ - feed
+ - data
xss_dangerous_tags:
- applet
- meta
diff --git a/system/config/site.yaml b/system/config/site.yaml
index bb2ee6e..292e49d 100644
--- a/system/config/site.yaml
+++ b/system/config/site.yaml
@@ -17,17 +17,17 @@ summary:
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
+# '/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
-# '/new/(.*)': '/blog/$1' # Regex any /new/my-page URL to /blog/my-page Route
+# '/something/else': '/blog/sample-3' # Alias for /blog/sample-3
+# '/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
+#menu: # Menu Example
# - text: Source
# icon: github
# url: https://github.com/getgrav/grav
diff --git a/system/config/streams.yaml b/system/config/streams.yaml
index 9053b90..1af0024 100644
--- a/system/config/streams.yaml
+++ b/system/config/streams.yaml
@@ -1,6 +1,6 @@
schemes:
image:
- type: ReadOnlyStream
+ type: Stream
paths:
- user://images
- system://images
diff --git a/system/config/system.yaml b/system/config/system.yaml
index 065e059..405a961 100644
--- a/system/config/system.yaml
+++ b/system/config/system.yaml
@@ -13,7 +13,9 @@ intl_enabled: true # Special logic for PHP Interna
languages:
supported: [] # List of languages supported. eg: [en, fr, de]
+ default_lang: # Default is the first supported language. Must be one of the supported languages
include_default_lang: true # Include the default lang prefix in all URLs
+ pages_fallback_only: false # Only fallback to find page content through supported languages
translations: true # Enable translations by default
translations_fallback: true # Fallback through supported translations if active lang doesn't exist
session_store_active: false # Store active language in session
@@ -52,7 +54,7 @@ pages:
special_chars: # List of special characters to automatically convert to entities
'>': 'gt'
'<': 'lt'
- types: [txt,xml,html,htm,json,rss,atom] # list of valid page types
+ types: [html,htm,xml,txt,json,rss,atom] # list of valid page types
append_url_extension: '' # Append page's extension in Page urls (e.g. '.html' results in /path/page.html)
expires: 604800 # Page expires time in seconds (604800 seconds = 7 days)
cache_control: # Can be blank for no setting, or a valid `cache-control` text value
@@ -65,6 +67,7 @@ pages:
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
+ hide_empty_folders: false # If folder has no .md file, should it be hidden
url_taxonomy_filters: true # Enable auto-magic URL-based taxonomy filters for page collections
frontmatter:
process_twig: false # Should the frontmatter be processed to replace Twig variables?
@@ -74,8 +77,11 @@ cache:
enabled: true # Set to true to enable caching
check:
method: file # Method to check for updates in pages: file|folder|hash|none
- driver: auto # One of: auto|file|apc|xcache|memcache|wincache
+ driver: auto # One of: auto|file|apcu|memcache|wincache
prefix: 'g' # Cache prefix string (prevents cache conflicts)
+ purge_at: '0 4 * * *' # How often to purge old file cache (using new scheduler)
+ clear_at: '0 3 * * *' # How often to clear cache (using new scheduler)
+ clear_job_type: 'standard' # Type to clear when processing the scheduled clear job `standard`|`all`
clear_images_by_default: true # By default grav will include processed images in cache clear, this can be disabled
cli_compatibility: false # Ensures only non-volatile drivers are used (file, redis, memcache, etc.)
lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite)
@@ -112,6 +118,11 @@ errors:
display: 0 # Display either (1) Full backtrace | (0) Simple Error | (-1) System Error
log: true # Log errors to /logs folder
+log:
+ handler: file # Log handler. Currently supported: file | syslog
+ syslog:
+ facility: local6 # Syslog facilities output
+
debugger:
enabled: false # Enable Grav debugger and following settings
shutdown:
@@ -123,6 +134,7 @@ images:
cache_perms: '0755' # MUST BE IN QUOTES!! Default cache folder perms. Usually '0755' or '0775'
debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example
auto_fix_orientation: false # Automatically fix the image orientation based on the Exif data
+ seofriendly: false # SEO-friendly processed image names
media:
enable_media_timestamp: false # Enable media timestamps
@@ -135,6 +147,7 @@ session:
initialize: true # Initialize session from Grav (if false, plugin needs to start the session)
timeout: 1800 # Timeout in seconds
name: grav-site # Name prefix of the session cookie. Use alphanumeric, dashes or underscores only. Do not use dots in the session name
+ uniqueness: path # Should sessions be `path` based or `security.salt` based
secure: false # Set session secure. If true, indicates that communication for this cookie must be over an encrypted transmission. Enable this only on sites that run exclusively on HTTPS
httponly: true # Set session HTTP only. If true, indicates that cookies should be used only over HTTP, and JavaScript modification is not allowed.
split: true # Sessions should be independent between site and plugins (such as admin)
@@ -147,6 +160,10 @@ gpm:
verify_peer: true # Sometimes on some systems (Windows most commonly) GPM is unable to connect because the SSL certificate cannot be verified. Disabling this setting might help.
official_gpm_only: true # By default GPM direct-install will only allow URLs via the official GPM proxy to ensure security
+accounts:
+ type: data # Account type: data or flex
+ storage: file # Flex storage type: file or folder
+
strict_mode:
yaml_compat: true # Grav 1.5+: Enables YAML backwards compatibility
twig_compat: true # Grav 1.5+: Enables deprecated Twig autoescape setting (autoescape: false)
diff --git a/system/defines.php b/system/defines.php
index 0c21d8d..3af6ede 100644
--- a/system/defines.php
+++ b/system/defines.php
@@ -1,19 +1,19 @@
فشل التحقق من صحة:'
- INVALID_INPUT: إدخال غير صحيح في
- MISSING_REQUIRED_FIELD: 'حقل مطلوب مفقود:'
-MONTHS_OF_THE_YEAR:
- - كانون الثاني
- - شباط
- - آذار/ مارس
- - نيسان
- - أيار
- - حزيران
- - تموز
- - آب
- - أيلول
- - تشرين الأول
- - تشرين الثاني
- - كانون الأول
-DAYS_OF_THE_WEEK:
- - الاثنين
- - الثلاثاء
- - الأربعاء
- - الخميس
- - الجمعة
- - السبت
- - الأحد
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\nالعنوان: %1$s\n---\n# خطأ: مادة أمامية غير صحيحة\n\nمسار: '%2$s'\n\n**%3$s**\n\n, , ,\n\n%4$s\n, , ,"
+ NICETIME:
+ NO_DATE_PROVIDED: لم يتم تقديم التاريخ
+ BAD_DATE: تاريخ خاطئ
+ AGO: من قبل
+ FROM_NOW: من الآن
+ SECOND: ثانية
+ MINUTE: دقيقة
+ HOUR: ساعة
+ DAY: يوم
+ WEEK: أسبوع
+ MONTH: شهر
+ YEAR: سنة
+ DECADE: عقد
+ SEC: ثانية
+ MIN: دقيقة
+ HR: ساعة
+ WK: أسبوع
+ MO: شهر
+ YR: سنة
+ DEC: عقد
+ SECOND_PLURAL: ثواني
+ MINUTE_PLURAL: 'دقائق'
+ HOUR_PLURAL: ساعات
+ DAY_PLURAL: أيام
+ WEEK_PLURAL: أسابيع
+ MONTH_PLURAL: أشهر
+ YEAR_PLURAL: سنوات
+ DECADE_PLURAL: عقود
+ SEC_PLURAL: ثواني
+ MIN_PLURAL: دقائق
+ HR_PLURAL: ساعات
+ WK_PLURAL: أسابيع
+ MO_PLURAL: أشهر
+ YR_PLURAL: سنوات
+ DEC_PLURAL: عقود
+ FORM:
+ VALIDATION_FAIL: فشل التحقق من صحة:
+ INVALID_INPUT: إدخال غير صحيح في
+ MISSING_REQUIRED_FIELD: 'حقل مطلوب مفقود:'
+ MONTHS_OF_THE_YEAR:
+ - 'كانون الثاني'
+ - 'شباط'
+ - 'آذار/ مارس'
+ - 'نيسان'
+ - 'أيار'
+ - 'حزيران'
+ - 'تموز'
+ - 'آب'
+ - 'أيلول'
+ - 'تشرين الأول'
+ - 'تشرين الثاني'
+ - 'كانون الأول'
+ DAYS_OF_THE_WEEK:
+ - 'الاثنين'
+ - 'الثلاثاء'
+ - 'الأربعاء'
+ - 'الخميس'
+ - 'الجمعة'
+ - 'السبت'
+ - 'الأحد'
diff --git a/system/languages/bg.yaml b/system/languages/bg.yaml
new file mode 100644
index 0000000..48daceb
--- /dev/null
+++ b/system/languages/bg.yaml
@@ -0,0 +1,62 @@
+---
+GRAV:
+ NICETIME:
+ NO_DATE_PROVIDED: Не е въведена дата
+ BAD_DATE: Невалидна дата
+ AGO: преди
+ FROM_NOW: от сега
+ SECOND: секунда
+ MINUTE: минута
+ HOUR: час
+ DAY: ден
+ WEEK: седмица
+ MONTH: месец
+ YEAR: година
+ DECADE: десетилетие
+ SEC: сек
+ MIN: мин
+ HR: ч
+ WK: седм
+ MO: мес
+ YR: г
+ DEC: дстлт
+ SECOND_PLURAL: секунди
+ MINUTE_PLURAL: минути
+ HOUR_PLURAL: часа
+ DAY_PLURAL: дена
+ WEEK_PLURAL: седмици
+ MONTH_PLURAL: месеца
+ YEAR_PLURAL: години
+ DECADE_PLURAL: десетилетия
+ SEC_PLURAL: сек
+ MIN_PLURAL: мин
+ HR_PLURAL: ч
+ WK_PLURAL: седм
+ MO_PLURAL: мес
+ YR_PLURAL: г
+ DEC_PLURAL: дстлт
+ FORM:
+ VALIDATION_FAIL: Неуспешна проверка:
+ INVALID_INPUT: Невалидно въвеждане в
+ MISSING_REQUIRED_FIELD: 'Липсва задължително поле:'
+ MONTHS_OF_THE_YEAR:
+ - 'януари'
+ - 'февруари'
+ - 'март'
+ - 'април'
+ - 'май'
+ - 'юни'
+ - 'юли'
+ - 'август'
+ - 'септември'
+ - 'октомври'
+ - 'ноември'
+ - 'декември'
+ DAYS_OF_THE_WEEK:
+ - 'понеделник'
+ - 'вторник'
+ - 'сряда'
+ - 'четвъртък'
+ - 'петък'
+ - 'събота'
+ - 'неделя'
diff --git a/system/languages/ca.yaml b/system/languages/ca.yaml
index b9906e6..bd04474 100644
--- a/system/languages/ca.yaml
+++ b/system/languages/ca.yaml
@@ -1,75 +1,62 @@
---
-FRONTMATTER_ERROR_PAGE: |
- ---
- title: %1$s
- ---
-
- # S'ha produït un error: Frontmatter invàlid
-
- Ruta: `%2$s`
-
- **%3$s**
-
- ```
- %4$s
- ```
-NICETIME:
- NO_DATE_PROVIDED: "No s'ha proporcionat data"
- BAD_DATE: Data invàlida
- AGO: abans
- FROM_NOW: "des d'ara"
- SECOND: segon
- MINUTE: minut
- HOUR: hora
- DAY: dia
- WEEK: setmana
- MONTH: mes
- YEAR: any
- DECADE: dècada
- SEC: s
- MIN: min
- HR: h
- WK: setm.
- MO: m.
- YR: a.
- DEC: dèc.
- SECOND_PLURAL: segons
- MINUTE_PLURAL: minuts
- HOUR_PLURAL: hores
- DAY_PLURAL: dies
- WEEK_PLURAL: setmanes
- MONTH_PLURAL: mesos
- YEAR_PLURAL: anys
- DECADE_PLURAL: dècades
- SEC_PLURAL: s
- MIN_PLURAL: min
- HR_PLURAL: h
- WK_PLURAL: setm.
- MO_PLURAL: mesos
- YR_PLURAL: anys
- DEC_PLURAL: dèc.
-FORM:
- VALIDATION_FAIL: 'Ha fallat la validació:'
- INVALID_INPUT: Entrada no vàlida a
- MISSING_REQUIRED_FIELD: 'Falta camp obligatori:'
-MONTHS_OF_THE_YEAR:
- - Gener
- - Febrer
- - Març
- - Abril
- - Maig
- - Juny
- - Juliol
- - Agost
- - Setembre
- - Octubre
- - Novembre
- - Desembre
-DAYS_OF_THE_WEEK:
- - Dilluns
- - Dimarts
- - Dimecres
- - Dijous
- - Divendres
- - Dissabte
- - Diumenge
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# S'ha produït un error: Frontmatter invàlid\n\nRuta: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ NICETIME:
+ NO_DATE_PROVIDED: No s'ha proporcionat data
+ BAD_DATE: Data invàlida
+ AGO: abans
+ FROM_NOW: des d'ara
+ SECOND: segon
+ MINUTE: minut
+ HOUR: hora
+ DAY: dia
+ WEEK: setmana
+ MONTH: mes
+ YEAR: any
+ DECADE: dècada
+ SEC: s
+ HR: h
+ WK: setm.
+ MO: m.
+ YR: a.
+ DEC: dèc.
+ SECOND_PLURAL: segons
+ MINUTE_PLURAL: minuts
+ HOUR_PLURAL: hores
+ DAY_PLURAL: dies
+ WEEK_PLURAL: setmanes
+ MONTH_PLURAL: mesos
+ YEAR_PLURAL: anys
+ DECADE_PLURAL: dècades
+ SEC_PLURAL: s
+ MIN_PLURAL: min
+ HR_PLURAL: h
+ WK_PLURAL: setm.
+ MO_PLURAL: mesos
+ YR_PLURAL: anys
+ DEC_PLURAL: dèc.
+ FORM:
+ VALIDATION_FAIL: Ha fallat la validació:
+ INVALID_INPUT: Entrada no vàlida a
+ MISSING_REQUIRED_FIELD: 'Falta camp obligatori:'
+ MONTHS_OF_THE_YEAR:
+ - 'Gener'
+ - 'Febrer'
+ - 'Març'
+ - 'Abril'
+ - 'Maig'
+ - 'Juny'
+ - 'Juliol'
+ - 'Agost'
+ - 'Setembre'
+ - 'Octubre'
+ - 'Novembre'
+ - 'Desembre'
+ DAYS_OF_THE_WEEK:
+ - 'Dilluns'
+ - 'Dimarts'
+ - 'Dimecres'
+ - 'Dijous'
+ - 'Divendres'
+ - 'Dissabte'
+ - 'Diumenge'
diff --git a/system/languages/cs.yaml b/system/languages/cs.yaml
index 68a49eb..80844c9 100644
--- a/system/languages/cs.yaml
+++ b/system/languages/cs.yaml
@@ -1,81 +1,144 @@
---
-INFLECTOR_UNCOUNTABLE:
- - vybavení
- - informace
- - rýže
- - peníze
- - druhy
- - série
- - ryba
- - ovce
-INFLECTOR_IRREGULAR:
- person: lidé
- man: muži
- child: děti
- sex: pohlaví
- move: pohyby
-INFLECTOR_ORDINALS:
- default: '.'
- first: '.'
- second: '.'
- third: '.'
-NICETIME:
- NO_DATE_PROVIDED: Datum nebylo vloženo
- BAD_DATE: Chybné datum
- AGO: zpět
- FROM_NOW: od teď
- SECOND: sekunda
- MINUTE: minuta
- HOUR: hodina
- DAY: den
- WEEK: týden
- MONTH: měsíc
- YEAR: rok
- DECADE: dekáda
- SEC: sek
- MIN: min
- HR: hod
- WK: t
- MO: m
- YR: r
- DEC: dek
- SECOND_PLURAL: sekundy
- MINUTE_PLURAL: minuty
- HOUR_PLURAL: hodiny
- DAY_PLURAL: dny
- WEEK_PLURAL: týdny
- MONTH_PLURAL: měsíce
- YEAR_PLURAL: roky
- DECADE_PLURAL: dekády
- SEC_PLURAL: sek
- MIN_PLURAL: min
- HR_PLURAL: hod
- WK_PLURAL: t
- MO_PLURAL: m
- YR_PLURAL: r
- DEC_PLURAL: dek
-FORM:
- VALIDATION_FAIL: 'Ověření se nezdařilo:'
- INVALID_INPUT: Neplatný vstup v
- MISSING_REQUIRED_FIELD: 'Chybí požadované pole:'
-MONTHS_OF_THE_YEAR:
- - ledna
- - února
- - března
- - dubna
- - května
- - června
- - července
- - srpna
- - září
- - října
- - listopadu
- - prosince
-DAYS_OF_THE_WEEK:
- - Pondělí
- - Úterý
- - Středa
- - Čtvrtek
- - Pátek
- - Sobota
- - Neděle
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Chyba: Chybný frontmatter\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ INFLECTOR_PLURALS:
+ '/(quiz)$/i': '\1zes'
+ '/^(ox)$/i': '\1en'
+ '/([m|l])ouse$/i': '\1ice'
+ '/(matr|vert|ind)ix|ex$/i': '\1ices'
+ '/(x|ch|ss|sh)$/i': '\1es'
+ '/([^aeiouy]|qu)ies$/i': '\1y'
+ '/([^aeiouy]|qu)y$/i': '\1ies'
+ '/(hive)$/i': '\1s'
+ '/(?:([^f])fe|([lr])f)$/i': '\1\2ves'
+ '/sis$/i': 'ses'
+ '/([ti])um$/i': '\1a'
+ '/(buffal|tomat)o$/i': '\1oes'
+ '/(bu)s$/i': '\1ses'
+ '/(alias|status)/i': '\1es'
+ '/(octop|vir)us$/i': '\1i'
+ '/(ax|test)is$/i': '\1es'
+ '/s$/i': 's'
+ '/$/': 's'
+ INFLECTOR_SINGULAR:
+ '/(quiz)zes$/i': '\1'
+ '/(matr)ices$/i': '\1ix'
+ '/(vert|ind)ices$/i': '\1ex'
+ '/^(ox)en/i': '\1'
+ '/(alias|status)es$/i': '\1'
+ '/([octop|vir])i$/i': '\1us'
+ '/(cris|ax|test)es$/i': '\1is'
+ '/(shoe)s$/i': '\1'
+ '/(o)es$/i': '\1'
+ '/(bus)es$/i': '\1'
+ '/([m|l])ice$/i': '\1ouse'
+ '/(x|ch|ss|sh)es$/i': '\1'
+ '/(m)ovies$/i': '\1ovie'
+ '/(s)eries$/i': '\1eries'
+ '/([^aeiouy]|qu)ies$/i': '\1y'
+ '/([lr])ves$/i': '\1f'
+ '/(tive)s$/i': '\1'
+ '/(hive)s$/i': '\1'
+ '/([^f])ves$/i': '\1fe'
+ '/(^analy)ses$/i': '\1sis'
+ '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2sis'
+ '/([ti])a$/i': '\1um'
+ '/(n)ews$/i': '\1ews'
+ INFLECTOR_UNCOUNTABLE:
+ - 'vybavení'
+ - 'informace'
+ - 'rýže'
+ - 'peníze'
+ - 'druhy'
+ - 'série'
+ - 'ryba'
+ - 'ovce'
+ INFLECTOR_IRREGULAR:
+ 'person': 'lidé'
+ 'man': 'muži'
+ 'child': 'děti'
+ 'sex': 'pohlaví'
+ 'move': 'pohyby'
+ INFLECTOR_ORDINALS:
+ 'default': '.'
+ 'first': '.'
+ 'second': '.'
+ 'third': '.'
+ NICETIME:
+ NO_DATE_PROVIDED: Datum nebylo vloženo
+ BAD_DATE: Chybné datum
+ AGO: zpět
+ FROM_NOW: od teď
+ JUST_NOW: právě teď
+ SECOND: sekunda
+ MINUTE: minuta
+ HOUR: hodina
+ DAY: den
+ WEEK: týden
+ MONTH: měsíc
+ YEAR: rok
+ DECADE: dekáda
+ SEC: sek
+ MIN: min
+ HR: hod
+ WK: t
+ MO: m
+ YR: r
+ DEC: dek
+ SECOND_PLURAL: sekundy
+ MINUTE_PLURAL: minuty
+ HOUR_PLURAL: hodiny
+ DAY_PLURAL: dny
+ WEEK_PLURAL: týdny
+ MONTH_PLURAL: měsíce
+ YEAR_PLURAL: roky
+ DECADE_PLURAL: dekády
+ SEC_PLURAL: sek
+ MIN_PLURAL: min
+ HR_PLURAL: hod
+ WK_PLURAL: t
+ MO_PLURAL: m
+ YR_PLURAL: r
+ DEC_PLURAL: dek
+ FORM:
+ VALIDATION_FAIL: Ověření se nezdařilo:
+ INVALID_INPUT: Neplatný vstup v
+ MISSING_REQUIRED_FIELD: 'Chybí požadované pole:'
+ MONTHS_OF_THE_YEAR:
+ - 'leden'
+ - 'únor'
+ - 'březen'
+ - 'duben'
+ - 'květen'
+ - 'červen'
+ - 'červenec'
+ - 'srpen'
+ - 'září'
+ - 'říjen'
+ - 'listopad'
+ - 'prosinec'
+ DAYS_OF_THE_WEEK:
+ - 'pondělí'
+ - 'úterý'
+ - 'středa'
+ - 'čtvrtek'
+ - 'pátek'
+ - 'sobota'
+ - 'neděle'
+ CRON:
+ EVERY: každý
+ EVERY_HOUR: každou hodinu
+ EVERY_MINUTE: každou minutu
+ EVERY_DAY_OF_WEEK: každý den v týdnu
+ EVERY_DAY_OF_MONTH: každý den v měsíci
+ EVERY_MONTH: každý měsíc
+ TEXT_PERIOD: Every
+ TEXT_MINS: ' at minute(s) past the hour'
+ TEXT_TIME: ' at :'
+ TEXT_DOW: ' on '
+ TEXT_MONTH: ' of '
+ TEXT_DOM: ' on '
+ ERROR1: Tag %s není podporován!
+ ERROR2: Chybný počet prvků
+ ERROR3: jquery_element musí být nastaven v nastaveních pro jqCron
+ ERROR4: Nerozpoznaný výraz
diff --git a/system/languages/da.yaml b/system/languages/da.yaml
index 47f5a45..17ce683 100644
--- a/system/languages/da.yaml
+++ b/system/languages/da.yaml
@@ -1,75 +1,62 @@
---
-FRONTMATTER_ERROR_PAGE: |
- ---
- Titel: %1$s
- ---
-
- # Fejl: Ugyldigt frontmatter
-
- Sti: `%2$s`
-
- **%3$s**
-
- ```
- %4$s
- ```
-NICETIME:
- NO_DATE_PROVIDED: Ingen dato angivet
- BAD_DATE: Ugyldig dato
- AGO: siden
- FROM_NOW: fra nu
- SECOND: sekund
- MINUTE: minut
- HOUR: time
- DAY: dag
- WEEK: uge
- MONTH: måned
- YEAR: år
- DECADE: årti
- SEC: sek
- MIN: min
- HR: t
- WK: u
- MO: md
- YR: år
- DEC: årti
- SECOND_PLURAL: sekunder
- MINUTE_PLURAL: minutter
- HOUR_PLURAL: timer
- DAY_PLURAL: dage
- WEEK_PLURAL: uger
- MONTH_PLURAL: måneder
- YEAR_PLURAL: år
- DECADE_PLURAL: årtier
- SEC_PLURAL: sek
- MIN_PLURAL: min
- HR_PLURAL: timer
- WK_PLURAL: uger
- MO_PLURAL: mdr
- YR_PLURAL: år
- DEC_PLURAL: årtier
-FORM:
- VALIDATION_FAIL: 'Validering mislykkedes:'
- INVALID_INPUT: Ugyldigt input i
- MISSING_REQUIRED_FIELD: 'Mangler obligatorisk felt:'
-MONTHS_OF_THE_YEAR:
- - Januar
- - Februar
- - Marts
- - April
- - Maj
- - Juni
- - Juli
- - August
- - September
- - Oktober
- - November
- - December
-DAYS_OF_THE_WEEK:
- - Mandag
- - Tirsdag
- - Onsdag
- - Torsdag
- - Fredag
- - Lørdag
- - Søndag
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\nTitel: %1$s\n---\n\n# Fejl: Ugyldigt frontmatter\n\nSti: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ NICETIME:
+ NO_DATE_PROVIDED: Ingen dato angivet
+ BAD_DATE: Ugyldig dato
+ AGO: siden
+ FROM_NOW: fra nu
+ SECOND: sekund
+ MINUTE: minut
+ HOUR: time
+ DAY: dag
+ WEEK: uge
+ MONTH: måned
+ YEAR: år
+ DECADE: årti
+ SEC: sek
+ HR: t
+ WK: u
+ MO: md
+ YR: år
+ DEC: årti
+ SECOND_PLURAL: sekunder
+ MINUTE_PLURAL: minutter
+ HOUR_PLURAL: timer
+ DAY_PLURAL: dage
+ WEEK_PLURAL: uger
+ MONTH_PLURAL: måneder
+ YEAR_PLURAL: år
+ DECADE_PLURAL: årtier
+ SEC_PLURAL: sek
+ MIN_PLURAL: min
+ HR_PLURAL: timer
+ WK_PLURAL: uger
+ MO_PLURAL: mdr
+ YR_PLURAL: år
+ DEC_PLURAL: årtier
+ FORM:
+ VALIDATION_FAIL: Validering mislykkedes:
+ INVALID_INPUT: Ugyldigt input i
+ MISSING_REQUIRED_FIELD: 'Mangler obligatorisk felt:'
+ MONTHS_OF_THE_YEAR:
+ - 'januar'
+ - 'februar'
+ - 'mars'
+ - 'april'
+ - 'mai'
+ - 'juni'
+ - 'juli'
+ - 'august'
+ - 'september'
+ - 'oktober'
+ - 'november'
+ - 'desember'
+ DAYS_OF_THE_WEEK:
+ - 'mandag'
+ - 'tirsdag'
+ - 'onsdag'
+ - 'torsdag'
+ - 'fredag'
+ - 'lørdag'
+ - 'søndag'
diff --git a/system/languages/de.yaml b/system/languages/de.yaml
index 9dff4cb..e9a2d4b 100644
--- a/system/languages/de.yaml
+++ b/system/languages/de.yaml
@@ -1,89 +1,144 @@
---
-FRONTMATTER_ERROR_PAGE: |
- ---
- title: %1$s
- ---
- # Fehler: Frontmatter enthält Fehler
-
- Pfad: `%2$s`
-
- **%3$s **
-
- ```
- %4$s
- ```
-INFLECTOR_UNCOUNTABLE:
- 1: Informationen
- 2: Reis
- 3: Geld
-INFLECTOR_IRREGULAR:
- person: Personen
- man: Menschen
- child: Kinder
- sex: Geschlecht
- move: Züge
-INFLECTOR_ORDINALS:
- default: '.'
- first: '.'
- second: '.'
- third: '.'
-NICETIME:
- NO_DATE_PROVIDED: Kein Datum angegeben
- BAD_DATE: Falsches Datum
- AGO: her
- FROM_NOW: ab jetzt
- SECOND: Sekunde
- MINUTE: Minute
- HOUR: Stunde
- DAY: Tag
- WEEK: Woche
- MONTH: Monat
- YEAR: Jahr
- DECADE: Jahrzehnt
- SEC: Sek.
- MIN: Min.
- HR: Std.
- WK: Wo.
- MO: Mo.
- YR: J.
- DEC: Dek.
- SECOND_PLURAL: Sekunden
- MINUTE_PLURAL: Minuten
- HOUR_PLURAL: Stunden
- DAY_PLURAL: Tage
- WEEK_PLURAL: Wochen
- MONTH_PLURAL: Monate
- YEAR_PLURAL: Jahre
- DECADE_PLURAL: Jahrzehnte
- SEC_PLURAL: Sekunden
- MIN_PLURAL: Minuten
- HR_PLURAL: Stunden
- WK_PLURAL: Wochen
- MO_PLURAL: Monate
- YR_PLURAL: Jahre
- DEC_PLURAL: Jahrzehnten
-FORM:
- VALIDATION_FAIL: 'Überprüfung fehlgeschlagen:'
- INVALID_INPUT: Ungültige Eingabe in
- MISSING_REQUIRED_FIELD: 'Erforderliches Feld fehlt:'
-MONTHS_OF_THE_YEAR:
- - Januar
- - Februar
- - März
- - April
- - Mai
- - Juni
- - Juli
- - August
- - September
- - Oktober
- - November
- - Dezember
-DAYS_OF_THE_WEEK:
- - Montag
- - Dienstag
- - Mittwoch
- - Donnerstag
- - Freitag
- - Samstag
- - Sonntag
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n# Fehler: Frontmatter enthält Fehler\n\nPfad: `%2$s`\n\n**%3$s ** \n\n```\n%4$s\n```"
+ INFLECTOR_PLURALS:
+ '/(quiz)$/i': '\1zes'
+ '/^(ox)$/i': '\1en'
+ '/([m|l])ouse$/i': '\1ice'
+ '/(matr|vert|ind)ix|ex$/i': '\1ice'
+ '/(x|ch|ss|sh)$/i': '\1es'
+ '/([^aeiouy]|qu)ies$/i': '\1y'
+ '/([^aeiouy]|qu)y$/i': '\1ies'
+ '/(hive)$/i': '\1s'
+ '/(?:([^f])fe|([lr])f)$/i': '\1\2ves'
+ '/sis$/i': 'ses'
+ '/([ti])um$/i': '\1a'
+ '/(buffal|tomat)o$/i': '\1oes'
+ '/(bu)s$/i': '\1ses'
+ '/(alias|status)/i': '\1es'
+ '/(octop|vir)us$/i': '\1i'
+ '/(ax|test)is$/i': '\1es'
+ '/s$/i': 's'
+ '/$/': 's'
+ INFLECTOR_SINGULAR:
+ '/(quiz)zes$/i': '\1'
+ '/(matr)ices$/i': '\1ix'
+ '/(vert|ind)ices$/i': '\1ex'
+ '/^(ox)en/i': '\1'
+ '/(alias|status)es$/i': '\1'
+ '/([octop|vir])i$/i': '\1us'
+ '/(cris|ax|test)es$/i': '\1ies'
+ '/(shoe)s$/i': '\1'
+ '/(o)es$/i': '\1'
+ '/(bus)es$/i': '\1'
+ '/([m|l])ice$/i': '\1ouse'
+ '/(x|ch|ss|sh)es$/i': '\1'
+ '/(m)ovies$/i': '\1ovie'
+ '/(s)eries$/i': '\1eries'
+ '/([^aeiouy]|qu)ies$/i': '\1y'
+ '/([lr])ves$/i': '\1f'
+ '/(tive)s$/i': '\1'
+ '/(hive)s$/i': '\1'
+ '/([^f])ves$/i': '\1fe'
+ '/(^analy)ses$/i': '\1sis'
+ '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2ves'
+ '/([ti])a$/i': '\1um'
+ '/(n)ews$/i': '\1ews'
+ INFLECTOR_UNCOUNTABLE:
+ - 'Ausstattung'
+ - 'Informationen'
+ - 'Reis'
+ - 'Geld'
+ - 'Arten'
+ - 'Serie'
+ - 'Fisch'
+ - 'Schaf'
+ INFLECTOR_IRREGULAR:
+ 'person': 'Personen'
+ 'man': 'Menschen'
+ 'child': 'Kinder'
+ 'sex': 'Geschlecht'
+ 'move': 'Züge'
+ INFLECTOR_ORDINALS:
+ 'default': '.'
+ 'first': '.'
+ 'second': '.'
+ 'third': '.'
+ NICETIME:
+ NO_DATE_PROVIDED: Kein Datum angegeben
+ BAD_DATE: Falsches Datum
+ AGO: her
+ FROM_NOW: ab jetzt
+ JUST_NOW: jetzt gerade
+ SECOND: Sekunde
+ MINUTE: Minute
+ HOUR: Stunde
+ DAY: Tag
+ WEEK: Woche
+ MONTH: Monat
+ YEAR: Jahr
+ DECADE: Jahrzehnt
+ SEC: Sek.
+ MIN: Min.
+ HR: Std.
+ WK: Wo.
+ MO: Mo.
+ YR: J.
+ DEC: Dez
+ SECOND_PLURAL: Sekunden
+ MINUTE_PLURAL: Minuten
+ HOUR_PLURAL: Stunden
+ DAY_PLURAL: Tage
+ WEEK_PLURAL: Wochen
+ MONTH_PLURAL: Monate
+ YEAR_PLURAL: Jahre
+ DECADE_PLURAL: Jahrzehnte
+ SEC_PLURAL: Sekunden
+ MIN_PLURAL: Minuten
+ HR_PLURAL: Stunden
+ WK_PLURAL: Wochen
+ MO_PLURAL: Monate
+ YR_PLURAL: Jahre
+ DEC_PLURAL: Jahrzehnten
+ FORM:
+ VALIDATION_FAIL: Überprüfung fehlgeschlagen:
+ INVALID_INPUT: Ungültige Eingabe in
+ MISSING_REQUIRED_FIELD: 'Erforderliches Feld fehlt:'
+ MONTHS_OF_THE_YEAR:
+ - 'Januar'
+ - 'Februar'
+ - 'März'
+ - 'April'
+ - 'Mai'
+ - 'Juni'
+ - 'Juli'
+ - 'August'
+ - 'September'
+ - 'Oktober'
+ - 'November'
+ - 'Dezember'
+ DAYS_OF_THE_WEEK:
+ - 'Montag'
+ - 'Dienstag'
+ - 'Mittwoch'
+ - 'Donnerstag'
+ - 'Freitag'
+ - 'Samstag'
+ - 'Sonntag'
+ CRON:
+ EVERY: jede
+ EVERY_HOUR: jede Stunde
+ EVERY_MINUTE: Jede Minute
+ EVERY_DAY_OF_WEEK: jeden Tag der Woche
+ EVERY_DAY_OF_MONTH: jeden Tag des Monats
+ EVERY_MONTH: jeden Monat
+ TEXT_PERIOD: Alle
+ TEXT_MINS: ' bei Minuten nach der vollen Stunde (n)'
+ TEXT_TIME: ' bei :'
+ TEXT_DOW: ' auf '
+ TEXT_MONTH: ' von '
+ TEXT_DOM: ' auf '
+ ERROR1: Der Tag %s wird nicht unterstützt!
+ ERROR2: Ungültige Anzahl von Elementen
+ ERROR3: jquery_element sollte in den jqCron Einstellungen gesetzt werden
+ ERROR4: Unbekannter Ausdruck
diff --git a/system/languages/el.yaml b/system/languages/el.yaml
index 16d24cb..1faa8dd 100644
--- a/system/languages/el.yaml
+++ b/system/languages/el.yaml
@@ -1,22 +1,63 @@
---
-MONTHS_OF_THE_YEAR:
- - Ιανουάριος
- - Φεβρουάριος
- - Μάρτιος
- - Απρίλιος
- - Μάιος
- - Ιούνιος
- - Ιούλιος
- - Αύγουστος
- - Σεπτέμβριος
- - Οκτώβριος
- - Νοέμβριος
- - Δεκέμβριος
-DAYS_OF_THE_WEEK:
- - Δευτέρα
- - Τρίτη
- - Τετάρτη
- - Πέμπτη
- - Παρασκευή
- - Σάββατο
- - Κυριακή
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\nΤίτλος: %1$s\n---\n\n# Σφάλμα: Μη έγκυρη διαδρομή Frontmatter\n\nΔιαδρομή: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ NICETIME:
+ NO_DATE_PROVIDED: Δεν δόθηκε καμία ημερομηνία
+ BAD_DATE: Εσφαλμένη ημερομηνία
+ AGO: πρίν
+ FROM_NOW: από τώρα
+ SECOND: δευτερόλεπτο
+ MINUTE: λεπτό
+ HOUR: ώρα
+ DAY: ημέρα
+ WEEK: εβδομάδα
+ MONTH: μήνας
+ YEAR: έτος
+ DECADE: δεκαετία
+ SEC: δευτερόλεπτο
+ MIN: λεπτό
+ HR: ώρα
+ WK: εβδ
+ MO: μην
+ YR: έτος
+ DEC: δεκαετία
+ SECOND_PLURAL: δευτερόλεπτα
+ MINUTE_PLURAL: λεπτά
+ HOUR_PLURAL: ώρες
+ DAY_PLURAL: ημέρες
+ WEEK_PLURAL: εβδομάδες
+ MONTH_PLURAL: μήνες
+ YEAR_PLURAL: έτη
+ DECADE_PLURAL: δεκαετίες
+ SEC_PLURAL: δευτ.
+ MIN_PLURAL: λεπτά
+ HR_PLURAL: ώρες
+ WK_PLURAL: εβδομάδες
+ MO_PLURAL: μήνες
+ YR_PLURAL: έτη
+ DEC_PLURAL: δεκαετίες
+ FORM:
+ VALIDATION_FAIL: Η επικύρωση απέτυχε:
+ INVALID_INPUT: Μη έγκυρα δεδομένα σε
+ MISSING_REQUIRED_FIELD: 'Λείπει το απαιτούμενο πεδίο:'
+ MONTHS_OF_THE_YEAR:
+ - 'Ιανουάριος'
+ - 'Φεβρουάριος'
+ - 'Μάρτιος'
+ - 'Απρίλιος'
+ - 'Μάιος'
+ - 'Ιούνιος'
+ - 'Ιούλιος'
+ - 'Αύγουστος'
+ - 'Σεπτέμβριος'
+ - 'Οκτώβριος'
+ - 'Νοέμβριος'
+ - 'Δεκέμβριος'
+ DAYS_OF_THE_WEEK:
+ - 'Δευτέρα'
+ - 'Τρίτη'
+ - 'Τετάρτη'
+ - 'Πέμπτη'
+ - 'Παρασκευή'
+ - 'Σάββατο'
+ - 'Κυριακή'
diff --git a/system/languages/en.yaml b/system/languages/en.yaml
index 7bda791..7c351e6 100644
--- a/system/languages/en.yaml
+++ b/system/languages/en.yaml
@@ -1,99 +1,120 @@
-FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Error: Invalid Frontmatter\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
-INFLECTOR_PLURALS:
- '/(quiz)$/i': '\1zes'
- '/^(ox)$/i': '\1en'
- '/([m|l])ouse$/i': '\1ice'
- '/(matr|vert|ind)ix|ex$/i': '\1ices'
- '/(x|ch|ss|sh)$/i': '\1es'
- '/([^aeiouy]|qu)ies$/i': '\1y'
- '/([^aeiouy]|qu)y$/i': '\1ies'
- '/(hive)$/i': '\1s'
- '/(?:([^f])fe|([lr])f)$/i': '\1\2ves'
- '/sis$/i': 'ses'
- '/([ti])um$/i': '\1a'
- '/(buffal|tomat)o$/i': '\1oes'
- '/(bu)s$/i': '\1ses'
- '/(alias|status)/i': '\1es'
- '/(octop|vir)us$/i': '\1i'
- '/(ax|test)is$/i': '\1es'
- '/s$/i': 's'
- '/$/': 's'
-INFLECTOR_SINGULAR:
- '/(quiz)zes$/i': '\1'
- '/(matr)ices$/i': '\1ix'
- '/(vert|ind)ices$/i': '\1ex'
- '/^(ox)en/i': '\1'
- '/(alias|status)es$/i': '\1'
- '/([octop|vir])i$/i': '\1us'
- '/(cris|ax|test)es$/i': '\1is'
- '/(shoe)s$/i': '\1'
- '/(o)es$/i': '\1'
- '/(bus)es$/i': '\1'
- '/([m|l])ice$/i': '\1ouse'
- '/(x|ch|ss|sh)es$/i': '\1'
- '/(m)ovies$/i': '\1ovie'
- '/(s)eries$/i': '\1eries'
- '/([^aeiouy]|qu)ies$/i': '\1y'
- '/([lr])ves$/i': '\1f'
- '/(tive)s$/i': '\1'
- '/(hive)s$/i': '\1'
- '/([^f])ves$/i': '\1fe'
- '/(^analy)ses$/i': '\1sis'
- '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2sis'
- '/([ti])a$/i': '\1um'
- '/(n)ews$/i': '\1ews'
- '/s$/i': ''
-INFLECTOR_UNCOUNTABLE: ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep']
-INFLECTOR_IRREGULAR:
- 'person': 'people'
- 'man': 'men'
- 'child': 'children'
- 'sex': 'sexes'
- 'move': 'moves'
-INFLECTOR_ORDINALS:
- 'default': 'th'
- 'first': 'st'
- 'second': 'nd'
- 'third': 'rd'
-NICETIME:
- NO_DATE_PROVIDED: No date provided
- BAD_DATE: Bad date
- AGO: ago
- FROM_NOW: from now
- JUST_NOW: just now
- SECOND: second
- MINUTE: minute
- HOUR: hour
- DAY: day
- WEEK: week
- MONTH: month
- YEAR: year
- DECADE: decade
- SEC: sec
- MIN: min
- HR: hr
- WK: wk
- MO: mo
- YR: yr
- DEC: dec
- SECOND_PLURAL: seconds
- MINUTE_PLURAL: minutes
- HOUR_PLURAL: hours
- DAY_PLURAL: days
- WEEK_PLURAL: weeks
- MONTH_PLURAL: months
- YEAR_PLURAL: years
- DECADE_PLURAL: decades
- SEC_PLURAL: secs
- MIN_PLURAL: mins
- HR_PLURAL: hrs
- WK_PLURAL: wks
- MO_PLURAL: mos
- YR_PLURAL: yrs
- DEC_PLURAL: decs
-FORM:
- VALIDATION_FAIL: Validation failed:
- INVALID_INPUT: Invalid input in
- MISSING_REQUIRED_FIELD: Missing required field:
-MONTHS_OF_THE_YEAR: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
-DAYS_OF_THE_WEEK: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
+---
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Error: Invalid Frontmatter\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ INFLECTOR_PLURALS:
+ '/(quiz)$/i': '\1zes'
+ '/^(ox)$/i': '\1en'
+ '/([m|l])ouse$/i': '\1ice'
+ '/(matr|vert|ind)ix|ex$/i': '\1ices'
+ '/(x|ch|ss|sh)$/i': '\1es'
+ '/([^aeiouy]|qu)ies$/i': '\1y'
+ '/([^aeiouy]|qu)y$/i': '\1ies'
+ '/(hive)$/i': '\1s'
+ '/(?:([^f])fe|([lr])f)$/i': '\1\2ves'
+ '/sis$/i': 'ses'
+ '/([ti])um$/i': '\1a'
+ '/(buffal|tomat)o$/i': '\1oes'
+ '/(bu)s$/i': '\1ses'
+ '/(alias|status)/i': '\1es'
+ '/(octop|vir)us$/i': '\1i'
+ '/(ax|test)is$/i': '\1es'
+ '/s$/i': 's'
+ '/$/': 's'
+ INFLECTOR_SINGULAR:
+ '/(quiz)zes$/i': '\1'
+ '/(matr)ices$/i': '\1ix'
+ '/(vert|ind)ices$/i': '\1ex'
+ '/^(ox)en/i': '\1'
+ '/(alias|status)es$/i': '\1'
+ '/([octop|vir])i$/i': '\1us'
+ '/(cris|ax|test)es$/i': '\1is'
+ '/(shoe)s$/i': '\1'
+ '/(o)es$/i': '\1'
+ '/(bus)es$/i': '\1'
+ '/([m|l])ice$/i': '\1ouse'
+ '/(x|ch|ss|sh)es$/i': '\1'
+ '/(m)ovies$/i': '\1ovie'
+ '/(s)eries$/i': '\1eries'
+ '/([^aeiouy]|qu)ies$/i': '\1y'
+ '/([lr])ves$/i': '\1f'
+ '/(tive)s$/i': '\1'
+ '/(hive)s$/i': '\1'
+ '/([^f])ves$/i': '\1fe'
+ '/(^analy)ses$/i': '\1sis'
+ '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2sis'
+ '/([ti])a$/i': '\1um'
+ '/(n)ews$/i': '\1ews'
+ '/s$/i': ''
+ INFLECTOR_UNCOUNTABLE: ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep']
+ INFLECTOR_IRREGULAR:
+ 'person': 'people'
+ 'man': 'men'
+ 'child': 'children'
+ 'sex': 'sexes'
+ 'move': 'moves'
+ INFLECTOR_ORDINALS:
+ 'default': 'th'
+ 'first': 'st'
+ 'second': 'nd'
+ 'third': 'rd'
+ NICETIME:
+ NO_DATE_PROVIDED: No date provided
+ BAD_DATE: Bad date
+ AGO: ago
+ FROM_NOW: from now
+ JUST_NOW: just now
+ SECOND: second
+ MINUTE: minute
+ HOUR: hour
+ DAY: day
+ WEEK: week
+ MONTH: month
+ YEAR: year
+ DECADE: decade
+ SEC: sec
+ MIN: min
+ HR: hr
+ WK: wk
+ MO: mo
+ YR: yr
+ DEC: dec
+ SECOND_PLURAL: seconds
+ MINUTE_PLURAL: minutes
+ HOUR_PLURAL: hours
+ DAY_PLURAL: days
+ WEEK_PLURAL: weeks
+ MONTH_PLURAL: months
+ YEAR_PLURAL: years
+ DECADE_PLURAL: decades
+ SEC_PLURAL: secs
+ MIN_PLURAL: mins
+ HR_PLURAL: hrs
+ WK_PLURAL: wks
+ MO_PLURAL: mos
+ YR_PLURAL: yrs
+ DEC_PLURAL: decs
+ FORM:
+ VALIDATION_FAIL: Validation failed:
+ INVALID_INPUT: Invalid input in
+ MISSING_REQUIRED_FIELD: Missing required field:
+ MONTHS_OF_THE_YEAR: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
+ DAYS_OF_THE_WEEK: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
+ YES: "Yes"
+ NO: "No"
+ CRON:
+ EVERY: every
+ EVERY_HOUR: every hour
+ EVERY_MINUTE: every minute
+ EVERY_DAY_OF_WEEK: every day of the week
+ EVERY_DAY_OF_MONTH: every day of the month
+ EVERY_MONTH: every month
+ TEXT_PERIOD: Every
+ TEXT_MINS: ' at minute(s) past the hour'
+ TEXT_TIME: ' at :'
+ TEXT_DOW: ' on '
+ TEXT_MONTH: ' of '
+ TEXT_DOM: ' on '
+ ERROR1: The tag %s is not supported!
+ ERROR2: Bad number of elements
+ ERROR3: The jquery_element should be set into jqCron settings
+ ERROR4: Unrecognized expression
diff --git a/system/languages/es.yaml b/system/languages/es.yaml
index b19b620..ce990ee 100644
--- a/system/languages/es.yaml
+++ b/system/languages/es.yaml
@@ -1,90 +1,95 @@
---
-FRONTMATTER_ERROR_PAGE: |
- ---
- title: %1$s
- ---
-
- # Error: Frontmatter Inválido
-
- Ruta: `%2$s`
-
- **%3$s**
-
- ```
- %4$s
- ```
-INFLECTOR_UNCOUNTABLE:
- 0: equipo
- 1: información
- 3: dinero
- 5: series
- 6: pescado
- 7: oveja
-INFLECTOR_IRREGULAR:
- man: hombres
- child: niños
- sex: sexos
-INFLECTOR_ORDINALS:
- first: ro
- second: do
- third: ro
-NICETIME:
- NO_DATE_PROVIDED: No se proporcionó fecha
- BAD_DATE: Fecha erronea
- AGO: antes
- FROM_NOW: desde ahora
- SECOND: segundo
- MINUTE: minuto
- HOUR: hora
- DAY: día
- WEEK: semana
- MONTH: mes
- YEAR: año
- DECADE: década
- SEC: seg
- MIN: min
- HR: h
- WK: sem
- MO: mes
- YR: año
- DEC: dec
- SECOND_PLURAL: segundos
- MINUTE_PLURAL: minutos
- HOUR_PLURAL: horas
- DAY_PLURAL: días
- WEEK_PLURAL: semanas
- MONTH_PLURAL: meses
- YEAR_PLURAL: años
- DECADE_PLURAL: décadas
- SEC_PLURAL: segs
- MIN_PLURAL: mins
- HR_PLURAL: hs
- WK_PLURAL: sem
- MO_PLURAL: mes
- YR_PLURAL: años
- DEC_PLURAL: décadas
-FORM:
- VALIDATION_FAIL: 'Falló la validación. '
- INVALID_INPUT: 'Dato inválido en: '
- MISSING_REQUIRED_FIELD: 'Falta el campo requerido: '
-MONTHS_OF_THE_YEAR:
- - Enero
- - Febrero
- - Marzo
- - Abril
- - Mayo
- - Junio
- - Julio
- - Agosto
- - Septiembre
- - Octubre
- - Noviembre
- - Diciembre
-DAYS_OF_THE_WEEK:
- - Lunes
- - Martes
- - Miércoles
- - Jueves
- - Viernes
- - Sábado
- - Domingo
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntítulo: %1$s\n---\n\n# Error: Frontmatter no válido\n\nRuta: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ INFLECTOR_UNCOUNTABLE:
+ - 'equipo'
+ - 'información'
+ - 'rice'
+ - 'dinero'
+ - 'species'
+ - 'series'
+ - 'pescado'
+ - 'oveja'
+ INFLECTOR_IRREGULAR:
+ 'man': 'hombres'
+ 'child': 'niños'
+ 'sex': 'sexos'
+ INFLECTOR_ORDINALS:
+ 'first': 'ro'
+ 'second': 'do'
+ 'third': 'ro'
+ NICETIME:
+ NO_DATE_PROVIDED: No se proporcionó fecha
+ BAD_DATE: Fecha errónea
+ AGO: antes
+ FROM_NOW: desde ahora
+ JUST_NOW: justo ahora
+ SECOND: segundo
+ MINUTE: minuto
+ HOUR: hora
+ DAY: día
+ WEEK: semana
+ MONTH: mes
+ YEAR: año
+ DECADE: década
+ SEC: seg
+ HR: h
+ WK: sem
+ MO: mes
+ YR: año
+ SECOND_PLURAL: segundos
+ MINUTE_PLURAL: minutos
+ HOUR_PLURAL: horas
+ DAY_PLURAL: días
+ WEEK_PLURAL: semanas
+ MONTH_PLURAL: meses
+ YEAR_PLURAL: años
+ DECADE_PLURAL: décadas
+ SEC_PLURAL: segs
+ HR_PLURAL: hs
+ WK_PLURAL: sem
+ MO_PLURAL: mes
+ YR_PLURAL: años
+ DEC_PLURAL: décadas
+ FORM:
+ VALIDATION_FAIL: 'Falló la validación: '
+ INVALID_INPUT: 'Dato inválido en: '
+ MISSING_REQUIRED_FIELD: 'Falta el campo requerido: '
+ MONTHS_OF_THE_YEAR:
+ - 'Enero'
+ - 'Febrero'
+ - 'Marzo'
+ - 'Abril'
+ - 'Mayo'
+ - 'Junio'
+ - 'Julio'
+ - 'Agosto'
+ - 'Septiembre'
+ - 'Octubre'
+ - 'Noviembre'
+ - 'Diciembre'
+ DAYS_OF_THE_WEEK:
+ - 'Lunes'
+ - 'Martes'
+ - 'Miércoles'
+ - 'Jueves'
+ - 'Viernes'
+ - 'Sábado'
+ - 'Domingo'
+ CRON:
+ EVERY: cada
+ EVERY_HOUR: cada hora
+ EVERY_MINUTE: cada minuto
+ EVERY_DAY_OF_WEEK: cada día de la semana
+ EVERY_DAY_OF_MONTH: cada día del mes
+ EVERY_MONTH: cada mes
+ TEXT_PERIOD: Cada
+ TEXT_MINS: ' a minuto(s) despues de la hora'
+ TEXT_TIME: ' a :'
+ TEXT_DOW: ' en '
+ TEXT_MONTH: ' de'
+ TEXT_DOM: ' en'
+ ERROR1: La etiqueta %s no está soportada!
+ ERROR2: El número de elementos es erroneo
+ ERROR3: El jquery_element debería establecerse en la configuración del jqCron
+ ERROR4: Expresión no reconocida
diff --git a/system/languages/et.yaml b/system/languages/et.yaml
new file mode 100644
index 0000000..fd794ad
--- /dev/null
+++ b/system/languages/et.yaml
@@ -0,0 +1,62 @@
+---
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\npealkiri: %1$s\n---\n\n# Viga: vigane Frontmatter'i\n\nasukoht: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ NICETIME:
+ NO_DATE_PROVIDED: Kuupäev määramata
+ BAD_DATE: Vigane kuupäev
+ AGO: tagasi
+ FROM_NOW: praegusest
+ SECOND: sekund
+ MINUTE: minut
+ HOUR: tundi
+ DAY: päev
+ WEEK: nädal
+ MONTH: kuu
+ YEAR: aasta
+ DECADE: 10 aastat
+ SEC: sek
+ HR: t
+ WK: näd
+ MO: k.
+ YR: a.
+ DEC: dekaad
+ SECOND_PLURAL: sekundit
+ MINUTE_PLURAL: minutit
+ HOUR_PLURAL: tundi
+ DAY_PLURAL: päeva
+ WEEK_PLURAL: nädalat
+ MONTH_PLURAL: kuud
+ YEAR_PLURAL: aastat
+ DECADE_PLURAL: dekaadi
+ SEC_PLURAL: sekundit
+ MIN_PLURAL: min
+ HR_PLURAL: t
+ WK_PLURAL: näd
+ MO_PLURAL: kuud
+ YR_PLURAL: aastat
+ DEC_PLURAL: dek.
+ FORM:
+ VALIDATION_FAIL: Kinnitamine nurjus:
+ INVALID_INPUT: 'Vigane sisend:'
+ MISSING_REQUIRED_FIELD: 'Nõutud väli puudub:'
+ MONTHS_OF_THE_YEAR:
+ - 'jaanuar'
+ - 'veebruar'
+ - 'märts'
+ - 'aprill'
+ - 'mai'
+ - 'juuni'
+ - 'juuli'
+ - 'august'
+ - 'september'
+ - 'oktoober'
+ - 'november'
+ - 'detsember'
+ DAYS_OF_THE_WEEK:
+ - 'esmaspäev'
+ - 'teisipäev'
+ - 'kolmapäev'
+ - 'neljapäev'
+ - 'reede'
+ - 'laupäev'
+ - 'pühapäev'
diff --git a/system/languages/eu.yaml b/system/languages/eu.yaml
new file mode 100644
index 0000000..4a9a58e
--- /dev/null
+++ b/system/languages/eu.yaml
@@ -0,0 +1,62 @@
+---
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "--- title: %1$s --- # Errorea: Baliogabeko Frontmatter Bidea: `%2$s` **%3$s** ``` %4$s ```"
+ NICETIME:
+ NO_DATE_PROVIDED: Ez da datarik ezarri
+ BAD_DATE: Okerreko data
+ AGO: ' duela'
+ FROM_NOW: oraindik aurrera
+ SECOND: segundo
+ MINUTE: minutu
+ HOUR: ordua
+ DAY: egun
+ WEEK: astea
+ MONTH: hilabetea
+ YEAR: urtea
+ DECADE: hamarkada
+ SEC: seg
+ HR: h
+ WK: ast
+ MO: hil
+ YR: urt
+ DEC: ham
+ SECOND_PLURAL: segundo
+ MINUTE_PLURAL: minutu
+ HOUR_PLURAL: ordu
+ DAY_PLURAL: egun
+ WEEK_PLURAL: aste
+ MONTH_PLURAL: hilabete
+ YEAR_PLURAL: urte
+ DECADE_PLURAL: hamarkada
+ SEC_PLURAL: segundo
+ MIN_PLURAL: minutu
+ HR_PLURAL: h
+ WK_PLURAL: ast
+ MO_PLURAL: hil
+ YR_PLURAL: urt
+ DEC_PLURAL: ham
+ FORM:
+ VALIDATION_FAIL: Balidazioak huts egin du
+ INVALID_INPUT: Baliogabeko sarrera
+ MISSING_REQUIRED_FIELD: 'Derrigorrezko eremua bete gabe:'
+ MONTHS_OF_THE_YEAR:
+ - 'Urtarrila'
+ - 'Otsaila'
+ - 'Martxoa'
+ - 'Apirila'
+ - 'Maiatza'
+ - 'Ekaina'
+ - 'Uztaila'
+ - 'Abuztua'
+ - 'Iraila'
+ - 'Urria'
+ - 'Azaroa'
+ - 'Abendua'
+ DAYS_OF_THE_WEEK:
+ - 'Astelehena'
+ - 'Asteartea'
+ - 'Azteazkena'
+ - 'Osteguna'
+ - 'Ostirala'
+ - 'Larunbata'
+ - 'Igandea'
diff --git a/system/languages/fa.yaml b/system/languages/fa.yaml
new file mode 100644
index 0000000..186f79d
--- /dev/null
+++ b/system/languages/fa.yaml
@@ -0,0 +1,62 @@
+---
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\nعنوان: %1$s\n---\n\n# خطا: Frontmatter غلط\n\nمسیر: %2$s\n\n**%3$s**\n\n```\n%4$s\n```"
+ NICETIME:
+ NO_DATE_PROVIDED: تاریخی ارائه نشده
+ BAD_DATE: تاریخ اشتباه
+ AGO: قبل
+ FROM_NOW: از حالا
+ SECOND: ثانیه
+ MINUTE: دقیقه
+ HOUR: ساعت
+ DAY: روز
+ WEEK: هفته
+ MONTH: ماه
+ YEAR: سال
+ DECADE: دهه
+ SEC: ثانیه
+ MIN: دقیقه
+ HR: ساعت
+ WK: هفته
+ MO: ماه
+ YR: سال
+ DEC: دهه
+ SECOND_PLURAL: ثانیه
+ MINUTE_PLURAL: دقیقه
+ HOUR_PLURAL: ساعت
+ DAY_PLURAL: روز
+ WEEK_PLURAL: هفته
+ MONTH_PLURAL: ماه
+ YEAR_PLURAL: سال
+ DECADE_PLURAL: دهه
+ SEC_PLURAL: ثانیه
+ MIN_PLURAL: دقیقه
+ HR_PLURAL: ساعت
+ WK_PLURAL: هفته
+ YR_PLURAL: سال
+ DEC_PLURAL: دهه
+ FORM:
+ VALIDATION_FAIL: سنجش اعتبار ناموفق بود
+ INVALID_INPUT: ورودی نامعتبر در
+ MISSING_REQUIRED_FIELD: 'قسمت ضروری جا افتاده:'
+ MONTHS_OF_THE_YEAR:
+ - 'ژانویه'
+ - 'فوریه'
+ - 'مارس'
+ - 'آوریل'
+ - 'می'
+ - 'ژوئن'
+ - 'ژوئیه'
+ - 'اوت'
+ - 'سپتامبر'
+ - 'اکتبر'
+ - 'نوامبر'
+ - 'دسامبر'
+ DAYS_OF_THE_WEEK:
+ - 'دوشنبه'
+ - 'سه شنبه'
+ - 'چهارشنبه'
+ - 'پنج شنبه'
+ - 'جمعه'
+ - 'شنبه'
+ - 'یکشنبه'
diff --git a/system/languages/fi.yaml b/system/languages/fi.yaml
index dd5a793..e84c19f 100644
--- a/system/languages/fi.yaml
+++ b/system/languages/fi.yaml
@@ -1,60 +1,134 @@
---
-NICETIME:
- NO_DATE_PROVIDED: Päivämäärää ei annettu
- BAD_DATE: Virheellinen päivämäärä
- AGO: sitten
- FROM_NOW: tästä lähtien
- SECOND: sekunti
- MINUTE: minuutti
- HOUR: tunti
- DAY: päivä
- WEEK: viikko
- MONTH: kuukausi
- YEAR: vuosi
- DECADE: vuosikymmen
- SEC: sek
- MIN: min
- HR: h
- WK: vk
- MO: kk
- YR: v
- DEC: vuosikymmen
- SECOND_PLURAL: sekuntia
- MINUTE_PLURAL: minuuttia
- HOUR_PLURAL: tuntia
- DAY_PLURAL: päivää
- WEEK_PLURAL: viikkoa
- MONTH_PLURAL: kuukautta
- YEAR_PLURAL: vuotta
- DECADE_PLURAL: vuosikymmentä
- SEC_PLURAL: sek
- MIN_PLURAL: min
- HR_PLURAL: h
- WK_PLURAL: v
- MO_PLURAL: kk
- YR_PLURAL: v
- DEC_PLURAL: vuosikymmentä
-FORM:
- VALIDATION_FAIL: 'Vahvistus epäonnistui:'
- MISSING_REQUIRED_FIELD: 'Puuttuva pakollinen kenttä:'
-MONTHS_OF_THE_YEAR:
- - Tammikuu
- - Helmikuu
- - Maaliskuu
- - Huhtikuu
- - Toukokuu
- - Kesäkuuta
- - Heinäkuu
- - Elokuu
- - Syyskuu
- - Lokakuu
- - Marraskuu
- - Joulukuu
-DAYS_OF_THE_WEEK:
- - Maanantai
- - Tiistai
- - Keskiviikko
- - Torstai
- - Perjantai
- - Lauantai
- - Sunnuntai
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\notsikko: %1$s\n---\n\n# Virhe: Virheellinen Frontmatter\n\nPolku: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ INFLECTOR_PLURALS:
+ '/(quiz)$/i': '\1zes'
+ '/^(ox)$/i': '\1en'
+ '/([m|l])ouse$/i': '\1ice'
+ '/(matr|vert|ind)ix|ex$/i': '\1ices'
+ '/(x|ch|ss|sh)$/i': '\1es'
+ '/([^aeiouy]|qu)ies$/i': '\1y'
+ '/([^aeiouy]|qu)y$/i': '\1ies'
+ '/(hive)$/i': '\1s'
+ '/(?:([^f])fe|([lr])f)$/i': '\1\2ves'
+ '/sis$/i': 'ses'
+ '/([ti])um$/i': '\1a'
+ '/(buffal|tomat)o$/i': '\1oes'
+ '/(bu)s$/i': '\1ses'
+ '/(alias|status)/i': '\1es'
+ '/(octop|vir)us$/i': '\1i'
+ '/(ax|test)is$/i': '\1es'
+ '/s$/i': 's'
+ '/$/': 's'
+ INFLECTOR_SINGULAR:
+ '/(quiz)zes$/i': '\1'
+ '/(matr)ices$/i': '\1ix'
+ '/(vert|ind)ices$/i': '\1ex'
+ '/^(ox)en/i': '\1'
+ '/(alias|status)es$/i': '\1'
+ '/([octop|vir])i$/i': '\1us'
+ '/(cris|ax|test)es$/i': '\1is'
+ '/(shoe)s$/i': '\1'
+ '/(o)es$/i': '\1'
+ '/(bus)es$/i': '\1'
+ '/([m|l])ice$/i': '\1ouse'
+ '/(x|ch|ss|sh)es$/i': '\1'
+ '/(m)ovies$/i': '\1ovie'
+ '/(s)eries$/i': '\1eries'
+ '/([^aeiouy]|qu)ies$/i': '\1y'
+ '/([lr])ves$/i': '\1f'
+ '/(tive)s$/i': '\1'
+ '/(hive)s$/i': '\1'
+ '/([^f])ves$/i': '\1fe'
+ '/(^analy)ses$/i': '\1sis'
+ '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2sis'
+ '/([ti])a$/i': '\1um'
+ '/(n)ews$/i': '\1ews'
+ INFLECTOR_UNCOUNTABLE:
+ - 'equipment'
+ - 'information'
+ - 'riisi'
+ - 'raha'
+ - 'lajit'
+ - 'series'
+ - 'kala'
+ - 'lammas'
+ INFLECTOR_IRREGULAR:
+ 'person': 'ihmiset'
+ 'man': 'miehet'
+ 'child': 'lapset'
+ 'sex': 'sukupuoli'
+ INFLECTOR_ORDINALS:
+ 'default': '.'
+ 'first': '.'
+ 'second': '.'
+ 'third': '.'
+ NICETIME:
+ NO_DATE_PROVIDED: Päivämäärää ei annettu
+ BAD_DATE: Virheellinen päivämäärä
+ AGO: sitten
+ FROM_NOW: tästä lähtien
+ JUST_NOW: juuri nyt
+ SECOND: sekuntti
+ MINUTE: minuutti
+ HOUR: tunti
+ DAY: päivä
+ WEEK: viikko
+ MONTH: kuukausi
+ YEAR: vuosi
+ DECADE: vuosikymmen
+ SEC: sek
+ MIN: min
+ HR: h
+ WK: vk
+ MO: kk
+ YR: v
+ DEC: vuosikymmen
+ SECOND_PLURAL: sekuntia
+ MINUTE_PLURAL: minuuttia
+ HOUR_PLURAL: tuntia
+ DAY_PLURAL: päivää
+ WEEK_PLURAL: viikkoa
+ MONTH_PLURAL: kuukautta
+ YEAR_PLURAL: vuotta
+ DECADE_PLURAL: vuosikymmentä
+ SEC_PLURAL: sek
+ MIN_PLURAL: min
+ HR_PLURAL: h
+ WK_PLURAL: v
+ MO_PLURAL: kk
+ YR_PLURAL: v
+ DEC_PLURAL: vuosikymmentä
+ FORM:
+ VALIDATION_FAIL: Vahvistus epäonnistui:
+ INVALID_INPUT: Syöte ei kelpaa
+ MISSING_REQUIRED_FIELD: 'Puuttuva pakollinen kenttä:'
+ MONTHS_OF_THE_YEAR:
+ - 'Tammikuu'
+ - 'Helmikuu'
+ - 'Maaliskuu'
+ - 'Huhtikuu'
+ - 'Toukokuu'
+ - 'Kesäkuuta'
+ - 'Heinäkuu'
+ - 'Elokuu'
+ - 'Syyskuu'
+ - 'Lokakuu'
+ - 'Marraskuu'
+ - 'Joulukuu'
+ DAYS_OF_THE_WEEK:
+ - 'Maanantai'
+ - 'Tiistai'
+ - 'Keskiviikko'
+ - 'Torstai'
+ - 'Perjantai'
+ - 'Lauantai'
+ - 'Sunnuntai'
+ CRON:
+ EVERY: joka
+ EVERY_HOUR: joka tunti
+ EVERY_MINUTE: joka minuutti
+ EVERY_DAY_OF_WEEK: viikon jokaisena päivänä
+ EVERY_DAY_OF_MONTH: kuukauden jokaisena päivänä
+ EVERY_MONTH: joka kuukausi
+ TEXT_PERIOD: Joka
diff --git a/system/languages/fr.yaml b/system/languages/fr.yaml
index d6dd912..fe2cbb7 100644
--- a/system/languages/fr.yaml
+++ b/system/languages/fr.yaml
@@ -1,138 +1,121 @@
---
-FRONTMATTER_ERROR_PAGE: |
- ---
- title: %1$s
- ---
-
- # Erreur : Frontmatter invalide
-
- Path: `%2$s`
-
- **%3$s**
-
- ```
- %4$s
- ```
-INFLECTOR_PLURALS:
- /(quiz)$/i: '\1zes'
- /^(ox)$/i: '\1en'
- "/([m|l])ouse$/i": '\1ice'
- /(matr|vert|ind)ix|ex$/i: '\1ices'
- /(x|ch|ss|sh)$/i: '\1es'
- "/([^aeiouy]|qu)ies$/i": '\1y'
- "/([^aeiouy]|qu)y$/i": '\1ies'
- /(hive)$/i: '\1s'
- "/(?:([^f])fe|([lr])f)$/i": '\1\2ves'
- /sis$/i: ses
- "/([ti])um$/i": '\1a'
- /(buffal|tomat)o$/i: '\1oes'
- /(bu)s$/i: '\1ses'
- /(alias|status)/i: '\1es'
- /(octop|vir)us$/i: '\1i'
- /(ax|test)is$/i: '\1es'
- /s$/i: s
- /$/: s
-INFLECTOR_SINGULAR:
- /(quiz)zes$/i: '\1'
- /(matr)ices$/i: '\1ix'
- /(vert|ind)ices$/i: '\1ex'
- /^(ox)en/i: '\1'
- /(alias|status)es$/i: '\1'
- "/([octop|vir])i$/i": '\1us'
- /(cris|ax|test)es$/i: '\1is'
- /(shoe)s$/i: '\1'
- /(o)es$/i: '\1'
- /(bus)es$/i: '\1'
- "/([m|l])ice$/i": '\1ouse'
- /(x|ch|ss|sh)es$/i: '\1'
- /(m)ovies$/i: '\1ovie'
- /(s)eries$/i: '\1eries'
- "/([^aeiouy]|qu)ies$/i": '\1y'
- "/([lr])ves$/i": '\1f'
- /(tive)s$/i: '\1'
- /(hive)s$/i: '\1'
- "/([^f])ves$/i": '\1fe'
- /(^analy)ses$/i: '\1sis'
- /((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i: '\1\2sis'
- "/([ti])a$/i": '\1um'
- /(n)ews$/i: '\1ews'
-INFLECTOR_UNCOUNTABLE:
- - équipement
- - informations
- - riz
- - argent
- - espèces
- - séries
- - poisson
- - mouton
-INFLECTOR_IRREGULAR:
- person: personnes
- man: hommes
- child: enfants
- sex: sexes
- move: déplacements
-INFLECTOR_ORDINALS:
- default: ème
- first: er
- second: ème
- third: ème
-NICETIME:
- NO_DATE_PROVIDED: Aucune date fournie
- BAD_DATE: Date erronée
- AGO: plus tôt
- FROM_NOW: à partir de maintenant
- SECOND: seconde
- MINUTE: minute
- HOUR: heure
- DAY: jour
- WEEK: semaine
- MONTH: mois
- YEAR: année
- DECADE: décennie
- SEC: s
- MIN: m
- HR: h
- WK: sem
- MO: m
- YR: an
- DEC: déc
- SECOND_PLURAL: secondes
- MINUTE_PLURAL: minutes
- HOUR_PLURAL: heures
- DAY_PLURAL: jours
- WEEK_PLURAL: semaines
- MONTH_PLURAL: mois
- YEAR_PLURAL: années
- DECADE_PLURAL: décennies
- SEC_PLURAL: s
- MIN_PLURAL: m
- HR_PLURAL: h
- WK_PLURAL: sem
- MO_PLURAL: mois
- YR_PLURAL: a
- DEC_PLURAL: décs
-FORM:
- VALIDATION_FAIL: 'La validation a échoué :'
- INVALID_INPUT: Saisie non valide
- MISSING_REQUIRED_FIELD: 'Champ obligatoire manquant :'
-MONTHS_OF_THE_YEAR:
- - Janvier
- - Février
- - Mars
- - Avril
- - Mai
- - Juin
- - Juillet
- - Août
- - Septembre
- - Octobre
- - Novembre
- - Décembre
-DAYS_OF_THE_WEEK:
- - Lundi
- - Mardi
- - Mercredi
- - Jeudi
- - Vendredi
- - Samedi
- - Dimanche
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntitre: %1$s\n---\n\n# Erreur : Frontmatter invalide\n\nChemin: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ INFLECTOR_PLURALS:
+ '/(quiz)$/i': '\1zes'
+ '/^(ox)$/i': '\1en'
+ '/([m|l])ouse$/i': '\1ice'
+ '/(matr|vert|ind)ix|ex$/i': '\1ices'
+ '/(x|ch|ss|sh)$/i': '\1es'
+ '/([^aeiouy]|qu)ies$/i': '\1y'
+ '/([^aeiouy]|qu)y$/i': '\1ies'
+ '/(hive)$/i': '\1s'
+ '/(?:([^f])fe|([lr])f)$/i': '\1\2ves'
+ '/sis$/i': 'ses'
+ '/([ti])um$/i': '\1a'
+ '/(buffal|tomat)o$/i': '\1es'
+ '/(ax|test)is$/i': '\1s'
+ '/s$/i': 's'
+ '/$/': 's'
+ INFLECTOR_SINGULAR:
+ '/(quiz)zes$/i': '\1'
+ '/(alias|status)es$/i': '\1'
+ '/([octop|vir])i$/i': '\1us'
+ INFLECTOR_UNCOUNTABLE:
+ - 'équipement'
+ - 'information'
+ - 'riz'
+ - 'argent'
+ - 'espèces'
+ - 'séries'
+ - 'poisson'
+ - 'mouton'
+ INFLECTOR_IRREGULAR:
+ 'person': 'personnes'
+ 'man': 'hommes'
+ 'child': 'enfants'
+ 'sex': 'sexes'
+ 'move': 'déplacements'
+ INFLECTOR_ORDINALS:
+ 'default': 'ème'
+ 'first': 'er'
+ 'second': 'ème'
+ 'third': 'ème'
+ NICETIME:
+ NO_DATE_PROVIDED: Aucune date fournie
+ BAD_DATE: Date erronée
+ AGO: plus tôt
+ FROM_NOW: à partir de maintenant
+ JUST_NOW: à l'instant
+ SECOND: seconde
+ MINUTE: minute
+ HOUR: heure
+ DAY: jour
+ WEEK: semaine
+ MONTH: mois
+ YEAR: année
+ DECADE: décennie
+ SEC: s
+ MIN: m
+ HR: h
+ WK: sem
+ MO: m
+ YR: an
+ DEC: déc
+ SECOND_PLURAL: secondes
+ MINUTE_PLURAL: minutes
+ HOUR_PLURAL: heures
+ DAY_PLURAL: jours
+ WEEK_PLURAL: semaines
+ MONTH_PLURAL: mois
+ YEAR_PLURAL: années
+ DECADE_PLURAL: décennies
+ SEC_PLURAL: s
+ MIN_PLURAL: m
+ HR_PLURAL: h
+ WK_PLURAL: sem
+ MO_PLURAL: mois
+ YR_PLURAL: a
+ DEC_PLURAL: décs
+ FORM:
+ VALIDATION_FAIL: La validation a échoué :
+ INVALID_INPUT: Saisie non valide
+ MISSING_REQUIRED_FIELD: 'Champ obligatoire manquant :'
+ MONTHS_OF_THE_YEAR:
+ - 'Janvier'
+ - 'Février'
+ - 'Mars'
+ - 'Avril'
+ - 'Mai'
+ - 'Juin'
+ - 'Juillet'
+ - 'Août'
+ - 'Septembre'
+ - 'Octobre'
+ - 'Novembre'
+ - 'Décembre'
+ DAYS_OF_THE_WEEK:
+ - 'Lundi'
+ - 'Mardi'
+ - 'Mercredi'
+ - 'Jeudi'
+ - 'Vendredi'
+ - 'Samedi'
+ - 'Dimanche'
+ CRON:
+ EVERY: chaque
+ EVERY_HOUR: toutes les heures
+ EVERY_MINUTE: chaque minute
+ EVERY_DAY_OF_WEEK: tous les jours de la semaine
+ EVERY_DAY_OF_MONTH: tous les jours du mois
+ EVERY_MONTH: chaque mois
+ TEXT_PERIOD: Chaque
+ TEXT_MINS: ' à minute(s) après l''heure'
+ TEXT_TIME: ' à:'
+ TEXT_DOW: ' sur '
+ TEXT_MONTH: ' de '
+ TEXT_DOM: ' sur '
+ ERROR1: La balise %s n'est pas supportée!
+ ERROR2: Nombre invalide d'éléments
+ ERROR3: L'élément jquery_element doit être défini dans les paramètres jqCron
+ ERROR4: Expression non reconnue
diff --git a/system/languages/he.yaml b/system/languages/he.yaml
new file mode 100644
index 0000000..afb63d7
--- /dev/null
+++ b/system/languages/he.yaml
@@ -0,0 +1,63 @@
+---
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\nכותרת: %1$s\n---\n# שגיאה: Fronmatter לא חוקי\nנתיב: `%2$s`\n**%3$s**\n```\n%4$s\n```"
+ NICETIME:
+ NO_DATE_PROVIDED: לא סופק תאריך
+ BAD_DATE: תאריך פגום
+ AGO: לפני
+ FROM_NOW: כרגע
+ SECOND: שנייה
+ MINUTE: דקה
+ HOUR: שעה
+ DAY: יום
+ WEEK: שבוע
+ MONTH: חודש
+ YEAR: שנה
+ DECADE: עשור
+ SEC: שנ'
+ MIN: דק'
+ HR: ש'
+ WK: שב'
+ MO: חו'
+ YR: שני'
+ DEC: עש'
+ SECOND_PLURAL: שניות
+ MINUTE_PLURAL: דקות
+ HOUR_PLURAL: שעות
+ DAY_PLURAL: ימים
+ WEEK_PLURAL: שבועות
+ MONTH_PLURAL: חודשים
+ YEAR_PLURAL: שנים
+ DECADE_PLURAL: עשורים
+ SEC_PLURAL: שנ'
+ MIN_PLURAL: דק'
+ HR_PLURAL: ש'
+ WK_PLURAL: שב'
+ MO_PLURAL: חו'
+ YR_PLURAL: שני'
+ DEC_PLURAL: עש'
+ FORM:
+ VALIDATION_FAIL: האימות נכשל:
+ INVALID_INPUT: קלט לא חוקי
+ MISSING_REQUIRED_FIELD: 'שדות חובה חסרים:'
+ MONTHS_OF_THE_YEAR:
+ - 'ינואר'
+ - 'פברואר'
+ - 'מרץ'
+ - 'אפריל'
+ - 'מאי'
+ - 'יוני'
+ - 'יולי'
+ - 'אוגוסט'
+ - 'ספטמבר'
+ - 'אוקטובר'
+ - 'נובמבר'
+ - 'דצמבר'
+ DAYS_OF_THE_WEEK:
+ - 'שני'
+ - 'שלישי'
+ - 'רביעי'
+ - 'חמישי'
+ - 'שישי'
+ - 'שבת'
+ - 'ראשון'
diff --git a/system/languages/hr.yaml b/system/languages/hr.yaml
index adcdde5..1b530b5 100644
--- a/system/languages/hr.yaml
+++ b/system/languages/hr.yaml
@@ -1,75 +1,76 @@
---
-INFLECTOR_UNCOUNTABLE:
- - oprema
- - informacije
- - riža
- - novac
- - vrsta
- - serija
- - riba
- - ovca
-INFLECTOR_IRREGULAR:
- person: osobe
- man: ljudi
- child: djeca
- sex: spolovi
- move: Pomakni
-NICETIME:
- NO_DATE_PROVIDED: Datum nije upisan
- BAD_DATE: Pogrešan datum
- AGO: prije
- FROM_NOW: od sada
- SECOND: sekunda
- MINUTE: minuta
- HOUR: sat
- DAY: dan
- WEEK: tjedan
- MONTH: mjesec
- YEAR: godina
- DECADE: desetljeće
- SEC: sek
- HR: sat
- WK: t
- MO: m
- YR: g
- DEC: des
- SECOND_PLURAL: sekundi
- MINUTE_PLURAL: minuta
- HOUR_PLURAL: sati
- DAY_PLURAL: dan
- WEEK_PLURAL: tjedana
- MONTH_PLURAL: mjeseci
- YEAR_PLURAL: godina
- DECADE_PLURAL: desetljeća
- SEC_PLURAL: sek
- MIN_PLURAL: min
- HR_PLURAL: sat
- WK_PLURAL: t
- MO_PLURAL: m
- YR_PLURAL: g
- DEC_PLURAL: des
-FORM:
- VALIDATION_FAIL: 'Validacija nije uspjela:'
- INVALID_INPUT: Pogrešan unos u
- MISSING_REQUIRED_FIELD: 'Nedostaje obavezno polje:'
-MONTHS_OF_THE_YEAR:
- - Siječanj
- - Veljača
- - Ožujak
- - Travanj
- - Svibanj
- - Lipanj
- - Srpanj
- - Kolovoz
- - Rujan
- - Listopad
- - Studeni
- - Prosinac
-DAYS_OF_THE_WEEK:
- - Ponedjeljak
- - Utorak
- - Srijeda
- - Četvrtak
- - Petak
- - Subota
- - Nedjelja
+GRAV:
+ INFLECTOR_UNCOUNTABLE:
+ - 'oprema'
+ - 'informacije'
+ - 'riža'
+ - 'novac'
+ - 'vrsta'
+ - 'serija'
+ - 'riba'
+ - 'ovca'
+ INFLECTOR_IRREGULAR:
+ 'person': 'osobe'
+ 'man': 'ljudi'
+ 'child': 'djeca'
+ 'sex': 'spolovi'
+ 'move': 'Pomakni'
+ NICETIME:
+ NO_DATE_PROVIDED: Datum nije upisan
+ BAD_DATE: Pogrešan datum
+ AGO: prije
+ FROM_NOW: od sada
+ SECOND: sekunda
+ MINUTE: minuta
+ HOUR: sat
+ DAY: dan
+ WEEK: tjedan
+ MONTH: mjesec
+ YEAR: godina
+ DECADE: desetljeće
+ SEC: sek
+ HR: sat
+ WK: t
+ MO: m
+ YR: g
+ DEC: des
+ SECOND_PLURAL: sekundi
+ MINUTE_PLURAL: minuta
+ HOUR_PLURAL: sati
+ DAY_PLURAL: dan
+ WEEK_PLURAL: tjedana
+ MONTH_PLURAL: mjeseci
+ YEAR_PLURAL: godina
+ DECADE_PLURAL: desetljeća
+ SEC_PLURAL: sek
+ MIN_PLURAL: min
+ HR_PLURAL: sat
+ WK_PLURAL: t
+ MO_PLURAL: m
+ YR_PLURAL: g
+ DEC_PLURAL: des
+ FORM:
+ VALIDATION_FAIL: Validacija nije uspjela:
+ INVALID_INPUT: Pogrešan unos u
+ MISSING_REQUIRED_FIELD: 'Nedostaje obavezno polje:'
+ MONTHS_OF_THE_YEAR:
+ - 'Siječanj'
+ - 'Veljača'
+ - 'Ožujak'
+ - 'Travanj'
+ - 'Svibanj'
+ - 'Lipanj'
+ - 'Srpanj'
+ - 'Kolovoz'
+ - 'Rujan'
+ - 'Listopad'
+ - 'Studeni'
+ - 'Prosinac'
+ DAYS_OF_THE_WEEK:
+ - 'Ponedjeljak'
+ - 'Utorak'
+ - 'Srijeda'
+ - 'Četvrtak'
+ - 'Petak'
+ - 'Subota'
+ - 'Nedjelja'
diff --git a/system/languages/hu.yaml b/system/languages/hu.yaml
index ae7662e..a834c45 100644
--- a/system/languages/hu.yaml
+++ b/system/languages/hu.yaml
@@ -1,138 +1,97 @@
---
-FRONTMATTER_ERROR_PAGE: |
- ---
- cím: %1$s
- ---
-
- # Hiba: Érvénytelen Frontmatter
-
- Elérési út: `%2$s`
-
- **%3$s**
-
- ```
- %4$s
- ```
-INFLECTOR_PLURALS:
- /(quiz)$/i: '\1zes'
- /^(ox)$/i: '\1en'
- "/([m|l])ouse$/i": '\1ice'
- /(matr|vert|ind)ix|ex$/i: '\1ices'
- /(x|ch|ss|sh)$/i: '\1es'
- "/([^aeiouy]|qu)ies$/i": '\1y'
- "/([^aeiouy]|qu)y$/i": '\1ies'
- /(hive)$/i: '\1s'
- "/(?:([^f])fe|([lr])f)$/i": '\1\2ves'
- /sis$/i: ses
- "/([ti])um$/i": '\1a'
- /(buffal|tomat)o$/i: '\1oes'
- /(bu)s$/i: '\1ses'
- /(alias|status)/i: '\1es'
- /(octop|vir)us$/i: '\1i'
- /(ax|test)is$/i: '\1es'
- /s$/i: s
- /$/: s
-INFLECTOR_SINGULAR:
- /(quiz)zes$/i: '\1'
- /(matr)ices$/i: '\1ix'
- /(vert|ind)ices$/i: '\1ex'
- /^(ox)en/i: '\1'
- /(alias|status)es$/i: '\1'
- "/([octop|vir])i$/i": '\1us'
- /(cris|ax|test)es$/i: '\1is'
- /(shoe)s$/i: '\1'
- /(o)es$/i: '\1'
- /(bus)es$/i: '\1'
- "/([m|l])ice$/i": '\1ouse'
- /(x|ch|ss|sh)es$/i: '\1'
- /(m)ovies$/i: '\1ovie'
- /(s)eries$/i: '\1eries'
- "/([^aeiouy]|qu)ies$/i": '\1y'
- "/([lr])ves$/i": '\1f'
- /(tive)s$/i: '\1'
- /(hive)s$/i: '\1'
- "/([^f])ves$/i": '\1fe'
- /(^analy)ses$/i: '\1sis'
- /((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i: '\1\2sis'
- "/([ti])a$/i": '\1um'
- /(n)ews$/i: '\1ews'
-INFLECTOR_UNCOUNTABLE:
- - felszerelés
- - információ
- - rizs
- - pénz
- - fajok
- - sorozat
- - hal
- - juh
-INFLECTOR_IRREGULAR:
- person: személyek
- man: férfiak
- child: gyerekek
- sex: nemek
- move: lépések
-INFLECTOR_ORDINALS:
- default: '.'
- first: '.'
- second: '.'
- third: '.'
-NICETIME:
- NO_DATE_PROVIDED: Nincs dátum megadva
- BAD_DATE: Hibás dátum
- AGO: elteltével
- FROM_NOW: mostantól
- SECOND: másodperc
- MINUTE: perc
- HOUR: óra
- DAY: nap
- WEEK: hét
- MONTH: hónap
- YEAR: év
- DECADE: évtized
- SEC: mp
- MIN: p
- HR: ó
- WK: hét
- MO: hó
- YR: év
- DEC: évt
- SECOND_PLURAL: másodperc
- MINUTE_PLURAL: perc
- HOUR_PLURAL: óra
- DAY_PLURAL: nap
- WEEK_PLURAL: hét
- MONTH_PLURAL: hónap
- YEAR_PLURAL: év
- DECADE_PLURAL: évtized
- SEC_PLURAL: mp
- MIN_PLURAL: perc
- HR_PLURAL: ó
- WK_PLURAL: hét
- MO_PLURAL: hó
- YR_PLURAL: év
- DEC_PLURAL: évt
-FORM:
- VALIDATION_FAIL: 'A validáció hibát talált:'
- INVALID_INPUT: 'Az itt megadott érték érvénytelen:'
- MISSING_REQUIRED_FIELD: 'Ez a kötelező mező nincs kitöltve:'
-MONTHS_OF_THE_YEAR:
- - január
- - február
- - március
- - április
- - május
- - június
- - július
- - augusztus
- - szeptember
- - október
- - november
- - december
-DAYS_OF_THE_WEEK:
- - hétfő
- - kedd
- - szerda
- - csütörtök
- - péntek
- - szombat
- - vasárnap
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ncím: %1$s\n---\n\n# Hiba: Érvénytelen Frontmatter\n\nElérési út: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ INFLECTOR_UNCOUNTABLE:
+ - 'felszerelés'
+ - 'információ'
+ - 'rizs'
+ - 'pénz'
+ - 'fajok'
+ - 'sorozat'
+ - 'hal'
+ - 'juh'
+ INFLECTOR_IRREGULAR:
+ 'person': 'személyek'
+ 'man': 'férfiak'
+ 'child': 'gyerekek'
+ 'sex': 'nemek'
+ 'move': 'lépések'
+ INFLECTOR_ORDINALS:
+ 'default': '.'
+ 'first': '.'
+ 'second': '.'
+ 'third': '.'
+ NICETIME:
+ NO_DATE_PROVIDED: Nincs dátum megadva
+ BAD_DATE: Hibás dátum
+ AGO: elteltével
+ FROM_NOW: mostantól
+ JUST_NOW: épp most
+ SECOND: másodperc
+ MINUTE: perc
+ HOUR: óra
+ DAY: nap
+ WEEK: hét
+ MONTH: hónap
+ YEAR: év
+ DECADE: évtized
+ SEC: mp
+ MIN: p
+ HR: ó
+ WK: hét
+ MO: hó
+ YR: év
+ DEC: évt
+ SECOND_PLURAL: másodperc
+ MINUTE_PLURAL: perc
+ HOUR_PLURAL: óra
+ DAY_PLURAL: nap
+ WEEK_PLURAL: hét
+ MONTH_PLURAL: hónap
+ YEAR_PLURAL: év
+ DECADE_PLURAL: évtized
+ SEC_PLURAL: mp
+ MIN_PLURAL: perc
+ HR_PLURAL: ó
+ WK_PLURAL: hét
+ MO_PLURAL: hó
+ YR_PLURAL: év
+ DEC_PLURAL: évt
+ FORM:
+ VALIDATION_FAIL: Érvényesítés nem sikerült:
+ INVALID_INPUT: 'A megadott érték érvénytelen:'
+ MISSING_REQUIRED_FIELD: 'Ez a kötelező mező nincs kitöltve:'
+ MONTHS_OF_THE_YEAR:
+ - 'január'
+ - 'február'
+ - 'március'
+ - 'április'
+ - 'május'
+ - 'június'
+ - 'július'
+ - 'augusztus'
+ - 'szeptember'
+ - 'október'
+ - 'november'
+ - 'december'
+ DAYS_OF_THE_WEEK:
+ - 'hétfő'
+ - 'kedd'
+ - 'szerda'
+ - 'csütörtök'
+ - 'péntek'
+ - 'szombat'
+ - 'vasárnap'
+ CRON:
+ EVERY: minden
+ EVERY_HOUR: óránként
+ EVERY_MINUTE: percenként
+ EVERY_DAY_OF_WEEK: a hét minden napján
+ EVERY_DAY_OF_MONTH: a hónap minden napján
+ EVERY_MONTH: minden hónapban
+ TEXT_PERIOD: Minden
+ TEXT_MINS: ' perccel az óra elteltével'
+ ERROR1: A %s címke nem engedélyezett!
+ ERROR2: Hibás elemszám
+ ERROR3: A jquery_element-et a jqCron beállítsokban kell meghatározni
+ ERROR4: Ismeretlen kifejezés
diff --git a/system/languages/id.yaml b/system/languages/id.yaml
new file mode 100644
index 0000000..41431f1
--- /dev/null
+++ b/system/languages/id.yaml
@@ -0,0 +1,95 @@
+---
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Error: Frontmatter tidak valid\n\nLokasi: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ INFLECTOR_UNCOUNTABLE:
+ - 'peralatan'
+ - 'informasi'
+ - 'nasi'
+ - 'uang'
+ - 'spesies'
+ - 'rangkaian'
+ - 'ikan'
+ - 'domba'
+ INFLECTOR_IRREGULAR:
+ 'person': 'orang-orang'
+ 'man': 'laki-laki'
+ 'child': 'anak-anak'
+ 'sex': 'jenis kelamin'
+ 'move': 'pindahkan'
+ NICETIME:
+ NO_DATE_PROVIDED: Tanggal tidak tersedia
+ BAD_DATE: Format tanggal salah
+ AGO: yang lalu
+ FROM_NOW: dari saat ini
+ JUST_NOW: baru saja
+ SECOND: detik
+ MINUTE: menit
+ HOUR: jam
+ DAY: hari
+ WEEK: pekan
+ MONTH: bulan
+ YEAR: tahun
+ DECADE: dekade
+ SEC: dtk
+ MIN: mnt
+ HR: j
+ WK: mng
+ MO: bln
+ YR: thn
+ DEC: desimal
+ SECOND_PLURAL: detik
+ MINUTE_PLURAL: menit
+ HOUR_PLURAL: jam
+ DAY_PLURAL: hari
+ WEEK_PLURAL: pekan
+ MONTH_PLURAL: bulan
+ YEAR_PLURAL: tahun
+ DECADE_PLURAL: dekade
+ SEC_PLURAL: dtk
+ MIN_PLURAL: mnt
+ HR_PLURAL: j
+ WK_PLURAL: mgg
+ MO_PLURAL: bln
+ YR_PLURAL: thn
+ DEC_PLURAL: dekade
+ FORM:
+ VALIDATION_FAIL: Validasi gagal:
+ INVALID_INPUT: Input tidak valid di
+ MISSING_REQUIRED_FIELD: 'Data yang diperlukan belum terisi:'
+ MONTHS_OF_THE_YEAR:
+ - 'Januari'
+ - 'Februari'
+ - 'Maret'
+ - 'April'
+ - 'Mei'
+ - 'Juni'
+ - 'Juli'
+ - 'Agustus'
+ - 'September'
+ - 'Oktober'
+ - 'November'
+ - 'Desember'
+ DAYS_OF_THE_WEEK:
+ - 'Senin'
+ - 'Selasa'
+ - 'Rabu'
+ - 'Kamis'
+ - 'Jumat'
+ - 'Sabtu'
+ - 'Minggu'
+ CRON:
+ EVERY: Setiap
+ EVERY_HOUR: Setiap jam
+ EVERY_MINUTE: Setiap menit
+ EVERY_DAY_OF_WEEK: Setiap hari selama seminggu
+ EVERY_DAY_OF_MONTH: pada tanggal setiap bulannya
+ EVERY_MONTH: setiap bulan
+ TEXT_PERIOD: Setiap
+ TEXT_TIME: ' pada :'
+ TEXT_DOW: ' pada '
+ TEXT_MONTH: ' pada '
+ TEXT_DOM: ' pada '
+ ERROR1: Tag %s tidak didukung!
+ ERROR2: Jumlah elemen tidak valid
+ ERROR3: jquery_element harus ditetapkan ke pengaturan jqCron
+ ERROR4: Ekspresi tidak dikenali
diff --git a/system/languages/is.yaml b/system/languages/is.yaml
new file mode 100644
index 0000000..55384fa
--- /dev/null
+++ b/system/languages/is.yaml
@@ -0,0 +1,69 @@
+---
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntitill: %1$s\n---\n\n# Villa: Ógilt efni á forsíðu\n\nSlóð: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ INFLECTOR_UNCOUNTABLE:
+ - 'equipment'
+ - 'upplýsingar'
+ - 'rice'
+ - 'money'
+ - 'species'
+ - 'series'
+ - 'fish'
+ - 'sheep'
+ NICETIME:
+ AGO: síðan
+ JUST_NOW: í þessu
+ SECOND: sekúndu
+ MINUTE: mínútu
+ HOUR: klukkustund
+ DAY: degi
+ WEEK: viku
+ MONTH: mánuði
+ YEAR: ári
+ DECADE: áratug
+ SEC: sek
+ MIN: mín
+ HR: klst
+ WK: vk
+ MO: mán
+ YR: ár
+ DEC: árat
+ SECOND_PLURAL: sekúndum
+ MINUTE_PLURAL: mínútum
+ HOUR_PLURAL: klukkustundum
+ DAY_PLURAL: dögum
+ WEEK_PLURAL: vikum
+ MONTH_PLURAL: mánuðum
+ YEAR_PLURAL: árum
+ DECADE_PLURAL: áratugum
+ SEC_PLURAL: sek
+ MIN_PLURAL: mín
+ HR_PLURAL: klst
+ WK_PLURAL: vik
+ MO_PLURAL: mán
+ YR_PLURAL: árum
+ DEC_PLURAL: árat
+ FORM:
+ VALIDATION_FAIL: Sannvottun mistókst:
+ MISSING_REQUIRED_FIELD: 'Vantar nauðsynlegan reit:'
+ MONTHS_OF_THE_YEAR:
+ - 'janúar'
+ - 'Febrúar'
+ - 'Mars'
+ - 'Apríl'
+ - 'Maí'
+ - 'Júní'
+ - 'Júlí'
+ - 'Ágúst'
+ - 'September'
+ - 'Október'
+ - 'Nóvember'
+ - 'Desember'
+ DAYS_OF_THE_WEEK:
+ - 'Mánudagur'
+ - 'Þriðjudagur'
+ - 'Miðvikudagur'
+ - 'Fimmtudagur'
+ - 'Föstudagur'
+ - 'Laugardagur'
+ - 'Sunnudagur'
diff --git a/system/languages/it.yaml b/system/languages/it.yaml
index 58a8998..741f721 100644
--- a/system/languages/it.yaml
+++ b/system/languages/it.yaml
@@ -1,62 +1,101 @@
---
-FRONTMATTER_ERROR_PAGE: "---Titolo: %1$s---# Errore: Frontmatter non valido: '%2$s' * *%3$s * * ' '%4$s ' '"
-NICETIME:
- NO_DATE_PROVIDED: Nessuna data fornita
- BAD_DATE: Data non valida
- AGO: fa
- FROM_NOW: da adesso
- SECOND: secondo
- MINUTE: minuto
- HOUR: ora
- DAY: giorno
- WEEK: settimana
- MONTH: mese
- YEAR: anno
- DECADE: decennio
- SEC: sec
- MIN: min
- HR: ora
- WK: settimana
- MO: mese
- YR: anno
- DEC: decennio
- SECOND_PLURAL: secondi
- MINUTE_PLURAL: minuti
- HOUR_PLURAL: ore
- DAY_PLURAL: giorni
- WEEK_PLURAL: settimane
- MONTH_PLURAL: mesi
- YEAR_PLURAL: anni
- DECADE_PLURAL: decadi
- SEC_PLURAL: secondi
- MIN_PLURAL: minuti
- HR_PLURAL: ore
- WK_PLURAL: settimane
- MO_PLURAL: mesi
- YR_PLURAL: anni
- DEC_PLURAL: decenni
-FORM:
- VALIDATION_FAIL: 'Validazione fallita:'
- INVALID_INPUT: Input non valido in
- MISSING_REQUIRED_FIELD: 'Campo richiesto mancante:'
-MONTHS_OF_THE_YEAR:
- - Gennaio
- - Febbraio
- - Marzo
- - Aprile
- - Maggio
- - Giugno
- - Luglio
- - Agosto
- - Settembre
- - Ottobre
- - Novembre
- - Dicembre
-DAYS_OF_THE_WEEK:
- - Lunedì
- - Martedì
- - Mercoledì
- - Giovedì
- - Venerdì
- - Sabato
- - Domenica
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---Titolo: %1$s---# Errore: Frontmatter non valido: '%2$s' * *%3$s * * ' '%4$s ' '"
+ INFLECTOR_UNCOUNTABLE:
+ - 'dotazione'
+ - 'informazione'
+ - 'riso'
+ - 'denaro'
+ - 'specie'
+ - 'serie'
+ - 'pesce'
+ - 'pecora'
+ INFLECTOR_IRREGULAR:
+ 'person': 'persone'
+ 'man': 'uomini'
+ 'child': 'bambino'
+ 'sex': 'sessi'
+ 'move': 'sposta'
+ INFLECTOR_ORDINALS:
+ 'default': '°'
+ 'first': '°'
+ 'second': 'o'
+ 'third': 'o'
+ NICETIME:
+ NO_DATE_PROVIDED: Nessuna data fornita
+ BAD_DATE: Data non valida
+ AGO: fa
+ FROM_NOW: da adesso
+ JUST_NOW: ora
+ SECOND: secondo
+ MINUTE: minuto
+ HOUR: ora
+ DAY: giorno
+ WEEK: settimana
+ MONTH: mese
+ YEAR: anno
+ DECADE: decennio
+ SEC: sec
+ MIN: min
+ HR: ora
+ WK: settimana
+ MO: mese
+ YR: anno
+ DEC: decennio
+ SECOND_PLURAL: secondi
+ MINUTE_PLURAL: minuti
+ HOUR_PLURAL: ore
+ DAY_PLURAL: giorni
+ WEEK_PLURAL: settimane
+ MONTH_PLURAL: mesi
+ YEAR_PLURAL: anni
+ DECADE_PLURAL: decadi
+ SEC_PLURAL: secondi
+ MIN_PLURAL: minuti
+ HR_PLURAL: ore
+ WK_PLURAL: settimane
+ MO_PLURAL: mesi
+ YR_PLURAL: anni
+ DEC_PLURAL: decenni
+ FORM:
+ VALIDATION_FAIL: Validazione fallita:
+ INVALID_INPUT: Input non valido in
+ MISSING_REQUIRED_FIELD: 'Campo richiesto mancante:'
+ MONTHS_OF_THE_YEAR:
+ - 'Gennaio'
+ - 'Febbraio'
+ - 'Marzo'
+ - 'Aprile'
+ - 'Maggio'
+ - 'Giugno'
+ - 'Luglio'
+ - 'Agosto'
+ - 'Settembre'
+ - 'Ottobre'
+ - 'Novembre'
+ - 'Dicembre'
+ DAYS_OF_THE_WEEK:
+ - 'Lunedì'
+ - 'Martedì'
+ - 'Mercoledì'
+ - 'Giovedì'
+ - 'Venerdì'
+ - 'Sabato'
+ - 'Domenica'
+ CRON:
+ EVERY: ogni
+ EVERY_HOUR: ogni ora
+ EVERY_MINUTE: ogni minuto
+ EVERY_DAY_OF_WEEK: ogni giorno della settimana
+ EVERY_DAY_OF_MONTH: ogni giorno del mese
+ EVERY_MONTH: ogni mese
+ TEXT_PERIOD: Ogni
+ TEXT_MINS: ' a minuto(i) dall''inizio dell''ora'
+ TEXT_TIME: ' alle :'
+ TEXT_DOW: ' su '
+ TEXT_MONTH: ' di '
+ TEXT_DOM: ' di '
+ ERROR1: Il tag %s non è supportato!
+ ERROR2: Numero di elementi non valido
+ ERROR3: Il jquery_element deve essere impostato nelle impostazioni di jqCron
+ ERROR4: Espressione non riconosciuta
diff --git a/system/languages/ja.yaml b/system/languages/ja.yaml
index c2d8ac9..0e3fa16 100644
--- a/system/languages/ja.yaml
+++ b/system/languages/ja.yaml
@@ -1,24 +1,15 @@
-FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Error: Invalid Frontmatter\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
-INFLECTOR_PLURALS: []
-
-INFLECTOR_SINGULAR: []
-
-INFLECTOR_UNCOUNTABLE: []
-
-INFLECTOR_IRREGULAR:
+---
+GRAV:
+ INFLECTOR_IRREGULAR:
'person': 'みんな'
'man': '人'
'child': '子供'
'sex': '性別'
'move': '移動'
-
-INFLECTOR_ORDINALS: []
-
-NICETIME:
+ NICETIME:
NO_DATE_PROVIDED: 日付が設定されていません
BAD_DATE: 不正な日付
AGO: 前
- FROM_NOW: from now
SECOND: 秒
MINUTE: 分
HOUR: 時
@@ -33,7 +24,6 @@ NICETIME:
WK: 週
MO: 月
YR: 年
- DEC: dec
SECOND_PLURAL: 秒
MINUTE_PLURAL: 分
HOUR_PLURAL: 時
@@ -49,9 +39,28 @@ NICETIME:
MO_PLURAL: 月
YR_PLURAL: 年
DEC_PLURAL: 10年
-FORM:
+ FORM:
VALIDATION_FAIL: バリデーション失敗 :
INVALID_INPUT: 不正な入力:
- MISSING_REQUIRED_FIELD: 必須項目が入力されていません:
-MONTHS_OF_THE_YEAR: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
-DAYS_OF_THE_WEEK: ['月', '火', '水', '木', '金', '土', '日']
+ MISSING_REQUIRED_FIELD: '必須項目が入力されていません:'
+ MONTHS_OF_THE_YEAR:
+ - '1月'
+ - '2月'
+ - '3月'
+ - '4月'
+ - '5月'
+ - '6月'
+ - '7月'
+ - '8月'
+ - '9月'
+ - '10月'
+ - '11月'
+ - '12月'
+ DAYS_OF_THE_WEEK:
+ - '月'
+ - '火'
+ - '水'
+ - '木'
+ - '金'
+ - '土'
+ - '日'
diff --git a/system/languages/ko.yaml b/system/languages/ko.yaml
new file mode 100644
index 0000000..6676a6d
--- /dev/null
+++ b/system/languages/ko.yaml
@@ -0,0 +1,63 @@
+---
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# 오류: 무효의 Frontmatter\n\n경로: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ NICETIME:
+ NO_DATE_PROVIDED: 제공된 날짜가 없습니다
+ BAD_DATE: 잘못된 날짜
+ AGO: 전
+ FROM_NOW: 후
+ SECOND: 초
+ MINUTE: 분
+ HOUR: 시간
+ DAY: 일
+ WEEK: 주
+ MONTH: 개월
+ YEAR: 년
+ DECADE: 년간
+ SEC: 초
+ MIN: 분
+ HR: 시간
+ WK: 주
+ MO: 개월
+ YR: 년
+ DEC: 년간
+ SECOND_PLURAL: 초
+ MINUTE_PLURAL: 분
+ HOUR_PLURAL: 시간
+ DAY_PLURAL: 일
+ WEEK_PLURAL: 주
+ MONTH_PLURAL: 개월
+ YEAR_PLURAL: 년
+ DECADE_PLURAL: 년간
+ SEC_PLURAL: 초
+ MIN_PLURAL: 분
+ HR_PLURAL: 시간
+ WK_PLURAL: 주
+ MO_PLURAL: 개월
+ YR_PLURAL: 년
+ DEC_PLURAL: 년간
+ FORM:
+ VALIDATION_FAIL: 유효성 검사 실패:
+ INVALID_INPUT: 잘못된 입력
+ MISSING_REQUIRED_FIELD: '누락 된 필수 필드:'
+ MONTHS_OF_THE_YEAR:
+ - '일월'
+ - '이월'
+ - '삼월'
+ - '사월'
+ - '오월'
+ - '유월'
+ - '칠월'
+ - '팔월'
+ - '구월'
+ - '시월'
+ - '십일월'
+ - '십이월'
+ DAYS_OF_THE_WEEK:
+ - '월요일'
+ - '화요일'
+ - '수요일'
+ - '목요일'
+ - '금요일'
+ - '토요일'
+ - '일요일'
diff --git a/system/languages/lt.yaml b/system/languages/lt.yaml
index 6b77aa1..aca0fc3 100644
--- a/system/languages/lt.yaml
+++ b/system/languages/lt.yaml
@@ -1,69 +1,78 @@
---
-INFLECTOR_UNCOUNTABLE:
- 2: ryžiai
- 3: pinigai
- 4: prieskoniai
- 5: serijos
- 6: žuvis
- 7: avis
-INFLECTOR_IRREGULAR:
- person: žmonės
- man: žmogus
- child: vaikai
- sex: lytys
- move: juda
-NICETIME:
- NO_DATE_PROVIDED: Nenurodyta data
- BAD_DATE: Neteisinga data
- AGO: prieš
- FROM_NOW: nuo dabar
- SECOND: sekundė
- MINUTE: minutė
- HOUR: valanda
- DAY: diena
- WEEK: savaitė
- MONTH: mėnuo
- YEAR: metai
- DECADE: dešimtmetis
- SEC: sek
- MIN: min
- HR: val
- WK: sav
- MO: mėn
- YR: m
- MINUTE_PLURAL: minutės
- HOUR_PLURAL: valandos
- DAY_PLURAL: dienos
- WEEK_PLURAL: savaitės
- MONTH_PLURAL: mėnesiai
- YEAR_PLURAL: metai
- DECADE_PLURAL: dešimtmečiai
- SEC_PLURAL: sek
- MIN_PLURAL: min
- HR_PLURAL: val
- WK_PLURAL: sav
- MO_PLURAL: mėn
- YR_PLURAL: m
-FORM:
- MISSING_REQUIRED_FIELD: 'Būtina užpildyti laukelį:'
-MONTHS_OF_THE_YEAR:
- - Sausis
- - Vasaris
- - Kovas
- - Balandis
- - Gegužė
- - Birželis
- - Liepa
- - Rugpjūtis
- - Rugsėjis
- - Spalis
- - Lakpritis
- - Gruodis
-DAYS_OF_THE_WEEK:
- - Pirmadienis
- - Antradienis
- - Trečiadienis
- - Ketvirtadienis
- - Penktadienis
- - Šeštadienis
- - Sekmadienis
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Klaida: klaidinga įžanginė konfigūracija\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n %4$s\n```"
+ INFLECTOR_UNCOUNTABLE:
+ - 'equipment'
+ - 'information'
+ - 'ryžiai'
+ - 'pinigai'
+ - 'prieskoniai'
+ - 'serijos'
+ - 'žuvis'
+ - 'avis'
+ INFLECTOR_IRREGULAR:
+ 'person': 'žmonės'
+ 'man': 'žmogus'
+ 'child': 'vaikai'
+ 'sex': 'lytys'
+ 'move': 'juda'
+ NICETIME:
+ NO_DATE_PROVIDED: Nenurodyta data
+ BAD_DATE: Neteisinga data
+ AGO: prieš
+ FROM_NOW: nuo dabar
+ SECOND: sekundė
+ MINUTE: minutė
+ HOUR: valanda
+ DAY: diena
+ WEEK: savaitė
+ MONTH: mėnuo
+ YEAR: metai
+ DECADE: dešimtmetis
+ SEC: sek.
+ MIN: min.
+ HR: val.
+ WK: sav.
+ MO: mėn.
+ YR: m.
+ DEC: dešimtmetis
+ SECOND_PLURAL: sekundės
+ MINUTE_PLURAL: minutės
+ HOUR_PLURAL: valandos
+ DAY_PLURAL: dienos
+ WEEK_PLURAL: savaitės
+ MONTH_PLURAL: mėnesiai
+ YEAR_PLURAL: metai
+ DECADE_PLURAL: dešimtmečiai
+ SEC_PLURAL: sek.
+ MIN_PLURAL: min.
+ HR_PLURAL: val.
+ WK_PLURAL: sav.
+ MO_PLURAL: mėn.
+ YR_PLURAL: m.
+ DEC_PLURAL: dešimtmečiai
+ FORM:
+ VALIDATION_FAIL: Patvirtinimas nepavyko:
+ INVALID_INPUT: Neteisingai įvesta į
+ MISSING_REQUIRED_FIELD: 'Būtina užpildyti laukelį:'
+ MONTHS_OF_THE_YEAR:
+ - 'Sausis'
+ - 'Vasaris'
+ - 'Kovas'
+ - 'Balandis'
+ - 'Gegužė'
+ - 'Birželis'
+ - 'Liepa'
+ - 'Rugpjūtis'
+ - 'Rugsėjis'
+ - 'Spalis'
+ - 'Lakpritis'
+ - 'Gruodis'
+ DAYS_OF_THE_WEEK:
+ - 'Pirmadienis'
+ - 'Antradienis'
+ - 'Trečiadienis'
+ - 'Ketvirtadienis'
+ - 'Penktadienis'
+ - 'Šeštadienis'
+ - 'Sekmadienis'
diff --git a/system/languages/nb.yaml b/system/languages/nb.yaml
index 629cd39..8033e79 100644
--- a/system/languages/nb.yaml
+++ b/system/languages/nb.yaml
@@ -1,2 +1,4 @@
-MONTHS_OF_THE_YEAR: ['januar', 'februar', 'mars', 'april', 'mai', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'desember']
-DAYS_OF_THE_WEEK: ['mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lørdag', 'søndag']
+---
+GRAV:
+ MONTHS_OF_THE_YEAR: ['januar', 'februar', 'mars', 'april', 'mai', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'desember']
+ DAYS_OF_THE_WEEK: ['mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lørdag', 'søndag']
diff --git a/system/languages/nl.yaml b/system/languages/nl.yaml
index 757fb72..9706d03 100644
--- a/system/languages/nl.yaml
+++ b/system/languages/nl.yaml
@@ -1,64 +1,144 @@
---
-INFLECTOR_IRREGULAR:
- person: personen
- man: mensen
- child: kinderen
- sex: geslacht
- move: verplaatsen
-NICETIME:
- NO_DATE_PROVIDED: geen datum opgegeven
- BAD_DATE: Datumformaat onjuist
- AGO: geleden
- FROM_NOW: vanaf nu
- SECOND: seconde
- MINUTE: minuut
- HOUR: uur
- DAY: dag
- WEEK: week
- MONTH: maand
- YEAR: jaar
- DECADE: decenium
- SEC: s
- MIN: min
- HR: u
- MO: ma
- YR: j
- SECOND_PLURAL: seconden
- MINUTE_PLURAL: minuten
- HOUR_PLURAL: uren
- DAY_PLURAL: dagen
- WEEK_PLURAL: weken
- MONTH_PLURAL: maanden
- YEAR_PLURAL: jaren
- DECADE_PLURAL: decennia
- SEC_PLURAL: seconden
- MIN_PLURAL: minuten
- HR_PLURAL: uren
- WK_PLURAL: weken
- MO_PLURAL: maanden
- YR_PLURAL: jaren
-FORM:
- VALIDATION_FAIL: 'Validatie mislukt:'
- INVALID_INPUT: Ongeldige invoer in
- MISSING_REQUIRED_FIELD: 'Verplicht veld ontbreekt:'
-MONTHS_OF_THE_YEAR:
- - Januari
- - Februari
- - Maart
- - april
- - Mei
- - Juni
- - Juli
- - Augustus
- - september
- - Oktober
- - november
- - december
-DAYS_OF_THE_WEEK:
- - Maandag
- - Dinsdag
- - Woensdag
- - Donderdag
- - Vrijdag
- - Zaterdag
- - Zondag
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntitel: %1$s\n---\n\n# Fout: ongeldige frontmatter\n\nPad: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ INFLECTOR_PLURALS:
+ '/(quiz)$/i': '\1zes'
+ '/^(ox)$/i': '\1en'
+ '/([m|l])ouse$/i': '\1ice'
+ '/(matr|vert|ind)ix|ex$/i': '\1ices'
+ '/(x|ch|ss|sh)$/i': '\1es'
+ '/([^aeiouy]|qu)ies$/i': '\1y'
+ '/([^aeiouy]|qu)y$/i': '\1ies'
+ '/(hive)$/i': '\1s'
+ '/(?:([^f])fe|([lr])f)$/i': '\1\2ves'
+ '/sis$/i': 'ses'
+ '/([ti])um$/i': '\1a'
+ '/(buffal|tomat)o$/i': '\1oes'
+ '/(bu)s$/i': '\1ses'
+ '/(alias|status)/i': '\1es'
+ '/(octop|vir)us$/i': '\1i'
+ '/(ax|test)is$/i': '\1es'
+ '/s$/i': 's'
+ '/$/': 's'
+ INFLECTOR_SINGULAR:
+ '/(quiz)zes$/i': '\1'
+ '/(matr)ices$/i': '\1ix'
+ '/(vert|ind)ices$/i': '\1ex'
+ '/^(ox)en/i': '\1'
+ '/(alias|status)es$/i': '\1'
+ '/([octop|vir])i$/i': '\1us'
+ '/(cris|ax|test)es$/i': '\1is'
+ '/(shoe)s$/i': '\1'
+ '/(o)es$/i': '\1'
+ '/(bus)es$/i': '\1'
+ '/([m|l])ice$/i': '\1ouse'
+ '/(x|ch|ss|sh)es$/i': '\1'
+ '/(m)ovies$/i': '\1ovie'
+ '/(s)eries$/i': '\1eries'
+ '/([^aeiouy]|qu)ies$/i': '\1y'
+ '/([lr])ves$/i': '\1f'
+ '/(tive)s$/i': '\1'
+ '/(hive)s$/i': '\1'
+ '/([^f])ves$/i': '\1fe'
+ '/(^analy)ses$/i': '\1sis'
+ '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2sis'
+ '/([ti])a$/i': '\1um'
+ '/(n)ews$/i': '\1ews'
+ INFLECTOR_UNCOUNTABLE:
+ - 'uitrusting'
+ - 'informatie'
+ - 'rijst'
+ - 'geld'
+ - 'soorten'
+ - 'reeks'
+ - 'vis'
+ - 'schaap'
+ INFLECTOR_IRREGULAR:
+ 'person': 'personen'
+ 'man': 'mensen'
+ 'child': 'kinderen'
+ 'sex': 'geslacht'
+ 'move': 'verplaatsen'
+ INFLECTOR_ORDINALS:
+ 'default': 'th'
+ 'first': 'st'
+ 'second': 'nd'
+ 'third': 'rd'
+ NICETIME:
+ NO_DATE_PROVIDED: geen datum opgegeven
+ BAD_DATE: Datumformaat onjuist
+ AGO: geleden
+ FROM_NOW: vanaf nu
+ JUST_NOW: zojuist
+ SECOND: seconde
+ MINUTE: minuut
+ HOUR: uur
+ DAY: dag
+ WEEK: week
+ MONTH: maand
+ YEAR: jaar
+ DECADE: decennium
+ SEC: s
+ MIN: min
+ HR: u
+ WK: week
+ MO: ma
+ YR: j
+ DEC: decennia
+ SECOND_PLURAL: seconden
+ MINUTE_PLURAL: minuten
+ HOUR_PLURAL: uren
+ DAY_PLURAL: dagen
+ WEEK_PLURAL: weken
+ MONTH_PLURAL: maanden
+ YEAR_PLURAL: jaren
+ DECADE_PLURAL: decennia
+ SEC_PLURAL: seconden
+ MIN_PLURAL: minuten
+ HR_PLURAL: uren
+ WK_PLURAL: weken
+ MO_PLURAL: maanden
+ YR_PLURAL: jaren
+ DEC_PLURAL: decennia
+ FORM:
+ VALIDATION_FAIL: Validatie mislukt:
+ INVALID_INPUT: Ongeldige invoer in
+ MISSING_REQUIRED_FIELD: 'Ontbrekend verplicht veld:'
+ MONTHS_OF_THE_YEAR:
+ - 'Januari'
+ - 'Februari'
+ - 'Maart'
+ - 'April'
+ - 'Mei'
+ - 'Juni'
+ - 'Juli'
+ - 'Augustus'
+ - 'September'
+ - 'Oktober'
+ - 'November'
+ - 'December'
+ DAYS_OF_THE_WEEK:
+ - 'Maandag'
+ - 'Dinsdag'
+ - 'Woensdag'
+ - 'Donderdag'
+ - 'Vrijdag'
+ - 'Zaterdag'
+ - 'Zondag'
+ CRON:
+ EVERY: elke
+ EVERY_HOUR: elk uur
+ EVERY_MINUTE: elke minuut
+ EVERY_DAY_OF_WEEK: elke dag van de week
+ EVERY_DAY_OF_MONTH: elke dag van de maand
+ EVERY_MONTH: elke maand
+ TEXT_PERIOD: Elke
+ TEXT_MINS: ' minuten te laat'
+ TEXT_TIME: ' op :'
+ TEXT_DOW: ' op '
+ TEXT_MONTH: ' van '
+ TEXT_DOM: ' op '
+ ERROR1: De tag %s wordt niet ondersteund!
+ ERROR2: Slecht aantal elementen
+ ERROR3: Het jquery_element moet ingesteld worden in de jqCron instellingen
+ ERROR4: Onbekende expressie
diff --git a/system/languages/no.yaml b/system/languages/no.yaml
index 4a74da2..5b57938 100644
--- a/system/languages/no.yaml
+++ b/system/languages/no.yaml
@@ -1,93 +1,77 @@
---
-FRONTMATTER_ERROR_PAGE: |
- ---
- Tittel: %1$s
- ---
-
- # Feilmelding: Ugyldig Frontmatter
-
- Pane: '%2$s'
-
- **%3$s **
-
- ```
- %4$s
- ```
-INFLECTOR_PLURALS:
- /(quiz)$/i: '\1zes'
- /^(ox)$/i: '\1en'
-INFLECTOR_UNCOUNTABLE:
- - utstyr
- - informasjon
- - ris
- - penger
- - arter
- - serier
- - fisk
- - sau
-INFLECTOR_IRREGULAR:
- person: folk
- man: menn
- child: barn
- sex: kjønn
- move: trekk
-NICETIME:
- NO_DATE_PROVIDED: Ingen dato gitt
- BAD_DATE: Dårlig dato
- AGO: siden
- FROM_NOW: fra nå
- SECOND: sekund
- MINUTE: minutt
- HOUR: time
- DAY: dag
- WEEK: uke
- MONTH: måned
- YEAR: år
- DECADE: tiår
- SEC: sek
- MIN: min
- HR: t
- WK: uke
- MO: må
- YR: år
- DEC: des
- SECOND_PLURAL: sekunder
- MINUTE_PLURAL: minutter
- HOUR_PLURAL: timer
- DAY_PLURAL: dager
- WEEK_PLURAL: uker
- MONTH_PLURAL: måneder
- YEAR_PLURAL: år
- DECADE_PLURAL: tiår
- SEC_PLURAL: sek
- MIN_PLURAL: min
- HR_PLURAL: timer
- WK_PLURAL: uker
- MO_PLURAL: mdr
- YR_PLURAL: år
- DEC_PLURAL: årtier
-FORM:
- VALIDATION_FAIL: 'Validering mislyktes:'
- INVALID_INPUT: Ugyldig innhold i
- MISSING_REQUIRED_FIELD: 'Mangler påkrevd felt:'
-MONTHS_OF_THE_YEAR:
- - januar
- - februar
- - mars
- - april
- - mai
- - juni
- - juli
- - august
- - september
- - oktober
- - november
- - desember
-DAYS_OF_THE_WEEK:
- - mandag
- - tirsdag
- - onsdag
- - torsdag
- - fredag
- - lørdag
- - søndag
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\nTittel: %1$s\n---\n\n# Feilmelding: Ugyldig Frontmatter\n\nSti: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ INFLECTOR_UNCOUNTABLE:
+ - 'utstyr'
+ - 'informasjon'
+ - 'ris'
+ - 'penger'
+ - 'arter'
+ - 'serier'
+ - 'fisk'
+ - 'sau'
+ INFLECTOR_IRREGULAR:
+ 'person': 'folk'
+ 'man': 'menn'
+ 'child': 'barn'
+ 'sex': 'kjønn'
+ 'move': 'trekk'
+ NICETIME:
+ NO_DATE_PROVIDED: Ingen dato gitt
+ BAD_DATE: Ugyldig dato
+ AGO: siden
+ FROM_NOW: fra nå
+ SECOND: sekund
+ MINUTE: minutt
+ HOUR: time
+ DAY: dag
+ WEEK: uke
+ MONTH: måned
+ YEAR: år
+ DECADE: tiår
+ SEC: sek
+ HR: t
+ WK: uke
+ MO: må
+ YR: år
+ DEC: tiår
+ SECOND_PLURAL: sekunder
+ MINUTE_PLURAL: minutter
+ HOUR_PLURAL: timer
+ DAY_PLURAL: dager
+ WEEK_PLURAL: uker
+ MONTH_PLURAL: måneder
+ YEAR_PLURAL: år
+ DECADE_PLURAL: tiår
+ SEC_PLURAL: sek
+ MIN_PLURAL: min
+ HR_PLURAL: timer
+ WK_PLURAL: uker
+ MO_PLURAL: md
+ YR_PLURAL: år
+ DEC_PLURAL: årtier
+ FORM:
+ VALIDATION_FAIL: Godkjenning mislyktes:
+ INVALID_INPUT: Ugyldig innhold i
+ MISSING_REQUIRED_FIELD: 'Mangler påkrevd felt:'
+ MONTHS_OF_THE_YEAR:
+ - 'januar'
+ - 'februar'
+ - 'mars'
+ - 'april'
+ - 'mai'
+ - 'juni'
+ - 'juli'
+ - 'august'
+ - 'september'
+ - 'oktober'
+ - 'november'
+ - 'desember'
+ DAYS_OF_THE_WEEK:
+ - 'mandag'
+ - 'tirsdag'
+ - 'onsdag'
+ - 'torsdag'
+ - 'fredag'
+ - 'lørdag'
+ - 'søndag'
diff --git a/system/languages/pl.yaml b/system/languages/pl.yaml
index 6a479bf..bf6fe37 100644
--- a/system/languages/pl.yaml
+++ b/system/languages/pl.yaml
@@ -1,75 +1,62 @@
---
-FRONTMATTER_ERROR_PAGE: |
- ---
- title: %1$s
- ---
-
- # Error: Nieprawidłowy Frontmatter
-
- Path: `%2$s`
-
- **%3$s**
-
- ```
- %4$s
- ```
-NICETIME:
- NO_DATE_PROVIDED: Nie podano daty
- BAD_DATE: Zła data
- AGO: temu
- FROM_NOW: od teraz
- SECOND: sekunda
- MINUTE: minuta
- HOUR: godzina
- DAY: dzień
- WEEK: tydzień
- MONTH: miesiąc
- YEAR: rok
- DECADE: dekada
- SEC: sek
- MIN: min
- HR: godz
- WK: tydz
- MO: m-c
- YR: rok
- DEC: dekada
- SECOND_PLURAL: sekund
- MINUTE_PLURAL: minut
- HOUR_PLURAL: godzin
- DAY_PLURAL: dni
- WEEK_PLURAL: tygodnie
- MONTH_PLURAL: miesięcy
- YEAR_PLURAL: lat
- DECADE_PLURAL: dekad
- SEC_PLURAL: sek
- MIN_PLURAL: min
- HR_PLURAL: godz
- WK_PLURAL: tyg
- MO_PLURAL: m-ce
- YR_PLURAL: lat
- DEC_PLURAL: dekad
-FORM:
- VALIDATION_FAIL: 'Weryfikacja nie powiodła się:'
- INVALID_INPUT: Nieprawidłowe dane wejściowe
- MISSING_REQUIRED_FIELD: 'Opuszczono wymagane pole:'
-MONTHS_OF_THE_YEAR:
- - Styczeń
- - Luty
- - Marzec
- - Kwiecień
- - Maj
- - Czerwiec
- - Lipiec
- - Sierpień
- - Wrzesień
- - Październik
- - Listopad
- - Grudzień
-DAYS_OF_THE_WEEK:
- - Poniedziałek
- - Wtorek
- - Środa
- - Czwartek
- - Piątek
- - Sobota
- - Niedziela
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Error: Nieprawidłowy Frontmatter\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ NICETIME:
+ NO_DATE_PROVIDED: Nie podano daty
+ BAD_DATE: Zła data
+ AGO: temu
+ FROM_NOW: od teraz
+ SECOND: sekunda
+ MINUTE: minuta
+ HOUR: godzina
+ DAY: dzień
+ WEEK: tydzień
+ MONTH: miesiąc
+ YEAR: rok
+ DECADE: dekada
+ SEC: sek
+ HR: godz
+ WK: tydz
+ MO: m-c
+ YR: rok
+ DEC: dekada
+ SECOND_PLURAL: sekund
+ MINUTE_PLURAL: minut
+ HOUR_PLURAL: godzin
+ DAY_PLURAL: dni
+ WEEK_PLURAL: tygodnie
+ MONTH_PLURAL: miesięcy
+ YEAR_PLURAL: lat
+ DECADE_PLURAL: dekad
+ SEC_PLURAL: sek
+ MIN_PLURAL: min
+ HR_PLURAL: godz
+ WK_PLURAL: tyg
+ MO_PLURAL: m-ce
+ YR_PLURAL: lat
+ DEC_PLURAL: dekad
+ FORM:
+ VALIDATION_FAIL: Weryfikacja nie powiodła się:
+ INVALID_INPUT: Nieprawidłowe dane wejściowe
+ MISSING_REQUIRED_FIELD: 'Opuszczono wymagane pole:'
+ MONTHS_OF_THE_YEAR:
+ - 'Styczeń'
+ - 'Luty'
+ - 'Marzec'
+ - 'Kwiecień'
+ - 'Maj'
+ - 'Czerwiec'
+ - 'Lipiec'
+ - 'Sierpień'
+ - 'Wrzesień'
+ - 'Październik'
+ - 'Listopad'
+ - 'Grudzień'
+ DAYS_OF_THE_WEEK:
+ - 'Poniedziałek'
+ - 'Wtorek'
+ - 'Środa'
+ - 'Czwartek'
+ - 'Piątek'
+ - 'Sobota'
+ - 'Niedziela'
diff --git a/system/languages/pt.yaml b/system/languages/pt.yaml
index a9da66a..bddd856 100644
--- a/system/languages/pt.yaml
+++ b/system/languages/pt.yaml
@@ -1,79 +1,37 @@
---
-FRONTMATTER_ERROR_PAGE: |
- ---
- título: %1$s
- ---
-
- # Erro: Frontmatter inválida
-
- Caminho: `%2$s`
-
- **%3$s**
-
- ```
- %4$s
- ```
-INFLECTOR_UNCOUNTABLE:
- 1: informação
- 2: arroz
- 3: dinheiro
-INFLECTOR_IRREGULAR:
- man: homens
- sex: sexos
-NICETIME:
- NO_DATE_PROVIDED: Não foi fornecida data
- BAD_DATE: Data inválida
- AGO: atrás
- FROM_NOW: a partir de agora
- SECOND: segundo
- MINUTE: minuto
- HOUR: hora
- DAY: dia
- WEEK: semana
- MONTH: mês
- YEAR: ano
- DECADE: década
- SEC: seg
- MIN: mín
- HR: h
- WK: sem
- MO: m
- YR: a
- DEC: dec
- SECOND_PLURAL: segundos
- MINUTE_PLURAL: minutos
- HOUR_PLURAL: horas
- DAY_PLURAL: dias
- WEEK_PLURAL: semanas
- MONTH_PLURAL: meses
- YEAR_PLURAL: anos
- DECADE_PLURAL: décadas
- SEC_PLURAL: seg
- MIN_PLURAL: mins
- HR_PLURAL: hrs
- WK_PLURAL: sems
- YR_PLURAL: anos
-FORM:
- VALIDATION_FAIL: 'Validação falhada: '
- MISSING_REQUIRED_FIELD: 'Campo obrigatório ausente:'
-MONTHS_OF_THE_YEAR:
- - Janeiro
- - Fevereiro
- - Março
- - Abril
- - Maio
- - Junho
- - Julho
- - Agosto
- - Setembro
- - Outubro
- - Novembro
- - Dezembro
-DAYS_OF_THE_WEEK:
- - Segunda
- - Terça
- - Quarta
- - Quinta
- - Sexta
- - Sábado
- - Domingo
+GRAV:
+ NICETIME:
+ NO_DATE_PROVIDED: Nenhuma data fornecida
+ AGO: atrás
+ SECOND: segundo
+ MINUTE: minuto
+ HOUR: hora
+ DAY: dia
+ WEEK: semana
+ MONTH: mês
+ YEAR: ano
+ DECADE: década
+ SEC: segundos
+ MIN: minutos
+ MINUTE_PLURAL: minutos
+ DAY_PLURAL: dias
+ WEEK_PLURAL: semanas
+ MONTH_PLURAL: meses
+ YEAR_PLURAL: anos
+ DECADE_PLURAL: decadas
+ FORM:
+ VALIDATION_FAIL: Falha na validação!
+ MISSING_REQUIRED_FIELD: 'Campo obrigatório requerido:'
+ MONTHS_OF_THE_YEAR:
+ - 'Janeiro'
+ - 'Fevereiro'
+ - 'Março'
+ - 'Abril'
+ - 'Maio'
+ - 'Junho'
+ - 'Julho'
+ - 'Agosto'
+ - 'Setembro'
+ - 'Outubro'
+ - 'Novembro'
+ - 'Dezembro'
diff --git a/system/languages/ro.yaml b/system/languages/ro.yaml
index a84a3c2..5bee578 100644
--- a/system/languages/ro.yaml
+++ b/system/languages/ro.yaml
@@ -1,101 +1,96 @@
---
-FRONTMATTER_ERROR_PAGE: |
- ---
- Titlu: %1$s
- ---
- # Eroare: Frontmatter este invalid
-
- Calea: `%2$s`
-
- **%3$s**
-
- ```
- %4$s
-INFLECTOR_PLURALS:
- /(quiz)$/i: '\1zes'
- /^(ox)$/i: '\1en'
- "/([m|l])ouse$/i": '\1ice'
- /(matr|vert|ind)ix|ex$/i: '\1ices'
- /(x|ch|ss|sh)$/i: '\1es'
- "/([^aeiouy]|qu)ies$/i": '\1y'
- "/([^aeiouy]|qu)y$/i": '\1ies'
- /(hive)$/i: '\1s'
- "/(?:([^f])fe|([lr])f)$/i": '\1\2ves'
- /sis$/i: ses
- "/([ti])um$/i": '\1a'
- /(buffal|tomat)o$/i: '\1oes'
-INFLECTOR_UNCOUNTABLE:
- - echipament
- - informaţie
- - orez
- - bani
- - specii
- - serii
- - peşte
- - oaie
-INFLECTOR_IRREGULAR:
- person: persoane
- man: bărbați
- child: copii
- sex: sexe
- move: mutări
-NICETIME:
- NO_DATE_PROVIDED: Nu există o dată prevăzută
- BAD_DATE: Dată incorectă
- AGO: în urmă
- FROM_NOW: de acum
- SECOND: secundă
- MINUTE: minut
- HOUR: oră
- DAY: zi
- WEEK: săptămână
- MONTH: lună
- YEAR: an
- DECADE: decadă
- SEC: sec
- MIN: min
- HR: oră
- WK: săpt
- MO: lună
- YR: an
- DEC: decadă
- SECOND_PLURAL: secunde
- MINUTE_PLURAL: minute
- HOUR_PLURAL: ore
- DAY_PLURAL: zile
- WEEK_PLURAL: săptămâni
- MONTH_PLURAL: luni
- YEAR_PLURAL: ani
- DECADE_PLURAL: decade
- SEC_PLURAL: sec
- MIN_PLURAL: min
- HR_PLURAL: ore
- WK_PLURAL: săpt
- MO_PLURAL: luni
- YR_PLURAL: ani
- DEC_PLURAL: decenii
-FORM:
- VALIDATION_FAIL: 'Validare nereușită'
- INVALID_INPUT: Date incorecte în
- MISSING_REQUIRED_FIELD: 'Câmp obligatoriu lipsă:'
-MONTHS_OF_THE_YEAR:
- - Ianuarie
- - Februarie
- - Martie
- - Aprilie
- - Mai
- - Iunie
- - Iulie
- - August
- - Septembrie
- - Octombrie
- - Noiembrie
- - Decembrie
-DAYS_OF_THE_WEEK:
- - Luni
- - Marți
- - Miercuri
- - Joi
- - Vineri
- - Sâmbătă
- - Duminică
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\nTitlu: %1$s\n---\n# Eroare: Frontmatter este invalid\n\nCalea: `%2$s`\n\n**%3$s**\n\n```\n%4$s"
+ INFLECTOR_UNCOUNTABLE:
+ - 'echipament'
+ - 'informaţie'
+ - 'orez'
+ - 'bani'
+ - 'specii'
+ - 'serii'
+ - 'peşte'
+ - 'oaie'
+ INFLECTOR_IRREGULAR:
+ 'person': 'persoane'
+ 'man': 'bărbați'
+ 'child': 'copii'
+ 'sex': 'sexe'
+ 'move': 'mutări'
+ NICETIME:
+ NO_DATE_PROVIDED: Nu există o dată prevăzută
+ BAD_DATE: Dată incorectă
+ AGO: în urmă
+ FROM_NOW: de acum
+ JUST_NOW: chiar acum
+ SECOND: secundă
+ MINUTE: minut
+ HOUR: oră
+ DAY: zi
+ WEEK: săptămână
+ MONTH: lună
+ YEAR: an
+ DECADE: decadă
+ SEC: secunde
+ MIN: minute
+ HR: oră
+ WK: săpt
+ MO: lună
+ YR: an
+ DEC: decadă
+ SECOND_PLURAL: secunde
+ MINUTE_PLURAL: minute
+ HOUR_PLURAL: ore
+ DAY_PLURAL: zile
+ WEEK_PLURAL: săptămâni
+ MONTH_PLURAL: luni
+ YEAR_PLURAL: ani
+ DECADE_PLURAL: decade
+ SEC_PLURAL: sec
+ MIN_PLURAL: min
+ HR_PLURAL: ore
+ WK_PLURAL: săpt
+ MO_PLURAL: luni
+ YR_PLURAL: ani
+ DEC_PLURAL: decenii
+ FORM:
+ VALIDATION_FAIL: Validare nereușită
+ INVALID_INPUT: Date incorecte în
+ MISSING_REQUIRED_FIELD: 'Câmp obligatoriu lipsă:'
+ MONTHS_OF_THE_YEAR:
+ - 'Ianuarie'
+ - 'Februarie'
+ - 'Martie'
+ - 'Aprilie'
+ - 'Mai'
+ - 'Iunie'
+ - 'Iulie'
+ - 'August'
+ - 'Septembrie'
+ - 'Octombrie'
+ - 'Noiembrie'
+ - 'Decembrie'
+ DAYS_OF_THE_WEEK:
+ - 'Luni'
+ - 'Marți'
+ - 'Miercuri'
+ - 'Joi'
+ - 'Vineri'
+ - 'Sâmbătă'
+ - 'Duminică'
+ CRON:
+ EVERY: la fiecare
+ EVERY_HOUR: la fiecare oră
+ EVERY_MINUTE: la fiecare minut
+ EVERY_DAY_OF_WEEK: fiecare zi a săptămânii
+ EVERY_DAY_OF_MONTH: fiecare zi a lunii
+ EVERY_MONTH: fiecare lună
+ TEXT_PERIOD: Fiecare
+ TEXT_MINS: ' la minut(e) ale fiecărei ore'
+ TEXT_TIME: ' la :'
+ TEXT_DOW: ' pe '
+ TEXT_MONTH: 'al(e) '
+ TEXT_DOM: ' pe '
+ ERROR1: Eticheta %s nu este acceptată!
+ ERROR2: Număr nevalid de elemente
+ ERROR3: jquery_element ar trebui setat în opțiunile jqCron
+ ERROR4: Expresie necunoscută
diff --git a/system/languages/ru.yaml b/system/languages/ru.yaml
index c8ba63e..78c5963 100644
--- a/system/languages/ru.yaml
+++ b/system/languages/ru.yaml
@@ -1,81 +1,101 @@
---
-FRONTMATTER_ERROR_PAGE: |
- ---
- title: %1$s
- ---
-
- # Ошибка: Недопустимое содержимое
-
- Path: `%2$s`
-
- **%3$s**
-
- ```
- %4$s
- ```
-INFLECTOR_IRREGULAR:
- person: люди
- man: человек
- child: ребенок
- sex: пол
- move: движется
-NICETIME:
- NO_DATE_PROVIDED: Дата не указана
- BAD_DATE: Неверная дата
- AGO: назад
- FROM_NOW: теперь
- SECOND: секунда
- MINUTE: минута
- HOUR: час
- DAY: д
- WEEK: неделя
- MONTH: месяц
- YEAR: год
- DECADE: десятилетие
- SEC: с
- MIN: мин
- HR: ч
- WK: нед.
- MO: мес.
- YR: г.
- DEC: гг.
- SECOND_PLURAL: секунды
- MINUTE_PLURAL: минуты
- HOUR_PLURAL: часы
- DAY_PLURAL: д
- WEEK_PLURAL: недели
- MONTH_PLURAL: месяцы
- YEAR_PLURAL: годы
- DECADE_PLURAL: десятилетия
- SEC_PLURAL: с
- MIN_PLURAL: мин
- HR_PLURAL: ч
- WK_PLURAL: нед
- MO_PLURAL: мес
- YR_PLURAL: г.
- DEC_PLURAL: гг.
-FORM:
- VALIDATION_FAIL: 'Проверка не удалась:'
- INVALID_INPUT: Неверный ввод в
- MISSING_REQUIRED_FIELD: 'Отсутствует необходимое поле:'
-MONTHS_OF_THE_YEAR:
- - Январь
- - Февраль
- - Март
- - Апрель
- - Май
- - Июнь
- - Июль
- - Август
- - Сентябрь
- - Октябрь
- - Ноябрь
- - Декабрь
-DAYS_OF_THE_WEEK:
- - Понедельник
- - Вторник
- - Среда
- - Четверг
- - Пятница
- - Суббота
- - Воскресенье
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Ошибка: недопустимое содержимое Frontmatter\n\nПуть: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ INFLECTOR_UNCOUNTABLE:
+ - 'экипировка'
+ - 'информация'
+ - 'рис'
+ - 'деньги'
+ - 'виды'
+ - 'серии'
+ - 'рыба'
+ - 'овца'
+ INFLECTOR_IRREGULAR:
+ 'person': 'люди'
+ 'man': 'человек'
+ 'child': 'ребенок'
+ 'sex': 'пол'
+ 'move': 'движется'
+ INFLECTOR_ORDINALS:
+ 'default': 'й'
+ 'first': 'й'
+ 'second': 'й'
+ 'third': 'й'
+ NICETIME:
+ NO_DATE_PROVIDED: Дата не указана
+ BAD_DATE: Неверная дата
+ AGO: назад
+ FROM_NOW: теперь
+ JUST_NOW: только что
+ SECOND: секунда
+ MINUTE: минута
+ HOUR: час
+ DAY: день
+ WEEK: неделя
+ MONTH: месяц
+ YEAR: год
+ DECADE: десятилетие
+ SEC: сек
+ MIN: мин
+ HR: ч
+ WK: нед
+ MO: мес
+ YR: г
+ DEC: дстлт
+ SECOND_PLURAL: сек
+ MINUTE_PLURAL: мин
+ HOUR_PLURAL: ч
+ DAY_PLURAL: д
+ WEEK_PLURAL: нед
+ MONTH_PLURAL: мес
+ YEAR_PLURAL: г
+ DECADE_PLURAL: дстлт
+ SEC_PLURAL: сек
+ MIN_PLURAL: мин
+ HR_PLURAL: ч
+ WK_PLURAL: нед
+ MO_PLURAL: мес
+ YR_PLURAL: г
+ DEC_PLURAL: дстлт
+ FORM:
+ VALIDATION_FAIL: Проверка не удалась:
+ INVALID_INPUT: Неверный ввод в
+ MISSING_REQUIRED_FIELD: 'Отсутствует необходимое поле:'
+ MONTHS_OF_THE_YEAR:
+ - 'январь'
+ - 'февраль'
+ - 'март'
+ - 'апрель'
+ - 'май'
+ - 'июнь'
+ - 'Июль'
+ - 'Август'
+ - 'Сентябрь'
+ - 'Октябрь'
+ - 'Ноябрь'
+ - 'Декабрь'
+ DAYS_OF_THE_WEEK:
+ - 'понедельник'
+ - 'вторник'
+ - 'среда'
+ - 'четверг'
+ - 'пятница'
+ - 'суббота'
+ - 'воскресенье'
+ CRON:
+ EVERY: раз в
+ EVERY_HOUR: раз в час
+ EVERY_MINUTE: раз в минуту
+ EVERY_DAY_OF_WEEK: каждый день недели
+ EVERY_DAY_OF_MONTH: каждый день недели
+ EVERY_MONTH: раз в месяц
+ TEXT_PERIOD: Каждый
+ TEXT_MINS: ' в минуте(ах) за час'
+ TEXT_TIME: ' в :'
+ TEXT_DOW: ' на '
+ TEXT_MONTH: ' из '
+ TEXT_DOM: ' на '
+ ERROR1: Тег %s не поддерживается!
+ ERROR2: Неверное количество элементов
+ ERROR3: jquery_element должен быть установлен в настройки jqCron
+ ERROR4: Выражение не распознано
diff --git a/system/languages/sk.yaml b/system/languages/sk.yaml
index 8651e7f..3defc36 100644
--- a/system/languages/sk.yaml
+++ b/system/languages/sk.yaml
@@ -1,42 +1,60 @@
---
-NICETIME:
- NO_DATE_PROVIDED: Neposkytnutý žiaden dátum
- BAD_DATE: Nesprávny dátum
- AGO: pred
- FROM_NOW: odteraz
- SECOND: sekunda
- MINUTE: minúta
- HOUR: hodina
- DAY: deň
- WEEK: týždeň
- MONTH: mesiac
- YEAR: rok
- DECADE: desaťročie
- SEC: sek
- MIN: min
- HR: hod
-FORM:
- VALIDATION_FAIL: 'Overenie zlyhalo:'
- INVALID_INPUT: Neplatný vstup v
- MISSING_REQUIRED_FIELD: 'Chýba vyžadované pole:'
-MONTHS_OF_THE_YEAR:
- - Január
- - Február
- - Marec
- - Apríl
- - Máj
- - Jún
- - Júl
- - August
- - September
- - Október
- - November
- - December
-DAYS_OF_THE_WEEK:
- - Pondelok
- - Utorok
- - Streda
- - Štvrtok
- - Piatok
- - Sobota
- - Nedeľa
+GRAV:
+ NICETIME:
+ NO_DATE_PROVIDED: Neposkytnutý žiaden dátum
+ BAD_DATE: Nesprávny dátum
+ AGO: pred
+ FROM_NOW: odteraz
+ SECOND: sekunda
+ MINUTE: minúta
+ HOUR: hodina
+ DAY: deň
+ WEEK: týždeň
+ MONTH: mesiac
+ YEAR: rok
+ DECADE: desaťročie
+ SEC: sek
+ HR: hod
+ WK: t
+ MO: m
+ YR: r
+ SECOND_PLURAL: sekúnd
+ MINUTE_PLURAL: minút
+ HOUR_PLURAL: hodín
+ DAY_PLURAL: dní
+ WEEK_PLURAL: týždňov
+ MONTH_PLURAL: mesiacov
+ YEAR_PLURAL: rokov
+ DECADE_PLURAL: dekád
+ SEC_PLURAL: sek
+ MIN_PLURAL: min
+ HR_PLURAL: hod
+ WK_PLURAL: t
+ MO_PLURAL: mes.
+ YR_PLURAL: rokov
+ DEC_PLURAL: dekád
+ FORM:
+ VALIDATION_FAIL: Overenie zlyhalo:
+ INVALID_INPUT: Neplatný vstup v
+ MISSING_REQUIRED_FIELD: 'Chýba vyžadované pole:'
+ MONTHS_OF_THE_YEAR:
+ - 'Január'
+ - 'Február'
+ - 'Marec'
+ - 'Apríl'
+ - 'Máj'
+ - 'Jún'
+ - 'Júl'
+ - 'August'
+ - 'September'
+ - 'Október'
+ - 'November'
+ - 'December'
+ DAYS_OF_THE_WEEK:
+ - 'Pondelok'
+ - 'Utorok'
+ - 'Streda'
+ - 'Štvrtok'
+ - 'Piatok'
+ - 'Sobota'
+ - 'Nedeľa'
diff --git a/system/languages/sl.yaml b/system/languages/sl.yaml
new file mode 100644
index 0000000..ae6032c
--- /dev/null
+++ b/system/languages/sl.yaml
@@ -0,0 +1,62 @@
+---
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Napaka: Neveljavna Frontmatter\n\nPath: `%2$s`\n\n**%3$s ** \n\n```\n%4$s \n```"
+ NICETIME:
+ NO_DATE_PROVIDED: Datum ni na voljo
+ BAD_DATE: Neveljaven datum
+ AGO: pred
+ FROM_NOW: od zdaj
+ SECOND: sekunda
+ MINUTE: minuta
+ HOUR: ura
+ DAY: dan
+ WEEK: teden
+ MONTH: mesec
+ YEAR: leto
+ DECADE: desetletje
+ SEC: sek
+ HR: ur
+ WK: T.
+ MO: m
+ YR: l
+ DEC: des
+ SECOND_PLURAL: sekund
+ MINUTE_PLURAL: minut
+ HOUR_PLURAL: ure
+ DAY_PLURAL: dnevi
+ WEEK_PLURAL: tednov
+ MONTH_PLURAL: mesecev
+ YEAR_PLURAL: leta
+ DECADE_PLURAL: desetletja
+ SEC_PLURAL: s
+ MIN_PLURAL: min
+ HR_PLURAL: ur
+ WK_PLURAL: t
+ MO_PLURAL: m
+ YR_PLURAL: l
+ DEC_PLURAL: des
+ FORM:
+ VALIDATION_FAIL: Preverjanje veljavnosti ni uspelo:
+ INVALID_INPUT: Neveljaven vnos v
+ MISSING_REQUIRED_FIELD: 'Manjka obvezno polje:'
+ MONTHS_OF_THE_YEAR:
+ - 'Januar'
+ - 'Februar'
+ - 'Marec'
+ - 'April'
+ - 'Maj'
+ - 'Junij'
+ - 'Julij'
+ - 'Avgust'
+ - 'September'
+ - 'Oktober'
+ - 'November'
+ - 'December'
+ DAYS_OF_THE_WEEK:
+ - 'Ponedeljek'
+ - 'Torek'
+ - 'Sreda'
+ - 'Četrtek'
+ - 'Petek'
+ - 'Sobota'
+ - 'Nedelja'
diff --git a/system/languages/sv.yaml b/system/languages/sv.yaml
index 8212eb3..e386c8c 100644
--- a/system/languages/sv.yaml
+++ b/system/languages/sv.yaml
@@ -1,62 +1,61 @@
---
-FRONTMATTER_ERROR_PAGE: '--- titel: %1$s --- # Fel: Ogiltig Frontmatter-sökväg: `%2$s` **%3$s** ``` %4$s ```'
-NICETIME:
- NO_DATE_PROVIDED: Inget datum har angivits
- BAD_DATE: Ogiltigt datum
- AGO: sedan
- FROM_NOW: från nu
- SECOND: sekund
- MINUTE: minut
- HOUR: timme
- DAY: dag
- WEEK: vecka
- MONTH: månad
- YEAR: år
- DECADE: årtionde
- SEC: sek
- MIN: min
- HR: t
- WK: v
- MO: m
- YR: år
- DEC: dec
- SECOND_PLURAL: sekunder
- MINUTE_PLURAL: minuter
- HOUR_PLURAL: timmar
- DAY_PLURAL: dagar
- WEEK_PLURAL: veckor
- MONTH_PLURAL: månader
- YEAR_PLURAL: år
- DECADE_PLURAL: årtionden
- SEC_PLURAL: sek
- MIN_PLURAL: min
- HR_PLURAL: t
- WK_PLURAL: v
- MO_PLURAL: må
- YR_PLURAL: år
- DEC_PLURAL: dec
-FORM:
- VALIDATION_FAIL: 'Kontrollen misslyckades:'
- INVALID_INPUT: Ogiltig indata i
- MISSING_REQUIRED_FIELD: 'Obligatoriskt fält måste fyllas i:'
-MONTHS_OF_THE_YEAR:
- - Januari
- - Februrari
- - Mars
- - April
- - Maj
- - Juni
- - Juli
- - Augusti
- - September
- - Oktober
- - November
- - December
-DAYS_OF_THE_WEEK:
- - Måndag
- - Tisdag
- - Onsdag
- - Torsdag
- - Fredag
- - Lördag
- - Söndag
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "--- titel: %1$s --- # Fel: Ogiltig Frontmatter-sökväg: `%2$s` **%3$s** ``` %4$s ```"
+ NICETIME:
+ NO_DATE_PROVIDED: Inget datum har angivits
+ BAD_DATE: Ogiltigt datum
+ AGO: sedan
+ FROM_NOW: fr.o.m nu
+ SECOND: sekund
+ MINUTE: minut
+ HOUR: timme
+ DAY: dag
+ WEEK: vecka
+ MONTH: månad
+ YEAR: år
+ DECADE: årtionde
+ SEC: sek
+ HR: t
+ WK: v
+ MO: m
+ YR: år
+ SECOND_PLURAL: sekunder
+ MINUTE_PLURAL: minuter
+ HOUR_PLURAL: timmar
+ DAY_PLURAL: dagar
+ WEEK_PLURAL: veckor
+ MONTH_PLURAL: månader
+ YEAR_PLURAL: år
+ DECADE_PLURAL: årtionden
+ SEC_PLURAL: sek
+ MIN_PLURAL: min
+ HR_PLURAL: t
+ WK_PLURAL: v
+ MO_PLURAL: må
+ YR_PLURAL: år
+ DEC_PLURAL: dec
+ FORM:
+ VALIDATION_FAIL: Kontrollen misslyckades:
+ INVALID_INPUT: Ogiltig indata i
+ MISSING_REQUIRED_FIELD: 'Obligatoriskt fält måste fyllas i:'
+ MONTHS_OF_THE_YEAR:
+ - 'Januari'
+ - 'Februari'
+ - 'Mars'
+ - 'April'
+ - 'Maj'
+ - 'Juni'
+ - 'Juli'
+ - 'Augusti'
+ - 'September'
+ - 'Oktober'
+ - 'November'
+ - 'December'
+ DAYS_OF_THE_WEEK:
+ - 'Måndag'
+ - 'Tisdag'
+ - 'Onsdag'
+ - 'Torsdag'
+ - 'Fredag'
+ - 'Lördag'
+ - 'Söndag'
diff --git a/system/languages/th.yaml b/system/languages/th.yaml
index d0023c2..e6ccfab 100644
--- a/system/languages/th.yaml
+++ b/system/languages/th.yaml
@@ -1,75 +1,56 @@
---
-FRONTMATTER_ERROR_PAGE: |
- ---
- ชื่อเรื่อง: %1$s
- ---
-
- # ข้อผิดพลาด: Invalid Frontmatter
-
- Path: `%2$s`
-
- **%3$s**
-
- ```
- %4$s
- ```
-NICETIME:
- NO_DATE_PROVIDED: ไม่มีวันที่ให้
- BAD_DATE: รูปแบบวันที่ผิด
- AGO: ที่ผ่านมา
- FROM_NOW: จากตอนนี้
- SECOND: วินาที
- MINUTE: นาที
- HOUR: ชั่วโมง
- DAY: วัน
- WEEK: สัปดาห์
- MONTH: เดือน
- YEAR: ปี
- DECADE: ทศวรรษที่ผ่านมา
- SEC: วิ
- MIN: นาที
- HR: ชม.
- WK: wk
- MO: mo
- YR: yr
- DEC: dec
- SECOND_PLURAL: วินาที
- MINUTE_PLURAL: นาที
- HOUR_PLURAL: ชั่วโมง
- DAY_PLURAL: วัน
- WEEK_PLURAL: สัปดาห์
- MONTH_PLURAL: เดือน
- YEAR_PLURAL: ปี
- DECADE_PLURAL: ทศวรรษที่ผ่านมา
- SEC_PLURAL: วินาที
- MIN_PLURAL: นาที
- HR_PLURAL: ชั่วโมง
- WK_PLURAL: wks
- MO_PLURAL: mos
- YR_PLURAL: ปี
- DEC_PLURAL: decs
-FORM:
- VALIDATION_FAIL: 'ตรวจสอบล้มเหลว: '
- INVALID_INPUT: ป้อนข้อมูลไม่ถูกต้องใน
- MISSING_REQUIRED_FIELD: 'ขาดข้อมูลที่จำเป็น:'
-MONTHS_OF_THE_YEAR:
- - มกราคม
- - กุมภาพันธ์
- - มีนาคม
- - เมษายน
- - พฤษภาคม
- - มิถุนายน
- - กรกฏาคม
- - สิงหาคม
- - กันยายน
- - ตุลาคม
- - พฤศจิกายน
- - ธันวาคม
-DAYS_OF_THE_WEEK:
- - จันทร์
- - อังคาร
- - พุธ
- - พฤหัสบดี
- - ศุกร์
- - เสาร์
- - อาทิตย์
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\nชื่อเรื่อง: %1$s\n---\n\n# ข้อผิดพลาด: Invalid Frontmatter\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ NICETIME:
+ NO_DATE_PROVIDED: ไม่มีวันที่ให้
+ BAD_DATE: รูปแบบวันที่ผิด
+ AGO: ที่ผ่านมา
+ FROM_NOW: จากตอนนี้
+ SECOND: วินาที
+ MINUTE: นาที
+ HOUR: ชั่วโมง
+ DAY: วัน
+ WEEK: สัปดาห์
+ MONTH: เดือน
+ YEAR: ปี
+ DECADE: ทศวรรษที่ผ่านมา
+ SEC: วิ
+ MIN: นาที
+ HR: ชม.
+ SECOND_PLURAL: วินาที
+ MINUTE_PLURAL: นาที
+ HOUR_PLURAL: ชั่วโมง
+ DAY_PLURAL: วัน
+ WEEK_PLURAL: สัปดาห์
+ MONTH_PLURAL: เดือน
+ YEAR_PLURAL: ปี
+ DECADE_PLURAL: ทศวรรษที่ผ่านมา
+ SEC_PLURAL: วินาที
+ MIN_PLURAL: นาที
+ HR_PLURAL: ชั่วโมง
+ YR_PLURAL: ปี
+ FORM:
+ VALIDATION_FAIL: 'ตรวจสอบล้มเหลว: '
+ INVALID_INPUT: ป้อนข้อมูลไม่ถูกต้องใน
+ MISSING_REQUIRED_FIELD: 'ขาดข้อมูลที่จำเป็น:'
+ MONTHS_OF_THE_YEAR:
+ - 'มกราคม'
+ - 'กุมภาพันธ์'
+ - 'มีนาคม'
+ - 'เมษายน'
+ - 'พฤษภาคม'
+ - 'มิถุนายน'
+ - 'กรกฏาคม'
+ - 'สิงหาคม'
+ - 'กันยายน'
+ - 'ตุลาคม'
+ - 'พฤศจิกายน'
+ - 'ธันวาคม'
+ DAYS_OF_THE_WEEK:
+ - 'จันทร์'
+ - 'อังคาร'
+ - 'พุธ'
+ - 'พฤหัสบดี'
+ - 'ศุกร์'
+ - 'เสาร์'
+ - 'อาทิตย์'
diff --git a/system/languages/tr.yaml b/system/languages/tr.yaml
index 68e3a85..2143f31 100644
--- a/system/languages/tr.yaml
+++ b/system/languages/tr.yaml
@@ -1,59 +1,63 @@
---
-NICETIME:
- NO_DATE_PROVIDED: Tarih yok
- BAD_DATE: Yanlış tarih
- AGO: önce
- FROM_NOW: (şimdiden)
- SECOND: saniye
- MINUTE: dakika
- HOUR: saat
- DAY: gün
- WEEK: hafta
- MONTH: ay
- YEAR: yıl
- DECADE: onyıl
- SEC: sn
- MIN: dk
- HR: sa
- WK: hft
- MO: ay
- YR: yl
- DEC: onyl
- SECOND_PLURAL: saniye
- MINUTE_PLURAL: dakika
- HOUR_PLURAL: saat
- DAY_PLURAL: gün
- WEEK_PLURAL: hafta
- MONTH_PLURAL: ay
- YEAR_PLURAL: yıl
- DECADE_PLURAL: onyıl
- SEC_PLURAL: sn
- MIN_PLURAL: dk
- HR_PLURAL: sa
- WK_PLURAL: hft
- MO_PLURAL: ay
- YR_PLURAL: yl
- DEC_PLURAL: onyl
-FORM:
- VALIDATION_FAIL: 'Doğrulama başarısız:'
-MONTHS_OF_THE_YEAR:
- - Ocak
- - Şubat
- - Mart
- - Nisan
- - Mayıs
- - Haziran
- - Temmuz
- - Ağustos
- - Eylül
- - Ekim
- - Kasım
- - Aralık
-DAYS_OF_THE_WEEK:
- - Pazartesi
- - Salı
- - Çarşamba
- - Perşembe
- - Cuma
- - Cumartesi
- - Pazar
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\nBaşlık: %1$s\n---\n\n# Hata: Geçersiz Önbölüm\n\nYol: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ NICETIME:
+ NO_DATE_PROVIDED: Sağlanan tarih yok
+ BAD_DATE: Yanlış tarih
+ AGO: önce
+ FROM_NOW: şu andan itibaren
+ SECOND: saniye
+ MINUTE: dakika
+ HOUR: saat
+ DAY: gün
+ WEEK: hafta
+ MONTH: ay
+ YEAR: yıl
+ DECADE: onyıl
+ SEC: sn
+ MIN: dk
+ HR: sa
+ WK: hft
+ MO: ay
+ YR: yl
+ DEC: onyl
+ SECOND_PLURAL: saniye
+ MINUTE_PLURAL: dakika
+ HOUR_PLURAL: saat
+ DAY_PLURAL: gün
+ WEEK_PLURAL: hafta
+ MONTH_PLURAL: ay
+ YEAR_PLURAL: yıl
+ DECADE_PLURAL: onyıl
+ SEC_PLURAL: sn
+ MIN_PLURAL: dk
+ HR_PLURAL: sa
+ WK_PLURAL: hft
+ MO_PLURAL: ay
+ YR_PLURAL: yıl
+ DEC_PLURAL: onyl
+ FORM:
+ VALIDATION_FAIL: Doğrulama başarısız:
+ INVALID_INPUT: Geçersiz bilgi girişi
+ MISSING_REQUIRED_FIELD: 'Gerekli alan eksik:'
+ MONTHS_OF_THE_YEAR:
+ - 'Ocak'
+ - 'Şubat'
+ - 'Mart'
+ - 'Nisan'
+ - 'Mayıs'
+ - 'Haziran'
+ - 'Temmuz'
+ - 'Ağustos'
+ - 'Eylül'
+ - 'Ekim'
+ - 'Kasım'
+ - 'Aralık'
+ DAYS_OF_THE_WEEK:
+ - 'Pazartesi'
+ - 'Salı'
+ - 'Çarşamba'
+ - 'Perşembe'
+ - 'Cuma'
+ - 'Cumartesi'
+ - 'Pazar'
diff --git a/system/languages/uk.yaml b/system/languages/uk.yaml
index 8ab7991..d264fba 100644
--- a/system/languages/uk.yaml
+++ b/system/languages/uk.yaml
@@ -1,75 +1,63 @@
---
-FRONTMATTER_ERROR_PAGE: |
- ---
- title: %1$s
- ---
-
- # Помилка: Недопустимий вміст
-
- Path: `%2$s`
-
- **%3$s**
-
- ```
- %4$s
- ```
-NICETIME:
- NO_DATE_PROVIDED: Не вказана дата
- BAD_DATE: Невірна дата
- AGO: назад
- FROM_NOW: відтепер
- SECOND: секунда
- MINUTE: хвилина
- HOUR: година
- DAY: день
- WEEK: тиждень
- MONTH: місяць
- YEAR: рік
- DECADE: десятиріччя
- SEC: с
- MIN: хв
- HR: год
- WK: тиж.
- MO: міс.
- YR: р.
- DEC: рр.
- SECOND_PLURAL: секунди
- MINUTE_PLURAL: хвилини
- HOUR_PLURAL: години
- DAY_PLURAL: дні
- WEEK_PLURAL: тижні
- MONTH_PLURAL: місяці
- YEAR_PLURAL: роки
- DECADE_PLURAL: десятиріччя
- SEC_PLURAL: с
- MIN_PLURAL: хв
- HR_PLURAL: год
- WK_PLURAL: тиж.
- MO_PLURAL: міс.
- YR_PLURAL: рр.
- DEC_PLURAL: рр.
-FORM:
- VALIDATION_FAIL: 'Перевірка не вдалася:'
- INVALID_INPUT: Невірне введення в
- MISSING_REQUIRED_FIELD: 'Відсутнє необхідне поле:'
-MONTHS_OF_THE_YEAR:
- - Січень
- - Лютий
- - Березень
- - Квітень
- - Травень
- - Червень
- - Липень
- - Серпень
- - Вересень
- - Жовтень
- - Листопад
- - Грудень
-DAYS_OF_THE_WEEK:
- - Понеділок
- - Вівторок
- - Середа
- - Четвер
- - "П'ятниця"
- - Субота
- - Неділя
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Помилка: Недопустимий вміст\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ NICETIME:
+ NO_DATE_PROVIDED: Не вказана дата
+ BAD_DATE: Невірна дата
+ AGO: назад
+ FROM_NOW: відтепер
+ SECOND: секунда
+ MINUTE: хвилина
+ HOUR: година
+ DAY: день
+ WEEK: тиждень
+ MONTH: місяць
+ YEAR: рік
+ DECADE: десятиріччя
+ SEC: с
+ MIN: хв
+ HR: год
+ WK: тиж.
+ MO: міс.
+ YR: р.
+ DEC: рр.
+ SECOND_PLURAL: секунди
+ MINUTE_PLURAL: хвилини
+ HOUR_PLURAL: години
+ DAY_PLURAL: дні
+ WEEK_PLURAL: тижні
+ MONTH_PLURAL: місяці
+ YEAR_PLURAL: роки
+ DECADE_PLURAL: десятиріччя
+ SEC_PLURAL: с
+ MIN_PLURAL: хв
+ HR_PLURAL: год
+ WK_PLURAL: тиж.
+ MO_PLURAL: міс.
+ YR_PLURAL: рр.
+ DEC_PLURAL: рр.
+ FORM:
+ VALIDATION_FAIL: Перевірка не вдалася:
+ INVALID_INPUT: Невірне введення в
+ MISSING_REQUIRED_FIELD: 'Відсутнє обов''язкове поле:'
+ MONTHS_OF_THE_YEAR:
+ - 'Січень'
+ - 'Лютий'
+ - 'Березень'
+ - 'Квітень'
+ - 'Травень'
+ - 'Червень'
+ - 'Липень'
+ - 'Серпень'
+ - 'Вересень'
+ - 'Жовтень'
+ - 'Листопад'
+ - 'Грудень'
+ DAYS_OF_THE_WEEK:
+ - 'Понеділок'
+ - 'Вівторок'
+ - 'Середа'
+ - 'Четвер'
+ - 'П''ятниця'
+ - 'Субота'
+ - 'Неділя'
diff --git a/system/languages/vi.yaml b/system/languages/vi.yaml
index e426993..0dc7e92 100644
--- a/system/languages/vi.yaml
+++ b/system/languages/vi.yaml
@@ -1,75 +1,63 @@
---
-FRONTMATTER_ERROR_PAGE: |
- ---
- title: %1$s
- ---
-
- # Error: Invalid Frontmatter
-
- Path: `%2$s`
-
- **%3$s**
-
- ```
- %4$s
- ```
-NICETIME:
- NO_DATE_PROVIDED: Không có ngày được cung cấp
- BAD_DATE: Ngày không hợp lệ
- AGO: cách đây
- FROM_NOW: từ bây giờ
- SECOND: giây
- MINUTE: phút
- HOUR: giờ
- DAY: ngày
- WEEK: tuần
- MONTH: tháng
- YEAR: năm
- DECADE: thập kỷ
- SEC: giây
- MIN: phút
- HR: giờ
- WK: tuần
- MO: tháng
- YR: năm
- DEC: thập kỷ
- SECOND_PLURAL: giây
- MINUTE_PLURAL: phút
- HOUR_PLURAL: giờ
- DAY_PLURAL: ngày
- WEEK_PLURAL: tuần
- MONTH_PLURAL: tháng
- YEAR_PLURAL: năm
- DECADE_PLURAL: thập kỷ
- SEC_PLURAL: giây
- MIN_PLURAL: phút
- HR_PLURAL: giờ
- WK_PLURAL: tuần
- MO_PLURAL: tháng
- YR_PLURAL: năm
- DEC_PLURAL: thập kỷ
-FORM:
- VALIDATION_FAIL: 'Xác nhận thất bại:'
- INVALID_INPUT: Dữ liệu nhập không hợp lệ cho
- MISSING_REQUIRED_FIELD: 'Thiếu trường bắt buộc:'
-MONTHS_OF_THE_YEAR:
- - Tháng 1
- - Tháng 2
- - Tháng 3
- - Tháng 4
- - Tháng 5
- - Tháng 6
- - Tháng 7
- - Tháng 8
- - Tháng 9
- - Tháng 10
- - Tháng Mười 11
- - Tháng 12
-DAYS_OF_THE_WEEK:
- - Thứ 2
- - Thứ 3
- - Thứ 4
- - Thứ 5
- - Thứ 6
- - Thứ 7
- - Chủ Nhật
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntiêu đề: %1$s\n---\n\n# Error: Trang không hợp lệ\n\nĐường dẫn: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ NICETIME:
+ NO_DATE_PROVIDED: Không có ngày được cung cấp
+ BAD_DATE: Ngày không hợp lệ
+ AGO: cách đây
+ FROM_NOW: từ bây giờ
+ SECOND: giây
+ MINUTE: phút
+ HOUR: giờ
+ DAY: ngày
+ WEEK: tuần
+ MONTH: tháng
+ YEAR: năm
+ DECADE: thập kỷ
+ SEC: giây
+ MIN: phút
+ HR: giờ
+ WK: tuần
+ MO: tháng
+ YR: năm
+ DEC: thập kỷ
+ SECOND_PLURAL: giây
+ MINUTE_PLURAL: phút
+ HOUR_PLURAL: giờ
+ DAY_PLURAL: ngày
+ WEEK_PLURAL: tuần
+ MONTH_PLURAL: tháng
+ YEAR_PLURAL: năm
+ DECADE_PLURAL: thập kỷ
+ SEC_PLURAL: giây
+ MIN_PLURAL: phút
+ HR_PLURAL: giờ
+ WK_PLURAL: tuần
+ MO_PLURAL: tháng
+ YR_PLURAL: năm
+ DEC_PLURAL: thập kỷ
+ FORM:
+ VALIDATION_FAIL: Xác nhận thất bại:
+ INVALID_INPUT: Dữ liệu nhập không hợp lệ cho
+ MISSING_REQUIRED_FIELD: 'Thiếu trường bắt buộc:'
+ MONTHS_OF_THE_YEAR:
+ - 'Tháng 1'
+ - 'Tháng 2'
+ - 'Tháng 3'
+ - 'Tháng 4'
+ - 'Tháng 5'
+ - 'Tháng 6'
+ - 'Tháng 7'
+ - 'Tháng 8'
+ - 'Tháng 9'
+ - 'Tháng 10'
+ - 'Tháng 11'
+ - 'Tháng 12'
+ DAYS_OF_THE_WEEK:
+ - 'Thứ 2'
+ - 'Thứ 3'
+ - 'Thứ 4'
+ - 'Thứ 5'
+ - 'Thứ 6'
+ - 'Thứ 7'
+ - 'Chủ Nhật'
diff --git a/system/languages/zh-tw.yaml b/system/languages/zh-tw.yaml
new file mode 100644
index 0000000..6ecfcc3
--- /dev/null
+++ b/system/languages/zh-tw.yaml
@@ -0,0 +1,42 @@
+---
+GRAV:
+ NICETIME:
+ SECOND: 秒
+ MINUTE: 分
+ HOUR: 小時
+ SECOND_PLURAL: 秒
+ MINUTE_PLURAL: 分
+ HOUR_PLURAL: 時
+ DAY_PLURAL: 日
+ WEEK_PLURAL: 周
+ MONTH_PLURAL: 月
+ YEAR_PLURAL: 年
+ DECADE_PLURAL: 十年
+ SEC_PLURAL: 秒
+ MIN_PLURAL: 分
+ HR_PLURAL: 時
+ WK_PLURAL: 周
+ MO_PLURAL: 月
+ YR_PLURAL: 年
+ DEC_PLURAL: 十年
+ MONTHS_OF_THE_YEAR:
+ - '一月'
+ - '二月'
+ - '三月'
+ - '四月'
+ - '五月'
+ - '六月'
+ - '七月'
+ - '八月'
+ - '九月'
+ - '十月'
+ - '十一月'
+ - '十二月'
+ DAYS_OF_THE_WEEK:
+ - '星期一'
+ - '星期二'
+ - '星期三'
+ - '星期四'
+ - '星期五'
+ - '星期六'
+ - '星期日'
diff --git a/system/languages/zh.yaml b/system/languages/zh.yaml
new file mode 100644
index 0000000..9ae1cdb
--- /dev/null
+++ b/system/languages/zh.yaml
@@ -0,0 +1,63 @@
+---
+GRAV:
+ FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# 錯誤: 不正確的 Frontmatter\n\n路徑: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+ NICETIME:
+ NO_DATE_PROVIDED: 沒有提供日期
+ BAD_DATE: 錯誤日期
+ AGO: 之前
+ FROM_NOW: 之後
+ JUST_NOW: 剛剛
+ SECOND: 秒
+ MINUTE: 分
+ HOUR: 小時
+ DAY: 天
+ WEEK: 週
+ MONTH: 月
+ YEAR: 年
+ DECADE: 十年
+ SEC: 秒
+ MIN: 分
+ HR: 小時
+ WK: 週
+ MO: 月
+ YR: 年
+ DEC: 十年
+ SECOND_PLURAL: 秒
+ MINUTE_PLURAL: 分
+ HOUR_PLURAL: 小時
+ DAY_PLURAL: 天
+ WEEK_PLURAL: 週
+ MONTH_PLURAL: 月
+ YEAR_PLURAL: 年
+ DECADE_PLURAL: 十年
+ SEC_PLURAL: 秒
+ MIN_PLURAL: 分
+ HR_PLURAL: 時
+ WK_PLURAL: 週
+ MO_PLURAL: 月
+ YR_PLURAL: 年
+ DEC_PLURAL: 十年
+ FORM:
+ MISSING_REQUIRED_FIELD: 遺漏必填欄位:
+ MONTHS_OF_THE_YEAR:
+ - '一月'
+ - '二月'
+ - '三月'
+ - '四月'
+ - '五月'
+ - '六月'
+ - '七月'
+ - '八月'
+ - '九月'
+ - '十月'
+ - '十一月'
+ - '十二月'
+ DAYS_OF_THE_WEEK:
+ - '星期一'
+ - '星期二'
+ - '星期三'
+ - '星期四'
+ - '星期五'
+ - '星期六'
+ - '星期日'
+
diff --git a/system/router.php b/system/router.php
index 2e5af82..9ab0558 100644
--- a/system/router.php
+++ b/system/router.php
@@ -1,13 +1,14 @@
or \n";
- }
-
-
- return $output;
- }
-
- /**
- * Build the JavaScript script tags.
- *
- * @param string $group name of the group
- * @param array $attributes
- *
- * @return string
- */
- public function js($group = 'head', $attributes = [])
- {
- if (!$this->js && !$this->inline_js) {
- return null;
- }
-
- // Sort array by priorities (larger priority first)
- uasort($this->js, array($this, 'sortAssetsByPriorityThenOrder'));
- uasort($this->inline_js, array($this, 'sortAssetsByPriorityThenOrder'));
-
- $inlineGroup = array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline';
-
- $attributes = $this->attributes($attributes);
-
- $output = '';
- $inline_js = '';
-
- if ($this->js_pipeline) {
- $pipeline_result = $this->pipelineJs($group, !$inlineGroup);
- $pipeline_html = ($inlineGroup ? '' : '' . "\n");
-
- if ($this->js_pipeline_before_excludes && $pipeline_result) {
- if ($inlineGroup) {
- $inline_js .= $pipeline_result;
- }
- else {
- $output .= $pipeline_html;
- }
- }
- foreach ($this->js_no_pipeline as $file) {
- if ($group && $file['group'] == $group) {
- if ($file['loading'] === 'inline') {
- $inline_js .= $this->gatherLinks([$file], JS_ASSET) . "\n";
- }
- else {
- $output .= '' . "\n";
- }
- }
- }
- if (!$this->js_pipeline_before_excludes && $pipeline_result) {
- if ($inlineGroup) {
- $inline_js .= $pipeline_result;
- }
- else {
- $output .= $pipeline_html;
- }
- }
- } else {
- foreach ($this->js as $file) {
- if ($group && $file['group'] == $group) {
- if ($inlineGroup || $file['loading'] === 'inline') {
- $inline_js .= $this->gatherLinks([$file], JS_ASSET) . "\n";
- }
- else {
- $output .= '' . "\n";
- }
- }
- }
- }
-
- // Render Inline JS
- foreach ($this->inline_js as $inline) {
- if ($group && $inline['group'] == $group) {
- $inline_js .= $inline['asset'] . "\n";
- }
- }
-
- if ($inline_js) {
- $attribute_string = isset($inline) && $inline['type'] ? " type=\"" . $inline['type'] . "\"" : '';
- $output .= "\n\n";
- }
-
- return $output;
- }
-
- /**
- * Minify and concatenate CSS
- *
- * @param string $group
- * @param bool $returnURL true if pipeline should return the URL, otherwise the content
- *
- * @return bool|string URL or generated content if available, else false
- */
- protected function pipelineCss($group = 'head', $returnURL = true)
- {
- // temporary list of assets to pipeline
- $temp_css = [];
-
- // clear no-pipeline assets lists
- $this->css_no_pipeline = [];
-
- // Compute uid based on assets and timestamp
- $uid = md5(json_encode($this->css) . $this->css_minify . $this->css_rewrite . $group);
- $file = $uid . '.css';
- $inline_file = $uid . '-inline.css';
-
- $relative_path = "{$this->base_url}{$this->assets_url}/{$file}";
-
- // If inline files exist set them on object
- if (file_exists($this->assets_dir . $inline_file)) {
- $this->css_no_pipeline = json_decode(file_get_contents($this->assets_dir . $inline_file), true);
- }
-
- // If pipeline exist return its URL or content
- if (file_exists($this->assets_dir . $file)) {
- if ($returnURL) {
- return $relative_path . $this->getTimestamp();
- }
- else {
- return file_get_contents($this->assets_dir . $file) . "\n";
- }
- }
-
- // Remove any non-pipeline files
- foreach ($this->css as $id => $asset) {
- if ($asset['group'] == $group) {
- if (!$asset['pipeline'] ||
- ($asset['remote'] && $this->css_pipeline_include_externals === false)) {
- $this->css_no_pipeline[$id] = $asset;
+ // If pipeline disabled, set to position if provided, else after
+ if (isset($options['pipeline'])) {
+ if ($options['pipeline'] === false) {
+ $exclude_type = ($type === $this::JS_TYPE || $type === $this::INLINE_JS_TYPE) ? $this::JS_TYPE : $this::CSS_TYPE;
+ $excludes = strtolower($exclude_type . '_pipeline_before_excludes');
+ if ($this->{$excludes}) {
+ $default = 'after';
} else {
- $temp_css[$id] = $asset;
+ $default = 'before';
}
+
+ $options['position'] = $options['position'] ?? $default;
}
+
+ unset($options['pipeline']);
}
- //if nothing found get out of here!
- if (count($temp_css) == 0) {
- return false;
+ // Add timestamp
+ $options['timestamp'] = $this->timestamp;
+
+ // Set order
+ $options['order'] = \count($this->$collection);
+
+ // Create asset of correct type
+ $asset_class = "\\Grav\\Common\\Assets\\{$type}";
+ $asset_object = new $asset_class();
+
+ // If exists
+ if ($asset_object->init($asset, $options)) {
+ $this->$collection[md5($asset)] = $asset_object;
}
- // Write non-pipeline files out
- if (!empty($this->css_no_pipeline)) {
- file_put_contents($this->assets_dir . $inline_file, json_encode($this->css_no_pipeline));
- }
+ return $this;
-
- $css_minify = $this->css_minify;
-
- // If this is a Windows server, and minify_windows is false (default value) skip the
- // minification process because it will cause Apache to die/crash due to insufficient
- // ThreadStackSize in httpd.conf - See: https://bugs.php.net/bug.php?id=47689
- if (strtoupper(substr(php_uname('s'), 0, 3)) === 'WIN' && !$this->css_minify_windows) {
- $css_minify = false;
- }
-
- // Concatenate files
- $buffer = $this->gatherLinks($temp_css, CSS_ASSET);
- if ($css_minify) {
- $minifier = new \MatthiasMullie\Minify\CSS();
- $minifier->add($buffer);
- $buffer = $minifier->minify();
- }
-
- // Write file
- if (strlen(trim($buffer)) > 0) {
- file_put_contents($this->assets_dir . $file, $buffer);
-
- if ($returnURL) {
- return $relative_path . $this->getTimestamp();
- }
- else {
- return $buffer . "\n";
- }
- } else {
- return false;
- }
}
/**
- * Minify and concatenate JS files.
+ * Add a CSS asset or a collection of assets.
*
- * @param string $group
- * @param bool $returnURL true if pipeline should return the URL, otherwise the content
- *
- * @return bool|string URL or generated content if available, else false
+ * @return $this
*/
- protected function pipelineJs($group = 'head', $returnURL = true)
+ public function addCss($asset)
{
- // temporary list of assets to pipeline
- $temp_js = [];
-
- // clear no-pipeline assets lists
- $this->js_no_pipeline = [];
-
- // Compute uid based on assets and timestamp
- $uid = md5(json_encode($this->js) . $this->js_minify . $group);
- $file = $uid . '.js';
- $inline_file = $uid . '-inline.js';
-
- $relative_path = "{$this->base_url}{$this->assets_url}/{$file}";
-
- // If inline files exist set them on object
- if (file_exists($this->assets_dir . $inline_file)) {
- $this->js_no_pipeline = json_decode(file_get_contents($this->assets_dir . $inline_file), true);
- }
-
- // If pipeline exist return its URL or content
- if (file_exists($this->assets_dir . $file)) {
- if ($returnURL) {
- return $relative_path . $this->getTimestamp();
- }
- else {
- return file_get_contents($this->assets_dir . $file) . "\n";
- }
- }
-
- // Remove any non-pipeline files
- foreach ($this->js as $id => $asset) {
- if ($asset['group'] == $group) {
- if (!$asset['pipeline'] ||
- ($asset['remote'] && $this->js_pipeline_include_externals === false)) {
- $this->js_no_pipeline[] = $asset;
- } else {
- $temp_js[$id] = $asset;
- }
- }
- }
-
- //if nothing found get out of here!
- if (count($temp_js) == 0) {
- return false;
- }
-
- // Write non-pipeline files out
- if (!empty($this->js_no_pipeline)) {
- file_put_contents($this->assets_dir . $inline_file, json_encode($this->js_no_pipeline));
- }
-
- // Concatenate files
- $buffer = $this->gatherLinks($temp_js, JS_ASSET);
- if ($this->js_minify) {
- $minifier = new \MatthiasMullie\Minify\JS();
- $minifier->add($buffer);
- $buffer = $minifier->minify();
- }
-
- // Write file
- if (strlen(trim($buffer)) > 0) {
- file_put_contents($this->assets_dir . $file, $buffer);
-
- if ($returnURL) {
- return $relative_path . $this->getTimestamp();
- }
- else {
- return $buffer . "\n";
- }
- } else {
- return false;
- }
+ return $this->addType(Assets::CSS_COLLECTION,Assets::CSS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::CSS_TYPE));
}
/**
- * Return the array of all the registered CSS assets
- * If a $key is provided, it will try to return only that asset
- * else it will return null
+ * Add an Inline CSS asset or a collection of assets.
*
- * @param null|string $key the asset key
- * @return array
+ * @return $this
*/
- public function getCss($key = null)
+ public function addInlineCss($asset)
{
- if (!empty($key)) {
- $asset_key = md5($key);
- if (isset($this->css[$asset_key])) {
- return $this->css[$asset_key];
- } else {
- return null;
- }
- }
-
- return $this->css;
+ return $this->addType(Assets::CSS_COLLECTION, Assets::INLINE_CSS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::INLINE_CSS_TYPE));
}
/**
- * Return the array of all the registered JS assets
- * If a $key is provided, it will try to return only that asset
- * else it will return null
+ * Add a JS asset or a collection of assets.
*
- * @param null|string $key the asset key
- * @return array
+ * @return $this
*/
- public function getJs($key = null)
+ public function addJs($asset)
{
- if (!empty($key)) {
- $asset_key = md5($key);
- if (isset($this->js[$asset_key])) {
- return $this->js[$asset_key];
- } else {
- return null;
- }
- }
-
- return $this->js;
+ return $this->addType(Assets::JS_COLLECTION, Assets::JS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::JS_TYPE));
}
/**
- * Set the whole array of CSS assets
+ * Add an Inline JS asset or a collection of assets.
*
- * @param $css
+ * @return $this
*/
- public function setCss($css)
+ public function addInlineJs($asset)
{
- $this->css = $css;
+ return $this->addType(Assets::JS_COLLECTION, Assets::INLINE_JS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::INLINE_JS_TYPE));
}
- /**
- * Set the whole array of JS assets
- *
- * @param $js
- */
- public function setJs($js)
- {
- $this->js = $js;
- }
-
- /**
- * Removes an item from the CSS array if set
- *
- * @param string $key The asset key
- */
- public function removeCss($key)
- {
- $asset_key = md5($key);
- if (isset($this->css[$asset_key])) {
- unset($this->css[$asset_key]);
- }
- }
-
- /**
- * Removes an item from the JS array if set
- *
- * @param string $key The asset key
- */
- public function removeJs($key)
- {
- $asset_key = md5($key);
- if (isset($this->js[$asset_key])) {
- unset($this->js[$asset_key]);
- }
- }
-
- /**
- * Return the array of all the registered collections
- *
- * @return array
- */
- public function getCollections()
- {
- return $this->collections;
- }
-
- /**
- * Set the array of collections explicitly
- *
- * @param $collections
- */
- public function setCollection($collections)
- {
- $this->collections = $collections;
- }
-
- /**
- * Determines if an asset exists as a collection, CSS or JS reference
- *
- * @param $asset
- *
- * @return bool
- */
- public function exists($asset)
- {
- if (isset($this->collections[$asset]) || isset($this->css[$asset]) || isset($this->js[$asset])) {
- return true;
- } else {
- return false;
- }
- }
/**
* Add/replace collection.
@@ -1000,445 +264,112 @@ class Assets
return $this;
}
- /**
- * Reset all assets.
- *
- * @return $this
- */
- public function reset()
+ protected function filterAssets($assets, $key, $value, $sort = false)
{
- return $this->resetCss()->resetJs();
- }
+ $results = array_filter($assets, function($asset) use ($key, $value) {
- /**
- * Reset JavaScript assets.
- *
- * @return $this
- */
- public function resetJs()
- {
- $this->js = [];
- $this->inline_js = [];
+ if ($key === 'position' && $value === 'pipeline') {
- return $this;
- }
+ $type = $asset->getType();
- /**
- * Reset CSS assets.
- *
- * @return $this
- */
- public function resetCss()
- {
- $this->css = [];
- $this->inline_css = [];
+ if ($asset->getRemote() && $this->{$type . '_pipeline_include_externals'} === false && $asset['position'] === 'pipeline' ) {
+ if ($this->{$type . '_pipeline_before_excludes'}) {
+ $asset->setPosition('after');
+ } else {
+ $asset->setPosition('before');
+ }
+ return false;
+ }
- return $this;
- }
-
- /**
- * Add all JavaScript assets within $directory
- *
- * @param string $directory Relative to the Grav root path, or a stream identifier
- *
- * @return $this
- */
- public function addDirJs($directory)
- {
- return $this->addDir($directory, self::JS_REGEX);
- }
-
- /**
- * Add all CSS assets within $directory
- *
- * @param string $directory Relative to the Grav root path, or a stream identifier
- *
- * @return $this
- */
- public function addDirCss($directory)
- {
- return $this->addDir($directory, self::CSS_REGEX);
- }
-
- /**
- * Add all assets matching $pattern within $directory.
- *
- * @param string $directory Relative to the Grav root path, or a stream identifier
- * @param string $pattern (regex)
- *
- * @return $this
- * @throws Exception
- */
- public function addDir($directory, $pattern = self::DEFAULT_REGEX)
- {
- $root_dir = rtrim(ROOT_DIR, '/');
-
- // Check if $directory is a stream.
- if (strpos($directory, '://')) {
- $directory = Grav::instance()['locator']->findResource($directory, null);
- }
-
- // Get files
- $files = $this->rglob($root_dir . DIRECTORY_SEPARATOR . $directory, $pattern, $root_dir . '/');
-
- // No luck? Nothing to do
- if (!$files) {
- return $this;
- }
-
- // Add CSS files
- if ($pattern === self::CSS_REGEX) {
- foreach ($files as $file) {
- $this->addCss($file);
}
- return $this;
- }
-
- // Add JavaScript files
- if ($pattern === self::JS_REGEX) {
- foreach ($files as $file) {
- $this->addJs($file);
- }
-
- return $this;
- }
-
- // Unknown pattern.
- foreach ($files as $asset) {
- $this->add($asset);
- }
-
- return $this;
- }
-
- /**
- * Determine whether a link is local or remote.
- *
- * Understands both "http://" and "https://" as well as protocol agnostic links "//"
- *
- * @param string $link
- *
- * @return bool
- */
- protected function isRemoteLink($link)
- {
- $base = Grav::instance()['uri']->rootUrl(true);
-
- // sanity check for local URLs with absolute URL's enabled
- if (Utils::startsWith($link, $base)) {
+ if ($asset[$key] === $value) return true;
return false;
+ });
+
+ if ($sort && !empty($results)) {
+ $results = $this->sortAssets($results);
}
- return ('http://' === substr($link, 0, 7) || 'https://' === substr($link, 0, 8) || '//' === substr($link, 0,
- 2));
+
+ return $results;
}
- /**
- * Build local links including grav asset shortcodes
- *
- * @param string $asset the asset string reference
- * @param bool $absolute build absolute asset link
- *
- * @return string the final link url to the asset
- */
- protected function buildLocalLink($asset, $absolute = false)
+ protected function sortAssets($assets)
{
- try {
- $asset = Grav::instance()['locator']->findResource($asset, $absolute);
- } catch (\Exception $e) {
- }
-
- $uri = $absolute ? $asset : $this->base_url . ltrim($asset, '/');
- return $asset ? $uri : false;
+ uasort ($assets, function($a, $b) {
+ if ($a['priority'] == $b['priority']) {
+ return $a['order'] - $b['order'];
+ }
+ return $b['priority'] - $a['priority'];
+ });
+ return $assets;
}
- /**
- * Get the last modification time of asset
- *
- * @param string $asset the asset string reference
- *
- * @return string the last modifcation time or false on error
- */
- protected function getLastModificationTime($asset)
+ public function render($type, $group = 'head', $attributes = [])
{
- $file = GRAV_ROOT . $asset;
- if (Grav::instance()['locator']->isStream($asset)) {
- $file = $this->buildLocalLink($asset, true);
+ $before_output = '';
+ $pipeline_output = '';
+ $after_output = '';
+
+ $assets = 'assets_' . $type;
+ $pipeline_enabled = $type . '_pipeline';
+ $render_pipeline = 'render' . ucfirst($type);
+
+ $group_assets = $this->filterAssets($this->$assets, 'group', $group);
+ $pipeline_assets = $this->filterAssets($group_assets, 'position', 'pipeline', true);
+ $before_assets = $this->filterAssets($group_assets, 'position', 'before', true);
+ $after_assets = $this->filterAssets($group_assets, 'position', 'after', true);
+
+ // Pipeline
+ if ($this->{$pipeline_enabled}) {
+ $options = array_merge($this->pipeline_options, ['timestamp' => $this->timestamp]);
+
+ $pipeline = new Pipeline($options);
+ $pipeline_output = $pipeline->$render_pipeline($pipeline_assets, $group, $attributes);
+ } else {
+ foreach ($pipeline_assets as $asset) {
+ $pipeline_output .= $asset->render();
+ }
}
- return file_exists($file) ? filemtime($file) : false;
+ // Before Pipeline
+ foreach ($before_assets as $asset) {
+ $before_output .= $asset->render();
+ }
+
+ // After Pipeline
+ foreach ($after_assets as $asset) {
+ $after_output .= $asset->render();
+ }
+
+ return $before_output . $pipeline_output . $after_output;
}
+
/**
- * Build an HTML attribute string from an array.
+ * Build the CSS link tags.
*
- * @param array $attributes
+ * @param string $group name of the group
+ * @param array $attributes
*
* @return string
*/
- protected function attributes(array $attributes)
+ public function css($group = 'head', $attributes = [])
{
- $html = '';
- $no_key = ['loading'];
-
- foreach ($attributes as $key => $value) {
- // For numeric keys we will assume that the key and the value are the same
- // as this will convert HTML attributes such as "required" to a correct
- // form like required="required" instead of using incorrect numerics.
- if (is_numeric($key)) {
- $key = $value;
- }
- if (is_array($value)) {
- $value = implode(' ', $value);
- }
-
- if (in_array($key, $no_key)) {
- $element = htmlentities($value, ENT_QUOTES, 'UTF-8', false);
- } else {
- $element = $key . '="' . htmlentities($value, ENT_QUOTES, 'UTF-8', false) . '"';
- }
-
- $html .= ' ' . $element;
- }
-
- return $html;
+ return $this->render('css', $group, $attributes);
}
/**
- * Download and concatenate the content of several links.
+ * Build the JavaScript script tags.
*
- * @param array $links
- * @param bool $css
+ * @param string $group name of the group
+ * @param array $attributes
*
* @return string
*/
- protected function gatherLinks(array $links, $css = true)
+ public function js($group = 'head', $attributes = [])
{
- $buffer = '';
-
-
- foreach ($links as $asset) {
- $relative_dir = '';
- $local = true;
-
- $link = $asset['asset'];
- $relative_path = $link;
-
- if ($this->isRemoteLink($link)) {
- $local = false;
- if ('//' === substr($link, 0, 2)) {
- $link = 'http:' . $link;
- }
- } else {
- // Fix to remove relative dir if grav is in one
- if (($this->base_url != '/') && (strpos($this->base_url, $link) == 0)) {
- $base_url = '#' . preg_quote($this->base_url, '#') . '#';
- $relative_path = ltrim(preg_replace($base_url, '/', $link, 1), '/');
- }
-
- $relative_dir = dirname($relative_path);
- $link = ROOT_DIR . $relative_path;
- }
-
- $file = ($this->fetch_command instanceof Closure) ? @$this->fetch_command->__invoke($link) : @file_get_contents($link);
-
- // No file found, skip it...
- if ($file === false) {
- continue;
- }
-
- // Double check last character being
- if (!$css) {
- $file = rtrim($file, ' ;') . ';';
- }
-
- // If this is CSS + the file is local + rewrite enabled
- if ($css && $local && $this->css_rewrite) {
- $file = $this->cssRewrite($file, $relative_dir);
- }
-
- $file = rtrim($file) . PHP_EOL;
- $buffer .= $file;
- }
-
- // Pull out @imports and move to top
- if ($css) {
- $buffer = $this->moveImports($buffer);
- }
-
- return $buffer;
+ return $this->render('js', $group, $attributes);
}
-
- /**
- * Finds relative CSS urls() and rewrites the URL with an absolute one
- *
- * @param string $file the css source file
- * @param string $relative_path relative path to the css file
- *
- * @return mixed
- */
- protected function cssRewrite($file, $relative_path)
- {
- // Strip any sourcemap comments
- $file = preg_replace(self::CSS_SOURCEMAP_REGEX, '', $file);
-
- // Find any css url() elements, grab the URLs and calculate an absolute path
- // Then replace the old url with the new one
- $file = preg_replace_callback(self::CSS_URL_REGEX, function ($matches) use ($relative_path) {
-
- $old_url = $matches[2];
-
- // Ensure link is not rooted to webserver, a data URL, or to a remote host
- if (Utils::startsWith($old_url, '/') || Utils::startsWith($old_url, 'data:') || $this->isRemoteLink($old_url)) {
- return $matches[0];
- }
-
- $new_url = $this->base_url . ltrim(Utils::normalizePath($relative_path . '/' . $old_url), '/');
-
- return str_replace($old_url, $new_url, $matches[0]);
- }, $file);
-
- return $file;
- }
-
- /**
- * Moves @import statements to the top of the file per the CSS specification
- *
- * @param string $file the file containing the combined CSS files
- *
- * @return string the modified file with any @imports at the top of the file
- */
- protected function moveImports($file)
- {
- $this->imports = [];
-
- $file = preg_replace_callback(self::CSS_IMPORT_REGEX, function ($matches) {
- $this->imports[] = $matches[0];
-
- return '';
- }, $file);
-
- return implode("\n", $this->imports) . "\n\n" . $file;
- }
-
- /**
- * Recursively get files matching $pattern within $directory.
- *
- * @param string $directory
- * @param string $pattern (regex)
- * @param string $ltrim Will be trimmed from the left of the file path
- *
- * @return array
- */
- protected function rglob($directory, $pattern, $ltrim = null)
- {
- $iterator = new RegexIterator(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory,
- FilesystemIterator::SKIP_DOTS)), $pattern);
- $offset = strlen($ltrim);
- $files = [];
-
- foreach ($iterator as $file) {
- $files[] = substr($file->getPathname(), $offset);
- }
-
- return $files;
- }
-
- /**
- * Sets the state of CSS Pipeline
- *
- * @param boolean $value
- */
- public function setCssPipeline($value)
- {
- $this->css_pipeline = (bool)$value;
- }
-
- /**
- * Sets the state of JS Pipeline
- *
- * @param boolean $value
- */
- public function setJsPipeline($value)
- {
- $this->js_pipeline = (bool)$value;
- }
-
- /**
- * Explicitly set's a timestamp for assets
- *
- * @param $value
- */
- public function setTimestamp($value)
- {
- $this->timestamp = $value;
- }
-
- /**
- * Get the timestamp for assets
- *
- * @return string
- */
- public function getTimestamp($include_join = true)
- {
- if ($this->timestamp) {
- $timestamp = $include_join ? '?' . $this->timestamp : $this->timestamp;
- return $timestamp;
- }
- return;
- }
-
- /**
- *
- *
- * @param $asset
- * @return string
- */
- public function getQuerystring($asset)
- {
- $querystring = '';
-
- if (!empty($asset['query'])) {
- if (Utils::contains($asset['asset'], '?')) {
- $querystring .= '&' . $asset['query'];
- } else {
- $querystring .= '?' . $asset['query'];
- }
- }
-
- if ($this->timestamp) {
- if (Utils::contains($asset['asset'], '?') || $querystring) {
- $querystring .= '&' . $this->timestamp;
- } else {
- $querystring .= '?' . $this->timestamp;
- }
- }
-
- return $querystring;
- }
-
- /**
- * @return string
- */
- public function __toString()
- {
- return '';
- }
-
- /**
- * @param $a
- * @param $b
- *
- * @return mixed
- */
- protected function sortAssetsByPriorityThenOrder($a, $b)
- {
- if ($a['priority'] == $b['priority']) {
- return $a['order'] - $b['order'];
- }
-
- return $b['priority'] - $a['priority'];
- }
-
}
diff --git a/system/src/Grav/Common/Assets/BaseAsset.php b/system/src/Grav/Common/Assets/BaseAsset.php
new file mode 100644
index 0000000..49e3864
--- /dev/null
+++ b/system/src/Grav/Common/Assets/BaseAsset.php
@@ -0,0 +1,198 @@
+ 'head',
+ 'position' => 'pipeline',
+ 'priority' => 10,
+ 'modified' => null,
+ 'asset' => null
+ ];
+
+ // Merge base defaults
+ $elements = array_merge($base_config, $elements);
+
+ parent::__construct($elements, $key);
+ }
+
+ public function init($asset, $options)
+ {
+ $config = Grav::instance()['config'];
+ $uri = Grav::instance()['uri'];
+
+ // set attributes
+ foreach ($options as $key => $value) {
+ if ($this->hasProperty($key)) {
+ $this->setProperty($key, $value);
+ } else {
+ $this->attributes[$key] = $value;
+ }
+ }
+
+ // Force priority to be an int
+ $this->priority = (int) $this->priority;
+
+ // Do some special stuff for CSS/JS (not inline)
+ if (!Utils::startsWith($this->getType(), 'inline')) {
+ $this->base_url = rtrim($uri->rootUrl($config->get('system.absolute_urls')), '/') . '/';
+ $this->remote = static::isRemoteLink($asset);
+
+ // Move this to render?
+ if (!$this->remote) {
+
+ $asset_parts = parse_url($asset);
+ if (isset($asset_parts['query'])) {
+ $this->query = $asset_parts['query'];
+ unset($asset_parts['query']);
+ $asset = Uri::buildUrl($asset_parts);
+ }
+
+ $locator = Grav::instance()['locator'];
+
+ if ($locator->isStream($asset)) {
+ $path = $locator->findResource($asset, true);
+ } else {
+ $path = GRAV_ROOT . $asset;
+ }
+
+ // If local file is missing return
+ if ($path === false) {
+ return false;
+ }
+
+ $file = new \SplFileInfo($path);
+
+ $asset = $this->buildLocalLink($file->getPathname());
+
+ $this->modified = $file->isFile() ? $file->getMTime() : false;
+ }
+ }
+
+ $this->asset = $asset;
+
+ return $this;
+ }
+
+ public function getAsset()
+ {
+ return $this->asset;
+ }
+
+ public function getRemote()
+ {
+ return $this->remote;
+ }
+
+ public function setPosition($position)
+ {
+ $this->position = $position;
+ return $this;
+ }
+
+
+ /**
+ *
+ * Get the last modification time of asset
+ *
+ * @param string $asset the asset string reference
+ *
+ * @return string the last modifcation time or false on error
+ */
+// protected function getLastModificationTime($asset)
+// {
+// $file = GRAV_ROOT . $asset;
+// if (Grav::instance()['locator']->isStream($asset)) {
+// $file = $this->buildLocalLink($asset, true);
+// }
+//
+// return file_exists($file) ? filemtime($file) : false;
+// }
+
+ /**
+ *
+ * Build local links including grav asset shortcodes
+ *
+ * @param string $asset the asset string reference
+ *
+ * @return string the final link url to the asset
+ */
+ protected function buildLocalLink($asset)
+ {
+ if ($asset) {
+ return $this->base_url . ltrim(Utils::replaceFirstOccurrence(GRAV_ROOT, '', $asset), '/');
+ }
+ return false;
+ }
+
+
+ /**
+ * Implements JsonSerializable interface.
+ *
+ * @return array
+ */
+ public function jsonSerialize()
+ {
+ return ['type' => $this->getType(), 'elements' => $this->getElements()];
+ }
+
+ /**
+ * Placeholder for AssetUtilsTrait method
+ *
+ * @param string $file
+ * @param string $dir
+ * @param bool $local
+ */
+ protected function cssRewrite($file, $dir, $local)
+ {
+ return;
+ }
+}
diff --git a/system/src/Grav/Common/Assets/Css.php b/system/src/Grav/Common/Assets/Css.php
new file mode 100644
index 0000000..405d8a3
--- /dev/null
+++ b/system/src/Grav/Common/Assets/Css.php
@@ -0,0 +1,40 @@
+ 'css',
+ 'attributes' => [
+ 'type' => 'text/css',
+ 'rel' => 'stylesheet'
+ ]
+ ];
+
+ $merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
+
+ parent::__construct($merged_attributes, $key);
+ }
+
+ public function render()
+ {
+ if (isset($this->attributes['loading']) && $this->attributes['loading'] === 'inline') {
+ $buffer = $this->gatherLinks( [$this], self::CSS_ASSET);
+ return "\n";
+ }
+
+ return 'renderAttributes() . ">\n";
+ }
+}
diff --git a/system/src/Grav/Common/Assets/InlineCss.php b/system/src/Grav/Common/Assets/InlineCss.php
new file mode 100644
index 0000000..c48dc48
--- /dev/null
+++ b/system/src/Grav/Common/Assets/InlineCss.php
@@ -0,0 +1,32 @@
+ 'css',
+ 'position' => 'after'
+ ];
+
+ $merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
+
+ parent::__construct($merged_attributes, $key);
+ }
+
+ public function render()
+ {
+ return '\n";
+ }
+}
diff --git a/system/src/Grav/Common/Assets/InlineJs.php b/system/src/Grav/Common/Assets/InlineJs.php
new file mode 100644
index 0000000..11e4442
--- /dev/null
+++ b/system/src/Grav/Common/Assets/InlineJs.php
@@ -0,0 +1,32 @@
+ 'js',
+ 'position' => 'after'
+ ];
+
+ $merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
+
+ parent::__construct($merged_attributes, $key);
+ }
+
+ public function render()
+ {
+ return '\n";
+ }
+}
diff --git a/system/src/Grav/Common/Assets/Js.php b/system/src/Grav/Common/Assets/Js.php
new file mode 100644
index 0000000..cce86d9
--- /dev/null
+++ b/system/src/Grav/Common/Assets/Js.php
@@ -0,0 +1,36 @@
+ 'js',
+ ];
+
+ $merged_attributes = Utils::arrayMergeRecursiveUnique($base_options, $elements);
+
+ parent::__construct($merged_attributes, $key);
+ }
+
+ public function render()
+ {
+ if (isset($this->attributes['loading']) && $this->attributes['loading'] === 'inline') {
+ $buffer = $this->gatherLinks( [$this], self::JS_ASSET);
+ return '\n";
+ }
+
+ return '\n";
+ }
+}
diff --git a/system/src/Grav/Common/Assets/Pipeline.php b/system/src/Grav/Common/Assets/Pipeline.php
new file mode 100644
index 0000000..b009585
--- /dev/null
+++ b/system/src/Grav/Common/Assets/Pipeline.php
@@ -0,0 +1,273 @@
+base_url = rtrim($uri->rootUrl($config->get('system.absolute_urls')), '/') . '/';
+ $this->assets_dir = $locator->findResource('asset://') . DS;
+ $this->assets_url = $locator->findResource('asset://', false);
+ }
+
+ /**
+ * Minify and concatenate CSS
+ *
+ * @param array $assets
+ * @param string $group
+ * @param array $attributes
+ *
+ * @return bool|string URL or generated content if available, else false
+ */
+ public function renderCss($assets, $group, $attributes = [])
+ {
+ // temporary list of assets to pipeline
+ $inline_group = false;
+
+ if (array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline') {
+ $inline_group = true;
+ unset($attributes['loading']);
+ }
+
+ // Store Attributes
+ $this->attributes = array_merge(['type' => 'text/css', 'rel' => 'stylesheet'], $attributes);
+
+ // Compute uid based on assets and timestamp
+ $json_assets = json_encode($assets);
+ $uid = md5($json_assets . $this->css_minify . $this->css_rewrite . $group);
+ $file = $uid . '.css';
+ $relative_path = "{$this->base_url}{$this->assets_url}/{$file}";
+
+ $buffer = null;
+
+ if (file_exists($this->assets_dir . $file)) {
+ $buffer = file_get_contents($this->assets_dir . $file) . "\n";
+ } else {
+ //if nothing found get out of here!
+ if (empty($assets)) {
+ return false;
+ }
+
+ // Concatenate files
+ $buffer = $this->gatherLinks($assets, self::CSS_ASSET);
+
+ // Minify if required
+ if ($this->shouldMinify('css')) {
+ $minifier = new \MatthiasMullie\Minify\CSS();
+ $minifier->add($buffer);
+ $buffer = $minifier->minify();
+ }
+
+ // Write file
+ if (trim($buffer) !== '') {
+ file_put_contents($this->assets_dir . $file, $buffer);
+ }
+ }
+
+ if ($inline_group) {
+ $output = "\n";
+ } else {
+ $this->asset = $relative_path;
+ $output = 'renderAttributes() . ">\n";
+ }
+
+ return $output;
+ }
+
+ /**
+ * Minify and concatenate JS files.
+ *
+ * @param array $assets
+ * @param string $group
+ * @param array $attributes
+ *
+ * @return bool|string URL or generated content if available, else false
+ */
+ public function renderJs($assets, $group, $attributes = [])
+ {
+ // temporary list of assets to pipeline
+ $inline_group = false;
+
+ if (array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline') {
+ $inline_group = true;
+ unset($attributes['loading']);
+ }
+
+ // Store Attributes
+ $this->attributes = $attributes;
+
+ // Compute uid based on assets and timestamp
+ $json_assets = json_encode($assets);
+ $uid = md5($json_assets . $this->js_minify . $group);
+ $file = $uid . '.js';
+ $relative_path = "{$this->base_url}{$this->assets_url}/{$file}";
+
+ $buffer = null;
+
+ if (file_exists($this->assets_dir . $file)) {
+ $buffer = file_get_contents($this->assets_dir . $file) . "\n";
+ } else {
+ //if nothing found get out of here!
+ if (empty($assets)) {
+ return false;
+ }
+
+ // Concatenate files
+ $buffer = $this->gatherLinks($assets, self::JS_ASSET);
+
+ // Minify if required
+ if ($this->shouldMinify('js')) {
+ $minifier = new \MatthiasMullie\Minify\JS();
+ $minifier->add($buffer);
+ $buffer = $minifier->minify();
+ }
+
+ // Write file
+ if (trim($buffer) !== '') {
+ file_put_contents($this->assets_dir . $file, $buffer);
+ }
+ }
+
+ if ($inline_group) {
+ $output = '\n";
+ } else {
+ $this->asset = $relative_path;
+ $output = '\n";
+ }
+
+ return $output;
+ }
+
+
+ /**
+ * Finds relative CSS urls() and rewrites the URL with an absolute one
+ *
+ * @param string $file the css source file
+ * @param string $dir , $local relative path to the css file
+ * @param bool $local is this a local or remote asset
+ *
+ * @return mixed
+ */
+ protected function cssRewrite($file, $dir, $local)
+ {
+ // Strip any sourcemap comments
+ $file = preg_replace(self::CSS_SOURCEMAP_REGEX, '', $file);
+
+ // Find any css url() elements, grab the URLs and calculate an absolute path
+ // Then replace the old url with the new one
+ $file = (string)preg_replace_callback(self::CSS_URL_REGEX, function ($matches) use ($dir, $local) {
+
+ $old_url = $matches[2];
+
+ // Ensure link is not rooted to web server, a data URL, or to a remote host
+ if (preg_match(self::FIRST_FORWARDSLASH_REGEX, $old_url) || Utils::startsWith($old_url, 'data:') || $this->isRemoteLink($old_url)) {
+ return $matches[0];
+ }
+
+ // clean leading /
+ $old_url = Utils::normalizePath($dir . '/' . $old_url);
+ if (preg_match(self::FIRST_FORWARDSLASH_REGEX, $old_url)) {
+ $old_url = ltrim($old_url, '/');
+ }
+
+ $new_url = ($local ? $this->base_url: '') . $old_url;
+
+ $fixed = str_replace($matches[2], $new_url, $matches[0]);
+
+ return $fixed;
+ }, $file);
+
+ return $file;
+ }
+
+
+
+ private function shouldMinify($type = 'css')
+ {
+ $check = $type . '_minify';
+ $win_check = $type . '_minify_windows';
+
+ $minify = (bool) $this->$check;
+
+ // If this is a Windows server, and minify_windows is false (default value) skip the
+ // minification process because it will cause Apache to die/crash due to insufficient
+ // ThreadStackSize in httpd.conf - See: https://bugs.php.net/bug.php?id=47689
+ if (stripos(php_uname('s'), 'WIN') === 0 && !$this->{$win_check}) {
+ $minify = false;
+ }
+
+ return $minify;
+ }
+}
diff --git a/system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php b/system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php
new file mode 100644
index 0000000..0a1d149
--- /dev/null
+++ b/system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php
@@ -0,0 +1,182 @@
+rootUrl(true);
+
+ // Sanity check for local URLs with absolute URL's enabled
+ if (Utils::startsWith($link, $base)) {
+ return false;
+ }
+
+ return (0 === strpos($link, 'http://') || 0 === strpos($link, 'https://') || 0 === strpos($link, '//'));
+ }
+
+ /**
+ * Download and concatenate the content of several links.
+ *
+ * @param array $assets
+ * @param bool $css
+ *
+ * @return string
+ */
+ protected function gatherLinks(array $assets, $css = true)
+ {
+ $buffer = '';
+
+
+ foreach ($assets as $id => $asset) {
+ $local = true;
+
+ $link = $asset->getAsset();
+ $relative_path = $link;
+
+ if (static::isRemoteLink($link)) {
+ $local = false;
+ if (0 === strpos($link, '//')) {
+ $link = 'http:' . $link;
+ }
+ $relative_dir = \dirname($relative_path);
+ } else {
+ // Fix to remove relative dir if grav is in one
+ if (($this->base_url !== '/') && Utils::startsWith($relative_path, $this->base_url)) {
+ $base_url = '#' . preg_quote($this->base_url, '#') . '#';
+ $relative_path = ltrim(preg_replace($base_url, '/', $link, 1), '/');
+ }
+
+ $relative_dir = \dirname($relative_path);
+ $link = ROOT_DIR . $relative_path;
+ }
+
+ $file = ($this->fetch_command instanceof \Closure) ? @$this->fetch_command->__invoke($link) : @file_get_contents($link);
+
+ // No file found, skip it...
+ if ($file === false) {
+ continue;
+ }
+
+ // Double check last character being
+ if (!$css) {
+ $file = rtrim($file, ' ;') . ';';
+ }
+
+ // If this is CSS + the file is local + rewrite enabled
+ if ($css && $this->css_rewrite) {
+ $file = $this->cssRewrite($file, $relative_dir, $local);
+ }
+
+ $file = rtrim($file) . PHP_EOL;
+ $buffer .= $file;
+ }
+
+ // Pull out @imports and move to top
+ if ($css) {
+ $buffer = $this->moveImports($buffer);
+ }
+
+ return $buffer;
+ }
+
+ /**
+ * Moves @import statements to the top of the file per the CSS specification
+ *
+ * @param string $file the file containing the combined CSS files
+ *
+ * @return string the modified file with any @imports at the top of the file
+ */
+ protected function moveImports($file)
+ {
+ $imports = [];
+
+ $file = (string)preg_replace_callback(self::CSS_IMPORT_REGEX, function ($matches) use (&$imports) {
+ $imports[] = $matches[0];
+
+ return '';
+ }, $file);
+
+ return implode("\n", $imports) . "\n\n" . $file;
+ }
+
+ /**
+ *
+ * Build an HTML attribute string from an array.
+ *
+ * @return string
+ */
+ protected function renderAttributes()
+ {
+ $html = '';
+ $no_key = ['loading'];
+
+ foreach ($this->attributes as $key => $value) {
+ if (is_numeric($key)) {
+ $key = $value;
+ }
+ if (\is_array($value)) {
+ $value = implode(' ', $value);
+ }
+
+ if (\in_array($key, $no_key, true)) {
+ $element = htmlentities($value, ENT_QUOTES, 'UTF-8', false);
+ } else {
+ $element = $key . '="' . htmlentities($value, ENT_QUOTES, 'UTF-8', false) . '"';
+ }
+
+ $html .= ' ' . $element;
+ }
+
+ return $html;
+ }
+
+ /**
+ * Render Querystring
+ *
+ * @param string $asset
+ * @return string
+ */
+ protected function renderQueryString($asset = null)
+ {
+ $querystring = '';
+
+ $asset = $asset ?? $this->asset;
+
+ if (!empty($this->query)) {
+ if (Utils::contains($asset, '?')) {
+ $querystring .= '&' . $this->query;
+ } else {
+ $querystring .= '?' . $this->query;
+ }
+ }
+
+ if ($this->timestamp) {
+ if (Utils::contains($asset, '?') || $querystring) {
+ $querystring .= '&' . $this->timestamp;
+ } else {
+ $querystring .= '?' . $this->timestamp;
+ }
+ }
+
+ return $querystring;
+ }
+}
diff --git a/system/src/Grav/Common/Assets/Traits/LegacyAssetsTrait.php b/system/src/Grav/Common/Assets/Traits/LegacyAssetsTrait.php
new file mode 100644
index 0000000..ccf9da2
--- /dev/null
+++ b/system/src/Grav/Common/Assets/Traits/LegacyAssetsTrait.php
@@ -0,0 +1,118 @@
+ null, 'group' => null];
+ $arguments = $this->createArgumentsFromLegacy($args, $defaults);
+ break;
+
+ case(Assets::JS_TYPE):
+ $defaults = ['priority' => null, 'pipeline' => true, 'loading' => null, 'group' => null];
+ $arguments = $this->createArgumentsFromLegacy($args, $defaults);
+ break;
+
+ case(Assets::INLINE_JS_TYPE):
+ $defaults = ['priority' => null, 'group' => null, 'attributes' => null];
+ $arguments = $this->createArgumentsFromLegacy($args, $defaults);
+
+ // special case to handle old attributes being passed in
+ if (isset($arguments['attributes'])) {
+ $old_attributes = $arguments['attributes'];
+ $arguments = array_merge($arguments, $old_attributes);
+ }
+ unset($arguments['attributes']);
+
+ break;
+
+ default:
+ case(Assets::CSS_TYPE):
+ $defaults = ['priority' => null, 'pipeline' => true, 'group' => null, 'loading' => null];
+ $arguments = $this->createArgumentsFromLegacy($args, $defaults);
+ }
+
+ return $arguments;
+ }
+
+ protected function createArgumentsFromLegacy(array $args, array $defaults)
+ {
+ // Remove arguments with old default values.
+ $arguments = [];
+ foreach ($args as $arg) {
+ $default = current($defaults);
+ if ($arg !== $default) {
+ $arguments[key($defaults)] = $arg;
+ }
+ next($defaults);
+ }
+
+ return $arguments;
+ }
+
+ /**
+ * Convenience wrapper for async loading of JavaScript
+ *
+ * @param string|array $asset
+ * @param int $priority
+ * @param bool $pipeline
+ * @param string $group name of the group
+ *
+ * @return \Grav\Common\Assets
+ * @deprecated Please use dynamic method with ['loading' => 'async'].
+ */
+ public function addAsyncJs($asset, $priority = 10, $pipeline = true, $group = 'head')
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use dynamic method with [\'loading\' => \'async\']', E_USER_DEPRECATED);
+
+ return $this->addJs($asset, $priority, $pipeline, 'async', $group);
+ }
+
+ /**
+ * Convenience wrapper for deferred loading of JavaScript
+ *
+ * @param string|array $asset
+ * @param int $priority
+ * @param bool $pipeline
+ * @param string $group name of the group
+ *
+ * @return \Grav\Common\Assets
+ * @deprecated Please use dynamic method with ['loading' => 'defer'].
+ */
+ public function addDeferJs($asset, $priority = 10, $pipeline = true, $group = 'head')
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use dynamic method with [\'loading\' => \'defer\']', E_USER_DEPRECATED);
+
+ return $this->addJs($asset, $priority, $pipeline, 'defer', $group);
+ }
+
+}
diff --git a/system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php b/system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php
new file mode 100644
index 0000000..84b83e3
--- /dev/null
+++ b/system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php
@@ -0,0 +1,343 @@
+collections[$asset]) || isset($this->assets_css[$asset]) || isset($this->assets_js[$asset]);
+ }
+
+ /**
+ * Return the array of all the registered collections
+ *
+ * @return array
+ */
+ public function getCollections()
+ {
+ return $this->collections;
+ }
+
+ /**
+ * Set the array of collections explicitly
+ *
+ * @param array $collections
+ *
+ * @return $this
+ */
+ public function setCollection($collections)
+ {
+ $this->collections = $collections;
+
+ return $this;
+ }
+
+ /**
+ * Return the array of all the registered CSS assets
+ * If a $key is provided, it will try to return only that asset
+ * else it will return null
+ *
+ * @param null|string $key the asset key
+ * @return array
+ */
+ public function getCss($key = null)
+ {
+ if (null !== $key) {
+ $asset_key = md5($key);
+
+ return $this->assets_css[$asset_key] ?? null;
+ }
+
+ return $this->assets_css;
+ }
+
+ /**
+ * Return the array of all the registered JS assets
+ * If a $key is provided, it will try to return only that asset
+ * else it will return null
+ *
+ * @param null|string $key the asset key
+ * @return array
+ */
+ public function getJs($key = null)
+ {
+ if (null !== $key) {
+ $asset_key = md5($key);
+
+ return $this->assets_js[$asset_key] ?? null;
+ }
+
+ return $this->assets_js;
+ }
+
+ /**
+ * Set the whole array of CSS assets
+ *
+ * @param array $css
+ *
+ * @return $this
+ */
+ public function setCss($css)
+ {
+ $this->assets_css = $css;
+
+ return $this;
+ }
+
+ /**
+ * Set the whole array of JS assets
+ *
+ * @param array $js
+ *
+ * @return $this
+ */
+ public function setJs($js)
+ {
+ $this->assets_js = $js;
+
+ return $this;
+ }
+
+ /**
+ * Removes an item from the CSS array if set
+ *
+ * @param string $key The asset key
+ *
+ * @return $this
+ */
+ public function removeCss($key)
+ {
+ $asset_key = md5($key);
+ if (isset($this->assets_css[$asset_key])) {
+ unset($this->assets_css[$asset_key]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Removes an item from the JS array if set
+ *
+ * @param string $key The asset key
+ *
+ * @return $this
+ */
+ public function removeJs($key)
+ {
+ $asset_key = md5($key);
+ if (isset($this->assets_js[$asset_key])) {
+ unset($this->assets_js[$asset_key]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the state of CSS Pipeline
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setCssPipeline($value)
+ {
+ $this->css_pipeline = (bool)$value;
+
+ return $this;
+ }
+
+ /**
+ * Sets the state of JS Pipeline
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setJsPipeline($value)
+ {
+ $this->js_pipeline = (bool)$value;
+
+ return $this;
+ }
+
+ /**
+ * Reset all assets.
+ *
+ * @return $this
+ */
+ public function reset()
+ {
+ $this->resetCss();
+ $this->resetJs();
+ $this->setCssPipeline(false);
+ $this->setJsPipeline(false);
+
+ return $this;
+ }
+
+ /**
+ * Reset JavaScript assets.
+ *
+ * @return $this
+ */
+ public function resetJs()
+ {
+ $this->assets_js = [];
+
+ return $this;
+ }
+
+ /**
+ * Reset CSS assets.
+ *
+ * @return $this
+ */
+ public function resetCss()
+ {
+ $this->assets_css = [];
+
+ return $this;
+ }
+
+ /**
+ * Explicitly set's a timestamp for assets
+ *
+ * @param string|int $value
+ */
+ public function setTimestamp($value)
+ {
+ $this->timestamp = $value;
+ }
+
+ /**
+ * Get the timestamp for assets
+ *
+ * @param bool $include_join
+ * @return string
+ */
+ public function getTimestamp($include_join = true)
+ {
+ if ($this->timestamp) {
+ return $include_join ? '?' . $this->timestamp : $this->timestamp;
+ }
+
+ return null;
+ }
+
+ /**
+ * Add all assets matching $pattern within $directory.
+ *
+ * @param string $directory Relative to the Grav root path, or a stream identifier
+ * @param string $pattern (regex)
+ *
+ * @return $this
+ */
+ public function addDir($directory, $pattern = self::DEFAULT_REGEX)
+ {
+ $root_dir = rtrim(ROOT_DIR, '/');
+
+ // Check if $directory is a stream.
+ if (strpos($directory, '://')) {
+ $directory = Grav::instance()['locator']->findResource($directory, null);
+ }
+
+ // Get files
+ $files = $this->rglob($root_dir . DIRECTORY_SEPARATOR . $directory, $pattern, $root_dir . '/');
+
+ // No luck? Nothing to do
+ if (!$files) {
+ return $this;
+ }
+
+ // Add CSS files
+ if ($pattern === self::CSS_REGEX) {
+ foreach ($files as $file) {
+ $this->addCss($file);
+ }
+
+ return $this;
+ }
+
+ // Add JavaScript files
+ if ($pattern === self::JS_REGEX) {
+ foreach ($files as $file) {
+ $this->addJs($file);
+ }
+
+ return $this;
+ }
+
+ // Unknown pattern.
+ foreach ($files as $asset) {
+ $this->add($asset);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add all JavaScript assets within $directory
+ *
+ * @param string $directory Relative to the Grav root path, or a stream identifier
+ *
+ * @return $this
+ */
+ public function addDirJs($directory)
+ {
+ return $this->addDir($directory, self::JS_REGEX);
+ }
+
+ /**
+ * Add all CSS assets within $directory
+ *
+ * @param string $directory Relative to the Grav root path, or a stream identifier
+ *
+ * @return $this
+ */
+ public function addDirCss($directory)
+ {
+ return $this->addDir($directory, self::CSS_REGEX);
+ }
+
+ /**
+ * Recursively get files matching $pattern within $directory.
+ *
+ * @param string $directory
+ * @param string $pattern (regex)
+ * @param string $ltrim Will be trimmed from the left of the file path
+ *
+ * @return array
+ */
+ protected function rglob($directory, $pattern, $ltrim = null)
+ {
+ $iterator = new \RegexIterator(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory,
+ \FilesystemIterator::SKIP_DOTS)), $pattern);
+ $offset = \strlen($ltrim);
+ $files = [];
+
+ foreach ($iterator as $file) {
+ $files[] = substr($file->getPathname(), $offset);
+ }
+
+ return $files;
+ }
+
+
+}
diff --git a/system/src/Grav/Common/Backup/Backups.php b/system/src/Grav/Common/Backup/Backups.php
new file mode 100644
index 0000000..9582b9e
--- /dev/null
+++ b/system/src/Grav/Common/Backup/Backups.php
@@ -0,0 +1,261 @@
+addListener('onSchedulerInitialized', [$this, 'onSchedulerInitialized']);
+ Grav::instance()->fireEvent('onBackupsInitialized', new Event(['backups' => $this]));
+ }
+
+ public function setup()
+ {
+ if (null === static::$backup_dir) {
+ static::$backup_dir = Grav::instance()['locator']->findResource('backup://', true, true);
+ Folder::create(static::$backup_dir);
+ }
+ }
+
+ public function onSchedulerInitialized(Event $event)
+ {
+ /** @var Scheduler $scheduler */
+ $scheduler = $event['scheduler'];
+
+ /** @var Inflector $inflector */
+ $inflector = Grav::instance()['inflector'];
+
+ foreach (static::getBackupProfiles() as $id => $profile) {
+ $at = $profile['schedule_at'];
+ $name = $inflector::hyphenize($profile['name']);
+ $logs = 'logs/backup-' . $name . '.out';
+ /** @var Job $job */
+ $job = $scheduler->addFunction('Grav\Common\Backup\Backups::backup', [$id], $name );
+ $job->at($at);
+ $job->output($logs);
+ $job->backlink('/tools/backups');
+ }
+ }
+
+ public function getBackupDownloadUrl($backup, $base_url)
+ {
+ $param_sep = $param_sep = Grav::instance()['config']->get('system.param_sep', ':');
+ $download = urlencode(base64_encode($backup));
+ $url = rtrim(Grav::instance()['uri']->rootUrl(true), '/') . '/' . trim($base_url,
+ '/') . '/task' . $param_sep . 'backup/download' . $param_sep . $download . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');
+
+ return $url;
+ }
+
+ public static function getBackupProfiles()
+ {
+ return Grav::instance()['config']->get('backups.profiles');
+ }
+
+ public static function getPurgeConfig()
+ {
+ return Grav::instance()['config']->get('backups.purge');
+ }
+
+ public function getBackupNames()
+ {
+ return array_column(static::getBackupProfiles(), 'name');
+ }
+
+ public static function getTotalBackupsSize()
+ {
+ $backups = static::getAvailableBackups();
+ $size = array_sum(array_column($backups, 'size'));
+
+ return $size ?? 0;
+ }
+
+ public static function getAvailableBackups($force = false)
+ {
+ if ($force || null === static::$backups) {
+ static::$backups = [];
+ $backups_itr = new \GlobIterator(static::$backup_dir . '/*.zip', \FilesystemIterator::KEY_AS_FILENAME);
+ $inflector = Grav::instance()['inflector'];
+ $long_date_format = DATE_RFC2822;
+
+ /**
+ * @var string $name
+ * @var \SplFileInfo $file
+ */
+ foreach ($backups_itr as $name => $file) {
+
+ if (preg_match(static::BACKUP_FILENAME_REGEXZ, $name, $matches)) {
+ $date = \DateTime::createFromFormat(static::BACKUP_DATE_FORMAT, $matches[2]);
+ $timestamp = $date->getTimestamp();
+ $backup = new \stdClass();
+ $backup->title = $inflector->titleize($matches[1]);
+ $backup->time = $date;
+ $backup->date = $date->format($long_date_format);
+ $backup->filename = $name;
+ $backup->path = $file->getPathname();
+ $backup->size = $file->getSize();
+ static::$backups[$timestamp] = $backup;
+ }
+ }
+ // Reverse Key Sort to get in reverse date order
+ krsort(static::$backups);
+ }
+
+ return static::$backups;
+ }
+
+ /**
+ * Backup
+ *
+ * @param int $id
+ * @param callable|null $status
+ *
+ * @return null|string
+ */
+ public static function backup($id = 0, callable $status = null)
+ {
+ $profiles = static::getBackupProfiles();
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+
+ if (isset($profiles[$id])) {
+ $backup = (object) $profiles[$id];
+ } else {
+ throw new \RuntimeException('No backups defined...');
+ }
+
+ $name = Grav::instance()['inflector']->underscorize($backup->name);
+ $date = date(static::BACKUP_DATE_FORMAT, time());
+ $filename = trim($name, '_') . '--' . $date . '.zip';
+ $destination = static::$backup_dir . DS . $filename;
+ $max_execution_time = ini_set('max_execution_time', 600);
+ $backup_root = $backup->root;
+
+ if ($locator->isStream($backup_root)) {
+ $backup_root = $locator->findResource($backup_root);
+ } else {
+ $backup_root = rtrim(GRAV_ROOT . $backup_root, '/');
+ }
+
+ if (!file_exists($backup_root)) {
+ throw new \RuntimeException("Backup location: {$backup_root} does not exist...");
+ }
+
+ $options = [
+ 'exclude_files' => static::convertExclude($backup->exclude_files ?? ''),
+ 'exclude_paths' => static::convertExclude($backup->exclude_paths ?? ''),
+ ];
+
+ /** @var Archiver $archiver */
+ $archiver = Archiver::create('zip');
+ $archiver->setArchive($destination)->setOptions($options)->compress($backup_root, $status)->addEmptyFolders($options['exclude_paths'], $status);
+
+ $status && $status([
+ 'type' => 'message',
+ 'message' => 'Done...',
+ ]);
+
+ $status && $status([
+ 'type' => 'progress',
+ 'complete' => true
+ ]);
+
+ if ($max_execution_time !== false) {
+ ini_set('max_execution_time', $max_execution_time);
+ }
+
+ // Log the backup
+ Grav::instance()['log']->notice('Backup Created: ' . $destination);
+
+ // Fire Finished event
+ Grav::instance()->fireEvent('onBackupFinished', new Event(['backup' => $destination]));
+
+ // Purge anything required
+ static::purge();
+
+ // Log
+ $log = JsonFile::instance(Grav::instance()['locator']->findResource("log://backup.log", true, true));
+ $log->content([
+ 'time' => time(),
+ 'location' => $destination
+ ]);
+ $log->save();
+
+ return $destination;
+ }
+
+ public static function purge()
+ {
+ $purge_config = static::getPurgeConfig();
+ $trigger = $purge_config['trigger'];
+ $backups = static::getAvailableBackups(true);
+
+ switch ($trigger)
+ {
+ case 'number':
+ $backups_count = count($backups);
+ if ($backups_count > $purge_config['max_backups_count']) {
+ $last = end($backups);
+ unlink ($last->path);
+ static::purge();
+ }
+ break;
+
+ case 'time':
+ $last = end($backups);
+ $now = new \DateTime();
+ $interval = $now->diff($last->time);
+ if ($interval->days > $purge_config['max_backups_time']) {
+ unlink($last->path);
+ static::purge();
+ }
+ break;
+
+ default:
+ $used_space = static::getTotalBackupsSize();
+ $max_space = $purge_config['max_backups_space'] * 1024 * 1024 * 1024;
+ if ($used_space > $max_space) {
+ $last = end($backups);
+ unlink($last->path);
+ static::purge();
+ }
+ break;
+ }
+ }
+
+ protected static function convertExclude($exclude)
+ {
+ $lines = preg_split("/[\s,]+/", $exclude);
+ return array_map('trim', $lines, array_fill(0, \count($lines), '/'));
+ }
+}
diff --git a/system/src/Grav/Common/Backup/ZipBackup.php b/system/src/Grav/Common/Backup/ZipBackup.php
deleted file mode 100644
index 517d7da..0000000
--- a/system/src/Grav/Common/Backup/ZipBackup.php
+++ /dev/null
@@ -1,144 +0,0 @@
-findResource('backup://', true);
-
- if (!$destination) {
- throw new \RuntimeException('The backup folder is missing.');
- }
- }
-
- $name = substr(strip_tags(Grav::instance()['config']->get('site.title', basename(GRAV_ROOT))), 0, 20);
-
- $inflector = new Inflector();
-
- if (is_dir($destination)) {
- $date = date('YmdHis', time());
- $filename = trim($inflector->hyphenize($name), '-') . '-' . $date . '.zip';
- $destination = rtrim($destination, DS) . DS . $filename;
- }
-
- $messager && $messager([
- 'type' => 'message',
- 'level' => 'info',
- 'message' => 'Creating new Backup "' . $destination . '"'
- ]);
- $messager && $messager([
- 'type' => 'message',
- 'level' => 'info',
- 'message' => ''
- ]);
-
- $zip = new \ZipArchive();
- $zip->open($destination, \ZipArchive::CREATE);
-
- $max_execution_time = ini_set('max_execution_time', 600);
-
- static::folderToZip(GRAV_ROOT, $zip, strlen(rtrim(GRAV_ROOT, DS) . DS), $messager);
-
- $messager && $messager([
- 'type' => 'progress',
- 'percentage' => false,
- 'complete' => true
- ]);
-
- $messager && $messager([
- 'type' => 'message',
- 'level' => 'info',
- 'message' => ''
- ]);
- $messager && $messager([
- 'type' => 'message',
- 'level' => 'info',
- 'message' => 'Saving and compressing archive...'
- ]);
-
- $zip->close();
-
- if ($max_execution_time !== false) {
- ini_set('max_execution_time', $max_execution_time);
- }
-
- return $destination;
- }
-
- /**
- * @param $folder
- * @param $zipFile
- * @param $exclusiveLength
- * @param $messager
- */
- private static function folderToZip($folder, \ZipArchive $zipFile, $exclusiveLength, callable $messager = null)
- {
- $handle = opendir($folder);
- while (false !== $f = readdir($handle)) {
- if ($f !== '.' && $f !== '..') {
- $filePath = "$folder/$f";
- // Remove prefix from file path before add to zip.
- $localPath = substr($filePath, $exclusiveLength);
-
- if (in_array($f, static::$ignoreFolders)) {
- continue;
- }
- if (in_array($localPath, static::$ignorePaths)) {
- $zipFile->addEmptyDir($f);
- continue;
- }
-
- if (is_file($filePath)) {
- $zipFile->addFile($filePath, $localPath);
-
- $messager && $messager([
- 'type' => 'progress',
- 'percentage' => false,
- 'complete' => false
- ]);
- } elseif (is_dir($filePath)) {
- // Add sub-directory.
- $zipFile->addEmptyDir($localPath);
- static::folderToZip($filePath, $zipFile, $exclusiveLength, $messager);
- }
- }
- }
- closedir($handle);
- }
-}
diff --git a/system/src/Grav/Common/Browser.php b/system/src/Grav/Common/Browser.php
index bb0a3fb..037d454 100644
--- a/system/src/Grav/Common/Browser.php
+++ b/system/src/Grav/Common/Browser.php
@@ -1,8 +1,9 @@
getLongVersion());
- return intval($version[0]);
+ return (int)$version[0];
}
/**
@@ -134,4 +135,15 @@ class Browser
return true;
}
+
+ /**
+ * Determine if “Do Not Track” is set by browser
+ * @see https://www.w3.org/TR/tracking-dnt/
+ *
+ * @return bool
+ */
+ public function isTrackable(): bool
+ {
+ return !(isset($_SERVER['HTTP_DNT']) && $_SERVER['HTTP_DNT'] === '1');
+ }
}
diff --git a/system/src/Grav/Common/Cache.php b/system/src/Grav/Common/Cache.php
index 71eb29f..314ab11 100644
--- a/system/src/Grav/Common/Cache.php
+++ b/system/src/Grav/Common/Cache.php
@@ -1,8 +1,9 @@
config = $grav['config'];
$this->now = time();
- $this->cache_dir = $grav['locator']->findResource('cache://doctrine', true, true);
+ if (null === $this->enabled) {
+ $this->enabled = (bool)$this->config->get('system.cache.enabled');
+ }
/** @var Uri $uri */
$uri = $grav['uri'];
$prefix = $this->config->get('system.cache.prefix');
-
- if (is_null($this->enabled)) {
- $this->enabled = (bool)$this->config->get('system.cache.enabled');
- }
+ $uniqueness = substr(md5($uri->rootUrl(true) . $this->config->key() . GRAV_VERSION), 2, 8);
// Cache key allows us to invalidate all cache on configuration changes.
- $this->key = ($prefix ? $prefix : 'g') . '-' . substr(md5($uri->rootUrl(true) . $this->config->key() . GRAV_VERSION),
- 2, 8);
-
+ $this->key = ($prefix ? $prefix : 'g') . '-' . $uniqueness;
+ $this->cache_dir = $grav['locator']->findResource('cache://doctrine/' . $uniqueness, true, true);
$this->driver_setting = $this->config->get('system.cache.driver');
-
$this->driver = $this->getCacheDriver();
-
- // Set the cache namespace to our unique key
$this->driver->setNamespace($this->key);
+
+ /** @var EventDispatcher $dispatcher */
+ $dispatcher = Grav::instance()['events'];
+ $dispatcher->addListener('onSchedulerInitialized', [$this, 'onSchedulerInitialized']);
+ }
+
+ /**
+ * @return CacheInterface
+ */
+ public function getSimpleCache()
+ {
+ if (null === $this->simpleCache) {
+ $cache = new \Grav\Framework\Cache\Adapter\DoctrineCache($this->driver, '', $this->getLifetime());
+
+ // Disable cache key validation.
+ $cache->setValidation(false);
+
+ $this->simpleCache = $cache;
+ }
+
+ return $this->simpleCache;
+ }
+
+ /**
+ * Deletes the old out of date file-based caches
+ *
+ * @return int
+ */
+ public function purgeOldCache()
+ {
+ $cache_dir = dirname($this->cache_dir);
+ $current = basename($this->cache_dir);
+ $count = 0;
+
+ foreach (new \DirectoryIterator($cache_dir) as $file) {
+ $dir = $file->getBasename();
+ if ($dir === $current || $file->isDot() || $file->isFile()) {
+ continue;
+ }
+
+ Folder::delete($file->getPathname());
+ $count++;
+ }
+
+ return $count;
}
/**
* Public accessor to set the enabled state of the cache
*
- * @param $enabled
+ * @param bool|int $enabled
*/
public function setEnabled($enabled)
{
- $this->enabled = (bool) $enabled;
+ $this->enabled = (bool)$enabled;
}
/**
@@ -184,19 +231,15 @@ class Cache extends Getters
// CLI compatibility requires a non-volatile cache driver
if ($this->config->get('system.cache.cli_compatibility') && (
- $setting == 'auto' || $this->isVolatileDriver($setting))) {
+ $setting === 'auto' || $this->isVolatileDriver($setting))) {
$setting = $driver_name;
}
- if (!$setting || $setting == 'auto') {
+ if (!$setting || $setting === 'auto') {
if (extension_loaded('apcu')) {
$driver_name = 'apcu';
- } elseif (extension_loaded('apc')) {
- $driver_name = 'apc';
} elseif (extension_loaded('wincache')) {
$driver_name = 'wincache';
- } elseif (extension_loaded('xcache')) {
- $driver_name = 'xcache';
}
} else {
$driver_name = $setting;
@@ -206,9 +249,6 @@ class Cache extends Getters
switch ($driver_name) {
case 'apc':
- $driver = new DoctrineCache\ApcCache();
- break;
-
case 'apcu':
$driver = new DoctrineCache\ApcuCache();
break;
@@ -217,45 +257,53 @@ class Cache extends Getters
$driver = new DoctrineCache\WinCacheCache();
break;
- case 'xcache':
- $driver = new DoctrineCache\XcacheCache();
- break;
-
case 'memcache':
- $memcache = new \Memcache();
- $memcache->connect($this->config->get('system.cache.memcache.server', 'localhost'),
- $this->config->get('system.cache.memcache.port', 11211));
- $driver = new DoctrineCache\MemcacheCache();
- $driver->setMemcache($memcache);
+ if (extension_loaded('memcache')) {
+ $memcache = new \Memcache();
+ $memcache->connect($this->config->get('system.cache.memcache.server', 'localhost'),
+ $this->config->get('system.cache.memcache.port', 11211));
+ $driver = new DoctrineCache\MemcacheCache();
+ $driver->setMemcache($memcache);
+ } else {
+ throw new \LogicException('Memcache PHP extension has not been installed');
+ }
break;
case 'memcached':
- $memcached = new \Memcached();
- $memcached->addServer($this->config->get('system.cache.memcached.server', 'localhost'),
- $this->config->get('system.cache.memcached.port', 11211));
- $driver = new DoctrineCache\MemcachedCache();
- $driver->setMemcached($memcached);
+ if (extension_loaded('memcached')) {
+ $memcached = new \Memcached();
+ $memcached->addServer($this->config->get('system.cache.memcached.server', 'localhost'),
+ $this->config->get('system.cache.memcached.port', 11211));
+ $driver = new DoctrineCache\MemcachedCache();
+ $driver->setMemcached($memcached);
+ } else {
+ throw new \LogicException('Memcached PHP extension has not been installed');
+ }
break;
case 'redis':
- $redis = new \Redis();
- $socket = $this->config->get('system.cache.redis.socket', false);
- $password = $this->config->get('system.cache.redis.password', false);
+ if (extension_loaded('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);
+ if ($socket) {
+ $redis->connect($socket);
+ } else {
+ $redis->connect($this->config->get('system.cache.redis.server', 'localhost'),
+ $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);
} else {
- $redis->connect($this->config->get('system.cache.redis.server', 'localhost'),
- $this->config->get('system.cache.redis.port', 6379));
+ throw new \LogicException('Redis PHP extension has not been installed');
}
-
- // Authenticate with password if set
- if ($password && !$redis->auth($password)) {
- throw new \RedisException('Redis authentication failed');
- }
-
- $driver = new DoctrineCache\RedisCache();
- $driver->setRedis($redis);
break;
default:
@@ -277,9 +325,9 @@ class Cache extends Getters
{
if ($this->enabled) {
return $this->driver->fetch($id);
- } else {
- return false;
}
+
+ return false;
}
/**
@@ -310,6 +358,21 @@ class Cache extends Getters
if ($this->enabled) {
return $this->driver->delete($id);
}
+
+ return false;
+ }
+
+ /**
+ * Deletes all cache
+ *
+ * @return bool
+ */
+ public function deleteAll()
+ {
+ if ($this->enabled) {
+ return $this->driver->deleteAll();
+ }
+
return false;
}
@@ -324,6 +387,7 @@ class Cache extends Getters
if ($this->enabled) {
return $this->driver->contains(($id));
}
+
return false;
}
@@ -382,6 +446,12 @@ class Cache extends Getters
}
+ // Delete entries in the doctrine cache if required
+ if (in_array($remove, ['all', 'standard'])) {
+ $cache = Grav::instance()['cache'];
+ $cache->driver->deleteAll();
+ }
+
// Clearing cache event to add paths to clear
Grav::instance()->fireEvent('onBeforeCacheClear', new Event(['remove' => $remove, 'paths' => &$remove_paths]));
@@ -422,7 +492,7 @@ class Cache extends Getters
$output[] = '';
- if (($remove == 'all' || $remove == 'standard') && file_exists($user_config)) {
+ if (($remove === 'all' || $remove === 'standard') && file_exists($user_config)) {
touch($user_config);
$output[] = 'Touched: ' . $user_config;
@@ -440,6 +510,24 @@ class Cache extends Getters
return $output;
}
+ public static function invalidateCache()
+ {
+ $user_config = USER_DIR . 'config/system.yaml';
+
+ if (file_exists($user_config)) {
+ touch($user_config);
+ }
+
+ // Clear stat cache
+ @clearstatcache();
+
+ // Clear opcache
+ if (function_exists('opcache_reset')) {
+ @opcache_reset();
+ }
+
+ }
+
/**
* Set the cache lifetime programmatically
@@ -496,15 +584,71 @@ class Cache extends Getters
/**
* is this driver a volatile driver in that it resides in PHP process memory
*
- * @param $setting
+ * @param string $setting
* @return bool
*/
public function isVolatileDriver($setting)
{
if (in_array($setting, ['apc', 'apcu', 'xcache', 'wincache'])) {
return true;
- } else {
- return false;
}
+
+ return false;
}
+
+ /**
+ * Static function to call as a scheduled Job to purge old Doctrine files
+ */
+ public static function purgeJob()
+ {
+ /** @var Cache $cache */
+ $cache = Grav::instance()['cache'];
+ $deleted_folders = $cache->purgeOldCache();
+
+ echo 'Purged ' . $deleted_folders . ' old cache folders...';
+ }
+
+ /**
+ * Static function to call as a scheduled Job to clear Grav cache
+ *
+ * @param string $type
+ */
+ public static function clearJob($type)
+ {
+ $result = static::clearCache($type);
+ static::invalidateCache();
+
+ echo strip_tags(implode("\n", $result));
+ }
+
+ public function onSchedulerInitialized(Event $event)
+ {
+ /** @var Scheduler $scheduler */
+ $scheduler = $event['scheduler'];
+ $config = Grav::instance()['config'];
+
+ // File Cache Purge
+ $at = $config->get('system.cache.purge_at');
+ $name = 'cache-purge';
+ $logs = 'logs/' . $name . '.out';
+
+ $job = $scheduler->addFunction('Grav\Common\Cache::purgeJob', [], $name );
+ $job->at($at);
+ $job->output($logs);
+ $job->backlink('/config/system#caching');
+
+ // Cache Clear
+ $at = $config->get('system.cache.clear_at');
+ $clear_type = $config->get('system.cache.clear_job_type');
+ $name = 'cache-clear';
+ $logs = 'logs/' . $name . '.out';
+
+ $job = $scheduler->addFunction('Grav\Common\Cache::clearJob', [$clear_type], $name );
+ $job->at($at);
+ $job->output($logs);
+ $job->backlink('/config/system#caching');
+
+ }
+
+
}
diff --git a/system/src/Grav/Common/Composer.php b/system/src/Grav/Common/Composer.php
index 2671925..c6abb2c 100644
--- a/system/src/Grav/Common/Composer.php
+++ b/system/src/Grav/Common/Composer.php
@@ -1,8 +1,9 @@
checksum)) {
+ if (null === $this->checksum) {
$this->checksum = md5(json_encode($this->files) . $this->version);
}
@@ -197,11 +198,9 @@ abstract class CompiledBase
$cache = include $filename;
if (
- !is_array($cache)
- || !isset($cache['checksum'])
- || !isset($cache['data'])
- || !isset($cache['@class'])
- || $cache['@class'] != get_class($this)
+ !\is_array($cache)
+ || !isset($cache['checksum'], $cache['data'], $cache['@class'])
+ || $cache['@class'] !== \get_class($this)
) {
return false;
}
@@ -212,7 +211,7 @@ abstract class CompiledBase
}
$this->createObject($cache['data']);
- $this->timestamp = isset($cache['timestamp']) ? $cache['timestamp'] : 0;
+ $this->timestamp = $cache['timestamp'] ?? 0;
$this->finalizeObject();
@@ -243,7 +242,7 @@ abstract class CompiledBase
}
$cache = [
- '@class' => get_class($this),
+ '@class' => \get_class($this),
'timestamp' => time(),
'checksum' => $this->checksum(),
'files' => $this->files,
diff --git a/system/src/Grav/Common/Config/CompiledBlueprints.php b/system/src/Grav/Common/Config/CompiledBlueprints.php
index a29ecde..9ad7be6 100644
--- a/system/src/Grav/Common/Config/CompiledBlueprints.php
+++ b/system/src/Grav/Common/Config/CompiledBlueprints.php
@@ -1,27 +1,30 @@
version = 2;
+ }
/**
* Returns checksum from the configuration files.
diff --git a/system/src/Grav/Common/Config/CompiledConfig.php b/system/src/Grav/Common/Config/CompiledConfig.php
index 6f21123..1c92edd 100644
--- a/system/src/Grav/Common/Config/CompiledConfig.php
+++ b/system/src/Grav/Common/Config/CompiledConfig.php
@@ -1,8 +1,9 @@
version = 1;
+ }
+
/**
* Set blueprints for the configuration.
*
@@ -63,7 +61,7 @@ class CompiledConfig extends CompiledBase
*/
protected function createObject(array $data = [])
{
- if ($this->withDefaults && empty($data) && is_callable($this->callable)) {
+ if ($this->withDefaults && empty($data) && \is_callable($this->callable)) {
$blueprints = $this->callable;
$data = $blueprints()->getDefaults();
}
diff --git a/system/src/Grav/Common/Config/CompiledLanguages.php b/system/src/Grav/Common/Config/CompiledLanguages.php
index 610e347..ef18145 100644
--- a/system/src/Grav/Common/Config/CompiledLanguages.php
+++ b/system/src/Grav/Common/Config/CompiledLanguages.php
@@ -1,8 +1,9 @@
version = 1;
+ }
/**
* Create configuration object.
diff --git a/system/src/Grav/Common/Config/Config.php b/system/src/Grav/Common/Config/Config.php
index a8e2bdf..7b80846 100644
--- a/system/src/Grav/Common/Config/Config.php
+++ b/system/src/Grav/Common/Config/Config.php
@@ -1,8 +1,9 @@
checksum();
+ if (null === $this->key) {
+ $this->key = md5($this->checksum . $this->timestamp);
+ }
+
+ return $this->key;
}
public function checksum($checksum = null)
@@ -90,7 +101,7 @@ class Config extends Data
{
$setup = Grav::instance()['setup']->toArray();
foreach ($setup as $key => $value) {
- if ($key === 'streams' || !is_array($value)) {
+ if ($key === 'streams' || !\is_array($value)) {
// Optimized as streams and simple values are fully defined in setup.
$this->items[$key] = $value;
} else {
@@ -98,14 +109,13 @@ class Config extends Data
}
}
- // Override the media.upload_limit based on PHP values
- $upload_limit = Utils::getUploadLimit();
- $this->items['system']['media']['upload_limit'] = $upload_limit > 0 ? $upload_limit : 1024*1024*1024;
+ // Legacy value - Override the media.upload_limit based on PHP values
+ $this->items['system']['media']['upload_limit'] = Utils::getUploadLimit();
}
/**
* @return mixed
- * @deprecated
+ * @deprecated 1.5 Use Grav::instance()['languages'] instead.
*/
public function getLanguages()
{
diff --git a/system/src/Grav/Common/Config/ConfigFileFinder.php b/system/src/Grav/Common/Config/ConfigFileFinder.php
index 83b13ec..1e7adca 100644
--- a/system/src/Grav/Common/Config/ConfigFileFinder.php
+++ b/system/src/Grav/Common/Config/ConfigFileFinder.php
@@ -1,8 +1,9 @@
items = Utils::arrayMergeRecursiveUnique($this->items, $data);
}
+
+ public function flattenByLang($lang)
+ {
+ $language = $this->items[$lang];
+ return Utils::arrayFlattenDotNotation($language);
+ }
+
+ public function unflatten($array)
+ {
+ return Utils::arrayUnflattenDotNotation($array);
+ }
}
diff --git a/system/src/Grav/Common/Config/Setup.php b/system/src/Grav/Common/Config/Setup.php
index fba7fd1..6a4d336 100644
--- a/system/src/Grav/Common/Config/Setup.php
+++ b/system/src/Grav/Common/Config/Setup.php
@@ -1,8 +1,9 @@
'unknown',
+ '127.0.0.1' => 'localhost',
+ '::1' => 'localhost'
+ ];
+
+ /**
+ * @var string Current environment normalized to lower case.
+ */
public static $environment;
protected $streams = [
@@ -38,7 +51,7 @@ class Setup extends Data
// If not defined, environment will be set up in the constructor.
],
'asset' => [
- 'type' => 'ReadOnlyStream',
+ 'type' => 'Stream',
'prefixes' => [
'' => ['assets'],
]
@@ -109,7 +122,7 @@ class Setup extends Data
]
],
'image' => [
- 'type' => 'ReadOnlyStream',
+ 'type' => 'Stream',
'prefixes' => [
'' => ['user://images', 'system://images']
]
@@ -120,6 +133,13 @@ class Setup extends Data
'' => ['user://pages']
]
],
+ 'user-data' => [
+ 'type' => 'Stream',
+ 'force' => true,
+ 'prefixes' => [
+ '' => ['user://data']
+ ]
+ ],
'account' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
@@ -133,12 +153,26 @@ class Setup extends Data
*/
public function __construct($container)
{
- $environment = null !== static::$environment ? static::$environment : ($container['uri']->environment() ?: 'localhost');
+ // If no environment is set, make sure we get one (CLI or hostname).
+ if (!static::$environment) {
+ if (\defined('GRAV_CLI')) {
+ static::$environment = 'cli';
+ } else {
+ /** @var ServerRequestInterface $request */
+ $request = $container['request'];
+ $host = $request->getUri()->getHost();
+
+ static::$environment = Utils::substrToString($host, ':');
+ }
+ }
+
+ // Resolve server aliases to the proper environment.
+ $environment = $this->environments[static::$environment] ?? static::$environment;
// Pre-load setup.php which contains our initial configuration.
// Configuration may contain dynamic parts, which is why we need to always load it.
- // If "GRAVE_SETUP_PATH" has been defined, use it, otherwise use defaults.
- $file = defined('GRAV_SETUP_PATH') ? GRAV_SETUP_PATH : GRAV_ROOT . '/setup.php';
+ // If "GRAV_SETUP_PATH" has been defined, use it, otherwise use defaults.
+ $file = \defined('GRAV_SETUP_PATH') ? GRAV_SETUP_PATH : GRAV_ROOT . '/setup.php';
$setup = is_file($file) ? (array) include $file : [];
// Add default streams defined in beginning of the class.
@@ -151,8 +185,8 @@ class Setup extends Data
parent::__construct($setup);
// Set up environment.
- $this->def('environment', $environment ?: 'cli');
- $this->def('streams.schemes.environment.prefixes', ['' => $environment ? ["user://{$this->environment}"] : []]);
+ $this->def('environment', $environment);
+ $this->def('streams.schemes.environment.prefixes', ['' => ["user://{$this->get('environment')}"]]);
}
/**
@@ -212,8 +246,8 @@ class Setup extends Data
$locator->addPath($scheme, '', $config['paths']);
}
- $override = isset($config['override']) ? $config['override'] : false;
- $force = isset($config['force']) ? $config['force'] : false;
+ $override = $config['override'] ?? false;
+ $force = $config['force'] ?? false;
if (isset($config['prefixes'])) {
foreach ((array)$config['prefixes'] as $prefix => $paths) {
@@ -232,7 +266,7 @@ class Setup extends Data
{
$schemes = [];
foreach ((array) $this->get('streams.schemes') as $scheme => $config) {
- $type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
+ $type = $config['type'] ?? 'ReadOnlyStream';
if ($type[0] !== '\\') {
$type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type;
}
@@ -251,8 +285,8 @@ class Setup extends Data
*/
protected function check(UniformResourceLocator $locator)
{
- $streams = isset($this->items['streams']['schemes']) ? $this->items['streams']['schemes'] : null;
- if (!is_array($streams)) {
+ $streams = $this->items['streams']['schemes'] ?? null;
+ if (!\is_array($streams)) {
throw new \InvalidArgumentException('Configuration is missing streams.schemes!');
}
$diff = array_keys(array_diff_key($this->streams, $streams));
@@ -271,10 +305,14 @@ class Setup extends Data
// Create security.yaml if it doesn't exist.
$filename = $locator->findResource('config://security.yaml', true, true);
- $file = YamlFile::instance($filename);
- if (!$file->exists()) {
- $file->save(['salt' => Utils::generateRandomString(14)]);
- $file->free();
+ $security_file = CompiledYamlFile::instance($filename);
+ $security_content = (array)$security_file->content();
+
+ if (!isset($security_content['salt'])) {
+ $security_content = array_merge($security_content, ['salt' => Utils::generateRandomString(14)]);
+ $security_file->content($security_content);
+ $security_file->save();
+ $security_file->free();
}
} catch (\RuntimeException $e) {
throw new \RuntimeException(sprintf('Grav failed to initialize: %s', $e->getMessage()), 500, $e);
diff --git a/system/src/Grav/Common/Data/Blueprint.php b/system/src/Grav/Common/Data/Blueprint.php
index bf33d41..2beeea8 100644
--- a/system/src/Grav/Common/Data/Blueprint.php
+++ b/system/src/Grav/Common/Data/Blueprint.php
@@ -1,8 +1,9 @@
blueprintSchema) {
+ $this->blueprintSchema = clone $this->blueprintSchema;
+ }
+ }
+
+ public function setScope($scope)
+ {
+ $this->scope = $scope;
+ }
+
/**
* Set default values for field types.
*
@@ -51,7 +68,60 @@ class Blueprint extends BlueprintForm
{
$this->initInternals();
- return $this->blueprintSchema->getDefaults();
+ if (null === $this->defaults) {
+ $this->defaults = $this->blueprintSchema->getDefaults();
+ }
+
+ return $this->defaults;
+ }
+
+ /**
+ * Initialize blueprints with its dynamic fields.
+ *
+ * @return $this
+ */
+ public function init()
+ {
+ foreach ($this->dynamic as $key => $data) {
+ // Locate field.
+ $path = explode('/', $key);
+ $current = &$this->items;
+
+ foreach ($path as $field) {
+ if (\is_object($current)) {
+ // Handle objects.
+ if (!isset($current->{$field})) {
+ $current->{$field} = [];
+ }
+
+ $current = &$current->{$field};
+ } else {
+ // Handle arrays and scalars.
+ if (!\is_array($current)) {
+ $current = [$field => []];
+ } elseif (!isset($current[$field])) {
+ $current[$field] = [];
+ }
+
+ $current = &$current[$field];
+ }
+ }
+
+ // Set dynamic property.
+ foreach ($data as $property => $call) {
+ $action = $call['action'];
+ $method = 'dynamic' . ucfirst($action);
+
+ if (isset($this->handlers[$action])) {
+ $callable = $this->handlers[$action];
+ $callable($current, $property, $call);
+ } elseif (method_exists($this, $method)) {
+ $this->{$method}($current, $property, $call);
+ }
+ }
+ }
+
+ return $this;
}
/**
@@ -70,6 +140,20 @@ class Blueprint extends BlueprintForm
return $this->blueprintSchema->mergeData($data1, $data2, $name, $separator);
}
+ /**
+ * Process data coming from a form.
+ *
+ * @param array $data
+ * @param array $toggles
+ * @return array
+ */
+ public function processForm(array $data, array $toggles = [])
+ {
+ $this->initInternals();
+
+ return $this->blueprintSchema->processForm($data, $toggles);
+ }
+
/**
* Return data fields that do not exist in blueprints.
*
@@ -101,15 +185,32 @@ class Blueprint extends BlueprintForm
* Filter data by using blueprints.
*
* @param array $data
+ * @param bool $missingValuesAsNull
+ * @param bool $keepEmptyValues
* @return array
*/
- public function filter(array $data)
+ public function filter(array $data, bool $missingValuesAsNull = false, bool $keepEmptyValues = false)
{
$this->initInternals();
- return $this->blueprintSchema->filter($data);
+ return $this->blueprintSchema->filter($data, $missingValuesAsNull, $keepEmptyValues);
}
+
+ /**
+ * Flatten data by using blueprints.
+ *
+ * @param array $data
+ * @return array
+ */
+ public function flattenData(array $data)
+ {
+ $this->initInternals();
+
+ return $this->blueprintSchema->flattenData($data);
+ }
+
+
/**
* Return blueprint data schema.
*
@@ -122,20 +223,28 @@ class Blueprint extends BlueprintForm
return $this->blueprintSchema;
}
+ public function addDynamicHandler(string $name, callable $callable): void
+ {
+ $this->handlers[$name] = $callable;
+ }
+
/**
* Initialize validator.
*/
protected function initInternals()
{
- if (!isset($this->blueprintSchema)) {
+ if (null === $this->blueprintSchema) {
$types = Grav::instance()['plugins']->formFieldTypes;
$this->blueprintSchema = new BlueprintSchema;
+
if ($types) {
$this->blueprintSchema->setTypes($types);
}
+
$this->blueprintSchema->embed('', $this->items);
$this->blueprintSchema->init();
+ $this->defaults = null;
}
}
@@ -162,17 +271,19 @@ class Blueprint extends BlueprintForm
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
- if (is_string($path) && !$locator->isStream($path)) {
+ if (\is_string($path) && !$locator->isStream($path)) {
// Find path overrides.
- $paths = isset($this->overrides[$path]) ? (array) $this->overrides[$path] : [];
+ $paths = (array) ($this->overrides[$path] ?? null);
// Add path pointing to default context.
if ($context === null) {
$context = $this->context;
}
- if ($context && $context[strlen($context)-1] !== '/') {
+
+ if ($context && $context[\strlen($context)-1] !== '/') {
$context .= '/';
}
+
$path = $context . $path;
if (!preg_match('/\.yaml$/', $path)) {
@@ -186,7 +297,7 @@ class Blueprint extends BlueprintForm
$files = [];
foreach ($paths as $lookup) {
- if (is_string($lookup) && strpos($lookup, '://')) {
+ if (\is_string($lookup) && strpos($lookup, '://')) {
$files = array_merge($files, $locator->findResources($lookup));
} else {
$files[] = $lookup;
@@ -205,27 +316,29 @@ class Blueprint extends BlueprintForm
{
$params = $call['params'];
- if (is_array($params)) {
+ if (\is_array($params)) {
$function = array_shift($params);
} else {
$function = $params;
$params = [];
}
- list($o, $f) = preg_split('/::/', $function, 2);
+ [$o, $f] = explode('::', $function, 2);
+
+ $data = null;
if (!$f) {
- if (function_exists($o)) {
- $data = call_user_func_array($o, $params);
+ if (\function_exists($o)) {
+ $data = \call_user_func_array($o, $params);
}
} else {
if (method_exists($o, $f)) {
- $data = call_user_func_array(array($o, $f), $params);
+ $data = \call_user_func_array([$o, $f], $params);
}
}
// If function returns a value,
- if (isset($data)) {
- if (isset($field[$property]) && is_array($field[$property]) && is_array($data)) {
+ if (null !== $data) {
+ if (\is_array($data) && isset($field[$property]) && \is_array($field[$property])) {
// Combine field and @data-field together.
$field[$property] += $data;
} else {
@@ -243,12 +356,75 @@ class Blueprint extends BlueprintForm
protected function dynamicConfig(array &$field, $property, array &$call)
{
$value = $call['params'];
-
- $default = isset($field[$property]) ? $field[$property] : null;
+ $default = $field[$property] ?? null;
$config = Grav::instance()['config']->get($value, $default);
- if (!is_null($config)) {
+ if (null !== $config) {
$field[$property] = $config;
}
}
+
+ /**
+ * @param array $field
+ * @param string $property
+ * @param array $call
+ */
+ protected function dynamicSecurity(array &$field, $property, array &$call)
+ {
+ if ($property || !empty($field['validate']['ignore'])) {
+ return;
+ }
+
+ $grav = Grav::instance();
+ $actions = (array)$call['params'];
+
+ /** @var UserInterface $user */
+ if (isset($grav['user'])) {
+ $user = Grav::instance()['user'];
+ foreach ($actions as $action) {
+ if (!$user->authorize($action)) {
+ $this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param array $field
+ * @param string $property
+ * @param array $call
+ */
+ protected function dynamicScope(array &$field, $property, array &$call)
+ {
+ if ($property && $property !== 'ignore') {
+ return;
+ }
+
+ $scopes = (array)$call['params'];
+ $matches = \in_array($this->scope, $scopes, true);
+ if ($this->scope && $property !== 'ignore') {
+ $matches = !$matches;
+ }
+
+ if ($matches) {
+ $this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
+ return;
+ }
+ }
+
+ protected function addPropertyRecursive(array &$field, $property, $value)
+ {
+ if (\is_array($value) && isset($field[$property]) && \is_array($field[$property])) {
+ $field[$property] = array_merge_recursive($field[$property], $value);
+ } else {
+ $field[$property] = $value;
+ }
+
+ if (!empty($field['fields'])) {
+ foreach ($field['fields'] as $key => &$child) {
+ $this->addPropertyRecursive($child, $property, $value);
+ }
+ }
+ }
}
diff --git a/system/src/Grav/Common/Data/BlueprintSchema.php b/system/src/Grav/Common/Data/BlueprintSchema.php
index 3205c38..898450d 100644
--- a/system/src/Grav/Common/Data/BlueprintSchema.php
+++ b/system/src/Grav/Common/Data/BlueprintSchema.php
@@ -1,8 +1,9 @@
true
];
+ /**
+ * @return array
+ */
+ public function getTypes()
+ {
+ return $this->types;
+ }
+
+ /**
+ * @param string $name
+ * @return array
+ */
+ public function getType($name)
+ {
+ return $this->types[$name] ?? [];
+ }
+
/**
* Validate data against blueprints.
*
@@ -47,35 +65,90 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
}
/**
- * Filter data by using blueprints.
- *
- * @param array $data
+ * @param array $data
+ * @param array $toggles
* @return array
*/
- public function filter(array $data)
+ public function processForm(array $data, array $toggles = [])
{
- return $this->filterArray($data, $this->nested);
+ return $this->processFormRecursive($data, $toggles, $this->nested);
+ }
+
+ /**
+ * Filter data by using blueprints.
+ *
+ * @param array $data Incoming data, for example from a form.
+ * @param bool $missingValuesAsNull Include missing values as nulls.
+ * @param bool $keepEmptyValues Include empty values.
+ * @return array
+ */
+ public function filter(array $data, $missingValuesAsNull = false, $keepEmptyValues = false)
+ {
+ return $this->filterArray($data, $this->nested, $missingValuesAsNull, $keepEmptyValues);
+ }
+
+ /**
+ * Flatten data by using blueprints.
+ *
+ * @param array $data Data to be flattened.
+ * @return array
+ */
+ public function flattenData(array $data)
+ {
+ return $this->flattenArray($data, $this->nested, '');
}
/**
* @param array $data
* @param array $rules
- * @returns array
+ * @param string $prefix
+ * @return array
+ */
+ protected function flattenArray(array $data, array $rules, string $prefix)
+ {
+ $array = [];
+
+ foreach ($data as $key => $field) {
+ $val = $rules[$key] ?? $rules['*'] ?? null;
+ $rule = is_string($val) ? $this->items[$val] : null;
+
+ if ($rule || isset($val['*'])) {
+ // Item has been defined in blueprints.
+ $array[$prefix.$key] = $field;
+ } elseif (is_array($field) && is_array($val)) {
+ // Array has been defined in blueprints.
+ $array += $this->flattenArray($field, $val, $prefix . $key . '.');
+ } else {
+ // Undefined/extra item.
+ $array[$prefix.$key] = $field;
+ }
+ }
+ return $array;
+ }
+
+ /**
+ * @param array $data
+ * @param array $rules
+ * @return array
* @throws \RuntimeException
- * @internal
*/
protected function validateArray(array $data, array $rules)
{
$messages = $this->checkRequired($data, $rules);
foreach ($data as $key => $field) {
- $val = isset($rules[$key]) ? $rules[$key] : (isset($rules['*']) ? $rules['*'] : null);
- $rule = is_string($val) ? $this->items[$val] : null;
+ $val = $rules[$key] ?? $rules['*'] ?? null;
+ $rule = \is_string($val) ? $this->items[$val] : null;
if ($rule) {
// Item has been defined in blueprints.
+ if (!empty($rule['disabled']) || !empty($rule['validate']['ignore'])) {
+ // Skip validation in the ignored field.
+ continue;
+ }
+
$messages += Validation::validate($field, $rule);
- } elseif (is_array($field) && is_array($val)) {
+ } elseif (\is_array($field) && \is_array($val)) {
// Array has been defined in blueprints.
$messages += $this->validateArray($field, $val);
} elseif (isset($rules['validation']) && $rules['validation'] === 'strict') {
@@ -90,32 +163,99 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
/**
* @param array $data
* @param array $rules
+ * @param bool $missingValuesAsNull
+ * @param bool $keepEmptyValues
* @return array
- * @internal
*/
- protected function filterArray(array $data, array $rules)
+ protected function filterArray(array $data, array $rules, $missingValuesAsNull, $keepEmptyValues)
{
- $results = array();
+ $results = [];
+
+ if ($missingValuesAsNull) {
+ // First pass is to fill up all the fields with null. This is done to lock the ordering of the fields.
+ foreach ($rules as $key => $rule) {
+ if ($key && !isset($results[$key])) {
+ $val = $rules[$key] ?? $rules['*'] ?? null;
+ $rule = \is_string($val) ? $this->items[$val] : null;
+
+ if (empty($rule['disabled']) && empty($rule['validate']['ignore'])) {
+ continue;
+ }
+ }
+ }
+ }
+
foreach ($data as $key => $field) {
- $val = isset($rules[$key]) ? $rules[$key] : (isset($rules['*']) ? $rules['*'] : null);
- $rule = is_string($val) ? $this->items[$val] : null;
+ $val = $rules[$key] ?? $rules['*'] ?? null;
+ $rule = \is_string($val) ? $this->items[$val] : null;
if ($rule) {
// Item has been defined in blueprints.
+ if (!empty($rule['disabled']) || !empty($rule['validate']['ignore'])) {
+ // Skip any data in the ignored field.
+ unset($results[$key]);
+ continue;
+ }
+
$field = Validation::filter($field, $rule);
- } elseif (is_array($field) && is_array($val)) {
+ } elseif (\is_array($field) && \is_array($val)) {
// Array has been defined in blueprints.
- $field = $this->filterArray($field, $val);
+ $field = $this->filterArray($field, $val, $missingValuesAsNull, $keepEmptyValues);
+
} elseif (isset($rules['validation']) && $rules['validation'] === 'strict') {
- $field = null;
+ // Skip any extra data.
+ continue;
}
- if (isset($field) && (!is_array($field) || !empty($field))) {
+ if ($keepEmptyValues || (null !== $field && (!\is_array($field) || !empty($field)))) {
$results[$key] = $field;
}
}
- return $results;
+ return $results ?: null;
+ }
+
+ /**
+ * @param array|null $data
+ * @param array $toggles
+ * @param array $nested
+ * @return array|null
+ */
+ protected function processFormRecursive(?array $data, array $toggles, array $nested)
+ {
+ foreach ($nested as $key => $value) {
+ if ($key === '') {
+ continue;
+ }
+ if ($key === '*') {
+ // TODO: Add support to collections.
+ continue;
+ }
+ if (is_array($value)) {
+ // Recursively fetch the items.
+ $data[$key] = $this->processFormRecursive($data[$key] ?? null, $toggles[$key] ?? [], $value);
+ } else {
+ $field = $this->get($value);
+ // Do not add the field if:
+ if (
+ // Not an input field
+ !$field
+ // Field has been disabled
+ || !empty($field['disabled'])
+ // Field validation is set to be ignored
+ || !empty($field['validate']['ignore'])
+ // Field is toggleable and the toggle is turned off
+ || (!empty($field['toggleable']) && empty($toggles[$key]))
+ ) {
+ continue;
+ }
+ if (!isset($data[$key])) {
+ $data[$key] = null;
+ }
+ }
+ }
+
+ return $data;
}
/**
@@ -128,10 +268,18 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
$messages = [];
foreach ($fields as $name => $field) {
- if (!is_string($field)) {
+ if (!\is_string($field)) {
continue;
}
+
$field = $this->items[$field];
+
+ // Skip ignored field, it will not be required.
+ if (!empty($field['disabled']) || !empty($field['validate']['ignore'])) {
+ continue;
+ }
+
+ // Check if required.
if (isset($field['validate']['required'])
&& $field['validate']['required'] === true) {
@@ -142,9 +290,9 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
continue;
}
- $value = isset($field['label']) ? $field['label'] : $field['name'];
+ $value = $field['label'] ?? $field['name'];
$language = Grav::instance()['language'];
- $message = sprintf($language->translate('FORM.MISSING_REQUIRED_FIELD', null, true) . ' %s', $language->translate($value));
+ $message = sprintf($language->translate('GRAV.FORM.MISSING_REQUIRED_FIELD', null, true) . ' %s', $language->translate($value));
$messages[$field['name']][] = $message;
}
}
@@ -161,7 +309,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
{
$value = $call['params'];
- $default = isset($field[$property]) ? $field[$property] : null;
+ $default = $field[$property] ?? null;
$config = Grav::instance()['config']->get($value, $default);
if (null !== $config) {
diff --git a/system/src/Grav/Common/Data/Blueprints.php b/system/src/Grav/Common/Data/Blueprints.php
index c7090c5..0b0719d 100644
--- a/system/src/Grav/Common/Data/Blueprints.php
+++ b/system/src/Grav/Common/Data/Blueprints.php
@@ -1,8 +1,9 @@
types === null) {
- $this->types = array();
+ $this->types = [];
$grav = Grav::instance();
@@ -87,7 +91,7 @@ class Blueprints
{
$blueprint = new Blueprint($name);
- if (is_array($this->search) || is_object($this->search)) {
+ if (\is_array($this->search) || \is_object($this->search)) {
// Page types.
$blueprint->setOverrides($this->search);
$blueprint->setContext('blueprints://pages');
diff --git a/system/src/Grav/Common/Data/Data.php b/system/src/Grav/Common/Data/Data.php
index a9ceca3..bfae2da 100644
--- a/system/src/Grav/Common/Data/Data.php
+++ b/system/src/Grav/Common/Data/Data.php
@@ -1,8 +1,9 @@
items = $items;
$this->blueprints = $blueprints;
@@ -70,14 +70,16 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
{
$old = $this->get($name, null, $separator);
if ($old !== null) {
- if (!is_array($old)) {
+ if (!\is_array($old)) {
throw new \RuntimeException('Value ' . $old);
}
- if (is_object($value)) {
+
+ if (\is_object($value)) {
$value = (array) $value;
- } elseif (!is_array($value)) {
+ } elseif (!\is_array($value)) {
throw new \RuntimeException('Value ' . $value);
}
+
$value = $this->blueprints()->mergeData($old, $value, $name, $separator);
}
@@ -108,9 +110,10 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
*/
public function joinDefaults($name, $value, $separator = '.')
{
- if (is_object($value)) {
+ if (\is_object($value)) {
$value = (array) $value;
}
+
$old = $this->get($name, null, $separator);
if ($old !== null) {
$value = $this->blueprints()->mergeData($value, $old, $name, $separator);
@@ -125,16 +128,16 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
* Get value from the configuration and join it with given data.
*
* @param string $name Dot separated path to the requested value.
- * @param array $value Value to be joined.
+ * @param array|object $value Value to be joined.
* @param string $separator Separator, defaults to '.'
* @return array
* @throws \RuntimeException
*/
public function getJoined($name, $value, $separator = '.')
{
- if (is_object($value)) {
+ if (\is_object($value)) {
$value = (array) $value;
- } elseif (!is_array($value)) {
+ } elseif (!\is_array($value)) {
throw new \RuntimeException('Value ' . $value);
}
@@ -145,7 +148,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
return $value;
}
- if (!is_array($old)) {
+ if (!\is_array($old)) {
throw new \RuntimeException('Value ' . $old);
}
@@ -195,11 +198,14 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
/**
* @return $this
- * Filter all items by using blueprints.
*/
public function filter()
{
- $this->items = $this->blueprints()->filter($this->items);
+ $args = func_get_args();
+ $missingValuesAsNull = (bool)(array_shift($args) ?: false);
+ $keepEmptyValues = (bool)(array_shift($args) ?: false);
+
+ $this->items = $this->blueprints()->filter($this->items, $missingValuesAsNull, $keepEmptyValues);
return $this;
}
@@ -223,7 +229,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
{
if (!$this->blueprints){
$this->blueprints = new Blueprint;
- } elseif (is_callable($this->blueprints)) {
+ } elseif (\is_callable($this->blueprints)) {
// Lazy load blueprints.
$blueprints = $this->blueprints;
$this->blueprints = $blueprints();
@@ -282,6 +288,12 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
if ($storage) {
$this->storage = $storage;
}
+
return $this->storage;
}
+
+ public function jsonSerialize()
+ {
+ return $this->items;
+ }
}
diff --git a/system/src/Grav/Common/Data/DataInterface.php b/system/src/Grav/Common/Data/DataInterface.php
index aa61d26..91279b2 100644
--- a/system/src/Grav/Common/Data/DataInterface.php
+++ b/system/src/Grav/Common/Data/DataInterface.php
@@ -1,8 +1,9 @@
translate($field['validate']['message'])
- : $language->translate('FORM.INVALID_INPUT', null, true) . ' "' . $language->translate($name) . '"';
+ : $language->translate('GRAV.FORM.INVALID_INPUT', null, true) . ' "' . $language->translate($name) . '"';
+ // Validate type with fallback type text.
+ $method = 'type' . str_replace('-', '_', $type);
+
// If this is a YAML field validate/filter as such
- if ($type != 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
+ if (isset($field['yaml']) && $field['yaml'] === true) {
$method = 'typeYaml';
}
- if (method_exists(__CLASS__, $method)) {
- $success = self::$method($value, $validate, $field);
- } else {
- $success = true;
- }
+ $messages = [];
+ $success = method_exists(__CLASS__, $method) ? self::$method($value, $validate, $field) : true;
if (!$success) {
$messages[$field['name']][] = $message;
}
// Check individual rules.
foreach ($validate as $rule => $params) {
- $method = 'validate' . ucfirst(strtr($rule, '-', '_'));
+ $method = 'validate' . ucfirst(str_replace('-', '_', $rule));
if (method_exists(__CLASS__, $method)) {
$success = self::$method($value, $params);
@@ -89,29 +86,27 @@ class Validation
*/
public static function filter($value, array $field)
{
- $validate = isset($field['validate']) ? (array) $field['validate'] : [];
+ $validate = (array)($field['filter'] ?? $field['validate'] ?? null);
// If value isn't required, we will return null if empty value is given.
- if (empty($validate['required']) && ($value === null || $value === '')) {
+ if (($value === null || $value === '') && empty($validate['required'])) {
return null;
}
if (!isset($field['type'])) {
$field['type'] = 'text';
}
+ $type = $field['filter']['type'] ?? $field['validate']['type'] ?? $field['type'];
-
- // Validate type with fallback type text.
- $type = (string) isset($field['validate']['type']) ? $field['validate']['type'] : $field['type'];
- $method = 'filter' . ucfirst(strtr($type, '-', '_'));
+ $method = 'filter' . ucfirst(str_replace('-', '_', $type));
// If this is a YAML field validate/filter as such
- if ($type !== 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
+ if (isset($field['yaml']) && $field['yaml'] === true) {
$method = 'filterYaml';
}
if (!method_exists(__CLASS__, $method)) {
- $method = 'filterText';
+ $method = isset($field['array']) && $field['array'] === true ? 'filterArray' : 'filterText';
}
return self::$method($value, $validate, $field);
@@ -127,22 +122,26 @@ class Validation
*/
public static function typeText($value, array $params, array $field)
{
- if (!is_string($value) && !is_numeric($value)) {
+ if (!\is_string($value) && !is_numeric($value)) {
return false;
}
$value = (string)$value;
- if (isset($params['min']) && strlen($value) < $params['min']) {
+ if (!empty($params['trim'])) {
+ $value = trim($value);
+ }
+
+ if (isset($params['min']) && \strlen($value) < $params['min']) {
return false;
}
- if (isset($params['max']) && strlen($value) > $params['max']) {
+ if (isset($params['max']) && \strlen($value) > $params['max']) {
return false;
}
- $min = isset($params['min']) ? $params['min'] : 0;
- if (isset($params['step']) && (strlen($value) - $min) % $params['step'] == 0) {
+ $min = $params['min'] ?? 0;
+ if (isset($params['step']) && (\strlen($value) - $min) % $params['step'] === 0) {
return false;
}
@@ -155,17 +154,25 @@ class Validation
protected static function filterText($value, array $params, array $field)
{
+ if (!\is_string($value) && !is_numeric($value)) {
+ return '';
+ }
+
+ if (!empty($params['trim'])) {
+ $value = trim($value);
+ }
+
return (string) $value;
}
protected static function filterCommaList($value, array $params, array $field)
{
- return is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
+ return \is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
}
public static function typeCommaList($value, array $params, array $field)
{
- return is_array($value) ? true : self::typeText($value, $params, $field);
+ return \is_array($value) ? true : self::typeText($value, $params, $field);
}
protected static function filterLower($value, array $params)
@@ -234,6 +241,7 @@ class Validation
{
// Set multiple: true so checkboxes can easily use min/max counts to control number of options required
$field['multiple'] = true;
+
return self::typeArray((array) $value, $params, $field);
}
@@ -252,16 +260,10 @@ class Validation
*/
public static function typeCheckbox($value, array $params, array $field)
{
- $value = (string) $value;
+ $value = (string)$value;
+ $field_value = (string)($field['value'] ?? '1');
- if (!isset($field['value'])) {
- $field['value'] = 1;
- }
- if (isset($value) && $value != $field['value']) {
- return false;
- }
-
- return true;
+ return $value === $field_value;
}
/**
@@ -287,6 +289,10 @@ class Validation
*/
public static function typeToggle($value, array $params, array $field)
{
+ if (\is_bool($value)) {
+ $value = (int)$value;
+ }
+
return self::typeArray((array) $value, $params, $field);
}
@@ -300,12 +306,12 @@ class Validation
*/
public static function typeFile($value, array $params, array $field)
{
- return self::typeArray((array) $value, $params, $field);
+ return self::typeArray((array)$value, $params, $field);
}
protected static function filterFile($value, array $params, array $field)
{
- return (array) $value;
+ return (array)$value;
}
/**
@@ -343,12 +349,9 @@ class Validation
return false;
}
- $min = isset($params['min']) ? $params['min'] : 0;
- if (isset($params['step']) && fmod($value - $min, $params['step']) == 0) {
- return false;
- }
+ $min = $params['min'] ?? 0;
- return true;
+ return !(isset($params['step']) && fmod($value - $min, $params['step']) === 0);
}
protected static function filterNumber($value, array $params, array $field)
@@ -408,10 +411,10 @@ class Validation
*/
public static function typeEmail($value, array $params, array $field)
{
- $values = !is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value;
+ $values = !\is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value;
- foreach ($values as $value) {
- if (!(self::typeText($value, $params, $field) && filter_var($value, FILTER_VALIDATE_EMAIL))) {
+ foreach ($values as $val) {
+ if (!(self::typeText($val, $params, $field) && filter_var($val, FILTER_VALIDATE_EMAIL))) {
return false;
}
}
@@ -445,9 +448,11 @@ class Validation
{
if ($value instanceof \DateTime) {
return true;
- } elseif (!is_string($value)) {
+ }
+ if (!\is_string($value)) {
return false;
- } elseif (!isset($params['format'])) {
+ }
+ if (!isset($params['format'])) {
return false !== strtotime($value);
}
@@ -479,10 +484,10 @@ class Validation
*/
public static function typeDate($value, array $params, array $field)
{
- $params = array($params);
if (!isset($params['format'])) {
$params['format'] = 'Y-m-d';
}
+
return self::typeDatetime($value, $params, $field);
}
@@ -496,10 +501,10 @@ class Validation
*/
public static function typeTime($value, array $params, array $field)
{
- $params = array($params);
if (!isset($params['format'])) {
$params['format'] = 'H:i';
}
+
return self::typeDatetime($value, $params, $field);
}
@@ -513,10 +518,10 @@ class Validation
*/
public static function typeMonth($value, array $params, array $field)
{
- $params = array($params);
if (!isset($params['format'])) {
$params['format'] = 'Y-m';
}
+
return self::typeDatetime($value, $params, $field);
}
@@ -533,6 +538,7 @@ class Validation
if (!isset($params['format']) && !preg_match('/^\d{4}-W\d{2}$/u', $value)) {
return false;
}
+
return self::typeDatetime($value, $params, $field);
}
@@ -546,72 +552,80 @@ class Validation
*/
public static function typeArray($value, array $params, array $field)
{
- if (!is_array($value)) {
+ if (!\is_array($value)) {
return false;
}
if (isset($field['multiple'])) {
- if (isset($params['min']) && count($value) < $params['min']) {
+ if (isset($params['min']) && \count($value) < $params['min']) {
return false;
}
- if (isset($params['max']) && count($value) > $params['max']) {
+ if (isset($params['max']) && \count($value) > $params['max']) {
return false;
}
- $min = isset($params['min']) ? $params['min'] : 0;
- if (isset($params['step']) && (count($value) - $min) % $params['step'] == 0) {
+ $min = $params['min'] ?? 0;
+ if (isset($params['step']) && (\count($value) - $min) % $params['step'] === 0) {
return false;
}
}
- $options = isset($field['options']) ? array_keys($field['options']) : array();
- $values = isset($field['use']) && $field['use'] == 'keys' ? array_keys($value) : $value;
- if ($options && array_diff($values, $options)) {
- return false;
+ $options = $field['options'] ?? [];
+ $use = $field['use'] ?? 'values';
+
+ if (empty($field['selectize']) || empty($field['multiple'])) {
+ $options = array_keys($options);
+ }
+ if ($use === 'keys') {
+ $value = array_keys($value);
}
- return true;
+ return !($options && array_diff($value, $options));
}
protected static function filterArray($value, $params, $field)
{
$values = (array) $value;
- $options = isset($field['options']) ? array_keys($field['options']) : array();
- $multi = isset($field['multiple']) ? $field['multiple'] : false;
+ $options = isset($field['options']) ? array_keys($field['options']) : [];
+ $multi = $field['multiple'] ?? false;
- if (count($values) == 1 && isset($values[0]) && $values[0] == '') {
+ if (\count($values) === 1 && isset($values[0]) && $values[0] === '') {
return null;
}
if ($options) {
- $useKey = isset($field['use']) && $field['use'] == 'keys';
- foreach ($values as $key => $value) {
- $values[$key] = $useKey ? (bool) $value : $value;
+ $useKey = isset($field['use']) && $field['use'] === 'keys';
+ foreach ($values as $key => $val) {
+ $values[$key] = $useKey ? (bool) $val : $val;
}
}
if ($multi) {
- foreach ($values as $key => $value) {
- if (is_array($value)) {
- $value = implode(',', $value);
- $values[$key] = array_map('trim', explode(',', $value));
+ foreach ($values as $key => $val) {
+ if (\is_array($val)) {
+ $val = implode(',', $val);
+ $values[$key] = array_map('trim', explode(',', $val));
} else {
- $values[$key] = trim($value);
+ $values[$key] = trim($val);
}
}
}
if (isset($field['ignore_empty']) && Utils::isPositive($field['ignore_empty'])) {
- foreach ($values as $key => $value) {
- foreach ($value as $inner_key => $inner_value) {
- if ($inner_value == '') {
- unset($value[$inner_key]);
+ foreach ($values as $key => $val) {
+ if ($val === '') {
+ unset($values[$key]);
+ } elseif (\is_array($val)) {
+ foreach ($val as $inner_key => $inner_value) {
+ if ($inner_value === '') {
+ unset($val[$inner_key]);
+ }
}
}
- $values[$key] = $value;
+ $values[$key] = $val;
}
}
@@ -620,7 +634,7 @@ class Validation
public static function typeList($value, array $params, array $field)
{
- if (!is_array($value)) {
+ if (!\is_array($value)) {
return false;
}
@@ -628,7 +642,7 @@ class Validation
foreach ($value as $key => $item) {
foreach ($field['fields'] as $subKey => $subField) {
$subKey = trim($subKey, '.');
- $subValue = isset($item[$subKey]) ? $item[$subKey] : null;
+ $subValue = $item[$subKey] ?? null;
self::validate($subValue, $subField);
}
}
@@ -644,7 +658,7 @@ class Validation
public static function filterYaml($value, $params)
{
- if (!is_string($value)) {
+ if (!\is_string($value)) {
return $value;
}
@@ -670,6 +684,23 @@ class Validation
return $value;
}
+ /**
+ * Input value which can be ignored.
+ *
+ * @param mixed $value Value to be validated.
+ * @param array $params Validation parameters.
+ * @param array $field Blueprint for the field.
+ * @return bool True if validation succeeded.
+ */
+ public static function typeUnset($value, array $params, array $field)
+ {
+ return true;
+ }
+
+ public static function filterUnset($value, array $params, array $field)
+ {
+ return null;
+ }
// HTML5 attributes (min, max and range are handled inside the types)
@@ -677,9 +708,9 @@ class Validation
{
if (is_scalar($value)) {
return (bool) $params !== true || $value !== '';
- } else {
- return (bool) $params !== true || !empty($value);
}
+
+ return (bool) $params !== true || !empty($value);
}
public static function validatePattern($value, $params)
@@ -702,12 +733,12 @@ class Validation
public static function typeBool($value, $params)
{
- return is_bool($value) || $value == 1 || $value == 0;
+ return \is_bool($value) || $value == 1 || $value == 0;
}
public static function validateBool($value, $params)
{
- return is_bool($value) || $value == 1 || $value == 0;
+ return \is_bool($value) || $value == 1 || $value == 0;
}
protected static function filterBool($value, $params)
@@ -722,7 +753,7 @@ class Validation
public static function validateFloat($value, $params)
{
- return is_float(filter_var($value, FILTER_VALIDATE_FLOAT));
+ return \is_float(filter_var($value, FILTER_VALIDATE_FLOAT));
}
protected static function filterFloat($value, $params)
@@ -737,20 +768,17 @@ class Validation
public static function validateInt($value, $params)
{
- return is_numeric($value) && (int) $value == $value;
+ return is_numeric($value) && (int)$value == $value;
}
protected static function filterInt($value, $params)
{
- return (int) $value;
+ return (int)$value;
}
public static function validateArray($value, $params)
{
- return is_array($value)
- || ($value instanceof \ArrayAccess
- && $value instanceof \Traversable
- && $value instanceof \Countable);
+ return \is_array($value) || ($value instanceof \ArrayAccess && $value instanceof \Traversable && $value instanceof \Countable);
}
public static function filterItem_List($value, $params)
diff --git a/system/src/Grav/Common/Data/ValidationException.php b/system/src/Grav/Common/Data/ValidationException.php
index 9272894..8bdb057 100644
--- a/system/src/Grav/Common/Data/ValidationException.php
+++ b/system/src/Grav/Common/Data/ValidationException.php
@@ -1,8 +1,9 @@
messages = $messages;
$language = Grav::instance()['language'];
- $this->message = $language->translate('FORM.VALIDATION_FAIL', null, true) . ' ' . $this->message;
+ $this->message = $language->translate('GRAV.FORM.VALIDATION_FAIL', null, true) . ' ' . $this->message;
foreach ($messages as $variable => &$list) {
$list = array_unique($list);
diff --git a/system/src/Grav/Common/Debugger.php b/system/src/Grav/Common/Debugger.php
index 8fee09f..3afce37 100644
--- a/system/src/Grav/Common/Debugger.php
+++ b/system/src/Grav/Common/Debugger.php
@@ -1,18 +1,29 @@
init() gets called.
$this->enabled = true;
- $this->debugbar = new StandardDebugBar();
- $this->debugbar['time']->addMeasure('Loading', $this->debugbar['time']->getRequestStartTime(), microtime(true));
+ $debugbar = new DebugBar();
+ $debugbar->addCollector(new PhpInfoCollector());
+ $debugbar->addCollector(new MessagesCollector());
+ $debugbar->addCollector(new RequestDataCollector());
+ $debugbar->addCollector(new TimeDataCollector($_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME));
+
+ $debugbar['time']->addMeasure('Server', $debugbar['time']->getRequestStartTime(), GRAV_REQUEST_TIME);
+ $debugbar['time']->addMeasure('Loading', GRAV_REQUEST_TIME, $currentTime);
+ $debugbar['time']->addMeasure('Debugger', $currentTime, microtime(true));
+
+ $this->debugbar = $debugbar;
// Set deprecation collector.
$this->setErrorHandler();
@@ -60,21 +91,28 @@ class Debugger
*/
public function init()
{
+ if ($this->initialized) {
+ return $this;
+ }
+
$this->grav = Grav::instance();
$this->config = $this->grav['config'];
// Enable/disable debugger based on configuration.
- $this->enabled = $this->config->get('system.debugger.enabled');
+ $this->enabled = (bool)$this->config->get('system.debugger.enabled');
if ($this->enabled()) {
+ $this->initialized = true;
$plugins_config = (array)$this->config->get('plugins');
ksort($plugins_config);
-
- $this->debugbar->addCollector(new ConfigCollector((array)$this->config->get('system'), 'Config'));
- $this->debugbar->addCollector(new ConfigCollector($plugins_config, 'Plugins'));
+ $debugbar = $this->debugbar;
+ $debugbar->addCollector(new MemoryCollector());
+ $debugbar->addCollector(new ExceptionsCollector());
+ $debugbar->addCollector(new ConfigCollector((array)$this->config->get('system'), 'Config'));
+ $debugbar->addCollector(new ConfigCollector($plugins_config, 'Plugins'));
$this->addMessage('Grav v' . GRAV_VERSION);
}
@@ -86,12 +124,12 @@ class Debugger
*
* @param bool $state If null, the method returns the enabled value. If set, the method sets the enabled state
*
- * @return null
+ * @return bool
*/
public function enabled($state = null)
{
if ($state !== null) {
- $this->enabled = $state;
+ $this->enabled = (bool)$state;
}
return $this->enabled;
@@ -147,7 +185,7 @@ class Debugger
/**
* Adds a data collector
*
- * @param $collector
+ * @param DataCollectorInterface $collector
*
* @return $this
* @throws \DebugBar\DebugBarException
@@ -162,9 +200,9 @@ class Debugger
/**
* Returns a data collector
*
- * @param $collector
+ * @param DataCollectorInterface $collector
*
- * @return \DebugBar\DataCollector\DataCollectorInterface
+ * @return DataCollectorInterface
* @throws \DebugBar\DebugBarException
*/
public function getCollector($collector)
@@ -229,14 +267,14 @@ class Debugger
/**
* Start a timer with an associated name and description
*
- * @param $name
+ * @param string $name
* @param string|null $description
*
* @return $this
*/
public function startTimer($name, $description = null)
{
- if ($name[0] === '_' || $this->enabled()) {
+ if (strpos($name, '_') === 0 || $this->enabled()) {
$this->debugbar['time']->startMeasure($name, $description);
$this->timers[] = $name;
}
@@ -253,7 +291,7 @@ class Debugger
*/
public function stopTimer($name)
{
- if (in_array($name, $this->timers, true) && ($name[0] === '_' || $this->enabled())) {
+ if (\in_array($name, $this->timers, true) && (strpos($name, '_') === 0 || $this->enabled())) {
$this->debugbar['time']->stopMeasure($name);
}
@@ -263,7 +301,7 @@ class Debugger
/**
* Dump variables into the Messages tab of the Debug Bar
*
- * @param $message
+ * @param mixed $message
* @param string $label
* @param bool $isString
*
@@ -286,7 +324,7 @@ class Debugger
*/
public function addException(\Exception $e)
{
- if ($this->enabled()) {
+ if ($this->initialized && $this->enabled()) {
$this->debugbar['exceptions']->addException($e);
}
@@ -321,57 +359,183 @@ class Debugger
return true;
}
- $backtrace = debug_backtrace(false);
+ // Figure out error scope from the error.
+ $scope = 'unknown';
+ if (stripos($errstr, 'grav') !== false) {
+ $scope = 'grav';
+ } elseif (strpos($errfile, '/twig/') !== false) {
+ $scope = 'twig';
+ } elseif (stripos($errfile, '/yaml/') !== false) {
+ $scope = 'yaml';
+ } elseif (strpos($errfile, '/vendor/') !== false) {
+ $scope = 'vendor';
+ }
+
+ // Clean up backtrace to make it more useful.
+ $backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
// Skip current call.
array_shift($backtrace);
+ // Find yaml file where the error happened.
+ if ($scope === 'yaml') {
+ foreach ($backtrace as $current) {
+ if (isset($current['args'])) {
+ foreach ($current['args'] as $arg) {
+ if ($arg instanceof \SplFileInfo) {
+ $arg = $arg->getPathname();
+ }
+ if (\is_string($arg) && preg_match('/.+\.(yaml|md)$/i', $arg)) {
+ $errfile = $arg;
+ $errline = 0;
+
+ break 2;
+ }
+ }
+ }
+ }
+ }
+
+ // Filter arguments.
+ $cut = 0;
+ $previous = null;
+ foreach ($backtrace as $i => &$current) {
+ if (isset($current['args'])) {
+ $args = [];
+ foreach ($current['args'] as $arg) {
+ if (\is_string($arg)) {
+ $arg = "'" . $arg . "'";
+ if (mb_strlen($arg) > 100) {
+ $arg = 'string';
+ }
+ } elseif (\is_bool($arg)) {
+ $arg = $arg ? 'true' : 'false';
+ } elseif (\is_scalar($arg)) {
+ $arg = $arg;
+ } elseif (\is_object($arg)) {
+ $arg = get_class($arg) . ' $object';
+ } elseif (\is_array($arg)) {
+ $arg = '$array';
+ } else {
+ $arg = '$object';
+ }
+
+ $args[] = $arg;
+ }
+ $current['args'] = $args;
+ }
+
+ $object = $current['object'] ?? null;
+ unset($current['object']);
+
+ $reflection = null;
+ if ($object instanceof TemplateWrapper) {
+ $reflection = new \ReflectionObject($object);
+ $property = $reflection->getProperty('template');
+ $property->setAccessible(true);
+ $object = $property->getValue($object);
+ }
+
+ if ($object instanceof Template) {
+ $file = $current['file'] ?? null;
+
+ if (preg_match('`(Template.php|TemplateWrapper.php)$`', $file)) {
+ $current = null;
+ continue;
+ }
+
+ $debugInfo = $object->getDebugInfo();
+
+ $line = 1;
+ if (!$reflection) {
+ foreach ($debugInfo as $codeLine => $templateLine) {
+ if ($codeLine <= $current['line']) {
+ $line = $templateLine;
+ break;
+ }
+ }
+ }
+
+ $src = $object->getSourceContext();
+ //$code = preg_split('/\r\n|\r|\n/', $src->getCode());
+ //$current['twig']['twig'] = trim($code[$line - 1]);
+ $current['twig']['file'] = $src->getPath();
+ $current['twig']['line'] = $line;
+
+ $prevFile = $previous['file'] ?? null;
+ if ($prevFile && $file === $prevFile) {
+ $prevLine = $previous['line'];
+
+ $line = 1;
+ foreach ($debugInfo as $codeLine => $templateLine) {
+ if ($codeLine <= $prevLine) {
+ $line = $templateLine;
+ break;
+ }
+ }
+
+ //$previous['twig']['twig'] = trim($code[$line - 1]);
+ $previous['twig']['file'] = $src->getPath();
+ $previous['twig']['line'] = $line;
+ }
+
+ $cut = $i;
+ } elseif ($object instanceof ProcessorInterface) {
+ $cut = $cut ?: $i;
+ break;
+ }
+
+ $previous = &$backtrace[$i];
+ }
+ unset($current);
+
+ if ($cut) {
+ $backtrace = array_slice($backtrace, 0, $cut + 1);
+ }
+ $backtrace = array_values(array_filter($backtrace));
+
// Skip vendor libraries and the method where error was triggered.
- while ($current = array_shift($backtrace)) {
- if (isset($current['file']) && strpos($current['file'], 'vendor') !== false) {
+ foreach ($backtrace as $i => $current) {
+ if (!isset($current['file'])) {
+ continue;
+ }
+ if (strpos($current['file'], '/vendor/') !== false) {
+ $cut = $i + 1;
continue;
}
if (isset($current['function']) && ($current['function'] === 'user_error' || $current['function'] === 'trigger_error')) {
- $current = array_shift($backtrace);
+ $cut = $i + 1;
+ continue;
}
break;
}
- // Add back last call.
- array_unshift($backtrace, $current);
-
- // Filter arguments.
- foreach ($backtrace as &$current) {
- if (isset($current['args'])) {
- $args = [];
- foreach ($current['args'] as $arg) {
- if (\is_string($arg)) {
- $args[] = "'" . $arg . "'";
- } elseif (\is_bool($arg)) {
- $args[] = $arg ? 'true' : 'false';
- } elseif (\is_scalar($arg)) {
- $args[] = $arg;
- } elseif (\is_object($arg)) {
- $args[] = get_class($arg) . ' $object';
- } elseif (\is_array($arg)) {
- $args[] = '$array';
- } else {
- $args[] = '$object';
- }
- }
- $current['args'] = $args;
- }
+ if ($cut) {
+ $backtrace = array_slice($backtrace, $cut);
}
- unset($current);
+ $backtrace = array_values(array_filter($backtrace));
- $this->deprecations[] = [
+ $current = reset($backtrace);
+
+ // If the issue happened inside twig file, change the file and line to match that file.
+ $file = $current['twig']['file'] ?? '';
+ if ($file) {
+ $errfile = $file;
+ $errline = $current['twig']['line'] ?? 0;
+ }
+
+ $deprecation = [
+ 'scope' => $scope,
'message' => $errstr,
'file' => $errfile,
'line' => $errline,
'trace' => $backtrace,
+ 'count' => 1
];
+ $this->deprecations[] = $deprecation;
+
// Do not pass forward.
return true;
}
@@ -396,38 +560,37 @@ class Debugger
protected function getDepracatedMessage($deprecated)
{
- $scope = 'unknown';
- if (stripos($deprecated['message'], 'grav') !== false) {
- $scope = 'grav';
- } elseif (!isset($deprecated['file'])) {
- $scope = 'unknown';
- } elseif (stripos($deprecated['file'], 'twig') !== false) {
- $scope = 'twig';
- } elseif (stripos($deprecated['file'], 'yaml') !== false) {
- $scope = 'yaml';
- } elseif (stripos($deprecated['file'], 'vendor') !== false) {
- $scope = 'vendor';
- }
+ $scope = $deprecated['scope'];
$trace = [];
- foreach ($deprecated['trace'] as $current) {
- $class = isset($current['class']) ? $current['class'] : '';
- $type = isset($current['type']) ? $current['type'] : '';
- $function = $this->getFunction($current);
- if (isset($current['file'])) {
- $current['file'] = str_replace(GRAV_ROOT . '/', '', $current['file']);
+ if (isset($deprecated['trace'])) {
+ foreach ($deprecated['trace'] as $current) {
+ $class = $current['class'] ?? '';
+ $type = $current['type'] ?? '';
+ $function = $this->getFunction($current);
+ if (isset($current['file'])) {
+ $current['file'] = str_replace(GRAV_ROOT . '/', '', $current['file']);
+ }
+
+ unset($current['class'], $current['type'], $current['function'], $current['args']);
+
+ if (isset($current['twig'])) {
+ $trace[] = $current['twig'];
+ } else {
+ $trace[] = ['call' => $class . $type . $function] + $current;
+ }
}
-
- unset($current['class'], $current['type'], $current['function'], $current['args']);
-
- $trace[] = ['call' => $class . $type . $function] + $current;
}
+ $array = [
+ 'message' => $deprecated['message'],
+ 'file' => $deprecated['file'],
+ 'line' => $deprecated['line'],
+ 'trace' => $trace
+ ];
+
return [
- [
- 'message' => $deprecated['message'],
- 'trace' => $trace
- ],
+ array_filter($array),
$scope
];
}
@@ -438,6 +601,6 @@ class Debugger
return '';
}
- return $trace['function'] . '(' . implode(', ', $trace['args']) . ')';
+ return $trace['function'] . '(' . implode(', ', $trace['args'] ?? []) . ')';
}
}
diff --git a/system/src/Grav/Common/Errors/BareHandler.php b/system/src/Grav/Common/Errors/BareHandler.php
index 25c0e0a..52effc6 100644
--- a/system/src/Grav/Common/Errors/BareHandler.php
+++ b/system/src/Grav/Common/Errors/BareHandler.php
@@ -1,8 +1,9 @@
get('system.errors');
- $jsonRequest = $_SERVER && isset($_SERVER['HTTP_ACCEPT']) && $_SERVER['HTTP_ACCEPT'] == 'application/json';
+ $jsonRequest = $_SERVER && isset($_SERVER['HTTP_ACCEPT']) && $_SERVER['HTTP_ACCEPT'] === 'application/json';
// Setup Whoops-based error handler
$system = new SystemFacade;
- $whoops = new \Whoops\Run($system);
+ $whoops = new Whoops\Run($system);
$verbosity = 1;
@@ -49,17 +50,8 @@ class Errors
break;
}
- if (method_exists('Whoops\Util\Misc', 'isAjaxRequest')) { //Whoops 2.0
- if (Whoops\Util\Misc::isAjaxRequest() || $jsonRequest) {
- $whoops->pushHandler(new Whoops\Handler\JsonResponseHandler);
- }
- } elseif (function_exists('Whoops\isAjaxRequest')) { //Whoops 2.0.0-alpha
- if (Whoops\isAjaxRequest() || $jsonRequest) {
- $whoops->pushHandler(new Whoops\Handler\JsonResponseHandler);
- }
- } else { //Whoops 1.x
- $json_page = new Whoops\Handler\JsonResponseHandler;
- $json_page->onlyForAjaxRequests(true);
+ if (Whoops\Util\Misc::isAjaxRequest() || $jsonRequest) {
+ $whoops->pushHandler(new Whoops\Handler\JsonResponseHandler);
}
if (isset($config['log']) && $config['log']) {
@@ -70,7 +62,7 @@ class Errors
} catch (\Exception $e) {
echo $e;
}
- }, 'log');
+ });
}
$whoops->register();
diff --git a/system/src/Grav/Common/Errors/SimplePageHandler.php b/system/src/Grav/Common/Errors/SimplePageHandler.php
index 829f596..311d722 100644
--- a/system/src/Grav/Common/Errors/SimplePageHandler.php
+++ b/system/src/Grav/Common/Errors/SimplePageHandler.php
@@ -1,8 +1,9 @@
searchPaths[] = __DIR__ . "/Resources";
+ $this->searchPaths[] = __DIR__ . '/Resources';
}
/**
@@ -31,8 +32,8 @@ class SimplePageHandler extends Handler
$inspector = $this->getInspector();
$helper = new TemplateHelper();
- $templateFile = $this->getResource("layout.html.php");
- $cssFile = $this->getResource("error.css");
+ $templateFile = $this->getResource('layout.html.php');
+ $cssFile = $this->getResource('error.css');
$code = $inspector->getException()->getCode();
if ( ($code >= 400) && ($code < 600) )
@@ -46,9 +47,9 @@ class SimplePageHandler extends Handler
}
$vars = array(
- "stylesheet" => file_get_contents($cssFile),
- "code" => $code,
- "message" => filter_var(rawurldecode($message), FILTER_SANITIZE_STRING),
+ 'stylesheet' => file_get_contents($cssFile),
+ 'code' => $code,
+ 'message' => filter_var(rawurldecode($message), FILTER_SANITIZE_STRING),
);
$helper->setVariables($vars);
@@ -58,7 +59,7 @@ class SimplePageHandler extends Handler
}
/**
- * @param $resource
+ * @param string $resource
*
* @return string
* @throws \RuntimeException
@@ -74,7 +75,7 @@ class SimplePageHandler extends Handler
// Search through available search paths, until we find the
// resource we're after:
foreach ($this->searchPaths as $path) {
- $fullPath = $path . "/$resource";
+ $fullPath = "{$path}/{$resource}";
if (is_file($fullPath)) {
// Cache the result:
diff --git a/system/src/Grav/Common/Errors/SystemFacade.php b/system/src/Grav/Common/Errors/SystemFacade.php
index 5b73a2b..02ef0cf 100644
--- a/system/src/Grav/Common/Errors/SystemFacade.php
+++ b/system/src/Grav/Common/Errors/SystemFacade.php
@@ -1,8 +1,9 @@
['.DS_Store'],
+ 'exclude_paths' => []
+ ];
+
+ protected $archive_file;
+
+ public static function create($compression)
+ {
+ if ($compression === 'zip') {
+ return new ZipArchiver();
+ }
+
+ return new ZipArchiver();
+ }
+
+ public function setArchive($archive_file)
+ {
+ $this->archive_file = $archive_file;
+ return $this;
+ }
+
+ public function setOptions($options)
+ {
+ // Set infinite PHP execution time if possible.
+ if (function_exists('set_time_limit') && !Utils::isFunctionDisabled('set_time_limit')) {
+ set_time_limit(0);
+ }
+
+ $this->options = $options + $this->options;
+ return $this;
+ }
+
+ public abstract function compress($folder, callable $status = null);
+
+ public abstract function extract($destination, callable $status = null);
+
+ public abstract function addEmptyFolders($folders, callable $status = null);
+
+ protected function getArchiveFiles($rootPath)
+ {
+ $exclude_paths = $this->options['exclude_paths'];
+ $exclude_files = $this->options['exclude_files'];
+ $dirItr = new \RecursiveDirectoryIterator($rootPath, \RecursiveDirectoryIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::UNIX_PATHS);
+ $filterItr = new RecursiveDirectoryFilterIterator($dirItr, $rootPath, $exclude_paths, $exclude_files);
+ $files = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
+
+ return $files;
+ }
+
+}
diff --git a/system/src/Grav/Common/Filesystem/Folder.php b/system/src/Grav/Common/Filesystem/Folder.php
index 0d69100..f17410c 100644
--- a/system/src/Grav/Common/Filesystem/Folder.php
+++ b/system/src/Grav/Common/Filesystem/Folder.php
@@ -1,8 +1,9 @@
getFilename()[0] === '.') {
+ if (strpos($file->getFilename(), '.') === 0) {
continue;
}
if (!$folders && $file->isDir()) {
@@ -255,7 +257,7 @@ abstract class Folder
if (isset($filters['value'])) {
$filter = $filters['value'];
if (is_callable($filter)) {
- $filePath = call_user_func($filter, $file);
+ $filePath = $filter($file);
} else {
$filePath = preg_replace($filter, '', $filePath);
}
@@ -316,7 +318,7 @@ abstract class Folder
if (!$success) {
$error = error_get_last();
- throw new \RuntimeException($error['message']);
+ throw new \RuntimeException($error['message'] ?? 'Unknown error');
}
// Make sure that the change will be detected when caching.
@@ -416,23 +418,27 @@ abstract class Folder
*/
public static function create($folder)
{
- if (is_dir($folder)) {
+ // Silence error for open_basedir; should fail in mkdir instead.
+ if (@is_dir($folder)) {
return;
}
$success = @mkdir($folder, 0777, true);
if (!$success) {
- $error = error_get_last();
- throw new \RuntimeException($error['message']);
+ // Take yet another look, make sure that the folder doesn't exist.
+ clearstatcache(true, $folder);
+ if (!@is_dir($folder)) {
+ throw new \RuntimeException(sprintf('Unable to create directory: %s', $folder));
+ }
}
}
/**
* Recursive copy of one directory to another
*
- * @param $src
- * @param $dest
+ * @param string $src
+ * @param string $dest
*
* @return bool
* @throws \RuntimeException
@@ -448,7 +454,7 @@ abstract class Folder
// If the destination directory does not exist create it
if (!is_dir($dest)) {
- static::mkdir($dest);
+ static::create($dest);
}
// Open the source directory to read in files
@@ -475,7 +481,7 @@ abstract class Folder
protected static function doDelete($folder, $include_target = true)
{
// Special case for symbolic links.
- if (is_link($folder)) {
+ if ($include_target && is_link($folder)) {
return @unlink($folder);
}
diff --git a/system/src/Grav/Common/Filesystem/RecursiveDirectoryFilterIterator.php b/system/src/Grav/Common/Filesystem/RecursiveDirectoryFilterIterator.php
new file mode 100644
index 0000000..c662fc4
--- /dev/null
+++ b/system/src/Grav/Common/Filesystem/RecursiveDirectoryFilterIterator.php
@@ -0,0 +1,67 @@
+current();
+ $filename = $file->getFilename();
+ $relative_filename = str_replace($this::$root . '/', '', $file->getPathname());
+
+ if ($file->isDir()) {
+ if (in_array($relative_filename, $this::$ignore_folders, true)) {
+ return false;
+ }
+ if (!in_array($filename, $this::$ignore_files, true)) {
+ return true;
+ }
+ } elseif ($file->isFile() && !in_array($filename, $this::$ignore_files, true)) {
+ return true;
+ }
+ return false;
+ }
+
+ public function getChildren()
+ {
+ /** @var RecursiveDirectoryFilterIterator $iterator */
+ $iterator = $this->getInnerIterator();
+
+ return new self($iterator->getChildren(), $this::$root, $this::$ignore_folders, $this::$ignore_files);
+ }
+}
diff --git a/system/src/Grav/Common/Filesystem/RecursiveFolderFilterIterator.php b/system/src/Grav/Common/Filesystem/RecursiveFolderFilterIterator.php
index 3972074..eb493db 100644
--- a/system/src/Grav/Common/Filesystem/RecursiveFolderFilterIterator.php
+++ b/system/src/Grav/Common/Filesystem/RecursiveFolderFilterIterator.php
@@ -1,8 +1,9 @@
get('system.pages.ignore_folders');
+
+ if (empty($ignore_folders)) {
+ $ignore_folders = Grav::instance()['config']->get('system.pages.ignore_folders');
}
+
+ $this::$ignore_folders = $ignore_folders;
}
/**
@@ -34,12 +39,9 @@ class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
*/
public function accept()
{
- /** @var $current \SplFileInfo */
+ /** @var \SplFileInfo $current */
$current = $this->current();
- if ($current->isDir() && !in_array($current->getFilename(), $this::$folder_ignores, true)) {
- return true;
- }
- return false;
+ return $current->isDir() && !in_array($current->getFilename(), $this::$ignore_folders, true);
}
}
diff --git a/system/src/Grav/Common/Filesystem/ZipArchiver.php b/system/src/Grav/Common/Filesystem/ZipArchiver.php
new file mode 100644
index 0000000..91212f7
--- /dev/null
+++ b/system/src/Grav/Common/Filesystem/ZipArchiver.php
@@ -0,0 +1,111 @@
+open($this->archive_file);
+
+ if ($archive === true) {
+ Folder::create($destination);
+
+ if (!$zip->extractTo($destination)) {
+ throw new \RuntimeException('ZipArchiver: ZIP failed to extract ' . $this->archive_file . ' to ' . $destination);
+ }
+
+ $zip->close();
+ return $this;
+ }
+
+ throw new \RuntimeException('ZipArchiver: Failed to open ' . $this->archive_file);
+ }
+
+ public function compress($source, callable $status = null)
+ {
+ if (!extension_loaded('zip')) {
+ throw new \InvalidArgumentException('ZipArchiver: Zip PHP module not installed...');
+ }
+
+ if (!file_exists($source)) {
+ throw new \InvalidArgumentException('ZipArchiver: ' . $source . ' cannot be found...');
+ }
+
+ $zip = new \ZipArchive();
+ if (!$zip->open($this->archive_file, \ZipArchive::CREATE)) {
+ throw new \InvalidArgumentException('ZipArchiver:' . $this->archive_file . ' cannot be created...');
+ }
+
+ // Get real path for our folder
+ $rootPath = realpath($source);
+
+ $files = $this->getArchiveFiles($rootPath);
+
+ $status && $status([
+ 'type' => 'count',
+ 'steps' => iterator_count($files),
+ ]);
+
+ foreach ($files as $file) {
+ $filePath = $file->getPathname();
+ $relativePath = ltrim(substr($filePath, strlen($rootPath)), '/');
+
+ if ($file->isDir()) {
+ $zip->addEmptyDir($relativePath);
+ } else {
+ $zip->addFile($filePath, $relativePath);
+ }
+
+ $status && $status([
+ 'type' => 'progress',
+ ]);
+ }
+
+ $status && $status([
+ 'type' => 'message',
+ 'message' => 'Compressing...'
+ ]);
+
+ $zip->close();
+
+ return $this;
+ }
+
+ public function addEmptyFolders($folders, callable $status = null)
+ {
+ if (!extension_loaded('zip')) {
+ throw new \InvalidArgumentException('ZipArchiver: Zip PHP module not installed...');
+ }
+
+ $zip = new \ZipArchive();
+ if (!$zip->open($this->archive_file)) {
+ throw new \InvalidArgumentException('ZipArchiver: ' . $this->archive_file . ' cannot be opened...');
+ }
+
+ $status && $status([
+ 'type' => 'message',
+ 'message' => 'Adding empty folders...'
+ ]);
+
+ foreach($folders as $folder) {
+ $zip->addEmptyDir($folder);
+ $status && $status([
+ 'type' => 'progress',
+ ]);
+ }
+
+ $zip->close();
+
+ return $this;
+ }
+}
diff --git a/system/src/Grav/Common/Form/FormFlash.php b/system/src/Grav/Common/Form/FormFlash.php
new file mode 100644
index 0000000..abd4565
--- /dev/null
+++ b/system/src/Grav/Common/Form/FormFlash.php
@@ -0,0 +1,167 @@
+findResource("tmp://forms/{$sessionId}", true, true);
+ }
+
+ /**
+ * @param UserInterface|null $user
+ * @return $this
+ */
+ public function setUser(UserInterface $user = null)
+ {
+ if ($user && $user->username) {
+ $this->user = [
+ 'username' => $user->username,
+ 'email' => $user->email ?? ''
+ ];
+ } else {
+ $this->user = null;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return array
+ * @deprecated 1.6 For backwards compatibility only, do not use
+ */
+ public function getLegacyFiles(): array
+ {
+ $fields = [];
+ foreach ($this->files as $field => $files) {
+ if (strpos($field, '/')) {
+ continue;
+ }
+ foreach ($files as $file) {
+ if (\is_array($file)) {
+ $file['tmp_name'] = $this->getTmpDir() . '/' . $file['tmp_name'];
+ $fields[$field][$file['path'] ?? $file['name']] = $file;
+ }
+ }
+ }
+
+ return $fields;
+ }
+
+ /**
+ * @param string $field
+ * @param string $filename
+ * @param array $upload
+ * @return bool
+ * @deprecated 1.6 For backwards compatibility only, do not use
+ */
+ public function uploadFile(string $field, string $filename, array $upload): bool
+ {
+ $tmp_dir = $this->getTmpDir();
+
+ Folder::create($tmp_dir);
+
+ $tmp_file = $upload['file']['tmp_name'];
+ $basename = basename($tmp_file);
+
+ if (!move_uploaded_file($tmp_file, $tmp_dir . '/' . $basename)) {
+ return false;
+ }
+
+ $upload['file']['tmp_name'] = $basename;
+ $upload['file']['name'] = $filename;
+
+ $this->addFileInternal($field, $filename, $upload['file']);
+
+ return true;
+ }
+
+ /**
+ * @param string $field
+ * @param string $filename
+ * @param array $upload
+ * @param array $crop
+ * @return bool
+ * @deprecated 1.6 For backwards compatibility only, do not use
+ */
+ public function cropFile(string $field, string $filename, array $upload, array $crop): bool
+ {
+ $tmp_dir = $this->getTmpDir();
+
+ Folder::create($tmp_dir);
+
+ $tmp_file = $upload['file']['tmp_name'];
+ $basename = basename($tmp_file);
+
+ if (!move_uploaded_file($tmp_file, $tmp_dir . '/' . $basename)) {
+ return false;
+ }
+
+ $upload['file']['tmp_name'] = $basename;
+ $upload['file']['name'] = $filename;
+
+ $this->addFileInternal($field, $filename, $upload['file'], $crop);
+
+ return true;
+ }
+
+ /**
+ * @return YamlFile
+ */
+ protected function getTmpIndex(): YamlFile
+ {
+ // Do not use CompiledYamlFile as the file can change multiple times per second.
+ return YamlFile::instance($this->getTmpDir() . '/index.yaml');
+ }
+
+ /**
+ * @param string $name
+ */
+ protected function removeTmpFile(string $name): void
+ {
+ $filename = $this->getTmpDir() . '/' . $name;
+ if ($name && is_file($filename)) {
+ unlink($filename);
+ }
+ }
+
+ protected function removeTmpDir(): void
+ {
+ $tmpDir = $this->getTmpDir();
+ if (file_exists($tmpDir)) {
+ Folder::delete($tmpDir);
+ }
+ }
+}
diff --git a/system/src/Grav/Common/GPM/AbstractCollection.php b/system/src/Grav/Common/GPM/AbstractCollection.php
index 3430a6a..a9e1ac9 100644
--- a/system/src/Grav/Common/GPM/AbstractCollection.php
+++ b/system/src/Grav/Common/GPM/AbstractCollection.php
@@ -1,8 +1,9 @@
items as $name => $package) {
- $items[$name] = $package->toArray();
- }
-
- return json_encode($items);
+ return json_encode($this->toArray());
}
public function toArray()
diff --git a/system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php b/system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php
index 78d93bc..bc4ecab 100644
--- a/system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php
+++ b/system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php
@@ -1,8 +1,9 @@
$item) {
+ foreach (self::$cache[get_called_class() . __METHOD__] as $name => $item) {
$this->append([$name => $item]);
}
}
diff --git a/system/src/Grav/Common/GPM/Common/Package.php b/system/src/Grav/Common/GPM/Common/Package.php
index fe4618a..d205f1e 100644
--- a/system/src/Grav/Common/GPM/Common/Package.php
+++ b/system/src/Grav/Common/GPM/Common/Package.php
@@ -1,8 +1,9 @@
data = $package;
if ($type) {
@@ -22,28 +27,44 @@ class Package {
}
}
- public function getData() {
+ /**
+ * @return Data
+ */
+ public function getData()
+ {
return $this->data;
}
- public function __get($key) {
+ public function __get($key)
+ {
return $this->data->get($key);
}
- public function __isset($key) {
- return isset($this->data->$key);
+ public function __set($key, $value)
+ {
+ return $this->data->set($key, $value);
}
- public function __toString() {
+ public function __isset($key)
+ {
+ return isset($this->data->{$key});
+ }
+
+ public function __toString()
+ {
return $this->toJson();
}
- public function toJson() {
+ public function toJson()
+ {
return $this->data->toJson();
}
- public function toArray() {
+ /**
+ * @return array
+ */
+ public function toArray()
+ {
return $this->data->toArray();
}
-
}
diff --git a/system/src/Grav/Common/GPM/GPM.php b/system/src/Grav/Common/GPM/GPM.php
index 696ba65..94eec7d 100644
--- a/system/src/Grav/Common/GPM/GPM.php
+++ b/system/src/Grav/Common/GPM/GPM.php
@@ -1,8 +1,9 @@
cache = [];
$this->installed = new Local\Packages();
try {
$this->repository = new Remote\Packages($refresh, $callback);
@@ -94,7 +97,7 @@ class GPM extends Iterator
/**
* Returns the amount of locally installed packages
- * @return integer Amount of installed packages
+ * @return int Amount of installed packages
*/
public function countInstalled()
{
@@ -144,7 +147,7 @@ class GPM extends Iterator
/**
* Checks if a Plugin is installed
* @param string $slug The slug of the Plugin
- * @return boolean True if the Plugin has been installed. False otherwise
+ * @return bool True if the Plugin has been installed. False otherwise
*/
public function isPluginInstalled($slug)
{
@@ -178,7 +181,7 @@ class GPM extends Iterator
/**
* Checks if a Theme is installed
* @param string $slug The slug of the Theme
- * @return boolean True if the Theme has been installed. False otherwise
+ * @return bool True if the Theme has been installed. False otherwise
*/
public function isThemeInstalled($slug)
{
@@ -187,7 +190,7 @@ class GPM extends Iterator
/**
* Returns the amount of updates available
- * @return integer Amount of available updates
+ * @return int Amount of available updates
*/
public function countUpdates()
{
@@ -242,13 +245,12 @@ class GPM extends Iterator
continue;
}
- $local_version = $plugin->version ? $plugin->version : 'Unknown';
+ $local_version = $plugin->version ?: 'Unknown';
$remote_version = $repository[$slug]->version;
if (version_compare($local_version, $remote_version) < 0) {
$repository[$slug]->available = $remote_version;
$repository[$slug]->version = $local_version;
- $repository[$slug]->name = $repository[$slug]->name;
$repository[$slug]->type = $repository[$slug]->release_type;
$items[$slug] = $repository[$slug];
}
@@ -262,7 +264,7 @@ class GPM extends Iterator
/**
* Get the latest release of a package from the GPM
*
- * @param $package_name
+ * @param string $package_name
*
* @return string|null
*/
@@ -285,7 +287,7 @@ class GPM extends Iterator
/**
* Check if a Plugin or Theme is updatable
* @param string $slug The slug of the package
- * @return boolean True if updatable. False otherwise or if not found
+ * @return bool True if updatable. False otherwise or if not found
*/
public function isUpdatable($slug)
{
@@ -295,7 +297,7 @@ class GPM extends Iterator
/**
* Checks if a Plugin is updatable
* @param string $plugin The slug of the Plugin
- * @return boolean True if the Plugin is updatable. False otherwise
+ * @return bool True if the Plugin is updatable. False otherwise
*/
public function isPluginUpdatable($plugin)
{
@@ -322,7 +324,7 @@ class GPM extends Iterator
continue;
}
- $local_version = $plugin->version ? $plugin->version : 'Unknown';
+ $local_version = $plugin->version ?: 'Unknown';
$remote_version = $repository[$slug]->version;
if (version_compare($local_version, $remote_version) < 0) {
@@ -341,7 +343,7 @@ class GPM extends Iterator
/**
* Checks if a Theme is Updatable
* @param string $theme The slug of the Theme
- * @return boolean True if the Theme is updatable. False otherwise
+ * @return bool True if the Theme is updatable. False otherwise
*/
public function isThemeUpdatable($theme)
{
@@ -351,7 +353,7 @@ class GPM extends Iterator
/**
* Get the release type of a package (stable / testing)
*
- * @param $package_name
+ * @param string $package_name
*
* @return string|null
*/
@@ -374,9 +376,9 @@ class GPM extends Iterator
/**
* Returns true if the package latest release is stable
*
- * @param $package_name
+ * @param string $package_name
*
- * @return boolean
+ * @return bool
*/
public function isStableRelease($package_name)
{
@@ -386,9 +388,9 @@ class GPM extends Iterator
/**
* Returns true if the package latest release is testing
*
- * @param $package_name
+ * @param string $package_name
*
- * @return boolean
+ * @return bool
*/
public function isTestingRelease($package_name)
{
@@ -503,8 +505,8 @@ class GPM extends Iterator
/**
* Download the zip package via the URL
*
- * @param $package_file
- * @param $tmp
+ * @param string $package_file
+ * @param string $tmp
* @return null|string
*/
public static function downloadPackage($package_file, $tmp)
@@ -519,7 +521,7 @@ class GPM extends Iterator
$output = Response::get($package_file, []);
if ($output) {
- Folder::mkdir($tmp);
+ Folder::create($tmp);
file_put_contents($tmp . DS . $filename, $output);
return $tmp . DS . $filename;
}
@@ -530,8 +532,8 @@ class GPM extends Iterator
/**
* Copy the local zip package to tmp
*
- * @param $package_file
- * @param $tmp
+ * @param string $package_file
+ * @param string $tmp
* @return null|string
*/
public static function copyPackage($package_file, $tmp)
@@ -540,7 +542,7 @@ class GPM extends Iterator
if (file_exists($package_file)) {
$filename = basename($package_file);
- Folder::mkdir($tmp);
+ Folder::create($tmp);
copy(realpath($package_file), $tmp . DS . $filename);
return $tmp . DS . $filename;
}
@@ -551,7 +553,7 @@ class GPM extends Iterator
/**
* Try to guess the package type from the source files
*
- * @param $source
+ * @param string $source
* @return bool|string
*/
public static function getPackageType($source)
@@ -564,44 +566,46 @@ class GPM extends Iterator
file_exists($source . 'system/config/system.yaml')
) {
return 'grav';
- } else {
- // must have a blueprint
- if (!file_exists($source . 'blueprints.yaml')) {
- return false;
- }
+ }
- // either theme or plugin
- $name = basename($source);
- if (Utils::contains($name, 'theme')) {
- return 'theme';
- } elseif (Utils::contains($name, 'plugin')) {
- return 'plugin';
- }
- foreach (glob($source . "*.php") as $filename) {
- $contents = file_get_contents($filename);
- if (preg_match($theme_regex, $contents)) {
- return 'theme';
- } elseif (preg_match($plugin_regex, $contents)) {
- return 'plugin';
- }
- }
+ // must have a blueprint
+ if (!file_exists($source . 'blueprints.yaml')) {
+ return false;
+ }
- // Assume it's a theme
+ // either theme or plugin
+ $name = basename($source);
+ if (Utils::contains($name, 'theme')) {
return 'theme';
}
+ if (Utils::contains($name, 'plugin')) {
+ return 'plugin';
+ }
+ foreach (glob($source . '*.php') as $filename) {
+ $contents = file_get_contents($filename);
+ if (preg_match($theme_regex, $contents)) {
+ return 'theme';
+ }
+ if (preg_match($plugin_regex, $contents)) {
+ return 'plugin';
+ }
+ }
+
+ // Assume it's a theme
+ return 'theme';
}
/**
* Try to guess the package name from the source files
*
- * @param $source
+ * @param string $source
* @return bool|string
*/
public static function getPackageName($source)
{
$ignore_yaml_files = ['blueprints', 'languages'];
- foreach (glob($source . "*.yaml") as $filename) {
+ foreach (glob($source . '*.yaml') as $filename) {
$name = strtolower(basename($filename, '.yaml'));
if (in_array($name, $ignore_yaml_files)) {
continue;
@@ -614,7 +618,7 @@ class GPM extends Iterator
/**
* Find/Parse the blueprint file
*
- * @param $source
+ * @param string $source
* @return array|bool
*/
public static function getBlueprints($source)
@@ -634,15 +638,15 @@ class GPM extends Iterator
/**
* Get the install path for a name and a particular type of package
*
- * @param $type
- * @param $name
+ * @param string $type
+ * @param string $name
* @return string
*/
public static function getInstallPath($type, $name)
{
$locator = Grav::instance()['locator'];
- if ($type == 'theme') {
+ if ($type === 'theme') {
$install_path = $locator->findResource('themes://', false) . DS . $name;
} else {
$install_path = $locator->findResource('plugins://', false) . DS . $name;
@@ -692,7 +696,7 @@ class GPM extends Iterator
}
$not_found = new \stdClass();
- $not_found->name = $inflector->camelize($search);
+ $not_found->name = $inflector::camelize($search);
$not_found->slug = $search;
$not_found->package_type = $type;
$not_found->install_path = str_replace('%name%', $search, $this->install_paths[$type]);
@@ -726,7 +730,7 @@ class GPM extends Iterator
$dependency = $dependency['name'];
}
- if ($dependency == $slug) {
+ if ($dependency === $slug) {
$dependent_packages[] = $package_name;
}
}
@@ -740,8 +744,8 @@ class GPM extends Iterator
/**
* Get the required version of a dependency of a package
*
- * @param $package_slug
- * @param $dependency_slug
+ * @param string $package_slug
+ * @param string $dependency_slug
*
* @return mixed
*/
@@ -804,7 +808,7 @@ class GPM extends Iterator
/**
* Check the passed packages list can be updated
*
- * @param $packages_names_list
+ * @param array $packages_names_list
*
* @throws \Exception
*/
@@ -833,19 +837,19 @@ class GPM extends Iterator
{
$dependencies = $this->calculateMergedDependenciesOfPackages($packages);
foreach ($dependencies as $dependency_slug => $dependencyVersionWithOperator) {
- if (in_array($dependency_slug, $packages)) {
+ if (\in_array($dependency_slug, $packages, true)) {
unset($dependencies[$dependency_slug]);
continue;
}
// Check PHP version
- if ($dependency_slug == 'php') {
+ if ($dependency_slug === 'php') {
$current_php_version = phpversion();
if (version_compare($this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator),
$current_php_version) === 1
) {
//Needs a Grav update first
- throw new \Exception("One of the packages require PHP " . $dependencies['php'] . ". Please update PHP to resolve this");
+ throw new \RuntimeException("One of the packages require PHP {$dependencies['php']}. Please update PHP to resolve this");
} else {
unset($dependencies[$dependency_slug]);
continue;
@@ -853,12 +857,12 @@ class GPM extends Iterator
}
//First, check for Grav dependency. If a dependency requires Grav > the current version, abort and tell.
- if ($dependency_slug == 'grav') {
+ if ($dependency_slug === 'grav') {
if (version_compare($this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator),
GRAV_VERSION) === 1
) {
//Needs a Grav update first
- throw new \Exception("One of the packages require Grav " . $dependencies['grav'] . ". Please update Grav to the latest release.");
+ throw new \RuntimeException("One of the packages require Grav {$dependencies['grav']}. Please update Grav to the latest release.");
} else {
unset($dependencies[$dependency_slug]);
continue;
@@ -888,7 +892,7 @@ class GPM extends Iterator
$currentlyInstalledVersion);
if (!$compatible) {
- throw new \Exception('Dependency ' . $dependency_slug . ' is required in an older version than the one installed. This package must be updated. Please get in touch with its developer.',
+ throw new \RuntimeException('Dependency ' . $dependency_slug . ' is required in an older version than the one installed. This package must be updated. Please get in touch with its developer.',
2);
}
}
@@ -899,7 +903,7 @@ class GPM extends Iterator
if ($this->firstVersionIsLower($latestRelease, $dependencyVersion)) {
//throw an exception if a required version cannot be found in the GPM yet
- throw new \Exception('Dependency ' . $package_yaml['name'] . ' is required in version ' . $dependencyVersion . ' which is higher than the latest release, ' . $latestRelease . '. Try running `bin/gpm -f index` to force a refresh of the GPM cache',
+ throw new \RuntimeException('Dependency ' . $package_yaml['name'] . ' is required in version ' . $dependencyVersion . ' which is higher than the latest release, ' . $latestRelease . '. Try running `bin/gpm -f index` to force a refresh of the GPM cache',
1);
}
@@ -950,7 +954,7 @@ class GPM extends Iterator
private function firstVersionIsLower($firstVersion, $secondVersion)
{
- return version_compare($firstVersion, $secondVersion) == -1;
+ return version_compare($firstVersion, $secondVersion) === -1;
}
/**
@@ -1005,7 +1009,7 @@ class GPM extends Iterator
$current_package_version_number = $this->calculateVersionNumberFromDependencyVersion($current_package_version_information);
if (!$current_package_version_number) {
- throw new \Exception('Bad format for version of dependency ' . $current_package_name . ' for package ' . $packageName,
+ throw new \RuntimeException('Bad format for version of dependency ' . $current_package_name . ' for package ' . $packageName,
1);
}
@@ -1021,7 +1025,7 @@ class GPM extends Iterator
if (!$currently_stored_version_is_in_next_significant_release_format && !$current_package_version_is_in_next_significant_release_format) {
//Comparing versions equals or higher, a simple version_compare is enough
if (version_compare($currently_stored_version_number,
- $current_package_version_number) == -1
+ $current_package_version_number) === -1
) { //Current package version is higher
$dependencies[$current_package_name] = $current_package_version_information;
}
@@ -1029,7 +1033,7 @@ class GPM extends Iterator
$compatible = $this->checkNextSignificantReleasesAreCompatible($currently_stored_version_number,
$current_package_version_number);
if (!$compatible) {
- throw new \Exception('Dependency ' . $current_package_name . ' is required in two incompatible versions',
+ throw new \RuntimeException('Dependency ' . $current_package_name . ' is required in two incompatible versions',
2);
}
}
@@ -1076,17 +1080,19 @@ class GPM extends Iterator
*/
public function calculateVersionNumberFromDependencyVersion($version)
{
- if ($version == '*') {
+ if ($version === '*') {
return null;
- } elseif ($version == '') {
- return null;
- } elseif ($this->versionFormatIsNextSignificantRelease($version)) {
- return trim(substr($version, 1));
- } elseif ($this->versionFormatIsEqualOrHigher($version)) {
- return trim(substr($version, 2));
- } else {
- return $version;
}
+ if ($version === '') {
+ return null;
+ }
+ if ($this->versionFormatIsNextSignificantRelease($version)) {
+ return trim(substr($version, 1));
+ }
+ if ($this->versionFormatIsEqualOrHigher($version)) {
+ return trim(substr($version, 2));
+ }
+ return $version;
}
/**
@@ -1094,13 +1100,13 @@ class GPM extends Iterator
*
* Example: returns true for $version: '~2.0'
*
- * @param $version
+ * @param string $version
*
* @return bool
*/
public function versionFormatIsNextSignificantRelease($version)
{
- return substr($version, 0, 1) == '~';
+ return strpos($version, '~') === 0;
}
/**
@@ -1108,13 +1114,13 @@ class GPM extends Iterator
*
* Example: returns true for $version: '>=2.0'
*
- * @param $version
+ * @param string $version
*
* @return bool
*/
public function versionFormatIsEqualOrHigher($version)
{
- return substr($version, 0, 2) == '>=';
+ return strpos($version, '>=') === 0;
}
/**
@@ -1135,12 +1141,12 @@ class GPM extends Iterator
$version1array = explode('.', $version1);
$version2array = explode('.', $version2);
- if (count($version1array) > count($version2array)) {
+ if (\count($version1array) > \count($version2array)) {
list($version1array, $version2array) = [$version2array, $version1array];
}
$i = 0;
- while ($i < count($version1array) - 1) {
+ while ($i < \count($version1array) - 1) {
if ($version1array[$i] != $version2array[$i]) {
return false;
}
diff --git a/system/src/Grav/Common/GPM/Installer.php b/system/src/Grav/Common/GPM/Installer.php
index f16cdcc..cd51163 100644
--- a/system/src/Grav/Common/GPM/Installer.php
+++ b/system/src/Grav/Common/GPM/Installer.php
@@ -1,8 +1,9 @@
'user/themes/antimatter']
* @param string $extracted The local path to the extacted ZIP package
+ * @param bool $keepExtracted True if you want to keep the original files
* @return bool True if everything went fine, False otherwise.
*/
- public static function install($zip, $destination, $options = [], $extracted = null)
+ public static function install($zip, $destination, $options = [], $extracted = null, $keepExtracted = false)
{
$destination = rtrim($destination, DS);
$options = array_merge(self::$options, $options);
@@ -88,15 +90,15 @@ class Installer
return false;
}
- if (self::lastErrorCode() == self::IS_LINK && $options['ignore_symlinks'] ||
- self::lastErrorCode() == self::EXISTS && !$options['overwrite']
+ if ((self::lastErrorCode() === self::IS_LINK && $options['ignore_symlinks']) ||
+ (self::lastErrorCode() === self::EXISTS && !$options['overwrite'])
) {
return false;
}
// Create a tmp location
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
- $tmp = $tmp_dir . '/Grav-' . uniqid();
+ $tmp = $tmp_dir . '/Grav-' . uniqid('', false);
if (!$extracted) {
$extracted = self::unZip($zip, $tmp);
@@ -111,7 +113,6 @@ class Installer
return false;
}
-
$is_install = true;
$installer = self::loadInstaller($extracted, $is_install);
@@ -140,7 +141,7 @@ class Installer
self::moveInstall($extracted, $install_path);
}
} else {
- self::sophisticatedInstall($extracted, $install_path, $options['ignores']);
+ self::sophisticatedInstall($extracted, $install_path, $options['ignores'], $keepExtracted);
}
Folder::delete($tmp);
@@ -165,8 +166,8 @@ class Installer
/**
* Unzip a file to somewhere
*
- * @param $zip_file
- * @param $destination
+ * @param string $zip_file
+ * @param string $destination
* @return bool|string
*/
public static function unZip($zip_file, $destination)
@@ -175,7 +176,7 @@ class Installer
$archive = $zip->open($zip_file);
if ($archive === true) {
- Folder::mkdir($destination);
+ Folder::create($destination);
$unzip = $zip->extractTo($destination);
@@ -189,9 +190,8 @@ class Installer
$package_folder_name = preg_replace('#\./$#', '', $zip->getNameIndex(0));
$zip->close();
- $extracted_folder = $destination . '/' . $package_folder_name;
- return $extracted_folder;
+ return $destination . '/' . $package_folder_name;
}
self::$error = self::ZIP_EXTRACT_ERROR;
@@ -216,7 +216,7 @@ class Installer
$install_file = $installer_file_folder . DS . 'install.php';
if (file_exists($install_file)) {
- require_once($install_file);
+ require_once $install_file;
} else {
return null;
}
@@ -253,8 +253,8 @@ class Installer
}
/**
- * @param $source_path
- * @param $install_path
+ * @param string $source_path
+ * @param string $install_path
*
* @return bool
*/
@@ -270,8 +270,8 @@ class Installer
}
/**
- * @param $source_path
- * @param $install_path
+ * @param string $source_path
+ * @param string $install_path
*
* @return bool
*/
@@ -279,24 +279,26 @@ class Installer
{
if (empty($source_path)) {
throw new \RuntimeException("Directory $source_path is missing");
- } else {
- Folder::rcopy($source_path, $install_path);
}
+ Folder::rcopy($source_path, $install_path);
+
return true;
}
/**
- * @param $source_path
- * @param $install_path
+ * @param string $source_path
+ * @param string $install_path
+ * @param array $ignores
+ * @param bool $keep_source
*
* @return bool
*/
- public static function sophisticatedInstall($source_path, $install_path, $ignores = [])
+ public static function sophisticatedInstall($source_path, $install_path, $ignores = [], $keep_source = false)
{
foreach (new \DirectoryIterator($source_path) as $file) {
- if ($file->isLink() || $file->isDot() || in_array($file->getFilename(), $ignores)) {
+ if ($file->isLink() || $file->isDot() || \in_array($file->getFilename(), $ignores, true)) {
continue;
}
@@ -304,7 +306,11 @@ class Installer
if ($file->isDir()) {
Folder::delete($path);
- Folder::move($file->getPathname(), $path);
+ if ($keep_source) {
+ Folder::copy($file->getPathname(), $path);
+ } else {
+ Folder::move($file->getPathname(), $path);
+ }
if ($file->getFilename() === 'bin') {
foreach (glob($path . DS . '*') as $bin_file) {
@@ -326,7 +332,7 @@ class Installer
* @param string $path The slug of the package(s)
* @param array $options Options to use for uninstalling
*
- * @return boolean True if everything went fine, False otherwise.
+ * @return bool True if everything went fine, False otherwise.
*/
public static function uninstall($path, $options = [])
{
@@ -368,7 +374,7 @@ class Installer
* @param string $destination The directory to run validations at
* @param array $exclude An array of constants to exclude from the validation
*
- * @return boolean True if validation passed. False otherwise
+ * @return bool True if validation passed. False otherwise
*/
public static function isValidDestination($destination, $exclude = [])
{
@@ -385,11 +391,11 @@ class Installer
self::$error = self::NOT_DIRECTORY;
}
- if (count($exclude) && in_array(self::$error, $exclude)) {
+ if (\count($exclude) && \in_array(self::$error, $exclude, true)) {
return true;
}
- return !(self::$error);
+ return !self::$error;
}
/**
@@ -397,7 +403,7 @@ class Installer
*
* @param string $target The local path to the Grav Instance
*
- * @return boolean True if is a Grav Instance. False otherwise
+ * @return bool True if is a Grav Instance. False otherwise
*/
public static function isGravInstance($target)
{
@@ -469,23 +475,23 @@ class Installer
if (self::$error_zip) {
switch(self::$error_zip) {
case \ZipArchive::ER_EXISTS:
- $msg .= "File already exists.";
+ $msg .= 'File already exists.';
break;
case \ZipArchive::ER_INCONS:
- $msg .= "Zip archive inconsistent.";
+ $msg .= 'Zip archive inconsistent.';
break;
case \ZipArchive::ER_MEMORY:
- $msg .= "Malloc failure.";
+ $msg .= 'Memory allocation failure.';
break;
case \ZipArchive::ER_NOENT:
- $msg .= "No such file.";
+ $msg .= 'No such file.';
break;
case \ZipArchive::ER_NOZIP:
- $msg .= "Not a zip archive.";
+ $msg .= 'Not a zip archive.';
break;
case \ZipArchive::ER_OPEN:
@@ -493,16 +499,20 @@ class Installer
break;
case \ZipArchive::ER_READ:
- $msg .= "Read error.";
+ $msg .= 'Read error.';
break;
case \ZipArchive::ER_SEEK:
- $msg .= "Seek error.";
+ $msg .= 'Seek error.';
break;
}
}
break;
+ case self::INVALID_SOURCE:
+ $msg = 'Invalid source file';
+ break;
+
default:
$msg = 'Unknown Error';
break;
@@ -513,7 +523,7 @@ class Installer
/**
* Returns the last error code of the occurred error
- * @return integer The code of the last error
+ * @return int|string The code of the last error
*/
public static function lastErrorCode()
{
diff --git a/system/src/Grav/Common/GPM/Licenses.php b/system/src/Grav/Common/GPM/Licenses.php
index 6ca596a..fd0f21c 100644
--- a/system/src/Grav/Common/GPM/Licenses.php
+++ b/system/src/Grav/Common/GPM/Licenses.php
@@ -1,8 +1,9 @@
content();
+ $data = (array)$licenses->content();
$slug = strtolower($slug);
if ($license && !self::validate($license)) {
return false;
}
- if (!is_string($license)) {
+ if (!\is_string($license)) {
if (isset($data['licenses'][$slug])) {
unset($data['licenses'][$slug]);
} else {
@@ -66,33 +67,29 @@ class Licenses
/**
* Returns the license for a Premium package
*
- * @param $slug
+ * @param string $slug
*
- * @return string
+ * @return array|string
*/
public static function get($slug = null)
{
$licenses = self::getLicenseFile();
- $data = $licenses->content();
+ $data = (array)$licenses->content();
$licenses->free();
$slug = strtolower($slug);
if (!$slug) {
- return isset($data['licenses']) ? $data['licenses'] : [];
+ return $data['licenses'] ?? [];
}
- if (!isset($data['licenses']) || !isset($data['licenses'][$slug])) {
- return '';
- }
-
- return $data['licenses'][$slug];
+ return $data['licenses'][$slug] ?? '';
}
/**
* Validates the License format
*
- * @param $license
+ * @param string $license
*
* @return bool
*/
@@ -114,7 +111,7 @@ class Licenses
{
if (!isset(self::$file)) {
- $path = Grav::instance()['locator']->findResource('user://data') . '/licenses.yaml';
+ $path = Grav::instance()['locator']->findResource('user-data://') . '/licenses.yaml';
if (!file_exists($path)) {
touch($path);
}
diff --git a/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php b/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php
index b976208..9acc083 100644
--- a/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php
+++ b/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php
@@ -1,8 +1,9 @@
$data) {
$data->set('slug', $name);
$this->items[$name] = new Package($data, $this->type);
diff --git a/system/src/Grav/Common/GPM/Local/Package.php b/system/src/Grav/Common/GPM/Local/Package.php
index d0fae3a..ff797a7 100644
--- a/system/src/Grav/Common/GPM/Local/Package.php
+++ b/system/src/Grav/Common/GPM/Local/Package.php
@@ -1,8 +1,9 @@
settings = $package->toArray();
- $html_description = \Parsedown::instance()->line($this->description);
- $this->data->set('slug', $package->slug);
+ $html_description = \Parsedown::instance()->line($this->__get('description'));
+ $this->data->set('slug', $package->__get('slug'));
$this->data->set('description_html', $html_description);
$this->data->set('description_plain', strip_tags($html_description));
- $this->data->set('symlink', is_link(USER_DIR . $package_type . DS . $this->slug));
+ $this->data->set('symlink', is_link(USER_DIR . $package_type . DS . $this->__get('slug')));
}
/**
diff --git a/system/src/Grav/Common/GPM/Local/Packages.php b/system/src/Grav/Common/GPM/Local/Packages.php
index 3120d21..14a0b0b 100644
--- a/system/src/Grav/Common/GPM/Local/Packages.php
+++ b/system/src/Grav/Common/GPM/Local/Packages.php
@@ -1,8 +1,9 @@
data['changelog'] as $version => $changelog) {
- preg_match("/[\w-\.]+/", $version, $cleanVersion);
+ preg_match("/[\w\-\.]+/", $version, $cleanVersion);
if (!$cleanVersion || version_compare($diff, $cleanVersion[0], '>=')) {
continue;
@@ -122,7 +123,7 @@ class GravCore extends AbstractPackageCollection
public function getMinPHPVersion()
{
// If non min set, assume current PHP version
- if (is_null($this->min_php)) {
+ if (null === $this->min_php) {
$this->min_php = phpversion();
}
return $this->min_php;
diff --git a/system/src/Grav/Common/GPM/Remote/Package.php b/system/src/Grav/Common/GPM/Remote/Package.php
index 830f63d..196e92b 100644
--- a/system/src/Grav/Common/GPM/Remote/Package.php
+++ b/system/src/Grav/Common/GPM/Remote/Package.php
@@ -1,8 +1,9 @@
data;
+ }
}
diff --git a/system/src/Grav/Common/GPM/Remote/Packages.php b/system/src/Grav/Common/GPM/Remote/Packages.php
index d8afd90..46bc31f 100644
--- a/system/src/Grav/Common/GPM/Remote/Packages.php
+++ b/system/src/Grav/Common/GPM/Remote/Packages.php
@@ -1,8 +1,9 @@
1 ? $args : array_shift($args);
$uri = $args[0];
- $options = $args[1];
- $callback = $args[2];
+ $options = $args[1] ?? [];
+ $callback = $args[2] ?? null;
$ch = curl_init($uri);
@@ -343,9 +345,9 @@ class Response
switch ($code) {
case '404':
- throw new \RuntimeException("Page not found");
+ throw new \RuntimeException('Page not found');
case '401':
- throw new \RuntimeException("Invalid LICENSE");
+ throw new \RuntimeException('Invalid LICENSE');
default:
throw new \RuntimeException("Error while trying to download (code: $code): $uri \nMessage: $error_message");
}
@@ -357,9 +359,9 @@ class Response
}
/**
- * @param $ch
- * @param $options
- * @param $callback
+ * @param resource $ch
+ * @param array $options
+ * @param bool $callback
*
* @return bool|mixed
*/
@@ -381,7 +383,7 @@ class Response
return curl_exec($ch);
}
- $max_redirects = isset($options['curl'][CURLOPT_MAXREDIRS]) ? $options['curl'][CURLOPT_MAXREDIRS] : 5;
+ $max_redirects = $options['curl'][CURLOPT_MAXREDIRS] ?? 5;
$options['curl'][CURLOPT_FOLLOWLOCATION] = false;
// open_basedir set but no redirects to follow, we can disable followlocation and proceed normally
@@ -405,8 +407,8 @@ class Response
if (curl_errno($rch)) {
$code = 0;
} else {
- $code = curl_getinfo($rch, CURLINFO_HTTP_CODE);
- if ($code == 301 || $code == 302 || $code == 303) {
+ $code = (int)curl_getinfo($rch, CURLINFO_HTTP_CODE);
+ if ($code === 301 || $code === 302 || $code === 303) {
preg_match('/Location:(.*?)\n/', $header, $matches);
$uri = trim(array_pop($matches));
} else {
diff --git a/system/src/Grav/Common/GPM/Upgrader.php b/system/src/Grav/Common/GPM/Upgrader.php
index 1c995bc..5d9406d 100644
--- a/system/src/Grav/Common/GPM/Upgrader.php
+++ b/system/src/Grav/Common/GPM/Upgrader.php
@@ -1,8 +1,9 @@
min_php)) {
+ if (null === $this->min_php) {
$this->min_php = $this->remote->getMinPHPVersion();
}
return $this->min_php;
@@ -125,7 +126,7 @@ class Upgrader
*/
public function isUpgradable()
{
- return version_compare($this->getLocalVersion(), $this->getRemoteVersion(), "<");
+ return version_compare($this->getLocalVersion(), $this->getRemoteVersion(), '<');
}
/**
diff --git a/system/src/Grav/Common/Getters.php b/system/src/Grav/Common/Getters.php
index 6f58fba..e69116e 100644
--- a/system/src/Grav/Common/Getters.php
+++ b/system/src/Grav/Common/Getters.php
@@ -1,8 +1,9 @@
gettersVariable;
return isset($this->{$var}[$offset]);
- } else {
- return isset($this->{$offset});
}
+
+ return isset($this->{$offset});
}
/**
@@ -88,10 +89,10 @@ abstract class Getters implements \ArrayAccess, \Countable
if ($this->gettersVariable) {
$var = $this->gettersVariable;
- return isset($this->{$var}[$offset]) ? $this->{$var}[$offset] : null;
- } else {
- return isset($this->{$offset}) ? $this->{$offset} : null;
+ return $this->{$var}[$offset] ?? null;
}
+
+ return $this->{$offset} ?? null;
}
/**
@@ -128,10 +129,10 @@ abstract class Getters implements \ArrayAccess, \Countable
{
if ($this->gettersVariable) {
$var = $this->gettersVariable;
- count($this->{$var});
- } else {
- count($this->toArray());
+ return \count($this->{$var});
}
+
+ return \count($this->toArray());
}
/**
@@ -145,16 +146,16 @@ abstract class Getters implements \ArrayAccess, \Countable
$var = $this->gettersVariable;
return $this->{$var};
- } else {
- $properties = (array)$this;
- $list = [];
- foreach ($properties as $property => $value) {
- if ($property[0] != "\0") {
- $list[$property] = $value;
- }
- }
-
- return $list;
}
+
+ $properties = (array)$this;
+ $list = [];
+ foreach ($properties as $property => $value) {
+ if ($property[0] !== "\0") {
+ $list[$property] = $value;
+ }
+ }
+
+ return $list;
}
}
diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php
index 84260d0..009d57d 100644
--- a/system/src/Grav/Common/Grav.php
+++ b/system/src/Grav/Common/Grav.php
@@ -1,21 +1,48 @@
'Grav\Common\Uri',
- 'events' => 'RocketTheme\Toolbox\Event\EventDispatcher',
- 'cache' => 'Grav\Common\Cache',
- 'Grav\Common\Service\SessionServiceProvider',
- 'plugins' => 'Grav\Common\Plugins',
- 'themes' => 'Grav\Common\Themes',
- 'twig' => 'Grav\Common\Twig\Twig',
- 'taxonomy' => 'Grav\Common\Taxonomy',
- 'language' => 'Grav\Common\Language\Language',
- 'pages' => 'Grav\Common\Page\Pages',
- 'Grav\Common\Service\TaskServiceProvider',
+ 'Grav\Common\Service\AccountsServiceProvider',
'Grav\Common\Service\AssetsServiceProvider',
- 'Grav\Common\Service\PageServiceProvider',
- 'Grav\Common\Service\OutputServiceProvider',
- 'browser' => 'Grav\Common\Browser',
- 'exif' => 'Grav\Common\Helpers\Exif',
- 'Grav\Common\Service\StreamsServiceProvider',
+ 'Grav\Common\Service\BackupsServiceProvider',
'Grav\Common\Service\ConfigServiceProvider',
- 'inflector' => 'Grav\Common\Inflector',
- 'siteSetupProcessor' => 'Grav\Common\Processors\SiteSetupProcessor',
- 'configurationProcessor' => 'Grav\Common\Processors\ConfigurationProcessor',
- 'errorsProcessor' => 'Grav\Common\Processors\ErrorsProcessor',
- 'debuggerInitProcessor' => 'Grav\Common\Processors\DebuggerInitProcessor',
- 'initializeProcessor' => 'Grav\Common\Processors\InitializeProcessor',
- 'pluginsProcessor' => 'Grav\Common\Processors\PluginsProcessor',
- 'themesProcessor' => 'Grav\Common\Processors\ThemesProcessor',
- 'tasksProcessor' => 'Grav\Common\Processors\TasksProcessor',
- 'assetsProcessor' => 'Grav\Common\Processors\AssetsProcessor',
- 'twigProcessor' => 'Grav\Common\Processors\TwigProcessor',
- 'pagesProcessor' => 'Grav\Common\Processors\PagesProcessor',
- 'debuggerAssetsProcessor' => 'Grav\Common\Processors\DebuggerAssetsProcessor',
- 'renderProcessor' => 'Grav\Common\Processors\RenderProcessor',
+ 'Grav\Common\Service\ErrorServiceProvider',
+ 'Grav\Common\Service\FilesystemServiceProvider',
+ 'Grav\Common\Service\InflectorServiceProvider',
+ 'Grav\Common\Service\LoggerServiceProvider',
+ 'Grav\Common\Service\OutputServiceProvider',
+ 'Grav\Common\Service\PagesServiceProvider',
+ 'Grav\Common\Service\RequestServiceProvider',
+ 'Grav\Common\Service\SessionServiceProvider',
+ 'Grav\Common\Service\StreamsServiceProvider',
+ 'Grav\Common\Service\TaskServiceProvider',
+ 'browser' => 'Grav\Common\Browser',
+ 'cache' => 'Grav\Common\Cache',
+ 'events' => 'RocketTheme\Toolbox\Event\EventDispatcher',
+ 'exif' => 'Grav\Common\Helpers\Exif',
+ 'plugins' => 'Grav\Common\Plugins',
+ 'scheduler' => 'Grav\Common\Scheduler\Scheduler',
+ 'taxonomy' => 'Grav\Common\Taxonomy',
+ 'themes' => 'Grav\Common\Themes',
+ 'twig' => 'Grav\Common\Twig\Twig',
+ 'uri' => 'Grav\Common\Uri',
];
/**
- * @var array All processors that are processed in $this->process()
+ * @var array All middleware processors that are processed in $this->process()
*/
- protected $processors = [
- 'siteSetupProcessor',
+ protected $middleware = [
'configurationProcessor',
+ 'loggerProcessor',
'errorsProcessor',
- 'debuggerInitProcessor',
+ 'debuggerProcessor',
'initializeProcessor',
'pluginsProcessor',
'themesProcessor',
+ 'requestProcessor',
'tasksProcessor',
+ 'backupsProcessor',
+ 'schedulerProcessor',
'assetsProcessor',
'twigProcessor',
'pagesProcessor',
@@ -88,6 +108,8 @@ class Grav extends Container
'renderProcessor',
];
+ protected $initialized = [];
+
/**
* Reset the Grav instance.
*/
@@ -119,21 +141,116 @@ class Grav extends Container
return self::$instance;
}
+ /**
+ * Setup Grav instance using specific environment.
+ *
+ * Initializes Grav streams by
+ *
+ * @param string|null $environment
+ * @return $this
+ */
+ public function setup(string $environment = null)
+ {
+ if (isset($this->initialized['setup'])) {
+ return $this;
+ }
+
+ $this->initialized['setup'] = true;
+
+ $this->measureTime('_setup', 'Site Setup', function () use ($environment) {
+ // Force environment if passed to the method.
+ if ($environment) {
+ Setup::$environment = $environment;
+ }
+
+ $this['setup'];
+ $this['streams'];
+ });
+
+ return $this;
+ }
+
/**
* Process a request
*/
public function process()
{
- // process all processors (e.g. config, initialize, assets, ..., render)
- foreach ($this->processors as $processor) {
- $processor = $this[$processor];
- $this->measureTime($processor->id, $processor->title, function () use ($processor) {
- $processor->process();
- });
+ if (isset($this->initialized['process'])) {
+ return;
}
+ // Initialize Grav if needed.
+ $this->setup();
+
+ $this->initialized['process'] = true;
+
+ $container = new Container(
+ [
+ 'configurationProcessor' => function () {
+ return new ConfigurationProcessor($this);
+ },
+ 'loggerProcessor' => function () {
+ return new LoggerProcessor($this);
+ },
+ 'errorsProcessor' => function () {
+ return new ErrorsProcessor($this);
+ },
+ 'debuggerProcessor' => function () {
+ return new DebuggerProcessor($this);
+ },
+ 'initializeProcessor' => function () {
+ return new InitializeProcessor($this);
+ },
+ 'backupsProcessor' => function () {
+ return new BackupsProcessor($this);
+ },
+ 'pluginsProcessor' => function () {
+ return new PluginsProcessor($this);
+ },
+ 'themesProcessor' => function () {
+ return new ThemesProcessor($this);
+ },
+ 'schedulerProcessor' => function () {
+ return new SchedulerProcessor($this);
+ },
+ 'requestProcessor' => function () {
+ return new RequestProcessor($this);
+ },
+ 'tasksProcessor' => function () {
+ return new TasksProcessor($this);
+ },
+ 'assetsProcessor' => function () {
+ return new AssetsProcessor($this);
+ },
+ 'twigProcessor' => function () {
+ return new TwigProcessor($this);
+ },
+ 'pagesProcessor' => function () {
+ return new PagesProcessor($this);
+ },
+ 'debuggerAssetsProcessor' => function () {
+ return new DebuggerAssetsProcessor($this);
+ },
+ 'renderProcessor' => function () {
+ return new RenderProcessor($this);
+ },
+ ]
+ );
+
+ $default = function (ServerRequestInterface $request) {
+ return new Response(404);
+ };
+
/** @var Debugger $debugger */
$debugger = $this['debugger'];
+
+ $collection = new RequestHandler($this->middleware, $default, $container);
+
+ $response = $collection->handle($this['request']);
+
+ $this->header($response);
+ echo $response->getBody();
+
$debugger->render();
register_shutdown_function([$this, 'shutdown']);
@@ -147,7 +264,7 @@ class Grav extends Container
// Initialize Locale if set and configured.
if ($this['language']->enabled() && $this['config']->get('system.languages.override_locale')) {
$language = $this['language']->getLanguage();
- setlocale(LC_ALL, strlen($language) < 3 ? ($language . '_' . strtoupper($language)) : $language);
+ setlocale(LC_ALL, \strlen($language) < 3 ? ($language . '_' . strtoupper($language)) : $language);
} elseif ($this['config']->get('system.default_locale')) {
setlocale(LC_ALL, $this['config']->get('system.default_locale'));
}
@@ -213,53 +330,22 @@ class Grav extends Container
/**
* Set response header.
+ *
+ * @param ResponseInterface|null $response
*/
- public function header()
+ public function header(ResponseInterface $response = null)
{
- /** @var Page $page */
- $page = $this['page'];
+ if (null === $response) {
+ /** @var PageInterface $page */
+ $page = $this['page'];
+ $response = new Response($page->httpResponseCode(), $page->httpHeaders(), '');
+ }
- $format = $page->templateFormat();
-
- header('Content-type: ' . Utils::getMimeByExtension($format, 'text/html'));
-
- $cache_control = $page->cacheControl();
-
- // Calculate Expires Headers if set to > 0
- $expires = $page->expires();
-
- if ($expires > 0) {
- $expires_date = gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT';
- if (!$cache_control) {
- header('Cache-Control: max-age=' . $expires);
+ header("HTTP/{$response->getProtocolVersion()} {$response->getStatusCode()} {$response->getReasonPhrase()}");
+ foreach ($response->getHeaders() as $key => $values) {
+ foreach ($values as $i => $value) {
+ header($key . ': ' . $value, $i === 0);
}
- header('Expires: ' . $expires_date);
- }
-
- // Set cache-control header
- if ($cache_control) {
- header('Cache-Control: ' . strtolower($cache_control));
- }
-
- // Set the last modified time
- if ($page->lastModified()) {
- $last_modified_date = gmdate('D, d M Y H:i:s', $page->modified()) . ' GMT';
- header('Last-Modified: ' . $last_modified_date);
- }
-
- // Calculate a Hash based on the raw file
- if ($page->eTag()) {
- header('ETag: "' . md5($page->raw() . $page->modified()).'"');
- }
-
- // Set HTTP response code
- if (isset($this['page']->header()->http_response_code)) {
- http_response_code($this['page']->header()->http_response_code);
- }
-
- // Vary: Accept-Encoding
- if ($this['config']->get('system.pages.vary_accept_encoding', false)) {
- header('Vary: Accept-Encoding');
}
}
@@ -286,7 +372,7 @@ class Grav extends Container
public function shutdown()
{
// Prevent user abort allowing onShutdown event to run without interruptions.
- if (function_exists('ignore_user_abort')) {
+ if (\function_exists('ignore_user_abort')) {
@ignore_user_abort(true);
}
@@ -300,7 +386,7 @@ class Grav extends Container
// the connection to the client open. This will make page loads to feel much faster.
// FastCGI allows us to flush all response data to the client and finish the request.
- $success = function_exists('fastcgi_finish_request') ? @fastcgi_finish_request() : false;
+ $success = \function_exists('fastcgi_finish_request') ? @fastcgi_finish_request() : false;
if (!$success) {
// Unfortunately without FastCGI there is no way to force close the connection.
@@ -323,7 +409,7 @@ class Grav extends Container
// Get length and close the connection.
header('Content-Length: ' . ob_get_length());
- header("Connection: close");
+ header('Connection: close');
ob_end_flush();
@ob_flush();
@@ -337,13 +423,33 @@ class Grav extends Container
/**
* Magic Catch All Function
- * Used to call closures like measureTime on the instance.
+ *
+ * Used to call closures.
+ *
* Source: http://stackoverflow.com/questions/419804/closures-as-class-members
*/
public function __call($method, $args)
{
- $closure = $this->$method;
- call_user_func_array($closure, $args);
+ $closure = $this->{$method};
+ \call_user_func_array($closure, $args);
+ }
+
+ /**
+ * Measure how long it takes to do an action.
+ *
+ * @param string $timerId
+ * @param string $timerTitle
+ * @param callable $callback
+ * @return mixed Returns value returned by the callable.
+ */
+ public function measureTime(string $timerId, string $timerTitle, callable $callback)
+ {
+ $debugger = $this['debugger'];
+ $debugger->startTimer($timerId, $timerTitle);
+ $result = $callback();
+ $debugger->stopTimer($timerId);
+
+ return $result;
}
/**
@@ -357,22 +463,15 @@ class Grav extends Container
{
$container = new static($values);
- $container['grav'] = $container;
-
$container['debugger'] = new Debugger();
- $debugger = $container['debugger'];
+ $container['grav'] = function (Container $container) {
+ user_error('Calling $grav[\'grav\'] or {{ grav.grav }} is deprecated since Grav 1.6, just use $grav or {{ grav }}', E_USER_DEPRECATED);
- // closure that measures time by wrapping a function into startTimer and stopTimer
- // The debugger can be passed to the closure. Should be more performant
- // then to get it from the container all time.
- $container->measureTime = function ($timerId, $timerTitle, $callback) use ($debugger) {
- $debugger->startTimer($timerId, $timerTitle);
- $callback();
- $debugger->stopTimer($timerId);
+ return $container;
};
$container->measureTime('_services', 'Services', function () use ($container) {
- $container->registerServices($container);
+ $container->registerServices();
});
return $container;
@@ -389,45 +488,20 @@ class Grav extends Container
protected function registerServices()
{
foreach (self::$diMap as $serviceKey => $serviceClass) {
- if (is_int($serviceKey)) {
- $this->registerServiceProvider($serviceClass);
+ if (\is_int($serviceKey)) {
+ $this->register(new $serviceClass);
} else {
- $this->registerService($serviceKey, $serviceClass);
+ $this[$serviceKey] = function ($c) use ($serviceClass) {
+ return new $serviceClass($c);
+ };
}
}
}
- /**
- * Register a service provider with the container.
- *
- * @param string $serviceClass
- *
- * @return void
- */
- protected function registerServiceProvider($serviceClass)
- {
- $this->register(new $serviceClass);
- }
-
- /**
- * Register a service with the container.
- *
- * @param string $serviceKey
- * @param string $serviceClass
- *
- * @return void
- */
- protected function registerService($serviceKey, $serviceClass)
- {
- $this[$serviceKey] = function ($c) use ($serviceClass) {
- return new $serviceClass($c);
- };
- }
-
/**
* This attempts to find media, other files, and download them
*
- * @param $path
+ * @param string $path
*/
public function fallbackUrl($path)
{
@@ -453,7 +527,7 @@ class Grav extends Container
$path_parts = pathinfo($path);
- /** @var Page $page */
+ /** @var PageInterface $page */
$page = $this['pages']->dispatch($path_parts['dirname'], true);
if ($page) {
@@ -466,8 +540,8 @@ class Grav extends Container
/** @var Medium $medium */
$medium = $media[$media_file];
foreach ($uri->query(null, true) as $action => $params) {
- if (in_array($action, ImageMedium::$magic_actions)) {
- call_user_func_array([&$medium, $action], explode(',', $params));
+ if (\in_array($action, ImageMedium::$magic_actions, true)) {
+ \call_user_func_array([&$medium, $action], explode(',', $params));
}
}
Utils::download($medium->path(), false);
@@ -486,7 +560,7 @@ class Grav extends Container
if ($extension) {
$download = true;
- if (in_array(ltrim($extension, '.'), $config->get('system.media.unsupported_inline_types', []))) {
+ if (\in_array(ltrim($extension, '.'), $config->get('system.media.unsupported_inline_types', []), true)) {
$download = false;
}
Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), $download);
diff --git a/system/src/Grav/Common/GravTrait.php b/system/src/Grav/Common/GravTrait.php
index a17a170..a82c23a 100644
--- a/system/src/Grav/Common/GravTrait.php
+++ b/system/src/Grav/Common/GravTrait.php
@@ -1,15 +1,16 @@
', '?'
0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G'
@@ -22,27 +23,31 @@ class Base32 {
0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'
0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'p', 'q', 'r', 's', 't', 'u', 'v', 'w'
0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF // 'x', 'y', 'z', '{', '|', '}', '~', 'DEL'
- );
+ ];
/**
* Encode in Base32
*
- * @param $bytes
+ * @param string $bytes
* @return string
*/
- public static function encode( $bytes ) {
- $i = 0; $index = 0; $digit = 0;
+ public static function encode($bytes)
+ {
+ $i = 0; $index = 0;
$base32 = '';
- $bytes_len = strlen($bytes);
- while( $i < $bytes_len ) {
- $currByte = ord($bytes{$i});
+ $bytesLen = \strlen($bytes);
+
+ while ($i < $bytesLen) {
+ $currByte = \ord($bytes[$i]);
+
/* Is the current digit going to span a byte boundary? */
- if( $index > 3 ) {
- if( ($i + 1) < $bytes_len ) {
- $nextByte = ord($bytes{$i+1});
+ if ($index > 3) {
+ if (($i + 1) < $bytesLen) {
+ $nextByte = \ord($bytes[$i+1]);
} else {
$nextByte = 0;
}
+
$digit = $currByte & (0xFF >> $index);
$index = ($index + 5) % 8;
$digit <<= $index;
@@ -51,9 +56,12 @@ class Base32 {
} else {
$digit = ($currByte >> (8 - ($index + 5))) & 0x1F;
$index = ($index + 5) % 8;
- if( $index === 0 ) $i++;
+ if ($index === 0) {
+ $i++;
+ }
}
- $base32 .= self::$base32Chars{$digit};
+
+ $base32 .= self::$base32Chars[$digit];
}
return $base32;
}
@@ -61,30 +69,42 @@ class Base32 {
/**
* Decode in Base32
*
- * @param $base32
+ * @param string $base32
* @return string
*/
- public static function decode( $base32 ) {
- $bytes = array();
- $base32_len = strlen($base32);
- for( $i=$base32_len*5/8-1; $i>=0; --$i ) {
+ public static function decode($base32)
+ {
+ $bytes = [];
+ $base32Len = \strlen($base32);
+ $base32LookupLen = \count(self::$base32Lookup);
+
+ for ($i = $base32Len * 5 / 8 - 1; $i >= 0; --$i) {
$bytes[] = 0;
}
- for( $i = 0, $index = 0, $offset = 0; $i < $base32_len; $i++ ) {
- $lookup = ord($base32{$i}) - ord('0');
+
+ for ($i = 0, $index = 0, $offset = 0; $i < $base32Len; $i++) {
+ $lookup = \ord($base32[$i]) - \ord('0');
+
/* Skip chars outside the lookup table */
- if( $lookup < 0 || $lookup >= count(self::$base32Lookup) ) {
+ if ($lookup < 0 || $lookup >= $base32LookupLen) {
continue;
}
+
$digit = self::$base32Lookup[$lookup];
+
/* If this digit is not in the table, ignore it */
- if( $digit == 0xFF ) continue;
- if( $index <= 3 ) {
+ if ($digit === 0xFF) {
+ continue;
+ }
+
+ if ($index <= 3) {
$index = ($index + 5) % 8;
- if( $index == 0) {
+ if ($index === 0) {
$bytes[$offset] |= $digit;
$offset++;
- if( $offset >= count($bytes) ) break;
+ if ($offset >= \count($bytes)) {
+ break;
+ }
} else {
$bytes[$offset] |= $digit << (8 - $index);
}
@@ -92,12 +112,18 @@ class Base32 {
$index = ($index + 5) % 8;
$bytes[$offset] |= ($digit >> $index);
$offset++;
- if ($offset >= count($bytes) ) break;
+ if ($offset >= \count($bytes)) {
+ break;
+ }
$bytes[$offset] |= $digit << (8 - $index);
}
}
+
$bites = '';
- foreach( $bytes as $byte ) $bites .= chr($byte);
+ foreach ($bytes as $byte) {
+ $bites .= \chr($byte);
+ }
+
return $bites;
}
}
diff --git a/system/src/Grav/Common/Helpers/Excerpts.php b/system/src/Grav/Common/Helpers/Excerpts.php
index 53faaf4..0943242 100644
--- a/system/src/Grav/Common/Helpers/Excerpts.php
+++ b/system/src/Grav/Common/Helpers/Excerpts.php
@@ -1,15 +1,16 @@
`
- * @param Page $page The current page object
+ * @param PageInterface $page The current page object
* @return string Returns final HTML string
*/
- public static function processImageHtml($html, Page $page)
+ public static function processImageHtml($html, PageInterface $page)
{
$excerpt = static::getExcerptFromHtml($html, 'img');
@@ -79,7 +80,7 @@ class Excerpts
/**
* Rebuild HTML tag from an excerpt array
*
- * @param $excerpt
+ * @param array $excerpt
* @return string
*/
public static function getHtmlFromExcerpt($excerpt)
@@ -110,12 +111,12 @@ class Excerpts
/**
* Process a Link excerpt
*
- * @param $excerpt
- * @param Page $page
+ * @param array $excerpt
+ * @param PageInterface $page
* @param string $type
* @return mixed
*/
- public static function processLinkExcerpt($excerpt, Page $page, $type = 'link')
+ public static function processLinkExcerpt($excerpt, PageInterface $page, $type = 'link')
{
$url = htmlspecialchars_decode(rawurldecode($excerpt['element']['attributes']['href']));
@@ -191,10 +192,10 @@ class Excerpts
* Process an image excerpt
*
* @param array $excerpt
- * @param Page $page
- * @return mixed
+ * @param PageInterface $page
+ * @return array
*/
- public static function processImageExcerpt(array $excerpt, Page $page)
+ public static function processImageExcerpt(array $excerpt, PageInterface $page)
{
$url = htmlspecialchars_decode(urldecode($excerpt['element']['attributes']['src']));
$url_parts = static::parseUrl($url);
@@ -203,15 +204,17 @@ class Excerpts
$filename = null;
if (!empty($url_parts['stream'])) {
- $filename = $url_parts['scheme'] . '://' . (isset($url_parts['path']) ? $url_parts['path'] : '');
+ $filename = $url_parts['scheme'] . '://' . ($url_parts['path'] ?? '');
- $media = $page->media();
+ $media = $page->getMedia();
} else {
+ $grav = Grav::instance();
+
// File is also local if scheme is http(s) and host matches.
$local_file = isset($url_parts['path'])
&& (empty($url_parts['scheme']) || in_array($url_parts['scheme'], ['http', 'https'], true))
- && (empty($url_parts['host']) || $url_parts['host'] === Grav::instance()['uri']->host());
+ && (empty($url_parts['host']) || $url_parts['host'] === $grav['uri']->host());
if ($local_file) {
$filename = basename($url_parts['path']);
@@ -220,18 +223,18 @@ class Excerpts
// Get the local path to page media if possible.
if ($folder === $page->url(false, false, false)) {
// Get the media objects for this page.
- $media = $page->media();
+ $media = $page->getMedia();
} else {
// see if this is an external page to this one
- $base_url = rtrim(Grav::instance()['base_url_relative'] . Grav::instance()['pages']->base(), '/');
+ $base_url = rtrim($grav['base_url_relative'] . $grav['pages']->base(), '/');
$page_route = '/' . ltrim(str_replace($base_url, '', $folder), '/');
- /** @var Page $ext_page */
- $ext_page = Grav::instance()['pages']->dispatch($page_route, true);
+ /** @var PageInterface $ext_page */
+ $ext_page = $grav['pages']->dispatch($page_route, true);
if ($ext_page) {
- $media = $ext_page->media();
+ $media = $ext_page->getMedia();
} else {
- Grav::instance()->fireEvent('onMediaLocate', new Event(['route' => $page_route, 'media' => &$media]));
+ $grav->fireEvent('onMediaLocate', new Event(['route' => $page_route, 'media' => &$media]));
}
}
}
@@ -247,10 +250,10 @@ class Excerpts
$medium = static::processMediaActions($medium, $url_parts);
$element_excerpt = $excerpt['element']['attributes'];
- $alt = isset($element_excerpt['alt']) ? $element_excerpt['alt'] : '';
- $title = isset($element_excerpt['title']) ? $element_excerpt['title'] : '';
- $class = isset($element_excerpt['class']) ? $element_excerpt['class'] : '';
- $id = isset($element_excerpt['id']) ? $element_excerpt['id'] : '';
+ $alt = $element_excerpt['alt'] ?? '';
+ $title = $element_excerpt['title'] ?? '';
+ $class = $element_excerpt['class'] ?? '';
+ $id = $element_excerpt['id'] ?? '';
$excerpt['element'] = $medium->parsedownElement($title, $alt, $class, $id, true);
@@ -265,9 +268,9 @@ class Excerpts
/**
* Process media actions
*
- * @param $medium
- * @param $url
- * @return mixed
+ * @param Medium $medium
+ * @param string|array $url
+ * @return Medium
*/
public static function processMediaActions($medium, $url)
{
@@ -283,7 +286,7 @@ class Excerpts
if (isset($url_parts['query'])) {
$actions = array_reduce(explode('&', $url_parts['query']), function ($carry, $item) {
$parts = explode('=', $item, 2);
- $value = isset($parts[1]) ? $parts[1] : null;
+ $value = $parts[1] ?? null;
$carry[] = ['method' => $parts[0], 'params' => $value];
return $carry;
@@ -352,11 +355,19 @@ class Excerpts
return $url_parts;
}
+ /**
+ * @param string $url
+ * @return bool|string
+ */
protected static function resolveStream($url)
{
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
- return $locator->isStream($url) ? ($locator->findResource($url, false) ?: $locator->findResource($url, false, true)) : $url;
+ if ($locator->isStream($url)) {
+ return $locator->findResource($url, false) ?: $locator->findResource($url, false, true);
+ }
+
+ return $url;
}
}
diff --git a/system/src/Grav/Common/Helpers/Exif.php b/system/src/Grav/Common/Helpers/Exif.php
index ee0c4ca..15aff66 100644
--- a/system/src/Grav/Common/Helpers/Exif.php
+++ b/system/src/Grav/Common/Helpers/Exif.php
@@ -1,15 +1,15 @@
.*)\] (?P\w+).(?P\w+): (?P.*[^ ]+) (?P[^ ]+) (?P[^ ]+)/';
+
+ /**
+ * Get the objects of a tailed file
+ *
+ * @param string $filepath
+ * @param int $lines
+ * @param bool $desc
+ * @return array
+ */
+ public function objectTail($filepath, $lines = 1, $desc = true)
+ {
+ $data = $this->tail($filepath, $lines);
+ $tailed_log = explode(PHP_EOL, $data);
+ $line_objects = [];
+
+ foreach ($tailed_log as $line) {
+ $line_objects[] = $this->parse($line);
+ }
+
+ return $desc ? $line_objects : array_reverse($line_objects);
+ }
+
+ /**
+ * Optimized way to get just the last few entries of a log file
+ *
+ * @param string $filepath
+ * @param int $lines
+ * @return bool|string
+ */
+ public function tail($filepath, $lines = 1) {
+
+ $f = @fopen($filepath, "rb");
+ if ($f === false) return false;
+
+ else $buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096));
+
+ fseek($f, -1, SEEK_END);
+ if (fread($f, 1) != "\n") $lines -= 1;
+
+ // Start reading
+ $output = '';
+ $chunk = '';
+ // While we would like more
+ while (ftell($f) > 0 && $lines >= 0) {
+ // Figure out how far back we should jump
+ $seek = min(ftell($f), $buffer);
+ // Do the jump (backwards, relative to where we are)
+ fseek($f, -$seek, SEEK_CUR);
+ // Read a chunk and prepend it to our output
+ $output = ($chunk = fread($f, $seek)) . $output;
+ // Jump back to where we started reading
+ fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);
+ // Decrease our line counter
+ $lines -= substr_count($chunk, "\n");
+ }
+ // While we have too many lines
+ // (Because of buffer size we might have read too many)
+ while ($lines++ < 0) {
+ // Find first newline and remove all text before that
+ $output = substr($output, strpos($output, "\n") + 1);
+ }
+ // Close file and return
+ fclose($f);
+
+ return trim($output);
+ }
+
+ /**
+ * Helper class to get level color
+ *
+ * @param string $level
+ * @return mixed|string
+ */
+ public static function levelColor($level)
+ {
+ $colors = [
+ 'DEBUG' => 'green',
+ 'INFO' => 'cyan',
+ 'NOTICE' => 'yellow',
+ 'WARNING' => 'yellow',
+ 'ERROR' => 'red',
+ 'CRITICAL' => 'red',
+ 'ALERT' => 'red',
+ 'EMERGENCY' => 'magenta'
+ ];
+ return $colors[$level] ?? 'white';
+ }
+
+ /**
+ * Parse a monolog row into array bits
+ *
+ * @param string $line
+ * @return array
+ */
+ public function parse($line)
+ {
+ if( !is_string($line) || strlen($line) === 0) {
+ return array();
+ }
+ preg_match($this->pattern, $line, $data);
+ if (!isset($data['date'])) {
+ return array();
+ }
+
+ preg_match('/(.*)- Trace:(.*)/', $data['message'], $matches);
+ if (is_array($matches) && isset($matches[1])) {
+ $data['message'] = trim($matches[1]);
+ $data['trace'] = trim($matches[2]);
+ }
+
+ return array(
+ 'date' => \DateTime::createFromFormat('Y-m-d H:i:s', $data['date']),
+ 'logger' => $data['logger'],
+ 'level' => $data['level'],
+ 'message' => $data['message'],
+ 'trace' => isset($data['trace']) ? $this->parseTrace($data['trace']) : null,
+ 'context' => json_decode($data['context'], true),
+ 'extra' => json_decode($data['extra'], true)
+ );
+ }
+
+ /**
+ * Parse text of trace into an array of lines
+ *
+ * @param string $trace
+ * @param int $rows
+ * @return array
+ */
+ public static function parseTrace($trace, $rows = 10)
+ {
+ $lines = array_filter(preg_split('/#\d*/m', $trace));
+ return array_slice($lines, 0, $rows);
+ }
+
+}
diff --git a/system/src/Grav/Common/Helpers/Truncator.php b/system/src/Grav/Common/Helpers/Truncator.php
index 474aae1..4c7d7f9 100644
--- a/system/src/Grav/Common/Helpers/Truncator.php
+++ b/system/src/Grav/Common/Helpers/Truncator.php
@@ -1,8 +1,9 @@
getElementsByTagName("body")->item(0);
+ $doc = self::htmlToDomDocument($html);
+ $container = $doc->getElementsByTagName('div')->item(0);
+ $container = $container->parentNode->removeChild($container);
// Iterate over words.
- $words = new DOMWordsIterator($body);
+ $words = new DOMWordsIterator($container);
$truncated = false;
foreach ($words as $word) {
@@ -65,7 +65,7 @@ class Truncator {
$words[$offset][1] + strlen($words[$offset][0])
);
- self::removeProceedingNodes($curNode, $body);
+ self::removeProceedingNodes($curNode, $container);
if (!empty($ellipsis)) {
self::insertEllipsis($curNode, $ellipsis);
@@ -80,32 +80,31 @@ class Truncator {
// Return original HTML if not truncated.
if ($truncated) {
- return self::innerHTML($body);
- } else {
- return $html;
+ $html = self::getCleanedHtml($doc, $container);
}
+
+ return $html;
}
/**
* Safely truncates HTML by a given number of letters.
* @param string $html Input HTML.
- * @param integer $limit Limit to how many letters we preserve.
+ * @param int $limit Limit to how many letters we preserve.
* @param string $ellipsis String to use as ellipsis (if any).
* @return string Safe truncated HTML.
*/
- public static function truncateLetters($html, $limit = 0, $ellipsis = "")
+ public static function truncateLetters($html, $limit = 0, $ellipsis = '')
{
if ($limit <= 0) {
return $html;
}
- $dom = self::htmlToDomDocument($html);
-
- // Grab the body of our DOM.
- $body = $dom->getElementsByTagName('body')->item(0);
+ $doc = self::htmlToDomDocument($html);
+ $container = $doc->getElementsByTagName('div')->item(0);
+ $container = $container->parentNode->removeChild($container);
// Iterate over letters.
- $letters = new DOMLettersIterator($body);
+ $letters = new DOMLettersIterator($container);
$truncated = false;
foreach ($letters as $letter) {
@@ -114,7 +113,7 @@ class Truncator {
$currentText = $letters->currentTextPosition();
$currentText[0]->nodeValue = mb_substr($currentText[0]->nodeValue, 0, $currentText[1] + 1);
- self::removeProceedingNodes($currentText[0], $body);
+ self::removeProceedingNodes($currentText[0], $container);
if (!empty($ellipsis)) {
self::insertEllipsis($currentText[0], $ellipsis);
@@ -128,10 +127,10 @@ class Truncator {
// Return original HTML if not truncated.
if ($truncated) {
- return self::innerHTML($body);
- } else {
- return $html;
+ $html = self::getCleanedHtml($doc, $container);
}
+
+ return $html;
}
/**
@@ -142,7 +141,7 @@ class Truncator {
public static function htmlToDomDocument($html)
{
if (!$html) {
- $html = '';
+ $html = '';
}
// Transform multibyte entities which otherwise display incorrectly.
@@ -154,7 +153,7 @@ class Truncator {
// Instantiate new DOMDocument object, and then load in UTF-8 HTML.
$dom = new DOMDocument();
$dom->encoding = 'UTF-8';
- $dom->loadHTML($html);
+ $dom->loadHTML("$html
");
return $dom;
}
@@ -187,6 +186,27 @@ class Truncator {
}
}
+ /**
+ * Clean extra code
+ *
+ * @param DOMDocument $doc
+ * @param $container
+ * @return string
+ */
+ private static function getCleanedHTML(DOMDocument $doc, $container)
+ {
+ while ($doc->firstChild) {
+ $doc->removeChild($doc->firstChild);
+ }
+
+ while ($container->firstChild ) {
+ $doc->appendChild($container->firstChild);
+ }
+
+ $html = trim($doc->saveHTML());
+ return $html;
+ }
+
/**
* Inserts an ellipsis
* @param DOMNode|DOMElement $domNode Element to insert after.
@@ -214,21 +234,102 @@ class Truncator {
}
/**
- * Returns the innerHTML of a particular DOMElement
- *
- * @param $element
- * @return string
+ * @inheritDoc
*/
- private static function innerHTML($element) {
- $innerHTML = '';
- $children = $element->childNodes;
- foreach ($children as $child)
- {
- $tmp_dom = new DOMDocument();
- $tmp_dom->appendChild($tmp_dom->importNode($child, true));
- $innerHTML.=trim($tmp_dom->saveHTML());
+ public function truncate(
+ $text,
+ $length = 100,
+ $ending = '...',
+ $exact = false,
+ $considerHtml = true
+ ) {
+ if ($considerHtml) {
+ // if the plain text is shorter than the maximum length, return the whole text
+ if (strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
+ return $text;
+ }
+ // splits all html-tags to scanable lines
+ preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
+ $total_length = strlen($ending);
+ $open_tags = array();
+ $truncate = '';
+ foreach ($lines as $line_matchings) {
+ // if there is any html-tag in this line, handle it and add it (uncounted) to the output
+ if (!empty($line_matchings[1])) {
+ // if it's an "empty element" with or without xhtml-conform closing slash
+ if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
+ // do nothing
+ // if tag is a closing tag
+ } else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
+ // delete tag from $open_tags list
+ $pos = array_search($tag_matchings[1], $open_tags);
+ if ($pos !== false) {
+ unset($open_tags[$pos]);
+ }
+ // if tag is an opening tag
+ } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
+ // add tag to the beginning of $open_tags list
+ array_unshift($open_tags, strtolower($tag_matchings[1]));
+ }
+ // add html-tag to $truncate'd text
+ $truncate .= $line_matchings[1];
+ }
+ // calculate the length of the plain text part of the line; handle entities as one character
+ $content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
+ if ($total_length+$content_length> $length) {
+ // the number of characters which are left
+ $left = $length - $total_length;
+ $entities_length = 0;
+ // search for html entities
+ if (preg_match_all('/&[0-9a-z]{2,8};|[0-9]{1,7};|[0-9a-f]{1,6};/i', $line_matchings[2], $entities, PREG_OFFSET_CAPTURE)) {
+ // calculate the real length of all entities in the legal range
+ foreach ($entities[0] as $entity) {
+ if ($entity[1]+1-$entities_length <= $left) {
+ $left--;
+ $entities_length += strlen($entity[0]);
+ } else {
+ // no more characters left
+ break;
+ }
+ }
+ }
+ $truncate .= substr($line_matchings[2], 0, $left+$entities_length);
+ // maximum lenght is reached, so get off the loop
+ break;
+ } else {
+ $truncate .= $line_matchings[2];
+ $total_length += $content_length;
+ }
+ // if the maximum length is reached, get off the loop
+ if($total_length>= $length) {
+ break;
+ }
+ }
+ } else {
+ if (strlen($text) <= $length) {
+ return $text;
+ } else {
+ $truncate = substr($text, 0, $length - strlen($ending));
+ }
}
- return $innerHTML;
+ // if the words shouldn't be cut in the middle...
+ if (!$exact) {
+ // ...search the last occurance of a space...
+ $spacepos = strrpos($truncate, ' ');
+ if (isset($spacepos)) {
+ // ...and cut the text in this position
+ $truncate = substr($truncate, 0, $spacepos);
+ }
+ }
+ // add the defined ending to the text
+ $truncate .= $ending;
+ if($considerHtml) {
+ // close all unclosed html-tags
+ foreach ($open_tags as $tag) {
+ $truncate .= '' . $tag . '>';
+ }
+ }
+ return $truncate;
}
}
diff --git a/system/src/Grav/Common/Helpers/YamlLinter.php b/system/src/Grav/Common/Helpers/YamlLinter.php
new file mode 100644
index 0000000..43bde82
--- /dev/null
+++ b/system/src/Grav/Common/Helpers/YamlLinter.php
@@ -0,0 +1,76 @@
+isStream($path)) {
+ $directory = $locator->getRecursiveIterator($path, $flags);
+ } else {
+ $directory = new \RecursiveDirectoryIterator($path, $flags);
+ }
+ $recursive = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
+ $iterator = new \RegexIterator($recursive, '/^.+\.'.$extensions.'$/i');
+
+ /** @var \RecursiveDirectoryIterator $file */
+ foreach ($iterator as $filepath => $file) {
+ try {
+ Yaml::parse(static::extractYaml($filepath));
+ } catch (\Exception $e) {
+ $lint_errors[str_replace(GRAV_ROOT, '', $filepath)] = $e->getMessage();
+ }
+ }
+
+ return $lint_errors;
+ }
+
+ protected static function extractYaml($path)
+ {
+ $extension = pathinfo($path, PATHINFO_EXTENSION);
+ if ($extension === 'md') {
+ $file = MarkdownFile::instance($path);
+ $contents = $file->frontmatter();
+ } else {
+ $contents = file_get_contents($path);
+ }
+ return $contents;
+ }
+
+}
diff --git a/system/src/Grav/Common/Inflector.php b/system/src/Grav/Common/Inflector.php
index 61c2338..a24bbfd 100644
--- a/system/src/Grav/Common/Inflector.php
+++ b/system/src/Grav/Common/Inflector.php
@@ -1,36 +1,35 @@
plural)) {
+ if (empty(static::$plural)) {
$language = Grav::instance()['language'];
- $this->plural = $language->translate('INFLECTOR_PLURALS', null, true) ?: [];
- $this->singular = $language->translate('INFLECTOR_SINGULAR', null, true) ?: [];
- $this->uncountable = $language->translate('INFLECTOR_UNCOUNTABLE', null, true) ?: [];
- $this->irregular = $language->translate('INFLECTOR_IRREGULAR', null, true) ?: [];
- $this->ordinals = $language->translate('INFLECTOR_ORDINALS', null, true) ?: [];
+ static::$plural = $language->translate('GRAV.INFLECTOR_PLURALS', null, true) ?: [];
+ static::$singular = $language->translate('GRAV.INFLECTOR_SINGULAR', null, true) ?: [];
+ static::$uncountable = $language->translate('GRAV.INFLECTOR_UNCOUNTABLE', null, true) ?: [];
+ static::$irregular = $language->translate('GRAV.INFLECTOR_IRREGULAR', null, true) ?: [];
+ static::$ordinals = $language->translate('GRAV.INFLECTOR_ORDINALS', null, true) ?: [];
}
}
@@ -42,29 +41,29 @@ class Inflector
*
* @return string Plural noun
*/
- public function pluralize($word, $count = 2)
+ public static function pluralize($word, $count = 2)
{
- $this->init();
+ static::init();
- if ($count == 1) {
+ if ((int)$count === 1) {
return $word;
}
$lowercased_word = strtolower($word);
- foreach ($this->uncountable as $_uncountable) {
- if (substr($lowercased_word, (-1 * strlen($_uncountable))) == $_uncountable) {
+ foreach (static::$uncountable as $_uncountable) {
+ if (substr($lowercased_word, -1 * strlen($_uncountable)) === $_uncountable) {
return $word;
}
}
- foreach ($this->irregular as $_plural => $_singular) {
+ foreach (static::$irregular as $_plural => $_singular) {
if (preg_match('/(' . $_plural . ')$/i', $word, $arr)) {
return preg_replace('/(' . $_plural . ')$/i', substr($arr[0], 0, 1) . substr($_singular, 1), $word);
}
}
- foreach ($this->plural as $rule => $replacement) {
+ foreach (static::$plural as $rule => $replacement) {
if (preg_match($rule, $word)) {
return preg_replace($rule, $replacement, $word);
}
@@ -82,28 +81,28 @@ class Inflector
*
* @return string Singular noun.
*/
- public function singularize($word, $count = 1)
+ public static function singularize($word, $count = 1)
{
- $this->init();
+ static::init();
- if ($count != 1) {
+ if ((int)$count !== 1) {
return $word;
}
$lowercased_word = strtolower($word);
- foreach ($this->uncountable as $_uncountable) {
- if (substr($lowercased_word, (-1 * strlen($_uncountable))) == $_uncountable) {
+ foreach (static::$uncountable as $_uncountable) {
+ if (substr($lowercased_word, -1 * strlen($_uncountable)) === $_uncountable) {
return $word;
}
}
- foreach ($this->irregular as $_plural => $_singular) {
+ foreach (static::$irregular as $_plural => $_singular) {
if (preg_match('/(' . $_singular . ')$/i', $word, $arr)) {
return preg_replace('/(' . $_singular . ')$/i', substr($arr[0], 0, 1) . substr($_plural, 1), $word);
}
}
- foreach ($this->singular as $rule => $replacement) {
+ foreach (static::$singular as $rule => $replacement) {
if (preg_match($rule, $word)) {
return preg_replace($rule, $replacement, $word);
}
@@ -129,11 +128,11 @@ class Inflector
*
* @return string Text formatted as title
*/
- public function titleize($word, $uppercase = '')
+ public static function titleize($word, $uppercase = '')
{
- $uppercase = $uppercase == 'first' ? 'ucfirst' : 'ucwords';
+ $uppercase = $uppercase === 'first' ? 'ucfirst' : 'ucwords';
- return $uppercase($this->humanize($this->underscorize($word)));
+ return $uppercase(static::humanize(static::underscorize($word)));
}
/**
@@ -149,7 +148,7 @@ class Inflector
*
* @return string UpperCamelCasedWord
*/
- public function camelize($word)
+ public static function camelize($word)
{
return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word)));
}
@@ -166,7 +165,7 @@ class Inflector
*
* @return string Underscored word
*/
- public function underscorize($word)
+ public static function underscorize($word)
{
$regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1_\2', $word);
$regex2 = preg_replace('/([a-zd])([A-Z])/', '\1_\2', $regex1);
@@ -187,7 +186,7 @@ class Inflector
*
* @return string hyphenized word
*/
- public function hyphenize($word)
+ public static function hyphenize($word)
{
$regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1-\2', $word);
$regex2 = preg_replace('/([a-z])([A-Z])/', '\1-\2', $regex1);
@@ -213,9 +212,9 @@ class Inflector
*
* @return string Human-readable word
*/
- public function humanize($word, $uppercase = '')
+ public static function humanize($word, $uppercase = '')
{
- $uppercase = $uppercase == 'all' ? 'ucwords' : 'ucfirst';
+ $uppercase = $uppercase === 'all' ? 'ucwords' : 'ucfirst';
return $uppercase(str_replace('_', ' ', preg_replace('/_id$/', '', $word)));
}
@@ -233,9 +232,9 @@ class Inflector
*
* @return string Returns a lowerCamelCasedWord
*/
- public function variablize($word)
+ public static function variablize($word)
{
- $word = $this->camelize($word);
+ $word = static::camelize($word);
return strtolower($word[0]) . substr($word, 1);
}
@@ -252,9 +251,9 @@ class Inflector
*
* @return string plural_table_name
*/
- public function tableize($class_name)
+ public static function tableize($class_name)
{
- return $this->pluralize($this->underscorize($class_name));
+ return static::pluralize(static::underscorize($class_name));
}
/**
@@ -269,9 +268,9 @@ class Inflector
*
* @return string SingularClassName
*/
- public function classify($table_name)
+ public static function classify($table_name)
{
- return $this->camelize($this->singularize($table_name));
+ return static::camelize(static::singularize($table_name));
}
/**
@@ -279,31 +278,27 @@ class Inflector
*
* This method converts 13 to 13th, 2 to 2nd ...
*
- * @param integer $number Number to get its ordinal value
+ * @param int $number Number to get its ordinal value
*
* @return string Ordinal representation of given string.
*/
- public function ordinalize($number)
+ public static function ordinalize($number)
{
- $this->init();
+ static::init();
- if (in_array(($number % 100), range(11, 13))) {
- return $number . $this->ordinals['default'];
- } else {
- switch (($number % 10)) {
- case 1:
- return $number . $this->ordinals['first'];
- break;
- case 2:
- return $number . $this->ordinals['second'];
- break;
- case 3:
- return $number . $this->ordinals['third'];
- break;
- default:
- return $number . $this->ordinals['default'];
- break;
- }
+ if (\in_array($number % 100, range(11, 13), true)) {
+ return $number . static::$ordinals['default'];
+ }
+
+ switch ($number % 10) {
+ case 1:
+ return $number . static::$ordinals['first'];
+ case 2:
+ return $number . static::$ordinals['second'];
+ case 3:
+ return $number . static::$ordinals['third'];
+ default:
+ return $number . static::$ordinals['default'];
}
}
@@ -314,7 +309,7 @@ class Inflector
*
* @return int
*/
- public function monthize($days)
+ public static function monthize($days)
{
$now = new \DateTime();
$end = new \DateTime();
@@ -325,7 +320,7 @@ class Inflector
// handle years
if ($diff->y > 0) {
- $diff->m = $diff->m + 12 * $diff->y;
+ $diff->m += 12 * $diff->y;
}
return $diff->m;
diff --git a/system/src/Grav/Common/Iterator.php b/system/src/Grav/Common/Iterator.php
index 99fc0c4..5f6dc7e 100644
--- a/system/src/Grav/Common/Iterator.php
+++ b/system/src/Grav/Common/Iterator.php
@@ -1,8 +1,9 @@
items[$key])) ? $this->items[$key] : null;
+ return $this->items[$key] ?? null;
}
/**
@@ -43,8 +44,8 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
public function __clone()
{
foreach ($this as $key => $value) {
- if (is_object($value)) {
- $this->$key = clone $this->$key;
+ if (\is_object($value)) {
+ $this->{$key} = clone $this->{$key};
}
}
}
@@ -62,7 +63,7 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
/**
* Remove item from the list.
*
- * @param $key
+ * @param string $key
*/
public function remove($key)
{
@@ -90,7 +91,7 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
{
$items = array_keys($this->items);
- return (isset($items[$key])) ? $this->offsetGet($items[$key]) : false;
+ return isset($items[$key]) ? $this->offsetGet($items[$key]) : false;
}
/**
@@ -175,7 +176,7 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
*/
public function slice($offset, $length = null)
{
- $this->items = array_slice($this->items, $offset, $length);
+ $this->items = \array_slice($this->items, $offset, $length);
return $this;
}
@@ -189,8 +190,9 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
*/
public function random($num = 1)
{
- if ($num > count($this->items)) {
- $num = count($this->items);
+ $count = \count($this->items);
+ if ($num > $count) {
+ $num = $count;
}
$this->items = array_intersect_key($this->items, array_flip((array)array_rand($this->items, $num)));
@@ -227,8 +229,8 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
{
foreach ($this->items as $key => $value) {
if (
- ($callback && !call_user_func($callback, $value, $key)) ||
- (!$callback && !(bool)$value)
+ (!$callback && !(bool)$value) ||
+ ($callback && !$callback($value, $key))
) {
unset($this->items[$key]);
}
@@ -251,7 +253,7 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
*/
public function sort(callable $callback = null, $desc = false)
{
- if (!$callback || !is_callable($callback)) {
+ if (!$callback || !\is_callable($callback)) {
return $this;
}
diff --git a/system/src/Grav/Common/Language/Language.php b/system/src/Grav/Common/Language/Language.php
index 076d103..cbc1b5d 100644
--- a/system/src/Grav/Common/Language/Language.php
+++ b/system/src/Grav/Common/Language/Language.php
@@ -1,8 +1,9 @@
default = reset($this->languages);
+ $default = $this->config->get('system.languages.default_lang');
+ if (isset($default) && $this->validate($default)) {
+ $this->default = $default;
+ } else {
+ $this->default = reset($this->languages);
+ }
+
+ $this->page_extensions = null;
if (empty($this->languages)) {
$this->enabled = false;
@@ -75,7 +88,7 @@ class Language
/**
* Sets the current supported languages manually
*
- * @param $langs
+ * @param array $langs
*/
public function setLanguages($langs)
{
@@ -92,17 +105,18 @@ class Language
{
$languagesArray = $this->languages; //Make local copy
sort($languagesArray);
+
return implode('|', array_reverse($languagesArray));
}
/**
* Gets language, active if set, else default
*
- * @return mixed
+ * @return string
*/
public function getLanguage()
{
- return $this->active ? $this->active : $this->default;
+ return $this->active ?: $this->default;
}
/**
@@ -118,7 +132,7 @@ class Language
/**
* Sets default language manually
*
- * @param $lang
+ * @param string $lang
*
* @return bool
*/
@@ -136,7 +150,7 @@ class Language
/**
* Gets current active language
*
- * @return mixed
+ * @return string
*/
public function getActive()
{
@@ -146,9 +160,9 @@ class Language
/**
* Sets active language manually
*
- * @param $lang
+ * @param string $lang
*
- * @return bool
+ * @return string|bool
*/
public function setActive($lang)
{
@@ -164,9 +178,9 @@ class Language
/**
* Sets the active language based on the first part of the URL
*
- * @param $uri
+ * @param string $uri
*
- * @return mixed
+ * @return string
*/
public function setActiveFromUri($uri)
{
@@ -189,29 +203,24 @@ class Language
}
} else {
// Try getting language from the session, else no active.
- if (isset($this->grav['session']) && $this->grav['session']->isStarted()
- && $this->config->get('system.languages.session_store_active', true)) {
+ if (isset($this->grav['session']) && $this->grav['session']->isStarted() &&
+ $this->config->get('system.languages.session_store_active', true)) {
$this->active = $this->grav['session']->active_language ?: null;
}
// if still null, try from http_accept_language header
- if ($this->active === null && $this->config->get('system.languages.http_accept_language')) {
- $preferred = $this->getBrowserLanguages();
- foreach ($preferred as $lang) {
- if ($this->validate($lang)) {
- $this->active = $lang;
- break;
- }
+ if ($this->active === null &&
+ $this->config->get('system.languages.http_accept_language') &&
+ $accept = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? false) {
+
+ $negotiator = new LanguageNegotiator();
+ $best_language = $negotiator->getBest($accept, $this->languages);
+
+ if ($best_language instanceof AcceptLanguage) {
+ $this->active = $best_language->getType();
+ } else {
+ $this->active = $this->getDefault();
}
- // Repeat if not found, try base language only - fixes Safari sending the language code always
- // with a locale (e.g. it-it or fr-fr).
- foreach ($preferred as $lang) {
- $lang = substr($lang, 0, 2);
- if ($this->validate($lang)) {
- $this->active = $lang;
- break;
- }
- }
}
}
}
@@ -222,7 +231,7 @@ class Language
/**
* Get's a URL prefix based on configuration
*
- * @param null $lang
+ * @param string|null $lang
* @return string
*/
public function getLanguageURLPrefix($lang = null)
@@ -238,7 +247,7 @@ class Language
/**
* Test to see if language is default and language should be included in the URL
*
- * @param null $lang
+ * @param string|null $lang
* @return bool
*/
public function isIncludeDefaultLanguage($lang = null)
@@ -248,11 +257,7 @@ class Language
$lang = $this->getLanguage();
}
- if ($this->default == $lang && $this->config->get('system.languages.include_default_lang') === false) {
- return false;
- } else {
- return true;
- }
+ return !($this->default === $lang && $this->config->get('system.languages.include_default_lang') === false);
}
/**
@@ -276,7 +281,7 @@ class Language
public function getFallbackPageExtensions($file_ext = null)
{
if (empty($this->page_extensions)) {
- if (empty($file_ext)) {
+ if (!$file_ext) {
$file_ext = CONTENT_EXT;
}
@@ -288,12 +293,19 @@ class Language
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);
- }
+ $key = \array_search($active_extension, $valid_lang_extensions, true);
- $this->page_extensions = array_merge($valid_lang_extensions, (array)$file_ext);
+ // Default behavior is to find any language other than active
+ if ($this->config->get('system.languages.pages_fallback_only')) {
+ $slice = \array_slice($valid_lang_extensions, 0, $key+1);
+ $valid_lang_extensions = array_reverse($slice);
+ } else {
+ unset($valid_lang_extensions[$key]);
+ array_unshift($valid_lang_extensions, $active_extension);
+ }
+ }
+ $valid_lang_extensions[] = $file_ext;
+ $this->page_extensions = $valid_lang_extensions;
} else {
$this->page_extensions = (array)$file_ext;
}
@@ -313,7 +325,8 @@ class Language
* $this->grav['pages']->init();
* ```
*/
- public function resetFallbackPageExtensions() {
+ public function resetFallbackPageExtensions()
+ {
$this->page_extensions = null;
}
@@ -330,7 +343,7 @@ class Language
if ($this->active) {
$active_extension = $this->active;
- $key = array_search($active_extension, $fallback_languages);
+ $key = \array_search($active_extension, $fallback_languages, true);
unset($fallback_languages[$key]);
array_unshift($fallback_languages, $active_extension);
}
@@ -346,23 +359,19 @@ class Language
/**
* Ensures the language is valid and supported
*
- * @param $lang
+ * @param string $lang
*
* @return bool
*/
public function validate($lang)
{
- if (in_array($lang, $this->languages)) {
- return true;
- }
-
- return false;
+ return \in_array($lang, $this->languages, true);
}
/**
* Translate a key and possibly arguments into a string using current lang and fallbacks
*
- * @param mixed $args The first argument is the lookup key value
+ * @param string|array $args The first argument is the lookup key value
* Other arguments can be passed and replaced in the translation with sprintf syntax
* @param array $languages
* @param bool $array_support
@@ -372,7 +381,7 @@ class Language
*/
public function translate($args, array $languages = null, $array_support = false, $html_out = false)
{
- if (is_array($args)) {
+ if (\is_array($args)) {
$lookup = array_shift($args);
} else {
$lookup = $args;
@@ -396,28 +405,28 @@ class Language
$translation = $this->getTranslation($lang, $lookup, $array_support);
if ($translation) {
- if (count($args) >= 1) {
+ if (\count($args) >= 1) {
return vsprintf($translation, $args);
- } else {
- return $translation;
}
+
+ return $translation;
}
}
}
if ($html_out) {
return '' . $lookup . '';
- } else {
- return $lookup;
}
+
+ return $lookup;
}
/**
* Translate Array
*
- * @param $key
- * @param $index
- * @param null $languages
+ * @param string $key
+ * @param string $index
+ * @param array|null $languages
* @param bool $html_out
*
* @return string
@@ -447,9 +456,9 @@ class Language
if ($html_out) {
return '' . $key . '[' . $index . ']';
- } else {
- return $key . '[' . $index . ']';
}
+
+ return $key . '[' . $index . ']';
}
/**
@@ -475,11 +484,13 @@ class Language
* Get the browser accepted languages
*
* @param array $accept_langs
- *
* @return array
+ * @deprecated 1.6 No longer used - using content negotiation.
*/
public function getBrowserLanguages($accept_langs = [])
{
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, no longer used', E_USER_DEPRECATED);
+
if (empty($this->http_accept_language)) {
if (empty($accept_langs) && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$accept_langs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
@@ -487,6 +498,8 @@ class Language
return $accept_langs;
}
+ $langs = [];
+
foreach (explode(',', $accept_langs) as $k => $pref) {
// split $pref again by ';q='
// and decorate the language entries by inverted position
@@ -504,4 +517,16 @@ class Language
return $this->http_accept_language;
}
+ /**
+ * Accessible wrapper to LanguageCodes
+ *
+ * @param string $code
+ * @param string $type
+ * @return bool
+ */
+ public function getLanguageCode($code, $type = 'name')
+ {
+ return LanguageCodes::get($code, $type);
+ }
+
}
diff --git a/system/src/Grav/Common/Language/LanguageCodes.php b/system/src/Grav/Common/Language/LanguageCodes.php
index 063d41d..780df7c 100644
--- a/system/src/Grav/Common/Language/LanguageCodes.php
+++ b/system/src/Grav/Common/Language/LanguageCodes.php
@@ -1,8 +1,9 @@
special_chars = ['>' => 'gt', '<' => 'lt', '"' => 'quot'];
if ($defaults === null) {
- $defaults = Grav::instance()['config']->get('system.pages.markdown');
+ $defaults = (array)Grav::instance()['config']->get('system.pages.markdown');
}
$this->setBreaksEnabled($defaults['auto_line_breaks']);
@@ -47,18 +48,18 @@ trait ParsedownGravTrait
$this->setMarkupEscaped($defaults['escape_markup']);
$this->setSpecialChars($defaults['special_chars']);
- $grav->fireEvent('onMarkdownInitialized', new Event(['markdown' => $this]));
+ $grav->fireEvent('onMarkdownInitialized', new Event(['markdown' => $this, 'page' => $page]));
}
/**
* Be able to define a new Block type or override an existing one
*
- * @param $type
- * @param $tag
+ * @param string $type
+ * @param string $tag
* @param bool $continuable
* @param bool $completable
- * @param $index
+ * @param int|null $index
*/
public function addBlockType($type, $tag, $continuable = false, $completable = false, $index = null)
{
@@ -87,9 +88,9 @@ trait ParsedownGravTrait
/**
* Be able to define a new Inline type or override an existing one
*
- * @param $type
- * @param $tag
- * @param $index
+ * @param string $type
+ * @param string $tag
+ * @param int|null $index
*/
public function addInlineType($type, $tag, $index = null)
{
@@ -107,7 +108,7 @@ trait ParsedownGravTrait
/**
* Overrides the default behavior to allow for plugin-provided blocks to be continuable
*
- * @param $Type
+ * @param string $Type
*
* @return bool
*/
@@ -121,7 +122,7 @@ trait ParsedownGravTrait
/**
* Overrides the default behavior to allow for plugin-provided blocks to be completable
*
- * @param $Type
+ * @param string $Type
*
* @return bool
*/
@@ -148,7 +149,7 @@ trait ParsedownGravTrait
/**
* Setter for special chars
*
- * @param $special_chars
+ * @param array $special_chars
*
* @return $this
*/
diff --git a/system/src/Grav/Common/Media/Interfaces/MediaCollectionInterface.php b/system/src/Grav/Common/Media/Interfaces/MediaCollectionInterface.php
index 06f71a5..9f91663 100644
--- a/system/src/Grav/Common/Media/Interfaces/MediaCollectionInterface.php
+++ b/system/src/Grav/Common/Media/Interfaces/MediaCollectionInterface.php
@@ -1,9 +1,23 @@
findResource('user://');
if (strpos($folder, $user) === 0) {
- return 'user://' . substr($folder, strlen($user)+1);
+ return 'user://' . substr($folder, \strlen($user)+1);
}
return null;
@@ -55,14 +67,14 @@ trait MediaTrait
*/
public function getMedia()
{
- $cache = $this->getMediaCache();
-
if ($this->media === null) {
+ $cache = $this->getMediaCache();
+
// Use cached media if possible.
$cacheKey = md5('media' . $this->getCacheKey());
- if (!$media = $cache->fetch($cacheKey)) {
+ if (!$media = $cache->get($cacheKey)) {
$media = new Media($this->getMediaFolder(), $this->getMediaOrder());
- $cache->save($cacheKey, $media);
+ $cache->set($cacheKey, $media);
}
$this->media = $media;
}
@@ -80,7 +92,7 @@ trait MediaTrait
{
$cache = $this->getMediaCache();
$cacheKey = md5('media' . $this->getCacheKey());
- $cache->save($cacheKey, $media);
+ $cache->set($cacheKey, $media);
$this->media = $media;
@@ -95,14 +107,19 @@ trait MediaTrait
$cache = $this->getMediaCache();
$cacheKey = md5('media' . $this->getCacheKey());
$cache->delete($cacheKey);
+
+ $this->media = null;
}
/**
- * @return Cache
+ * @return CacheInterface
*/
protected function getMediaCache()
{
- return Grav::instance()['cache'];
+ /** @var Cache $cache */
+ $cache = Grav::instance()['cache'];
+
+ return $cache->getSimpleCache();
}
/**
diff --git a/system/src/Grav/Common/Page/Collection.php b/system/src/Grav/Common/Page/Collection.php
index 5113e81..51de16b 100644
--- a/system/src/Grav/Common/Page/Collection.php
+++ b/system/src/Grav/Common/Page/Collection.php
@@ -1,8 +1,9 @@
items[$page->path()] = ['slug' => $page->slug()];
@@ -66,8 +68,8 @@ class Collection extends Iterator
/**
* Add a page with path and slug
*
- * @param $path
- * @param $slug
+ * @param string $path
+ * @param string $slug
* @return $this
*/
public function add($path, $slug)
@@ -100,6 +102,7 @@ class Collection extends Iterator
foreach($collection as $page) {
$this->addPage($page);
}
+
return $this;
}
@@ -117,6 +120,7 @@ class Collection extends Iterator
$this->items = array_uintersect($array1, $array2, function($val1, $val2) {
return strcmp($val1['slug'], $val2['slug']);
});
+
return $this;
}
@@ -130,13 +134,14 @@ class Collection extends Iterator
public function setParams(array $params)
{
$this->params = array_merge($this->params, $params);
+
return $this;
}
/**
* Returns current page.
*
- * @return Page
+ * @return PageInterface
*/
public function current()
{
@@ -166,13 +171,13 @@ class Collection extends Iterator
*/
public function offsetGet($offset)
{
- return !empty($this->items[$offset]) ? $this->pages->get($offset) : null;
+ return $this->pages->get($offset) ?: null;
}
/**
* Split collection into array of smaller collections.
*
- * @param $size
+ * @param int $size
* @return array|Collection[]
*/
public function batch($size)
@@ -190,19 +195,19 @@ class Collection extends Iterator
/**
* Remove item from the list.
*
- * @param Page|string|null $key
+ * @param PageInterface|string|null $key
*
* @return $this
* @throws \InvalidArgumentException
*/
public function remove($key = null)
{
- if ($key instanceof Page) {
+ if ($key instanceof PageInterface) {
$key = $key->path();
- } elseif (is_null($key)) {
- $key = key($this->items);
+ } elseif (null === $key) {
+ $key = (string)key($this->items);
}
- if (!is_string($key)) {
+ if (!\is_string($key)) {
throw new \InvalidArgumentException('Invalid argument $key.');
}
@@ -233,15 +238,11 @@ class Collection extends Iterator
*
* @param string $path
*
- * @return boolean True if item is first.
+ * @return bool True if item is first.
*/
public function isFirst($path)
{
- if ($this->items && $path == array_keys($this->items)[0]) {
- return true;
- } else {
- return false;
- }
+ return $this->items && $path === array_keys($this->items)[0];
}
/**
@@ -249,15 +250,11 @@ class Collection extends Iterator
*
* @param string $path
*
- * @return boolean True if item is last.
+ * @return bool True if item is last.
*/
public function isLast($path)
{
- if ($this->items && $path == array_keys($this->items)[count($this->items) - 1]) {
- return true;
- } else {
- return false;
- }
+ return $this->items && $path === array_keys($this->items)[\count($this->items) - 1];
}
/**
@@ -265,7 +262,7 @@ class Collection extends Iterator
*
* @param string $path
*
- * @return Page The previous item.
+ * @return PageInterface The previous item.
*/
public function prevSibling($path)
{
@@ -277,7 +274,7 @@ class Collection extends Iterator
*
* @param string $path
*
- * @return Page The next item.
+ * @return PageInterface The next item.
*/
public function nextSibling($path)
{
@@ -288,9 +285,9 @@ class Collection extends Iterator
* Returns the adjacent sibling based on a direction.
*
* @param string $path
- * @param integer $direction either -1 or +1
+ * @param int $direction either -1 or +1
*
- * @return Page The sibling item.
+ * @return PageInterface|Collection The sibling item.
*/
public function adjacentSibling($path, $direction = 1)
{
@@ -312,11 +309,11 @@ class Collection extends Iterator
*
* @param string $path the path the item
*
- * @return Integer the index of the current page.
+ * @return int the index of the current page.
*/
public function currentPosition($path)
{
- return array_search($path, array_keys($this->items));
+ return \array_search($path, \array_keys($this->items), true);
}
/**
@@ -325,14 +322,14 @@ class Collection extends Iterator
* Dates can be passed in as text that strtotime() can process
* http://php.net/manual/en/function.strtotime.php
*
- * @param $startDate
+ * @param string $startDate
* @param bool $endDate
- * @param $field
+ * @param string|null $field
*
* @return $this
* @throws \Exception
*/
- public function dateRange($startDate, $endDate = false, $field = false)
+ public function dateRange($startDate, $endDate = false, $field = null)
{
$start = Utils::date2timestamp($startDate);
$end = $endDate ? Utils::date2timestamp($endDate) : false;
@@ -350,6 +347,7 @@ class Collection extends Iterator
}
$this->items = $date_range;
+
return $this;
}
@@ -518,7 +516,7 @@ class Collection extends Iterator
/**
* Creates new collection with only pages of the specified type
*
- * @param $type
+ * @param string $type
*
* @return Collection The collection
*/
@@ -528,7 +526,7 @@ class Collection extends Iterator
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
- if ($page !== null && $page->template() == $type) {
+ if ($page !== null && $page->template() === $type) {
$items[$path] = $slug;
}
}
@@ -541,7 +539,7 @@ class Collection extends Iterator
/**
* Creates new collection with only pages of one of the specified types
*
- * @param $types
+ * @param string[] $types
*
* @return Collection The collection
*/
@@ -551,7 +549,7 @@ class Collection extends Iterator
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
- if ($page !== null && in_array($page->template(), $types)) {
+ if ($page !== null && \in_array($page->template(), $types, true)) {
$items[$path] = $slug;
}
}
@@ -564,7 +562,7 @@ class Collection extends Iterator
/**
* Creates new collection with only pages of one of the specified access levels
*
- * @param $accessLevels
+ * @param array $accessLevels
*
* @return Collection The collection
*/
@@ -576,19 +574,19 @@ class Collection extends Iterator
$page = $this->pages->get($path);
if ($page !== null && isset($page->header()->access)) {
- if (is_array($page->header()->access)) {
+ if (\is_array($page->header()->access)) {
//Multiple values for access
$valid = false;
foreach ($page->header()->access as $index => $accessLevel) {
- if (is_array($accessLevel)) {
+ if (\is_array($accessLevel)) {
foreach ($accessLevel as $innerIndex => $innerAccessLevel) {
- if (in_array($innerAccessLevel, $accessLevels)) {
+ if (\in_array($innerAccessLevel, $accessLevels)) {
$valid = true;
}
}
} else {
- if (in_array($index, $accessLevels)) {
+ if (\in_array($index, $accessLevels)) {
$valid = true;
}
}
@@ -598,7 +596,7 @@ class Collection extends Iterator
}
} else {
//Single value for access
- if (in_array($page->header()->access, $accessLevels)) {
+ if (\in_array($page->header()->access, $accessLevels)) {
$items[$path] = $slug;
}
}
diff --git a/system/src/Grav/Common/Page/Header.php b/system/src/Grav/Common/Page/Header.php
index 7df862b..e3854ae 100644
--- a/system/src/Grav/Common/Page/Header.php
+++ b/system/src/Grav/Common/Page/Header.php
@@ -1,8 +1,9 @@
true) for example
+ *
+ * @param array $var an Array of name value pairs where the name is the process and value is true or false
+ *
+ * @return array an Array of name value pairs where the name is the process and value is true or false
+ */
+ public function process($var = null);
+
+ /**
+ * Gets and Sets the slug for the Page. The slug is used in the URL routing. If not set it uses
+ * the parent folder from the path
+ *
+ * @param string $var the slug, e.g. 'my-blog'
+ *
+ * @return string the slug
+ */
+ public function slug($var = null);
+
+ /**
+ * Get/set order number of this page.
+ *
+ * @param int $var
+ *
+ * @return int|bool
+ */
+ public function order($var = null);
+
+ /**
+ * Gets and sets the identifier for this Page object.
+ *
+ * @param string $var the identifier
+ *
+ * @return string the identifier
+ */
+ public function id($var = null);
+
+ /**
+ * Gets and sets the modified timestamp.
+ *
+ * @param int $var modified unix timestamp
+ *
+ * @return int modified unix timestamp
+ */
+ public function modified($var = null);
+
+ /**
+ * Gets and sets the option to show the last_modified header for the page.
+ *
+ * @param boolean $var show last_modified header
+ *
+ * @return boolean show last_modified header
+ */
+ public function lastModified($var = null);
+
+ /**
+ * Get/set the folder.
+ *
+ * @param string $var Optional path
+ *
+ * @return string|null
+ */
+ public function folder($var = null);
+
+ /**
+ * Gets and sets the date for this Page object. This is typically passed in via the page headers
+ *
+ * @param string $var string representation of a date
+ *
+ * @return int unix timestamp representation of the date
+ */
+ public function date($var = null);
+
+ /**
+ * Gets and sets the date format for this Page object. This is typically passed in via the page headers
+ * using typical PHP date string structure - http://php.net/manual/en/function.date.php
+ *
+ * @param string $var string representation of a date format
+ *
+ * @return string string representation of a date format
+ */
+ public function dateformat($var = null);
+
+ /**
+ * Gets and sets the taxonomy array which defines which taxonomies this page identifies itself with.
+ *
+ * @param array $var an array of taxonomies
+ *
+ * @return array an array of taxonomies
+ */
+ public function taxonomy($var = null);
+
+ /**
+ * Gets the configured state of the processing method.
+ *
+ * @param string $process the process, eg "twig" or "markdown"
+ *
+ * @return bool whether or not the processing method is enabled for this Page
+ */
+ public function shouldProcess($process);
+
+ /**
+ * Returns whether or not this Page object has a .md file associated with it or if its just a directory.
+ *
+ * @return bool True if its a page with a .md file associated
+ */
+ public function isPage();
+
+ /**
+ * Returns whether or not this Page object is a directory or a page.
+ *
+ * @return bool True if its a directory
+ */
+ public function isDir();
+
+ /**
+ * Returns whether the page exists in the filesystem.
+ *
+ * @return bool
+ */
+ public function exists();
+}
diff --git a/system/src/Grav/Common/Page/Interfaces/PageInterface.php b/system/src/Grav/Common/Page/Interfaces/PageInterface.php
index 14d6cdc..24bf50c 100644
--- a/system/src/Grav/Common/Page/Interfaces/PageInterface.php
+++ b/system/src/Grav/Common/Page/Interfaces/PageInterface.php
@@ -1,9 +1,19 @@
save() in order to perform the move.
+ *
+ * @param PageInterface $parent New parent page.
+ *
+ * @return $this
+ */
+ public function move(PageInterface $parent);
+
+ /**
+ * Prepare a copy from the page. Copies also everything that's under the current page.
+ *
+ * Returns a new Page object for the copy.
+ * You need to call $this->save() in order to perform the move.
+ *
+ * @param PageInterface $parent New parent page.
+ *
+ * @return $this
+ */
+ public function copy(PageInterface $parent);
+
+ /**
+ * Get blueprints for the page.
+ *
+ * @return Blueprint
+ */
+ public function blueprints();
+
+ /**
+ * Get the blueprint name for this page. Use the blueprint form field if set
+ *
+ * @return string
+ */
+ public function blueprintName();
+
+ /**
+ * Validate page header.
+ *
+ * @throws Exception
+ */
+ public function validate();
+
+ /**
+ * Filter page header from illegal contents.
+ */
+ public function filter();
+
+ /**
+ * Get unknown header variables.
+ *
+ * @return array
+ */
+ public function extra();
+
+ /**
+ * Convert page to an array.
+ *
+ * @return array
+ */
+ public function toArray();
+
+ /**
+ * Convert page to YAML encoded string.
+ *
+ * @return string
+ */
+ public function toYaml();
+
+ /**
+ * Convert page to JSON encoded string.
+ *
+ * @return string
+ */
+ public function toJson();
+
+ /**
+ * Returns normalized list of name => form pairs.
+ *
+ * @return array
+ */
+ public function forms();
+
+ /**
+ * @param array $new
+ */
+ public function addForms(array $new);
+
+ /**
+ * Gets and sets the name field. If no name field is set, it will return 'default.md'.
+ *
+ * @param string $var The name of this page.
+ *
+ * @return string The name of this page.
+ */
+ public function name($var = null);
+
+ /**
+ * Returns child page type.
+ *
+ * @return string
+ */
+ public function childType();
+
+ /**
+ * Gets and sets the template field. This is used to find the correct Twig template file to render.
+ * If no field is set, it will return the name without the .md extension
+ *
+ * @param string $var the template name
+ *
+ * @return string the template name
+ */
+ public function template($var = null);
+
+ /**
+ * Allows a page to override the output render format, usually the extension provided
+ * in the URL. (e.g. `html`, `json`, `xml`, etc).
+ *
+ * @param null $var
+ *
+ * @return null
+ */
+ public function templateFormat($var = null);
+
+ /**
+ * Gets and sets the extension field.
+ *
+ * @param null $var
+ *
+ * @return null|string
+ */
+ public function extension($var = null);
+
+ /**
+ * Gets and sets the expires field. If not set will return the default
+ *
+ * @param int $var The new expires value.
+ *
+ * @return int The expires value
+ */
+ public function expires($var = null);
+
+ /**
+ * Gets and sets the cache-control property. If not set it will return the default value (null)
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control for more details on valid options
+ *
+ * @param null $var
+ * @return null
+ */
+ public function cacheControl($var = null);
+
+ public function ssl($var = null);
+
+ /**
+ * Returns the state of the debugger override etting for this page
+ *
+ * @return mixed
+ */
+ public function debugger();
+
+ /**
+ * Function to merge page metadata tags and build an array of Metadata objects
+ * that can then be rendered in the page.
+ *
+ * @param array $var an Array of metadata values to set
+ *
+ * @return array an Array of metadata values for the page
+ */
+ public function metadata($var = null);
+
+ /**
+ * Gets and sets the option to show the etag header for the page.
+ *
+ * @param bool $var show etag header
+ *
+ * @return bool show etag header
+ */
+ public function eTag($var = null);
+
+ /**
+ * Gets and sets the path to the .md file for this Page object.
+ *
+ * @param string $var the file path
+ *
+ * @return string|null the file path
+ */
+ public function filePath($var = null);
+
+ /**
+ * Gets the relative path to the .md file
+ *
+ * @return string The relative file path
+ */
+ public function filePathClean();
+
+ /**
+ * Gets and sets the order by which any sub-pages should be sorted.
+ *
+ * @param string $var the order, either "asc" or "desc"
+ *
+ * @return string the order, either "asc" or "desc"
+ * @deprecated 1.6
+ */
+ public function orderDir($var = null);
+
+ /**
+ * Gets and sets the order by which the sub-pages should be sorted.
+ *
+ * default - is the order based on the file system, ie 01.Home before 02.Advark
+ * title - is the order based on the title set in the pages
+ * date - is the order based on the date set in the pages
+ * folder - is the order based on the name of the folder with any numerics omitted
+ *
+ * @param string $var supported options include "default", "title", "date", and "folder"
+ *
+ * @return string supported options include "default", "title", "date", and "folder"
+ * @deprecated 1.6
+ */
+ public function orderBy($var = null);
+
+ /**
+ * Gets the manual order set in the header.
+ *
+ * @param string $var supported options include "default", "title", "date", and "folder"
+ *
+ * @return array
+ * @deprecated 1.6
+ */
+ public function orderManual($var = null);
+
+ /**
+ * Gets and sets the maxCount field which describes how many sub-pages should be displayed if the
+ * sub_pages header property is set for this page object.
+ *
+ * @param int $var the maximum number of sub-pages
+ *
+ * @return int the maximum number of sub-pages
+ * @deprecated 1.6
+ */
+ public function maxCount($var = null);
+
+ /**
+ * Gets and sets the modular var that helps identify this page is a modular child
+ *
+ * @param bool $var true if modular_twig
+ *
+ * @return bool true if modular_twig
+ */
+ public function modular($var = null);
+
+ /**
+ * Gets and sets the modular_twig var that helps identify this page as a modular child page that will need
+ * twig processing handled differently from a regular page.
+ *
+ * @param bool $var true if modular_twig
+ *
+ * @return bool true if modular_twig
+ */
+ public function modularTwig($var = null);
+
+ /**
+ * Returns children of this page.
+ *
+ * @return \Grav\Common\Page\Collection
+ */
+ public function children();
+
+ /**
+ * Check to see if this item is the first in an array of sub-pages.
+ *
+ * @return bool True if item is first.
+ */
+ public function isFirst();
+
+ /**
+ * Check to see if this item is the last in an array of sub-pages.
+ *
+ * @return bool True if item is last
+ */
+ public function isLast();
+
+ /**
+ * Gets the previous sibling based on current position.
+ *
+ * @return PageInterface the previous Page item
+ */
+ public function prevSibling();
+
+ /**
+ * Gets the next sibling based on current position.
+ *
+ * @return PageInterface the next Page item
+ */
+ public function nextSibling();
+
+ /**
+ * Returns the adjacent sibling based on a direction.
+ *
+ * @param int $direction either -1 or +1
+ *
+ * @return PageInterface|bool the sibling page
+ */
+ public function adjacentSibling($direction = 1);
+
+ /**
+ * Helper method to return an ancestor page.
+ *
+ * @param bool $lookup Name of the parent folder
+ *
+ * @return PageInterface page you were looking for if it exists
+ */
+ public function ancestor($lookup = null);
+
+ /**
+ * Helper method to return an ancestor page to inherit from. The current
+ * page object is returned.
+ *
+ * @param string $field Name of the parent folder
+ *
+ * @return PageInterface
+ */
+ public function inherited($field);
+
+ /**
+ * 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
+ *
+ * @return array
+ */
+ public function inheritedField($field);
+
+ /**
+ * Helper method to return a page.
+ *
+ * @param string $url the url of the page
+ * @param bool $all
+ *
+ * @return PageInterface page you were looking for if it exists
+ */
+ public function find($url, $all = false);
+
+ /**
+ * Get a collection of pages in the current context.
+ *
+ * @param string|array $params
+ * @param bool $pagination
+ *
+ * @return Collection
+ * @throws \InvalidArgumentException
+ */
+ public function collection($params = 'content', $pagination = true);
+
+ /**
+ * @param string|array $value
+ * @param bool $only_published
+ * @return mixed
+ * @internal
+ */
+ public function evaluate($value, $only_published = true);
+
+ /**
+ * Returns whether or not the current folder exists
+ *
+ * @return bool
+ */
+ public function folderExists();
+
+ /**
+ * Gets the Page Unmodified (original) version of the page.
+ *
+ * @return PageInterface The original version of the page.
+ */
+ public function getOriginal();
+
+ /**
+ * Gets the action.
+ *
+ * @return string The Action string.
+ */
+ public function getAction();
+}
diff --git a/system/src/Grav/Common/Page/Interfaces/PageRoutableInterface.php b/system/src/Grav/Common/Page/Interfaces/PageRoutableInterface.php
new file mode 100644
index 0000000..c3afeab
--- /dev/null
+++ b/system/src/Grav/Common/Page/Interfaces/PageRoutableInterface.php
@@ -0,0 +1,188 @@
+path = $path;
+ $this->setPath($path);
$this->media_order = $media_order;
$this->__wakeup();
- $this->init();
+ if ($load) {
+ $this->init();
+ }
}
/**
@@ -72,37 +75,40 @@ class Media extends AbstractMedia
*/
protected function init()
{
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
$config = Grav::instance()['config'];
+ $locator = Grav::instance()['locator'];
$exif_reader = isset(Grav::instance()['exif']) ? Grav::instance()['exif']->getReader() : false;
$media_types = array_keys(Grav::instance()['config']->get('media.types'));
// Handle special cases where page doesn't exist in filesystem.
- if (!is_dir($this->path)) {
+ if (!is_dir($this->getPath())) {
return;
}
- $iterator = new \FilesystemIterator($this->path, \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
+ $iterator = new \FilesystemIterator($this->getPath(), \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
$media = [];
/** @var \DirectoryIterator $info */
foreach ($iterator as $path => $info) {
// Ignore folders and Markdown files.
- if (!$info->isFile() || $info->getExtension() === 'md' || $info->getFilename()[0] === '.') {
+ if (!$info->isFile() || $info->getExtension() === 'md' || strpos($info->getFilename(), '.') === 0) {
continue;
}
// Find out what type we're dealing with
list($basename, $ext, $type, $extra) = $this->getFileParts($info->getFilename());
- if (!in_array(strtolower($ext), $media_types)) {
+ if (!\in_array(strtolower($ext), $media_types, true)) {
continue;
}
if ($type === 'alternative') {
- $media["{$basename}.{$ext}"][$type][$extra] = [ 'file' => $path, 'size' => $info->getSize() ];
+ $media["{$basename}.{$ext}"][$type][$extra] = ['file' => $path, 'size' => $info->getSize()];
} else {
- $media["{$basename}.{$ext}"][$type] = [ 'file' => $path, 'size' => $info->getSize() ];
+ $media["{$basename}.{$ext}"][$type] = ['file' => $path, 'size' => $info->getSize()];
}
}
@@ -147,7 +153,7 @@ class Media extends AbstractMedia
if (file_exists($meta_path)) {
$types['meta']['file'] = $meta_path;
- } elseif ($file_path && $medium->get('mime') === 'image/jpeg' && empty($types['meta']) && $config->get('system.media.auto_metadata_exif') && $exif_reader) {
+ } elseif ($file_path && $exif_reader && $medium->get('mime') === 'image/jpeg' && empty($types['meta']) && $config->get('system.media.auto_metadata_exif')) {
$meta = $exif_reader->read($file_path);
@@ -155,7 +161,11 @@ class Media extends AbstractMedia
$meta_data = $meta->getData();
$meta_trimmed = array_diff_key($meta_data, array_flip($this->standard_exif));
if ($meta_trimmed) {
- $file = File::instance($meta_path);
+ if ($locator->isStream($meta_path)) {
+ $file = File::instance($locator->findResource($meta_path, true, true));
+ } else {
+ $file = File::instance($meta_path);
+ }
$file->save(Yaml::dump($meta_trimmed));
$types['meta']['file'] = $meta_path;
}
@@ -202,12 +212,11 @@ class Media extends AbstractMedia
}
/**
- * Enable accessing the media path
- *
- * @return mixed
+ * @return string
+ * @deprecated 1.6 Use $this->getPath() instead.
*/
public function path()
{
- return $this->path;
+ return $this->getPath();
}
}
diff --git a/system/src/Grav/Common/Page/Medium/AbstractMedia.php b/system/src/Grav/Common/Page/Medium/AbstractMedia.php
index 58f9886..0ad4573 100644
--- a/system/src/Grav/Common/Page/Medium/AbstractMedia.php
+++ b/system/src/Grav/Common/Page/Medium/AbstractMedia.php
@@ -1,30 +1,55 @@
path;
+ }
+
+ public function setPath(?string $path)
+ {
+ $this->path = $path;
+ }
+
/**
* Get medium by filename.
*
@@ -48,73 +73,78 @@ abstract class AbstractMedia extends Getters implements MediaCollectionInterface
}
/**
- * @param mixed $offset
+ * Set file modification timestamps (query params) for all the media files.
*
- * @return mixed
+ * @param string|int|null $timestamp
+ * @return $this
*/
- public function offsetGet($offset)
+ public function setTimestamps($timestamp = null)
{
- $object = parent::offsetGet($offset);
+ /** @var Medium $instance */
+ foreach ($this->items as $instance) {
+ $instance->setTimestamp($timestamp);
+ }
- // It would be nice if previous image modification would not affect the later ones.
- //$object = $object ? clone($object) : null;
-
- return $object;
+ return $this;
}
/**
* Get a list of all media.
*
- * @return array|MediaObjectInterface[]
+ * @return MediaObjectInterface[]
*/
public function all()
{
- $this->instances = $this->orderMedia($this->instances);
+ $this->items = $this->orderMedia($this->items);
- return $this->instances;
+ return $this->items;
}
/**
* Get a list of all image media.
*
- * @return array|MediaObjectInterface[]
+ * @return MediaObjectInterface[]
*/
public function images()
{
$this->images = $this->orderMedia($this->images);
+
return $this->images;
}
/**
* Get a list of all video media.
*
- * @return array|MediaObjectInterface[]
+ * @return MediaObjectInterface[]
*/
public function videos()
{
$this->videos = $this->orderMedia($this->videos);
+
return $this->videos;
}
/**
* Get a list of all audio media.
*
- * @return array|MediaObjectInterface[]
+ * @return MediaObjectInterface[]
*/
public function audios()
{
$this->audios = $this->orderMedia($this->audios);
+
return $this->audios;
}
/**
* Get a list of all file media.
*
- * @return array|MediaObjectInterface[]
+ * @return MediaObjectInterface[]
*/
public function files()
{
$this->files = $this->orderMedia($this->files);
+
return $this->files;
}
@@ -122,9 +152,9 @@ abstract class AbstractMedia extends Getters implements MediaCollectionInterface
* @param string $name
* @param MediaObjectInterface $file
*/
- protected function add($name, $file)
+ public function add($name, $file)
{
- $this->instances[$name] = $file;
+ $this->offsetSet($name, $file);
switch ($file->type) {
case 'image':
$this->images[$name] = $file;
@@ -143,13 +173,14 @@ abstract class AbstractMedia extends Getters implements MediaCollectionInterface
/**
* Order the media based on the page's media_order
*
- * @param $media
+ * @param array $media
* @return array
*/
protected function orderMedia($media)
{
if (null === $this->media_order) {
- $page = Grav::instance()['pages']->get($this->path);
+ /** @var Page $page */
+ $page = Grav::instance()['pages']->get($this->getPath());
if ($page && isset($page->header()->media_order)) {
$this->media_order = array_map('trim', explode(',', $page->header()->media_order));
diff --git a/system/src/Grav/Common/Page/Medium/AudioMedium.php b/system/src/Grav/Common/Page/Medium/AudioMedium.php
index aae3597..74ef746 100644
--- a/system/src/Grav/Common/Page/Medium/AudioMedium.php
+++ b/system/src/Grav/Common/Page/Medium/AudioMedium.php
@@ -1,8 +1,9 @@
attributes['controls'] = true;
- }
- else
- {
+ } else {
unset($this->attributes['controls']);
}
+
return $this;
}
/**
* Allows to set the preload behaviour
*
- * @param $preload
+ * @param string $preload
* @return $this
*/
public function preload($preload)
{
- $validPreloadAttrs = array('auto','metadata','none');
+ $validPreloadAttrs = ['auto', 'metadata', 'none'];
- if (in_array($preload, $validPreloadAttrs))
- {
+ if (\in_array($preload, $validPreloadAttrs, true)) {
$this->attributes['preload'] = $preload;
}
+
return $this;
}
@@ -70,13 +69,14 @@ class AudioMedium extends Medium
* Allows to set the controlsList behaviour
* Separate multiple values with a hyphen
*
- * @param $controlsList
+ * @param string $controlsList
* @return $this
*/
public function controlsList($controlsList)
{
$controlsList = str_replace('-', ' ', $controlsList);
$this->attributes['controlsList'] = $controlsList;
+
return $this;
}
@@ -88,14 +88,12 @@ class AudioMedium extends Medium
*/
public function muted($status = false)
{
- if($status)
- {
+ if($status) {
$this->attributes['muted'] = true;
- }
- else
- {
+ } else {
unset($this->attributes['muted']);
}
+
return $this;
}
@@ -107,14 +105,12 @@ class AudioMedium extends Medium
*/
public function loop($status = false)
{
- if($status)
- {
+ if($status) {
$this->attributes['loop'] = true;
- }
- else
- {
+ } else {
unset($this->attributes['loop']);
}
+
return $this;
}
@@ -126,14 +122,12 @@ class AudioMedium extends Medium
*/
public function autoplay($status = false)
{
- if($status)
- {
+ if($status) {
$this->attributes['autoplay'] = true;
- }
- else
- {
+ } else {
unset($this->attributes['autoplay']);
}
+
return $this;
}
@@ -148,6 +142,7 @@ class AudioMedium extends Medium
parent::reset();
$this->attributes['controls'] = true;
+
return $this;
}
}
diff --git a/system/src/Grav/Common/Page/Medium/GlobalMedia.php b/system/src/Grav/Common/Page/Medium/GlobalMedia.php
index 74adc08..9fb30e9 100644
--- a/system/src/Grav/Common/Page/Medium/GlobalMedia.php
+++ b/system/src/Grav/Common/Page/Medium/GlobalMedia.php
@@ -1,8 +1,9 @@
guessType();
@@ -49,21 +51,16 @@ class ImageFile extends Image
}
// Computes the hash
- $this->hash = $this->getHash($type, $quality);
+ $this->hash = $this->getHash($type, $quality, $extras);
- // Generates the cache file
- $cacheFile = '';
+ // Seo friendly image names
+ $seofriendly = Grav::instance()['config']->get('system.images.seofriendly', false);
- if (!$this->prettyName || $this->prettyPrefix) {
- $cacheFile .= $this->hash;
- }
-
- if ($this->prettyPrefix) {
- $cacheFile .= '-';
- }
-
- if ($this->prettyName) {
- $cacheFile .= $this->prettyName;
+ if ($seofriendly) {
+ $mini_hash = substr($this->hash, 0, 4) . substr($this->hash, -4);
+ $cacheFile = "{$this->prettyName}-{$mini_hash}";
+ } else {
+ $cacheFile = "{$this->hash}-{$this->prettyName}";
}
$cacheFile .= '.' . $type;
@@ -107,4 +104,42 @@ class ImageFile extends Image
return $this->getFilename($file);
}
+
+ /**
+ * Gets the hash.
+ * @param string $type
+ * @param int $quality
+ * @param [] $extras
+ * @return null
+ */
+ public function getHash($type = 'guess', $quality = 80, $extras = [])
+ {
+ if (null === $this->hash) {
+ $this->generateHash($type, $quality, $extras);
+ }
+
+ return $this->hash;
+ }
+
+ /**
+ * Generates the hash.
+ * @param string $type
+ * @param int $quality
+ * @param array $extras
+ */
+ public function generateHash($type = 'guess', $quality = 80, $extras = [])
+ {
+ $inputInfos = $this->source->getInfos();
+
+ $datas = array(
+ $inputInfos,
+ $this->serializeOperations(),
+ $type,
+ $quality,
+ $extras
+ );
+
+ $this->hash = sha1(serialize($datas));
+ }
+
}
diff --git a/system/src/Grav/Common/Page/Medium/ImageMedium.php b/system/src/Grav/Common/Page/Medium/ImageMedium.php
index 72f23b4..ac29219 100644
--- a/system/src/Grav/Common/Page/Medium/ImageMedium.php
+++ b/system/src/Grav/Common/Page/Medium/ImageMedium.php
@@ -1,8 +1,9 @@
get('filepath')) === 0) {
+ $path = $this->get('filepath');
+ if (!$path || !file_exists($path) || !filesize($path)) {
return;
}
- $image_info = getimagesize($this->get('filepath'));
+ $image_info = getimagesize($path);
+
$this->def('width', $image_info[0]);
$this->def('height', $image_info[1]);
$this->def('mime', $image_info['mime']);
@@ -119,7 +122,7 @@ class ImageMedium extends Medium
/**
* Add meta file for the medium.
*
- * @param $filepath
+ * @param string $filepath
* @return $this
*/
public function addMetaFile($filepath)
@@ -185,7 +188,7 @@ class ImageMedium extends Medium
$this->reset();
}
- return trim(Grav::instance()['base_url'] . '/' . ltrim($output . $this->querystring() . $this->urlHash(), '/'), '\\');
+ return trim(Grav::instance()['base_url'] . '/' . $this->urlQuerystring($output), '\\');
}
/**
@@ -231,7 +234,7 @@ class ImageMedium extends Medium
/**
* Allows the ability to override the Inmage's Pretty name stored in cache
*
- * @param $name
+ * @param string $name
*/
public function setImagePrettyName($name)
{
@@ -260,11 +263,12 @@ class ImageMedium extends Medium
* widths. Existing image alternatives won't be overwritten.
*
* @param int|int[] $min_width
- * @param int [$max_width=2500]
- * @param int [$step=200]
+ * @param int $max_width
+ * @param int $step
* @return $this
*/
- public function derivatives($min_width, $max_width = 2500, $step = 200) {
+ public function derivatives($min_width, $max_width = 2500, $step = 200)
+ {
if (!empty($this->alternatives)) {
$max = max(array_keys($this->alternatives));
$base = $this->alternatives[$max];
@@ -331,7 +335,7 @@ class ImageMedium extends Medium
* Parsedown element for source display mode
*
* @param array $attributes
- * @param boolean $reset
+ * @param bool $reset
* @return array
*/
public function sourceParsedownElement(array $attributes, $reset = true)
@@ -344,7 +348,7 @@ class ImageMedium extends Medium
$attributes['sizes'] = $this->sizes();
}
- return [ 'name' => 'img', 'attributes' => $attributes ];
+ return ['name' => 'img', 'attributes' => $attributes];
}
/**
@@ -358,7 +362,7 @@ class ImageMedium extends Medium
if ($this->image) {
$this->image();
- $this->querystring('');
+ $this->medium_querystring = [];
$this->filter();
$this->clearAlternatives();
}
@@ -374,7 +378,7 @@ class ImageMedium extends Medium
/**
* Turn the current Medium into a Link
*
- * @param boolean $reset
+ * @param bool $reset
* @param array $attributes
* @return Link
*/
@@ -394,7 +398,7 @@ class ImageMedium extends Medium
*
* @param int $width
* @param int $height
- * @param boolean $reset
+ * @param bool $reset
* @return Link
*/
public function lightbox($width = null, $height = null, $reset = true)
@@ -404,7 +408,7 @@ class ImageMedium extends Medium
}
if ($width && $height) {
- $this->cropResize($width, $height);
+ $this->__call('cropResize', [$width, $height]);
}
return parent::lightbox($width, $height, $reset);
@@ -414,7 +418,7 @@ class ImageMedium extends Medium
* Sets or gets the quality of the image
*
* @param int $quality 0-100 quality
- * @return Medium
+ * @return int|$this
*/
public function quality($quality = null)
{
@@ -424,6 +428,7 @@ class ImageMedium extends Medium
}
$this->quality = $quality;
+
return $this;
}
@@ -443,6 +448,7 @@ class ImageMedium extends Medium
}
$this->format = $format;
+
return $this;
}
@@ -457,6 +463,7 @@ class ImageMedium extends Medium
if ($sizes) {
$this->sizes = $sizes;
+
return $this;
}
@@ -477,10 +484,12 @@ class ImageMedium extends Medium
*/
public function width($value = 'auto')
{
- if (!$value || $value === 'auto')
+ if (!$value || $value === 'auto') {
$this->attributes['width'] = $this->get('width');
- else
+ } else {
$this->attributes['width'] = $value;
+ }
+
return $this;
}
@@ -498,10 +507,12 @@ class ImageMedium extends Medium
*/
public function height($value = 'auto')
{
- if (!$value || $value === 'auto')
+ if (!$value || $value === 'auto') {
$this->attributes['height'] = $this->get('height');
- else
+ } else {
$this->attributes['height'] = $value;
+ }
+
return $this;
}
@@ -608,7 +619,7 @@ class ImageMedium extends Medium
$this->image->merge(ImageFile::open($overlay));
}
- return $this->image->cacheFile($this->format, $this->quality);
+ return $this->image->cacheFile($this->format, $this->quality, false, [$this->get('width'), $this->get('height'), $this->get('modified')]);
}
/**
diff --git a/system/src/Grav/Common/Page/Medium/Link.php b/system/src/Grav/Common/Page/Medium/Link.php
index 15aac7b..6978635 100644
--- a/system/src/Grav/Common/Page/Medium/Link.php
+++ b/system/src/Grav/Common/Page/Medium/Link.php
@@ -1,8 +1,9 @@
get('system.media.enable_media_timestamp', true)) {
- $this->querystring('&' . Grav::instance()['cache']->getKey());
+ $this->timestamp = Grav::instance()['cache']->getKey();
}
$this->def('mime', 'application/octet-stream');
@@ -112,6 +127,49 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
return false;
}
+ /**
+ * Get file modification time for the medium.
+ *
+ * @return int|null
+ */
+ public function modified()
+ {
+ $path = $this->get('filepath');
+
+ if (!file_exists($path)) {
+ return null;
+ }
+
+ return filemtime($path) ?: null;
+ }
+
+ /**
+ * @return int
+ */
+ public function size()
+ {
+ $path = $this->get('filepath');
+
+ if (!file_exists($path)) {
+ return 0;
+ }
+
+ return filesize($path) ?: 0;
+ }
+
+ /**
+ * Set querystring to file modification timestamp (or value provided as a parameter).
+ *
+ * @param string|int|null $timestamp
+ * @return $this
+ */
+ public function setTimestamp($timestamp = null)
+ {
+ $this->timestamp = (string)($timestamp ?? $this->modified());
+
+ return $this;
+ }
+
/**
* Returns an array containing just the metadata
*
@@ -125,7 +183,7 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
/**
* Add meta file for the medium.
*
- * @param $filepath
+ * @param string $filepath
*/
public function addMetaFile($filepath)
{
@@ -136,7 +194,7 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
/**
* Add alternative Medium to this Medium.
*
- * @param $ratio
+ * @param int|float $ratio
* @param Medium $alternative
*/
public function addAlternative($ratio, Medium $alternative)
@@ -184,11 +242,18 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
*/
public function relativePath($reset = true)
{
+ $output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $this->get('filepath'));
+
+ $locator = Grav::instance()['locator'];
+ if ($locator->isStream($output)) {
+ $output = $locator->findResource($output, false);
+ }
+
if ($reset) {
$this->reset();
}
- return str_replace(GRAV_ROOT, '', $this->get('filepath'));
+ return str_replace(GRAV_ROOT, '', $output);
}
/**
@@ -210,40 +275,60 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
$this->reset();
}
- return trim(Grav::instance()['base_url'] . '/' . ltrim($output . $this->querystring() . $this->urlHash(), '/'), '\\');
+ return trim(Grav::instance()['base_url'] . '/' . $this->urlQuerystring($output), '\\');
}
/**
* Get/set querystring for the file's url
*
* @param string $querystring
- * @param boolean $withQuestionmark
+ * @param bool $withQuestionmark
* @return string
*/
public function querystring($querystring = null, $withQuestionmark = true)
{
- if (!is_null($querystring)) {
- $this->set('querystring', ltrim($querystring, '?&'));
-
+ if (null !== $querystring) {
+ $this->medium_querystring[] = ltrim($querystring, '?&');
foreach ($this->alternatives as $alt) {
$alt->querystring($querystring, $withQuestionmark);
}
}
- $querystring = $this->get('querystring', '');
-
- if ($withQuestionmark && !empty($querystring)) {
- return '?' . $querystring;
- } else {
- return $querystring;
+ if (empty($this->medium_querystring)) {
+ return '';
}
+
+ // join the strings
+ $querystring = implode('&', $this->medium_querystring);
+ // explode all strings
+ $query_parts = explode('&', $querystring);
+ // Join them again now ensure the elements are unique
+ $querystring = implode('&', array_unique($query_parts));
+
+ return $withQuestionmark ? ('?' . $querystring) : $querystring;
+ }
+
+ /**
+ * Get the URL with full querystring
+ *
+ * @param string $url
+ * @return string
+ */
+ public function urlQuerystring($url)
+ {
+ $querystring = $this->querystring();
+ if (isset($this->timestamp) && !Utils::contains($querystring, $this->timestamp)) {
+ $querystring = empty($querystring) ? ('?' . $this->timestamp) : ($querystring . '&' . $this->timestamp);
+ }
+
+ return ltrim($url . $querystring . $this->urlHash(), '/');
}
/**
* Get/set hash for the file's url
*
* @param string $hash
- * @param boolean $withHash
+ * @param bool $withHash
* @return string
*/
public function urlHash($hash = null, $withHash = true)
@@ -254,11 +339,7 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
$hash = $this->get('urlHash', '');
- if ($withHash && !empty($hash)) {
- return '#' . $hash;
- } else {
- return $hash;
- }
+ return $withHash && !empty($hash) ? '#' . $hash : $hash;
}
/**
@@ -268,7 +349,7 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
* @param string $alt
* @param string $class
* @param string $id
- * @param boolean $reset
+ * @param bool $reset
* @return array
*/
public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true)
@@ -332,6 +413,8 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
case 'source':
$element = $this->sourceParsedownElement($attributes, false);
break;
+ default:
+ $element = [];
}
if ($reset) {
@@ -347,7 +430,7 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
* Parsedown element for source display mode
*
* @param array $attributes
- * @param boolean $reset
+ * @param bool $reset
* @return array
*/
protected function sourceParsedownElement(array $attributes, $reset = true)
@@ -359,7 +442,7 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
* Parsedown element for text display mode
*
* @param array $attributes
- * @param boolean $reset
+ * @param bool $reset
* @return array
*/
protected function textParsedownElement(array $attributes, $reset = true)
@@ -434,7 +517,7 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
*/
public function thumbnail($type = 'auto')
{
- if ($type !== 'auto' && !in_array($type, $this->thumbnailTypes)) {
+ if ($type !== 'auto' && !\in_array($type, $this->thumbnailTypes, true)) {
return $this;
}
@@ -451,7 +534,7 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
/**
* Turn the current Medium into a Link
*
- * @param boolean $reset
+ * @param bool $reset
* @param array $attributes
* @return Link
*/
@@ -475,7 +558,7 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
*
* @param int $width
* @param int $height
- * @param boolean $reset
+ * @param bool $reset
* @return Link
*/
public function lightbox($width = null, $height = null, $reset = true)
@@ -500,7 +583,7 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
{
$classes = func_get_args();
if (!empty($classes)) {
- $this->attributes['class'] = implode(',', (array)$classes);
+ $this->attributes['class'] = implode(',', $classes);
}
return $this;
@@ -510,7 +593,7 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
* Add an id to the element from Markdown or Twig
* Example: 
*
- * @param $id
+ * @param string $id
* @return $this
*/
public function id($id)
@@ -545,7 +628,7 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
public function __call($method, $args)
{
$qs = $method;
- if (count($args) > 1 || (count($args) == 1 && !empty($args[0]))) {
+ if (\count($args) > 1 || (\count($args) === 1 && !empty($args[0]))) {
$qs .= '=' . implode(',', array_map(function ($a) {
if (is_array($a)) {
$a = '[' . implode(',', $a) . ']';
diff --git a/system/src/Grav/Common/Page/Medium/MediumFactory.php b/system/src/Grav/Common/Page/Medium/MediumFactory.php
index b5b197e..dc372d8 100644
--- a/system/src/Grav/Common/Page/Medium/MediumFactory.php
+++ b/system/src/Grav/Common/Page/Medium/MediumFactory.php
@@ -1,8 +1,9 @@
get("media.types." . strtolower($ext));
- if (!$media_params) {
+ $media_params = $config->get('media.types.' . strtolower($ext));
+ if (!\is_array($media_params)) {
return null;
}
$params += $media_params;
// Add default settings for undefined variables.
- $params += $config->get('media.types.defaults');
+ $params += (array)$config->get('media.types.defaults');
+ $params += [
+ 'type' => 'file',
+ 'thumb' => 'media/thumb.png',
+ 'mime' => 'application/octet-stream',
+ 'filepath' => $file,
+ 'filename' => $filename,
+ 'basename' => $basename,
+ 'extension' => $ext,
+ 'path' => $path,
+ 'modified' => filemtime($file),
+ 'thumbnails' => []
+ ];
+
+ $locator = Grav::instance()['locator'];
+
+ $file = $locator->findResource("image://{$params['thumb']}");
+ if ($file) {
+ $params['thumbnails']['default'] = $file;
+ }
+
+ return static::fromArray($params);
+ }
+
+ /**
+ * Create Medium from an uploaded file
+ *
+ * @param FormFlashFile $uploadedFile
+ * @param array $params
+ * @return Medium
+ */
+ public static function fromUploadedFile(FormFlashFile $uploadedFile, array $params = [])
+ {
+ $parts = pathinfo($uploadedFile->getClientFilename());
+ $filename = $parts['basename'];
+ $ext = $parts['extension'];
+ $basename = $parts['filename'];
+ $file = $uploadedFile->getTmpFile();
+ $path = dirname($file);
+
+ $config = Grav::instance()['config'];
+
+ $media_params = $config->get('media.types.' . strtolower($ext));
+ if (!\is_array($media_params)) {
+ return null;
+ }
+
+ $params += $media_params;
+
+ // Add default settings for undefined variables.
+ $params += (array)$config->get('media.types.defaults');
$params += [
'type' => 'file',
'thumb' => 'media/thumb.png',
@@ -75,28 +127,22 @@ class MediumFactory
*/
public static function fromArray(array $items = [], Blueprint $blueprint = null)
{
- $type = isset($items['type']) ? $items['type'] : null;
+ $type = $items['type'] ?? null;
switch ($type) {
case 'image':
return new ImageMedium($items, $blueprint);
- break;
case 'thumbnail':
return new ThumbnailImageMedium($items, $blueprint);
- break;
case 'animated':
case 'vector':
return new StaticImageMedium($items, $blueprint);
- break;
case 'video':
return new VideoMedium($items, $blueprint);
- break;
case 'audio':
return new AudioMedium($items, $blueprint);
- break;
default:
return new Medium($items, $blueprint);
- break;
}
}
diff --git a/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php b/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php
index aaf6fde..627c361 100644
--- a/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php
+++ b/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php
@@ -1,8 +1,9 @@
url($reset);
- return [ 'name' => 'img', 'attributes' => $attributes ];
+ return ['name' => 'img', 'attributes' => $attributes];
}
}
diff --git a/system/src/Grav/Common/Page/Medium/StaticResizeTrait.php b/system/src/Grav/Common/Page/Medium/StaticResizeTrait.php
index 4e7d619..8d6d43f 100644
--- a/system/src/Grav/Common/Page/Medium/StaticResizeTrait.php
+++ b/system/src/Grav/Common/Page/Medium/StaticResizeTrait.php
@@ -1,8 +1,9 @@
bubble('thumbnail', [$type], false);
+
return $this->bubble('getThumbnail', [], false);
}
/**
* Turn the current Medium into a Link
*
- * @param boolean $reset
+ * @param bool $reset
* @param array $attributes
* @return Link
*/
@@ -103,7 +105,7 @@ class ThumbnailImageMedium extends ImageMedium
*
* @param int $width
* @param int $height
- * @param boolean $reset
+ * @param bool $reset
* @return Link
*/
public function lightbox($width = null, $height = null, $reset = true)
@@ -116,7 +118,7 @@ class ThumbnailImageMedium extends ImageMedium
*
* @param string $method
* @param array $arguments
- * @param boolean $testLinked
+ * @param bool $testLinked
* @return Medium
*/
protected function bubble($method, array $arguments = [], $testLinked = true)
diff --git a/system/src/Grav/Common/Page/Medium/VideoMedium.php b/system/src/Grav/Common/Page/Medium/VideoMedium.php
index 23271bf..c0b488a 100644
--- a/system/src/Grav/Common/Page/Medium/VideoMedium.php
+++ b/system/src/Grav/Common/Page/Medium/VideoMedium.php
@@ -1,8 +1,9 @@
attributes['autoplay'] = true;
+ if ($status) {
+ $this->attributes['autoplay'] = '';
} else {
unset($this->attributes['autoplay']);
}
@@ -94,6 +95,23 @@ class VideoMedium extends Medium
return $this;
}
+ /**
+ * Allows ability to set the preload option
+ *
+ * @param null $status
+ * @return $this
+ */
+ public function preload($status = null)
+ {
+ if ($status) {
+ $this->attributes['preload'] = $status;
+ } else {
+ unset($this->attributes['preload']);
+ }
+
+ return $this;
+ }
+
/**
* Allows to set the playsinline attribute
*
diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php
index 0e57b03..82ff71c 100644
--- a/system/src/Grav/Common/Page/Page.php
+++ b/system/src/Grav/Common/Page/Page.php
@@ -1,14 +1,14 @@
extension('.' . $file->getExtension());
+ } else {
+ $this->extension($extension);
+ }
+
+ // extract page language from page extension
+ $language = trim(basename($this->extension(), 'md'), '.') ?: null;
+ $this->language($language);
+
$this->hide_home_route = $config->get('system.home.hide_in_urls', false);
$this->home_route = $this->adjustRouteCase($config->get('system.home.alias'));
- $this->filePath($file->getPathName());
+ $this->filePath($file->getPathname());
$this->modified($file->getMTime());
$this->id($this->modified() . md5($this->filePath()));
$this->routable(true);
@@ -142,16 +158,6 @@ class Page implements PageInterface
$this->published();
$this->urlExtension();
- // some extension logic
- if (empty($extension)) {
- $this->extension('.' . $file->getExtension());
- } else {
- $this->extension($extension);
- }
-
- // extract page language from page extension
- $language = trim(basename($this->extension(), 'md'), '.') ?: null;
- $this->language($language);
return $this;
}
@@ -193,7 +199,7 @@ class Page implements PageInterface
$aPage = new Page();
$aPage->init(new \SplFileInfo($path), $language . '.md');
- $route = isset($aPage->header()->routes['default']) ? $aPage->header()->routes['default'] : $aPage->rawRoute();
+ $route = $aPage->header()->routes['default'] ?? $aPage->rawRoute();
if (!$route) {
$route = $aPage->route();
}
@@ -218,7 +224,7 @@ class Page implements PageInterface
*/
public function untranslatedLanguages($includeUnpublished = false)
{
- $filename = substr($this->name, 0, -(strlen($this->extension())));
+ $filename = substr($this->name, 0, -strlen($this->extension()));
$config = Grav::instance()['config'];
$languages = $config->get('system.languages.supported', []);
$untranslatedLanguages = [];
@@ -340,9 +346,9 @@ class Page implements PageInterface
$this->processFrontmatter();
}
}
- } catch (ParseException $e) {
+ } catch (\Exception $e) {
$file->raw(Grav::instance()['language']->translate([
- 'FRONTMATTER_ERROR_PAGE',
+ 'GRAV.FRONTMATTER_ERROR_PAGE',
$this->slug(),
$file->filename(),
$e->getMessage(),
@@ -360,10 +366,10 @@ class Page implements PageInterface
if ($var) {
if (isset($this->header->slug)) {
- $this->slug(($this->header->slug));
+ $this->slug($this->header->slug);
}
if (isset($this->header->routes)) {
- $this->routes = (array)($this->header->routes);
+ $this->routes = (array)$this->header->routes;
}
if (isset($this->header->title)) {
$this->title = trim($this->header->title);
@@ -408,12 +414,10 @@ class Page implements PageInterface
$this->markdown_extra = (bool)$this->header->markdown_extra;
}
if (isset($this->header->taxonomy)) {
- foreach ((array)$this->header->taxonomy as $taxonomy => $taxitems) {
- $this->taxonomy[$taxonomy] = (array)$taxitems;
- }
+ $this->taxonomy($this->header->taxonomy);
}
if (isset($this->header->max_count)) {
- $this->max_count = intval($this->header->max_count);
+ $this->max_count = (int)$this->header->max_count;
}
if (isset($this->header->process)) {
foreach ((array)$this->header->process as $process => $status) {
@@ -430,7 +434,7 @@ class Page implements PageInterface
$this->unpublishDate($this->header->unpublish_date);
}
if (isset($this->header->expires)) {
- $this->expires = intval($this->header->expires);
+ $this->expires = (int)$this->header->expires;
}
if (isset($this->header->cache_control)) {
$this->cache_control = $this->header->cache_control;
@@ -450,6 +454,9 @@ class Page implements PageInterface
if (isset($this->header->debugger)) {
$this->debugger = (bool)$this->header->debugger;
}
+ if (isset($this->header->append_url_extension)) {
+ $this->url_extension = $this->header->append_url_extension;
+ }
}
return $this->header;
@@ -458,7 +465,7 @@ class Page implements PageInterface
/**
* Get page language
*
- * @param $var
+ * @param string $var
*
* @return mixed
*/
@@ -474,20 +481,73 @@ class Page implements PageInterface
/**
* Modify a header value directly
*
- * @param $key
- * @param $value
+ * @param string $key
+ * @param mixed $value
*/
public function modifyHeader($key, $value)
{
$this->header->{$key} = $value;
}
+ /**
+ * @return int
+ */
+ public function httpResponseCode()
+ {
+ return (int)($this->header()->http_response_code ?? 200);
+ }
+
+ public function httpHeaders()
+ {
+ $headers = [];
+
+ $grav = Grav::instance();
+ $format = $this->templateFormat();
+ $cache_control = $this->cacheControl();
+ $expires = $this->expires();
+
+ // Set Content-Type header
+ $headers['Content-Type'] = Utils::getMimeByExtension($format, 'text/html');
+
+ // Calculate Expires Headers if set to > 0
+ if ($expires > 0) {
+ $expires_date = gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT';
+ if (!$cache_control) {
+ $headers['Cache-Control'] = 'max-age=' . $expires;
+ }
+ $headers['Expires'] = $expires_date;
+ }
+
+ // Set Cache-Control header
+ if ($cache_control) {
+ $headers['Cache-Control'] = strtolower($cache_control);
+ }
+
+ // Set Last-Modified header
+ if ($this->lastModified()) {
+ $last_modified_date = gmdate('D, d M Y H:i:s', $this->modified()) . ' GMT';
+ $headers['Last-Modified'] = $last_modified_date;
+ }
+
+ // Calculate ETag based on the raw file
+ if ($this->eTag()) {
+ $headers['ETag'] = '"' . md5($this->raw() . $this->modified()).'"';
+ }
+
+ // Set Vary: Accept-Encoding header
+ if ($grav['config']->get('system.pages.vary_accept_encoding', false)) {
+ $headers['Vary'] = 'Accept-Encoding';
+ }
+
+ return $headers;
+ }
+
/**
* Get the summary.
*
* @param int $size Max summary size.
*
- * @param boolean $textOnly Only count text size.
+ * @param bool $textOnly Only count text size.
*
* @return string
*/
@@ -551,7 +611,7 @@ class Page implements PageInterface
return mb_strimwidth($content, 0, $size, '...', 'utf-8');
}
- $summary = Utils::truncateHTML($content, $size);
+ $summary = Utils::truncateHtml($content, $size);
return html_entity_decode($summary);
}
@@ -613,13 +673,13 @@ class Page implements PageInterface
$process_markdown = $this->shouldProcess('markdown');
$process_twig = $this->shouldProcess('twig') || $this->modularTwig();
- $cache_enable = isset($this->header->cache_enable) ? $this->header->cache_enable : $config->get('system.cache.enabled',
+ $cache_enable = $this->header->cache_enable ?? $config->get('system.cache.enabled',
true);
- $twig_first = isset($this->header->twig_first) ? $this->header->twig_first : $config->get('system.pages.twig_first',
+ $twig_first = $this->header->twig_first ?? $config->get('system.pages.twig_first',
true);
// never cache twig means it's always run after content
- $never_cache_twig = isset($this->header->never_cache_twig) ? $this->header->never_cache_twig : $config->get('system.pages.never_cache_twig',
+ $never_cache_twig = $this->header->never_cache_twig ?? $config->get('system.pages.never_cache_twig',
false);
// if no cached-content run everything
@@ -687,6 +747,8 @@ class Page implements PageInterface
$this->content = str_replace("{$delimiter}
", '', $this->content);
}
+ // Fire event when Page::content() is called
+ Grav::instance()->fireEvent('onPageContent', new Event(['page' => $this]));
}
return $this->content;
@@ -709,8 +771,8 @@ class Page implements PageInterface
/**
* Add an entry to the page's contentMeta array
*
- * @param $name
- * @param $value
+ * @param string $name
+ * @param string $value
*/
public function addContentMeta($name, $value)
{
@@ -720,9 +782,9 @@ class Page implements PageInterface
/**
* Return the whole contentMeta array as it currently stands
*
- * @param null $name
+ * @param string|null $name
*
- * @return mixed
+ * @return string
*/
public function getContentMeta($name = null)
{
@@ -740,9 +802,9 @@ class Page implements PageInterface
/**
* Sets the whole content meta array in one shot
*
- * @param $content_meta
+ * @param array $content_meta
*
- * @return mixed
+ * @return array
*/
public function setContentMeta($content_meta)
{
@@ -812,13 +874,11 @@ class Page implements PageInterface
/**
* Needed by the onPageContentProcessed event to set the raw page content
*
- * @param $content
+ * @param string $content
*/
public function setRawContent($content)
{
- $content = $content === null ? '': $content;
-
- $this->content = $content;
+ $this->content = $content ?? '';
}
/**
@@ -835,7 +895,9 @@ class Page implements PageInterface
return $this->raw_content;
}
if ($name === 'route') {
- return $this->parent()->rawRoute();
+ $parent = $this->parent();
+
+ return $parent ? $parent->rawRoute() : '';
}
if ($name === 'order') {
$order = $this->order();
@@ -904,9 +966,9 @@ class Page implements PageInterface
/**
* Gets and Sets the Page raw content
*
- * @param null $var
+ * @param string|null $var
*
- * @return null
+ * @return string
*/
public function rawMarkdown($var = null)
{
@@ -962,11 +1024,11 @@ class Page implements PageInterface
*
* You need to call $this->save() in order to perform the move.
*
- * @param Page $parent New parent page.
+ * @param PageInterface $parent New parent page.
*
* @return $this
*/
- public function move(Page $parent)
+ public function move(PageInterface $parent)
{
if (!$this->_original) {
$clone = clone $this;
@@ -976,10 +1038,10 @@ class Page implements PageInterface
$this->_action = 'move';
if ($this->route() === $parent->route()) {
- throw new Exception('Failed: Cannot set page parent to self');
+ throw new \RuntimeException('Failed: Cannot set page parent to self');
}
if (Utils::startsWith($parent->rawRoute(), $this->rawRoute())) {
- throw new Exception('Failed: Cannot set page parent to a child of current page');
+ throw new \RuntimeException('Failed: Cannot set page parent to a child of current page');
}
$this->parent($parent);
@@ -1006,11 +1068,11 @@ class Page implements PageInterface
* Returns a new Page object for the copy.
* You need to call $this->save() in order to perform the move.
*
- * @param Page $parent New parent page.
+ * @param PageInterface $parent New parent page.
*
* @return $this
*/
- public function copy($parent)
+ public function copy(PageInterface $parent)
{
$this->move($parent);
$this->_action = 'copy';
@@ -1062,7 +1124,7 @@ class Page implements PageInterface
/**
* Validate page header.
*
- * @throws Exception
+ * @throws \Exception
*/
public function validate()
{
@@ -1135,6 +1197,87 @@ class Page implements PageInterface
return $this->id();
}
+ /**
+ * Returns normalized list of name => form pairs.
+ *
+ * @return array
+ */
+ public function forms()
+ {
+ if (null === $this->forms) {
+ $header = $this->header();
+
+ // Call event to allow filling the page header form dynamically (e.g. use case: Comments plugin)
+ $grav = Grav::instance();
+ $grav->fireEvent('onFormPageHeaderProcessed', new Event(['page' => $this, 'header' => $header]));
+
+ $rules = $header->rules ?? null;
+ if (!\is_array($rules)) {
+ $rules = [];
+ }
+
+ $forms = [];
+
+ // First grab page.header.form
+ $form = $this->normalizeForm($header->form ?? null, null, $rules);
+ if ($form) {
+ $forms[$form['name']] = $form;
+ }
+
+ // Append page.header.forms (override singular form if it clashes)
+ $headerForms = $header->forms ?? null;
+ if (\is_array($headerForms)) {
+ foreach ($headerForms as $name => $form) {
+ $form = $this->normalizeForm($form, $name, $rules);
+ if ($form) {
+ $forms[$form['name']] = $form;
+ }
+ }
+ }
+
+ $this->forms = $forms;
+ }
+
+ return $this->forms;
+ }
+
+ /**
+ * @param array $new
+ */
+ public function addForms(array $new)
+ {
+ // Initialize forms.
+ $this->forms();
+
+ foreach ($new as $form) {
+ $form = $this->normalizeForm($form);
+ if ($form) {
+ $this->forms[$form['name']] = $form;
+ }
+ }
+ }
+
+ protected function normalizeForm($form, $name = null, array $rules = [])
+ {
+ if (!\is_array($form)) {
+ return null;
+ }
+
+ // Ignore numeric indexes on name.
+ if (!$name || (string)(int)$name === (string)$name) {
+ $name = null;
+ }
+
+ $name = $name ?? $form['name'] ?? $this->slug();
+
+ $formRules = $form['rules'] ?? null;
+ if (!\is_array($formRules)) {
+ $formRules = [];
+ }
+
+ return ['name' => $name, 'rules' => $rules + $formRules] + $form;
+ }
+
/**
* Gets and sets the associated media as found in the page folder.
*
@@ -1186,7 +1329,7 @@ class Page implements PageInterface
$this->name = $var;
}
- return empty($this->name) ? 'default.md' : $this->name;
+ return $this->name ?: 'default.md';
}
/**
@@ -1231,12 +1374,47 @@ class Page implements PageInterface
{
if ($var !== null) {
$this->template_format = $var;
+ return $this->template_format;
}
- if (empty($this->template_format)) {
- $this->template_format = Grav::instance()['uri']->extension('html');
+ if (isset($this->template_format)) {
+ return $this->template_format;
}
+ // Set from URL extension set on page
+ $page_extension = trim($this->header->append_url_extension ?? '' , '.');
+ if (!empty($page_extension)) {
+ $this->template_format = $page_extension;
+
+ return $this->template_format;
+ }
+
+ // Set from uri extension
+ $uri_extension = Grav::instance()['uri']->extension();
+ if (is_string($uri_extension)) {
+ $this->template_format = $uri_extension;
+
+ return $this->template_format;
+ }
+
+ // Use content negotitation via the `accept:` header
+ $http_accept = $_SERVER['HTTP_ACCEPT'] ?? false;
+ if (is_string($http_accept)) {
+ $negotiator = new Negotiator();
+
+ $supported_types = Grav::instance()['config']->get('system.pages.types', ['html', 'json']);
+ $priorities = Utils::getMimeTypes($supported_types);
+
+ $media_type = $negotiator->getBest($http_accept, $priorities);
+ $mimetype = $media_type instanceof Accept ? $media_type->getValue() : '';
+
+ $this->template_format = Utils::getExtensionByMime($mimetype);
+
+ return $this->template_format;
+ }
+
+ // Last chance set a default type
+ $this->template_format = 'html';
return $this->template_format;
}
@@ -1272,9 +1450,8 @@ class Page implements PageInterface
}
// if not set in the page get the value from system config
- if (empty($this->url_extension)) {
- $this->url_extension = trim(isset($this->header->append_url_extension) ? $this->header->append_url_extension : Grav::instance()['config']->get('system.pages.append_url_extension',
- false));
+ if (null === $this->url_extension) {
+ $this->url_extension = Grav::instance()['config']->get('system.pages.append_url_extension', '');
}
return $this->url_extension;
@@ -1293,7 +1470,7 @@ class Page implements PageInterface
$this->expires = $var;
}
- return !isset($this->expires) ? Grav::instance()['config']->get('system.pages.expires') : $this->expires;
+ return $this->expires ?? Grav::instance()['config']->get('system.pages.expires');
}
/**
@@ -1309,7 +1486,7 @@ class Page implements PageInterface
$this->cache_control = $var;
}
- return !isset($this->cache_control) ? Grav::instance()['config']->get('system.pages.cache_control') : $this->cache_control;
+ return $this->cache_control ?? Grav::instance()['config']->get('system.pages.cache_control');
}
/**
@@ -1481,11 +1658,7 @@ class Page implements PageInterface
*/
public function debugger()
{
- if (isset($this->debugger) && $this->debugger === false) {
- return false;
- }
-
- return true;
+ return !(isset($this->debugger) && $this->debugger === false);
}
/**
@@ -1538,7 +1711,7 @@ class Page implements PageInterface
} else {
// If it this is a standard meta data type
if ($value) {
- if (in_array($key, $header_tag_http_equivs)) {
+ if (\in_array($key, $header_tag_http_equivs, true)) {
$this->metadata[$key] = [
'http_equiv' => $key,
'content' => htmlspecialchars($value, ENT_QUOTES, 'UTF-8')
@@ -1569,6 +1742,14 @@ class Page implements PageInterface
return $this->metadata;
}
+ /**
+ * Reset the metadata and pull from header again
+ */
+ public function resetMetadata()
+ {
+ $this->metadata = null;
+ }
+
/**
* Gets and Sets the slug for the Page. The slug is used in the URL routing. If not set it uses
* the parent folder from the path
@@ -1587,7 +1768,6 @@ class Page implements PageInterface
$this->slug = $this->adjustRouteCase(preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder)) ?: null;
}
-
return $this->slug;
}
@@ -1601,7 +1781,7 @@ class Page implements PageInterface
public function order($var = null)
{
if ($var !== null) {
- $order = !empty($var) ? sprintf('%02d.', (int)$var) : '';
+ $order = $var ? sprintf('%02d.', (int)$var) : '';
$this->folder($order . preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder));
return $order;
@@ -1609,7 +1789,7 @@ class Page implements PageInterface
preg_match(PAGE_ORDER_PREFIX_REGEX, $this->folder, $order);
- return isset($order[0]) ? $order[0] : false;
+ return $order[0] ?? false;
}
/**
@@ -1670,7 +1850,7 @@ class Page implements PageInterface
/** @var Config $config */
$config = $grav['config'];
- // get base route (multisite base and language)
+ // get base route (multi-site base and language)
$route = $include_base ? $pages->baseRoute() : '';
// add full route if configured to do so
@@ -1743,8 +1923,7 @@ class Page implements PageInterface
*/
public function unsetRouteSlug()
{
- unset($this->route);
- unset($this->slug);
+ unset($this->route, $this->slug);
}
/**
@@ -1761,7 +1940,8 @@ class Page implements PageInterface
}
if (empty($this->raw_route)) {
- $baseRoute = $this->parent ? (string)$this->parent()->rawRoute() : null;
+ $parent = $this->parent();
+ $baseRoute = $parent ? (string)$parent->rawRoute() : null;
$slug = $this->adjustRouteCase(preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder));
@@ -1866,9 +2046,9 @@ class Page implements PageInterface
/**
* Gets and sets the option to show the etag header for the page.
*
- * @param boolean $var show etag header
+ * @param bool $var show etag header
*
- * @return boolean show etag header
+ * @return bool show etag header
*/
public function eTag($var = null)
{
@@ -1885,9 +2065,9 @@ class Page implements PageInterface
/**
* Gets and sets the option to show the last_modified header for the page.
*
- * @param boolean $var show last_modified header
+ * @param bool $var show last_modified header
*
- * @return boolean show last_modified header
+ * @return bool show last_modified header
*/
public function lastModified($var = null)
{
@@ -1916,7 +2096,7 @@ class Page implements PageInterface
// Folder of the page.
$this->folder = basename(dirname($var));
// Path to the page.
- $this->path = dirname(dirname($var));
+ $this->path = dirname($var, 2);
}
return $this->path . '/' . $this->folder . '/' . ($this->name ?: '');
@@ -1929,9 +2109,7 @@ class Page implements PageInterface
*/
public function filePathClean()
{
- $path = str_replace(ROOT_DIR, '', $this->filePath());
-
- return $path;
+ return str_replace(ROOT_DIR, '', $this->filePath());
}
/**
@@ -1939,9 +2117,7 @@ class Page implements PageInterface
*/
public function relativePagePath()
{
- $path = str_replace('/' . $this->name(), '', $this->filePathClean());
-
- return $path;
+ return str_replace('/' . $this->name(), '', $this->filePathClean());
}
/**
@@ -2023,12 +2199,16 @@ class Page implements PageInterface
* @param string $var the order, either "asc" or "desc"
*
* @return string the order, either "asc" or "desc"
+ * @deprecated 1.6
*/
public function orderDir($var = null)
{
+ //user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED);
+
if ($var !== null) {
$this->order_dir = $var;
}
+
if (empty($this->order_dir)) {
$this->order_dir = 'asc';
}
@@ -2047,9 +2227,12 @@ class Page implements PageInterface
* @param string $var supported options include "default", "title", "date", and "folder"
*
* @return string supported options include "default", "title", "date", and "folder"
+ * @deprecated 1.6
*/
public function orderBy($var = null)
{
+ //user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED);
+
if ($var !== null) {
$this->order_by = $var;
}
@@ -2063,9 +2246,12 @@ class Page implements PageInterface
* @param string $var supported options include "default", "title", "date", and "folder"
*
* @return array
+ * @deprecated 1.6
*/
public function orderManual($var = null)
{
+ //user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED);
+
if ($var !== null) {
$this->order_manual = $var;
}
@@ -2080,9 +2266,12 @@ class Page implements PageInterface
* @param int $var the maximum number of sub-pages
*
* @return int the maximum number of sub-pages
+ * @deprecated 1.6
*/
public function maxCount($var = null)
{
+ //user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED);
+
if ($var !== null) {
$this->max_count = (int)$var;
}
@@ -2105,6 +2294,14 @@ class Page implements PageInterface
public function taxonomy($var = null)
{
if ($var !== null) {
+ // make sure first level are arrays
+ array_walk($var, function(&$value) {
+ $value = (array) $value;
+ });
+ // make sure all values are strings
+ array_walk_recursive($var, function(&$value) {
+ $value = (string) $value;
+ });
$this->taxonomy = $var;
}
@@ -2156,17 +2353,17 @@ class Page implements PageInterface
*/
public function shouldProcess($process)
{
- return isset($this->process[$process]) ? (bool)$this->process[$process] : false;
+ return (bool)($this->process[$process] ?? false);
}
/**
* Gets and Sets the parent object for this page
*
- * @param Page $var the parent page object
+ * @param PageInterface $var the parent page object
*
- * @return Page|null the parent page object if it exists.
+ * @return PageInterface|null the parent page object if it exists.
*/
- public function parent(Page $var = null)
+ public function parent(PageInterface $var = null)
{
if ($var) {
$this->parent = $var->path();
@@ -2183,7 +2380,7 @@ class Page implements PageInterface
/**
* Gets the top parent object for this page
*
- * @return Page|null the top parent page object if it exists.
+ * @return PageInterface|null the top parent page object if it exists.
*/
public function topParent()
{
@@ -2208,7 +2405,7 @@ class Page implements PageInterface
/**
* Returns children of this page.
*
- * @return \Grav\Common\Page\Collection
+ * @return Collection
*/
public function children()
{
@@ -2222,11 +2419,12 @@ class Page implements PageInterface
/**
* Check to see if this item is the first in an array of sub-pages.
*
- * @return boolean True if item is first.
+ * @return bool True if item is first.
*/
public function isFirst()
{
- $collection = $this->parent()->collection('content', false);
+ $parent = $this->parent();
+ $collection = $parent ? $parent->collection('content', false) : null;
if ($collection instanceof Collection) {
return $collection->isFirst($this->path());
}
@@ -2237,11 +2435,12 @@ class Page implements PageInterface
/**
* Check to see if this item is the last in an array of sub-pages.
*
- * @return boolean True if item is last
+ * @return bool True if item is last
*/
public function isLast()
{
- $collection = $this->parent()->collection('content', false);
+ $parent = $this->parent();
+ $collection = $parent ? $parent->collection('content', false) : null;
if ($collection instanceof Collection) {
return $collection->isLast($this->path());
}
@@ -2252,7 +2451,7 @@ class Page implements PageInterface
/**
* Gets the previous sibling based on current position.
*
- * @return Page the previous Page item
+ * @return PageInterface the previous Page item
*/
public function prevSibling()
{
@@ -2262,7 +2461,7 @@ class Page implements PageInterface
/**
* Gets the next sibling based on current position.
*
- * @return Page the next Page item
+ * @return PageInterface the next Page item
*/
public function nextSibling()
{
@@ -2272,13 +2471,14 @@ class Page implements PageInterface
/**
* Returns the adjacent sibling based on a direction.
*
- * @param integer $direction either -1 or +1
+ * @param int $direction either -1 or +1
*
- * @return Page|bool the sibling page
+ * @return PageInterface|bool the sibling page
*/
public function adjacentSibling($direction = 1)
{
- $collection = $this->parent()->collection('content', false);
+ $parent = $this->parent();
+ $collection = $parent ? $parent->collection('content', false) : null;
if ($collection instanceof Collection) {
return $collection->adjacentSibling($this->path(), $direction);
}
@@ -2289,18 +2489,17 @@ class Page implements PageInterface
/**
* Returns the item in the current position.
*
- * @param string $path the path the item
- *
- * @return Integer the index of the current page.
+ * @return int the index of the current page.
*/
public function currentPosition()
{
- $collection = $this->parent()->collection('content', false);
+ $parent = $this->parent();
+ $collection = $parent ? $parent->collection('content', false) : null;
if ($collection instanceof Collection) {
return $collection->currentPosition($this->path());
}
- return true;
+ return 1;
}
/**
@@ -2313,14 +2512,7 @@ class Page implements PageInterface
$uri_path = rtrim(urldecode(Grav::instance()['uri']->path()), '/') ?: '/';
$routes = Grav::instance()['pages']->routes();
- if (isset($routes[$uri_path])) {
- if ($routes[$uri_path] === $this->path()) {
- return true;
- }
-
- }
-
- return false;
+ return isset($routes[$uri_path]) && $routes[$uri_path] === $this->path();
}
/**
@@ -2337,7 +2529,7 @@ class Page implements PageInterface
$routes = Grav::instance()['pages']->routes();
if (isset($routes[$uri_path])) {
- /** @var Page $child_page */
+ /** @var PageInterface $child_page */
$child_page = $pages->dispatch($uri->route())->parent();
if ($child_page) {
while (!$child_page->root()) {
@@ -2360,9 +2552,8 @@ class Page implements PageInterface
public function home()
{
$home = Grav::instance()['config']->get('system.home.alias');
- $is_home = ($this->route() === $home || $this->rawRoute() === $home);
- return $is_home;
+ return $this->route() === $home || $this->rawRoute() === $home;
}
/**
@@ -2372,20 +2563,15 @@ class Page implements PageInterface
*/
public function root()
{
- if (!$this->parent && !$this->name && !$this->visible) {
- return true;
- }
-
- return false;
+ return !$this->parent && !$this->name && !$this->visible;
}
/**
* Helper method to return an ancestor page.
*
- * @param string $url The url of the page
* @param bool $lookup Name of the parent folder
*
- * @return \Grav\Common\Page\Page page you were looking for if it exists
+ * @return PageInterface page you were looking for if it exists
*/
public function ancestor($lookup = null)
{
@@ -2401,7 +2587,7 @@ class Page implements PageInterface
*
* @param string $field Name of the parent folder
*
- * @return Page
+ * @return PageInterface
*/
public function inherited($field)
{
@@ -2440,7 +2626,7 @@ class Page implements PageInterface
/** @var Pages $pages */
$inherited = $pages->inherited($this->route, $field);
- $inheritedParams = (array)$inherited->value('header.' . $field);
+ $inheritedParams = $inherited ? (array)$inherited->value('header.' . $field) : [];
$currentParams = (array)$this->value('header.' . $field);
if ($inheritedParams && is_array($inheritedParams)) {
$currentParams = array_replace_recursive($inheritedParams, $currentParams);
@@ -2455,7 +2641,7 @@ class Page implements PageInterface
* @param string $url the url of the page
* @param bool $all
*
- * @return \Grav\Common\Page\Page page you were looking for if it exists
+ * @return PageInterface page you were looking for if it exists
*/
public function find($url, $all = false)
{
@@ -2469,7 +2655,7 @@ class Page implements PageInterface
* Get a collection of pages in the current context.
*
* @param string|array $params
- * @param boolean $pagination
+ * @param bool $pagination
*
* @return Collection
* @throws \InvalidArgumentException
@@ -2505,7 +2691,7 @@ class Page implements PageInterface
/** @var Config $config */
$config = Grav::instance()['config'];
- $process_taxonomy = isset($params['url_taxonomy_filters']) ? $params['url_taxonomy_filters'] : $config->get('system.pages.url_taxonomy_filters');
+ $process_taxonomy = $params['url_taxonomy_filters'] ?? $config->get('system.pages.url_taxonomy_filters');
if ($process_taxonomy) {
foreach ((array)$config->get('site.taxonomies') as $taxonomy) {
@@ -2520,8 +2706,7 @@ class Page implements PageInterface
}
foreach ($items as $item) {
$item = rawurldecode($item);
- if (empty($page->taxonomy[$taxonomy]) || !in_array(htmlspecialchars_decode($item,
- ENT_QUOTES), $page->taxonomy[$taxonomy])
+ if (empty($page->taxonomy[$taxonomy]) || !\in_array(htmlspecialchars_decode($item, ENT_QUOTES), $page->taxonomy[$taxonomy], true)
) {
$collection->remove($page->path());
}
@@ -2537,12 +2722,9 @@ class Page implements PageInterface
// remove any inclusive sets from filer:
$sets = ['published', 'visible', 'modular', 'routable'];
foreach ($sets as $type) {
- if (isset($params['filter'][$type]) && isset($params['filter']['non-'.$type])) {
- if ($params['filter'][$type] && $params['filter']['non-'.$type]) {
- unset ($params['filter'][$type]);
- unset ($params['filter']['non-'.$type]);
- }
-
+ $var = "non-{$type}";
+ if (isset($params['filter'][$type], $params['filter'][$var]) && $params['filter'][$type] && $params['filter'][$var]) {
+ unset ($params['filter'][$type], $params['filter'][$var]);
}
}
@@ -2602,17 +2784,17 @@ class Page implements PageInterface
}
if (isset($params['dateRange'])) {
- $start = isset($params['dateRange']['start']) ? $params['dateRange']['start'] : 0;
- $end = isset($params['dateRange']['end']) ? $params['dateRange']['end'] : false;
- $field = isset($params['dateRange']['field']) ? $params['dateRange']['field'] : false;
+ $start = $params['dateRange']['start'] ?? 0;
+ $end = $params['dateRange']['end'] ?? false;
+ $field = $params['dateRange']['field'] ?? false;
$collection->dateRange($start, $end, $field);
}
if (isset($params['order'])) {
- $by = isset($params['order']['by']) ? $params['order']['by'] : 'default';
- $dir = isset($params['order']['dir']) ? $params['order']['dir'] : 'asc';
- $custom = isset($params['order']['custom']) ? $params['order']['custom'] : null;
- $sort_flags = isset($params['order']['sort_flags']) ? $params['order']['sort_flags'] : null;
+ $by = $params['order']['by'] ?? 'default';
+ $dir = $params['order']['dir'] ?? 'asc';
+ $custom = $params['order']['custom'] ?? null;
+ $sort_flags = $params['order']['sort_flags'] ?? null;
if (is_array($sort_flags)) {
$sort_flags = array_map('constant', $sort_flags); //transform strings to constant value
@@ -2625,7 +2807,7 @@ class Page implements PageInterface
}
/** @var Grav $grav */
- $grav = Grav::instance()['grav'];
+ $grav = Grav::instance();
// New Custom event to handle things like pagination.
$grav->fireEvent('onCollectionProcessed', new Event(['collection' => $collection]));
@@ -2634,7 +2816,7 @@ class Page implements PageInterface
if ($pagination) {
$params = $collection->params();
- $limit = isset($params['limit']) ? $params['limit'] : 0;
+ $limit = $params['limit'] ?? 0;
$start = !empty($params['pagination']) ? ($uri->currentPage() - 1) * $limit : 0;
if ($limit && $collection->count() > $limit) {
@@ -2649,7 +2831,6 @@ class Page implements PageInterface
* @param string|array $value
* @param bool $only_published
* @return mixed
- * @internal
*/
public function evaluate($value, $only_published = true)
{
@@ -2865,7 +3046,7 @@ class Page implements PageInterface
/**
* Reorders all siblings according to a defined order
*
- * @param $new_order
+ * @param array|null $new_order
*/
protected function doReorder($new_order)
{
@@ -2878,28 +3059,32 @@ class Page implements PageInterface
$this->_original->path($this->path());
- $siblings = $this->parent()->children();
- $siblings->order('slug', 'asc', $new_order);
+ $parent = $this->parent();
+ $siblings = $parent ? $parent->children() : null;
- $counter = 0;
+ if ($siblings) {
+ $siblings->order('slug', 'asc', $new_order);
- // Reorder all moved pages.
- foreach ($siblings as $slug => $page) {
- $order = (int)trim($page->order(), '.');
- $counter++;
+ $counter = 0;
- if ($order) {
- if ($page->path() === $this->path() && $this->folderExists()) {
- // Handle current page; we do want to change ordering number, but nothing else.
- $this->order($counter);
- $this->save(false);
- } else {
- // Handle all the other pages.
- $page = $pages->get($page->path());
- if ($page && $page->folderExists() && !$page->_action) {
- $page = $page->move($this->parent());
- $page->order($counter);
- $page->save(false);
+ // Reorder all moved pages.
+ foreach ($siblings as $slug => $page) {
+ $order = (int)trim($page->order(), '.');
+ $counter++;
+
+ if ($order) {
+ if ($page->path() === $this->path() && $this->folderExists()) {
+ // Handle current page; we do want to change ordering number, but nothing else.
+ $this->order($counter);
+ $this->save(false);
+ } else {
+ // Handle all the other pages.
+ $page = $pages->get($page->path());
+ if ($page && $page->folderExists() && !$page->_action) {
+ $page = $page->move($this->parent());
+ $page->order($counter);
+ $page->save(false);
+ }
}
}
}
@@ -2911,7 +3096,7 @@ class Page implements PageInterface
*
* @internal
*
- * @throws Exception
+ * @throws \Exception
*/
protected function doRelocation()
{
@@ -2961,18 +3146,13 @@ class Page implements PageInterface
{
$case_insensitive = Grav::instance()['config']->get('system.force_lowercase_urls');
- if ($case_insensitive) {
- return mb_strtolower($route);
- } else {
- return $route;
- }
+ return $case_insensitive ? mb_strtolower($route) : $route;
}
/**
* Gets the Page Unmodified (original) version of the page.
*
- * @return Page
- * The original version of the page.
+ * @return PageInterface The original version of the page.
*/
public function getOriginal()
{
@@ -2982,8 +3162,7 @@ class Page implements PageInterface
/**
* Gets the action.
*
- * @return string
- * The Action string.
+ * @return string The Action string.
*/
public function getAction()
{
diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php
index 0c527d1..d464278 100644
--- a/system/src/Grav/Common/Page/Pages.php
+++ b/system/src/Grav/Common/Page/Pages.php
@@ -1,8 +1,9 @@
grav[$type] . $this->baseRoute($lang);
}
@@ -254,7 +262,7 @@ class Pages
/**
* Returns a list of all pages.
*
- * @return array|Page[]
+ * @return array|PageInterface[]
*/
public function instances()
{
@@ -274,10 +282,10 @@ class Pages
/**
* Adds a page and assigns a route to it.
*
- * @param Page $page Page to be added.
+ * @param PageInterface $page Page to be added.
* @param string $route Optional route (uses route from the object if not set).
*/
- public function addPage(Page $page, $route = null)
+ public function addPage(PageInterface $page, $route = null)
{
if (!isset($this->instances[$page->path()])) {
$this->instances[$page->path()] = $page;
@@ -294,13 +302,13 @@ class Pages
/**
* Sort sub-pages in a page.
*
- * @param Page $page
+ * @param PageInterface $page
* @param string $order_by
* @param string $order_dir
*
* @return array
*/
- public function sort(Page $page, $order_by = null, $order_dir = null, $sort_flags = null)
+ public function sort(PageInterface $page, $order_by = null, $order_dir = null, $sort_flags = null)
{
if ($order_by === null) {
$order_by = $page->orderBy();
@@ -310,7 +318,7 @@ class Pages
}
$path = $page->path();
- $children = isset($this->children[$path]) ? $this->children[$path] : [];
+ $children = $this->children[$path] ?? [];
if (!$children) {
return $children;
@@ -331,9 +339,10 @@ class Pages
/**
* @param Collection $collection
- * @param $orderBy
+ * @param string|int $orderBy
* @param string $orderDir
- * @param null $orderManual
+ * @param array|null $orderManual
+ * @param int|null $sort_flags
*
* @return array
* @internal
@@ -365,12 +374,12 @@ class Pages
*
* @param string $path The filesystem full path of the page
*
- * @return Page
+ * @return PageInterface
* @throws \Exception
*/
public function get($path)
{
- return isset($this->instances[(string)$path]) ? $this->instances[(string)$path] : null;
+ return $this->instances[(string)$path] ?? null;
}
/**
@@ -382,7 +391,7 @@ class Pages
*/
public function children($path)
{
- $children = isset($this->children[(string)$path]) ? $this->children[(string)$path] : [];
+ $children = $this->children[(string)$path] ?? [];
return new Collection($children, [], $this);
}
@@ -393,7 +402,7 @@ class Pages
* @param string $route The relative URL of the page
* @param string $path The relative path of the ancestor folder
*
- * @return Page|null
+ * @return PageInterface|null
*/
public function ancestor($route, $path = null)
{
@@ -403,8 +412,10 @@ class Pages
if ($page && $page->path() === $path) {
return $page;
}
- if ($page && !$page->parent()->root()) {
- return $this->ancestor($page->parent()->route(), $path);
+
+ $parent = $page ? $page->parent() : null;
+ if ($parent && !$parent->root()) {
+ return $this->ancestor($parent->route(), $path);
}
}
@@ -417,7 +428,7 @@ class Pages
* @param string $route The relative route of the page
* @param string $field The field name of the ancestor to query for
*
- * @return Page|null
+ * @return PageInterface|null
*/
public function inherited($route, $field = null)
{
@@ -425,11 +436,12 @@ class Pages
$page = $this->dispatch($route, true);
- if ($page && $page->parent()->value('header.' . $field) !== null) {
- return $page->parent();
+ $parent = $page ? $page->parent() : null;
+ if ($parent && $parent->value('header.' . $field) !== null) {
+ return $parent;
}
- if ($page && !$page->parent()->root()) {
- return $this->inherited($page->parent()->route(), $field);
+ if ($parent && !$parent->root()) {
+ return $this->inherited($parent->route(), $field);
}
}
@@ -442,7 +454,7 @@ class Pages
* @param string $route The relative URL of the page
* @param bool $all
*
- * @return Page|null
+ * @return PageInterface|null
*/
public function find($route, $all = false)
{
@@ -456,7 +468,7 @@ class Pages
* @param bool $all
*
* @param bool $redirect
- * @return Page|null
+ * @return PageInterface|null
* @throws \Exception
*/
public function dispatch($route, $all = false, $redirect = true)
@@ -498,13 +510,13 @@ class Pages
$source_url = $uri->uri(false);
// Try Regex style redirects
- $site_redirects = $config->get("site.redirects");
+ $site_redirects = $config->get('site.redirects');
if (is_array($site_redirects)) {
foreach ((array)$site_redirects as $pattern => $replace) {
$pattern = '#^' . str_replace('/', '\/', ltrim($pattern, '^')) . '#';
try {
$found = preg_replace($pattern, $replace, $source_url);
- if ($found != $source_url) {
+ if ($found !== $source_url) {
$this->grav->redirectLangSafe($found);
}
} catch (ErrorException $e) {
@@ -514,7 +526,7 @@ class Pages
}
// Try Regex style routes
- $site_routes = $config->get("site.routes");
+ $site_routes = $config->get('site.routes');
if (is_array($site_routes)) {
foreach ((array)$site_routes as $pattern => $replace) {
$pattern = '#^' . str_replace('/', '\/', ltrim($pattern, '^')) . '#';
@@ -538,12 +550,13 @@ class Pages
/**
* Get root page.
*
- * @return Page
+ * @return PageInterface
*/
public function root()
{
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
+
return $this->instances[rtrim($locator->findResource('page://'), DS)];
}
@@ -577,15 +590,15 @@ class Pages
/**
* Get all pages
*
- * @param \Grav\Common\Page\Page $current
+ * @param PageInterface $current
*
* @return \Grav\Common\Page\Collection
*/
- public function all(Page $current = null)
+ public function all(PageInterface $current = null)
{
$all = new Collection();
- /** @var Page $current */
+ /** @var PageInterface $current */
$current = $current ?: $this->root();
if (!$current->root()) {
@@ -647,7 +660,7 @@ class Pages
/**
* Get list of route/title of all pages.
*
- * @param Page $current
+ * @param PageInterface $current
* @param int $level
* @param bool $rawRoutes
*
@@ -658,7 +671,7 @@ class Pages
* @param bool $limitLevels
* @return array
*/
- public function getList(Page $current = null, $level = 0, $rawRoutes = false, $showAll = true, $showFullpath = false, $showSlug = false, $showModular = false, $limitLevels = false)
+ public function getList(PageInterface $current = null, $level = 0, $rawRoutes = false, $showAll = true, $showFullpath = false, $showSlug = false, $showModular = false, $limitLevels = false)
{
if (!$current) {
if ($level) {
@@ -710,7 +723,6 @@ class Pages
public static function getTypes()
{
if (!self::$types) {
-
$grav = Grav::instance();
$scanBlueprintsAndTemplates = function () use ($grav) {
@@ -748,6 +760,18 @@ class Pages
$scanBlueprintsAndTemplates();
}
+ // Register custom paths to the locator.
+ $locator = $grav['locator'];
+ foreach (self::$types as $type => $paths) {
+ foreach ($paths as $k => $path) {
+ if (strpos($path, 'blueprints://') === 0) {
+ unset($paths[$k]);
+ }
+ }
+ if ($paths) {
+ $locator->addPath('blueprints', "pages/$type.yaml", $paths);
+ }
+ }
}
return self::$types;
@@ -788,7 +812,7 @@ class Pages
/** @var Admin $admin */
$admin = Grav::instance()['admin'];
- /** @var Page $page */
+ /** @var PageInterface $page */
$page = $admin->getPage($admin->route);
if ($page && $page->modular()) {
@@ -811,19 +835,18 @@ class Pages
$accessLevels = [];
foreach ($this->all() as $page) {
if (isset($page->header()->access)) {
- if (is_array($page->header()->access)) {
+ if (\is_array($page->header()->access)) {
foreach ($page->header()->access as $index => $accessLevel) {
- if (is_array($accessLevel)) {
+ if (\is_array($accessLevel)) {
foreach ($accessLevel as $innerIndex => $innerAccessLevel) {
- array_push($accessLevels, $innerIndex);
+ $accessLevels[] = $innerIndex;
}
} else {
- array_push($accessLevels, $index);
+ $accessLevels[] = $index;
}
}
} else {
-
- array_push($accessLevels, $page->header()->access);
+ $accessLevels[] = $page->header()->access;
}
}
}
@@ -962,7 +985,7 @@ class Pages
/**
* Accessible method to manually reset the pages cache
*
- * @param $pages_dir
+ * @param string $pages_dir
*/
public function resetPages($pages_dir)
{
@@ -985,13 +1008,13 @@ class Pages
* Recursive function to load & build page relationships.
*
* @param string $directory
- * @param Page|null $parent
+ * @param PageInterface|null $parent
*
- * @return Page
+ * @return PageInterface
* @throws \RuntimeException
* @internal
*/
- protected function recurse($directory, Page $parent = null)
+ protected function recurse($directory, PageInterface $parent = null)
{
$directory = rtrim($directory, DS);
$page = new Page;
@@ -1002,13 +1025,10 @@ class Pages
/** @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');
- }
+ // Stuff to do at root page
+ // Fire event for memory and time consuming plugins...
+ if ($parent === null && $config->get('system.pages.events.page')) {
+ $this->grav->fireEvent('onBuildPagesInitialized');
}
$page->path($directory);
@@ -1040,7 +1060,7 @@ class Pages
$folders = [];
$page_found = null;
- $page_extension = '';
+ $page_extension = '.md';
$last_modified = 0;
$iterator = new \FilesystemIterator($directory);
@@ -1049,7 +1069,7 @@ class Pages
$filename = $file->getFilename();
// Ignore all hidden files if set.
- if ($this->ignore_hidden && $filename && $filename[0] === '.') {
+ if ($this->ignore_hidden && $filename && strpos($filename, '.') === 0) {
continue;
}
@@ -1123,9 +1143,15 @@ class Pages
}
}
- // Set routability to false if no page found
+
if (!$content_exists) {
+ // Set routability to false if no page found
$page->routable(false);
+
+ // Hide empty folders if option set
+ if ($config->get('system.pages.hide_empty_folders')) {
+ $page->visible(false);
+ }
}
// Override the modified time if modular
@@ -1154,14 +1180,14 @@ class Pages
*/
protected function buildRoutes()
{
- /** @var $taxonomy Taxonomy */
+ /** @var Taxonomy $taxonomy */
$taxonomy = $this->grav['taxonomy'];
// Get the home route
$home = self::resetHomeRoute();
// Build routes and taxonomy map.
- /** @var $page Page */
+ /** @var PageInterface $page */
foreach ($this->instances as $page) {
if (!$page->root()) {
// process taxonomy
@@ -1175,7 +1201,7 @@ class Pages
$this->routes[$route] = $page_path;
// add raw route
- if ($raw_route != $route) {
+ if ($raw_route !== $route) {
$this->routes[$raw_route] = $page_path;
}
@@ -1196,9 +1222,10 @@ class Pages
}
// Alias and set default route to home page.
- if ($home && isset($this->routes['/' . $home])) {
- $this->routes['/'] = $this->routes['/' . $home];
- $this->get($this->routes['/' . $home])->route('/');
+ $homeRoute = '/' . $home;
+ if ($home && isset($this->routes[$homeRoute])) {
+ $this->routes['/'] = $this->routes[$homeRoute];
+ $this->get($this->routes[$homeRoute])->route('/');
}
}
@@ -1206,8 +1233,8 @@ class Pages
* @param string $path
* @param array $pages
* @param string $order_by
- * @param array $manual
- * @param int $sort_flags
+ * @param array|null $manual
+ * @param int|null $sort_flags
*
* @throws \RuntimeException
* @internal
@@ -1227,7 +1254,7 @@ class Pages
}
foreach ($pages as $key => $info) {
- $child = isset($this->instances[$key]) ? $this->instances[$key] : null;
+ $child = $this->instances[$key] ?? null;
if (!$child) {
throw new \RuntimeException("Page does not exist: {$key}");
}
@@ -1324,7 +1351,7 @@ class Pages
foreach ($list as $key => $dummy) {
$info = $pages[$key];
- $order = array_search($info['slug'], $manual);
+ $order = \array_search($info['slug'], $manual, true);
if ($order === false) {
$order = $i++;
}
diff --git a/system/src/Grav/Common/Page/Types.php b/system/src/Grav/Common/Page/Types.php
index 436d4f8..03e3f6e 100644
--- a/system/src/Grav/Common/Page/Types.php
+++ b/system/src/Grav/Common/Page/Types.php
@@ -1,8 +1,9 @@
systemBlueprints) {
- $blueprint = isset($this->systemBlueprints[$type]) ? $this->systemBlueprints[$type] : $this->systemBlueprints['default'];
+ $blueprint = $this->systemBlueprints[$type] ?? $this->systemBlueprints['default'];
}
if ($blueprint) {
@@ -43,7 +44,7 @@ class Types implements \ArrayAccess, \Iterator, \Countable
public function scanBlueprints($uri)
{
- if (!is_string($uri)) {
+ if (!\is_string($uri)) {
throw new \InvalidArgumentException('First parameter must be URI');
}
@@ -63,7 +64,7 @@ class Types implements \ArrayAccess, \Iterator, \Countable
public function scanTemplates($uri)
{
- if (!is_string($uri)) {
+ if (!\is_string($uri)) {
throw new \InvalidArgumentException('First parameter must be URI');
}
@@ -96,9 +97,10 @@ class Types implements \ArrayAccess, \Iterator, \Countable
if (strpos($name, '/')) {
continue;
}
- $list[$name] = ucfirst(strtr($name, '_', ' '));
+ $list[$name] = ucfirst(str_replace('_', ' ', $name));
}
ksort($list);
+
return $list;
}
@@ -109,9 +111,10 @@ class Types implements \ArrayAccess, \Iterator, \Countable
if (strpos($name, 'modular/') !== 0) {
continue;
}
- $list[$name] = trim(ucfirst(strtr(basename($name), '_', ' ')));
+ $list[$name] = ucfirst(trim(str_replace('_', ' ', basename($name))));
}
ksort($list);
+
return $list;
}
@@ -133,8 +136,6 @@ class Types implements \ArrayAccess, \Iterator, \Countable
$options['value'] = 'Url';
}
- $list = Folder::all($uri, $options);
-
- return $list;
+ return Folder::all($uri, $options);
}
}
diff --git a/system/src/Grav/Common/Plugin.php b/system/src/Grav/Common/Plugin.php
index ec5b346..d85fcb4 100644
--- a/system/src/Grav/Common/Plugin.php
+++ b/system/src/Grav/Common/Plugin.php
@@ -1,20 +1,21 @@
grav['events'];
foreach ($events as $eventName => $params) {
- if (is_string($params)) {
+ if (\is_string($params)) {
$dispatcher->addListener($eventName, [$this, $params]);
- } elseif (is_string($params[0])) {
- $dispatcher->addListener($eventName, [$this, $params[0]], isset($params[1]) ? $params[1] : 0);
+ } elseif (\is_string($params[0])) {
+ $dispatcher->addListener($eventName, [$this, $params[0]], $params[1] ?? 0);
} else {
foreach ($params as $listener) {
- $dispatcher->addListener($eventName, [$this, $listener[0]], isset($listener[1]) ? $listener[1] : 0);
+ $dispatcher->addListener($eventName, [$this, $listener[0]], $listener[1] ?? 0);
}
}
}
@@ -158,9 +169,9 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
$dispatcher = $this->grav['events'];
foreach ($events as $eventName => $params) {
- if (is_string($params)) {
+ if (\is_string($params)) {
$dispatcher->removeListener($eventName, [$this, $params]);
- } elseif (is_string($params[0])) {
+ } elseif (\is_string($params[0])) {
$dispatcher->removeListener($eventName, [$this, $params[0]]);
} else {
foreach ($params as $listener) {
@@ -173,7 +184,7 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
/**
* Whether or not an offset exists.
*
- * @param mixed $offset An offset to check for.
+ * @param string $offset An offset to check for.
* @return bool Returns TRUE on success or FALSE on failure.
*/
public function offsetExists($offset)
@@ -189,7 +200,7 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
/**
* Returns the value at specified offset.
*
- * @param mixed $offset The offset to retrieve.
+ * @param string $offset The offset to retrieve.
* @return mixed Can return all value types.
*/
public function offsetGet($offset)
@@ -199,30 +210,30 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
if ($offset === 'title') {
$offset = 'name';
}
- return isset($this->blueprint[$offset]) ? $this->blueprint[$offset] : null;
+ return $this->blueprint[$offset] ?? null;
}
/**
* Assigns a value to the specified offset.
*
- * @param mixed $offset The offset to assign the value to.
+ * @param string $offset The offset to assign the value to.
* @param mixed $value The value to set.
- * @throws LogicException
+ * @throws \LogicException
*/
public function offsetSet($offset, $value)
{
- throw new LogicException(__CLASS__ . ' blueprints cannot be modified.');
+ throw new \LogicException(__CLASS__ . ' blueprints cannot be modified.');
}
/**
* Unsets an offset.
*
- * @param mixed $offset The offset to unset.
- * @throws LogicException
+ * @param string $offset The offset to unset.
+ * @throws \LogicException
*/
public function offsetUnset($offset)
{
- throw new LogicException(__CLASS__ . ' blueprints cannot be modified.');
+ throw new \LogicException(__CLASS__ . ' blueprints cannot be modified.');
}
/**
@@ -248,7 +259,7 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
/**
* Merge global and page configurations.
*
- * @param Page $page The page to merge the configurations with the
+ * @param PageInterface $page The page to merge the configurations with the
* plugin settings.
* @param mixed $deep false = shallow|true = recursive|merge = recursive+unique
* @param array $params Array of additional configuration options to
@@ -257,7 +268,7 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
*
* @return Data
*/
- protected function mergeConfig(Page $page, $deep = false, $params = [], $type = 'plugins')
+ protected function mergeConfig(PageInterface $page, $deep = false, $params = [], $type = 'plugins')
{
$class_name = $this->name;
$class_name_merged = $class_name . '.merged';
@@ -265,10 +276,10 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
$page_header = $page->header();
$header = [];
- if (!isset($page_header->$class_name_merged) && isset($page_header->$class_name)) {
+ if (!isset($page_header->{$class_name_merged}) && isset($page_header->{$class_name})) {
// Get default plugin configurations and retrieve page header configuration
- $config = $page_header->$class_name;
- if (is_bool($config)) {
+ $config = $page_header->{$class_name};
+ if (\is_bool($config)) {
// Overwrite enabled option with boolean value in page header
$config = ['enabled' => $config];
}
@@ -277,8 +288,8 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
// Create new config object and set it on the page object so it's cached for next time
$page->modifyHeader($class_name_merged, new Data($header));
- } else if (isset($page_header->$class_name_merged)) {
- $merged = $page_header->$class_name_merged;
+ } else if (isset($page_header->{$class_name_merged})) {
+ $merged = $page_header->{$class_name_merged};
$header = $merged->toArray();
}
if (empty($header)) {
@@ -294,12 +305,12 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
/**
* Merge arrays based on deepness
*
- * @param bool $deep
- * @param $array1
- * @param $array2
- * @return array|mixed
+ * @param string|bool $deep
+ * @param array $array1
+ * @param array $array2
+ * @return array
*/
- private function mergeArrays($deep = false, $array1, $array2)
+ private function mergeArrays($deep, $array1, $array2)
{
if ($deep === 'merge') {
return Utils::arrayMergeRecursiveUnique($array1, $array2);
@@ -316,7 +327,7 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
*
* @param string $plugin_name The name of the plugin whose config it should store.
*
- * @return true
+ * @return bool
*/
public static function saveConfig($plugin_name)
{
@@ -338,7 +349,7 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
/**
* Simpler getter for the plugin blueprint
*
- * @return mixed
+ * @return Blueprint
*/
public function getBlueprint()
{
diff --git a/system/src/Grav/Common/Plugins.php b/system/src/Grav/Common/Plugins.php
index 2a9d523..d83c152 100644
--- a/system/src/Grav/Common/Plugins.php
+++ b/system/src/Grav/Common/Plugins.php
@@ -1,8 +1,9 @@
content(), $blueprint);
+ $obj = new Data((array)$file->content(), $blueprint);
// Override with user configuration.
$obj->merge(Grav::instance()['config']->get('plugins.' . $name) ?: []);
diff --git a/system/src/Grav/Common/Processors/AssetsProcessor.php b/system/src/Grav/Common/Processors/AssetsProcessor.php
index 6ca952d..8b46401 100644
--- a/system/src/Grav/Common/Processors/AssetsProcessor.php
+++ b/system/src/Grav/Common/Processors/AssetsProcessor.php
@@ -1,21 +1,30 @@
startTimer();
$this->container['assets']->init();
$this->container->fireEvent('onAssetsInitialized');
+ $this->stopTimer();
+
+ return $handler->handle($request);
}
}
diff --git a/system/src/Grav/Common/Processors/BackupsProcessor.php b/system/src/Grav/Common/Processors/BackupsProcessor.php
new file mode 100644
index 0000000..d2a822c
--- /dev/null
+++ b/system/src/Grav/Common/Processors/BackupsProcessor.php
@@ -0,0 +1,30 @@
+startTimer();
+ $backups = $this->container['backups'];
+ $backups->init();
+ $this->stopTimer();
+
+ return $handler->handle($request);
+ }
+}
diff --git a/system/src/Grav/Common/Processors/ConfigurationProcessor.php b/system/src/Grav/Common/Processors/ConfigurationProcessor.php
index 0347853..83ceb84 100644
--- a/system/src/Grav/Common/Processors/ConfigurationProcessor.php
+++ b/system/src/Grav/Common/Processors/ConfigurationProcessor.php
@@ -1,21 +1,30 @@
startTimer();
$this->container['config']->init();
$this->container['plugins']->setup();
+ $this->stopTimer();
+
+ return $handler->handle($request);
}
}
diff --git a/system/src/Grav/Common/Processors/DebuggerAssetsProcessor.php b/system/src/Grav/Common/Processors/DebuggerAssetsProcessor.php
index 643e8d1..11a39e8 100644
--- a/system/src/Grav/Common/Processors/DebuggerAssetsProcessor.php
+++ b/system/src/Grav/Common/Processors/DebuggerAssetsProcessor.php
@@ -1,20 +1,31 @@
startTimer();
$this->container['debugger']->addAssets();
+ $this->stopTimer();
+
+ return $handler->handle($request);
+
}
}
diff --git a/system/src/Grav/Common/Processors/DebuggerInitProcessor.php b/system/src/Grav/Common/Processors/DebuggerInitProcessor.php
deleted file mode 100644
index e3b4e1d..0000000
--- a/system/src/Grav/Common/Processors/DebuggerInitProcessor.php
+++ /dev/null
@@ -1,20 +0,0 @@
-container['debugger']->init();
- }
-}
diff --git a/system/src/Grav/Common/Processors/DebuggerProcessor.php b/system/src/Grav/Common/Processors/DebuggerProcessor.php
new file mode 100644
index 0000000..892af55
--- /dev/null
+++ b/system/src/Grav/Common/Processors/DebuggerProcessor.php
@@ -0,0 +1,29 @@
+startTimer();
+ $this->container['debugger']->init();
+ $this->stopTimer();
+
+ return $handler->handle($request);
+ }
+}
diff --git a/system/src/Grav/Common/Processors/ErrorsProcessor.php b/system/src/Grav/Common/Processors/ErrorsProcessor.php
index 7c7685d..b0ef6ca 100644
--- a/system/src/Grav/Common/Processors/ErrorsProcessor.php
+++ b/system/src/Grav/Common/Processors/ErrorsProcessor.php
@@ -1,20 +1,29 @@
startTimer();
$this->container['errors']->resetHandlers();
+ $this->stopTimer();
+
+ return $handler->handle($request);
}
}
diff --git a/system/src/Grav/Common/Processors/Events/RequestHandlerEvent.php b/system/src/Grav/Common/Processors/Events/RequestHandlerEvent.php
new file mode 100644
index 0000000..fd97e9e
--- /dev/null
+++ b/system/src/Grav/Common/Processors/Events/RequestHandlerEvent.php
@@ -0,0 +1,78 @@
+offsetGet('request');
+ }
+
+ /**
+ * @return Route
+ */
+ public function getRoute(): Route
+ {
+ return $this->getRequest()->getAttribute('route');
+ }
+
+ /**
+ * @return RequestHandler
+ */
+ public function getHandler(): RequestHandler
+ {
+ return $this->offsetGet('handler');
+ }
+
+ /**
+ * @return ResponseInterface|null
+ */
+ public function getResponse(): ?ResponseInterface
+ {
+ return $this->offsetGet('response');
+ }
+
+ /**
+ * @param ResponseInterface $response
+ * @return $this
+ */
+ public function setResponse(ResponseInterface $response): self
+ {
+ $this->offsetSet('response', $response);
+ $this->stopPropagation();
+
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @param MiddlewareInterface $middleware
+ * @return RequestHandlerEvent
+ */
+ public function addMiddleware(string $name, MiddlewareInterface $middleware): self
+ {
+ /** @var RequestHandler $handler */
+ $handler = $this['handler'];
+ $handler->addMiddleware($name, $middleware);
+
+ return $this;
+ }
+}
diff --git a/system/src/Grav/Common/Processors/InitializeProcessor.php b/system/src/Grav/Common/Processors/InitializeProcessor.php
index 6bf8cdd..fe2869c 100644
--- a/system/src/Grav/Common/Processors/InitializeProcessor.php
+++ b/system/src/Grav/Common/Processors/InitializeProcessor.php
@@ -1,8 +1,9 @@
startTimer();
+
/** @var Config $config */
$config = $this->container['config'];
$config->debug();
@@ -31,13 +38,24 @@ class InitializeProcessor extends ProcessorBase implements ProcessorInterface
}
// Initialize the timezone.
- if ($config->get('system.timezone')) {
- date_default_timezone_set($this->container['config']->get('system.timezone'));
+ $timezone = $config->get('system.timezone');
+ if ($timezone) {
+ date_default_timezone_set($timezone);
}
// FIXME: Initialize session should happen later after plugins have been loaded. This is a workaround to fix session issues in AWS.
if (isset($this->container['session']) && $config->get('system.session.initialize', true)) {
- $this->container['session']->init();
+ // TODO: remove in 2.0.
+ $this->container['accounts'];
+
+ try {
+ $this->container['session']->init();
+ } catch (SessionException $e) {
+ $this->container['session']->init();
+ $message = 'Session corruption detected, restarting session...';
+ $this->addMessage($message);
+ $this->container['messages']->add($message, 'error');
+ }
}
/** @var Uri $uri */
@@ -46,10 +64,17 @@ class InitializeProcessor extends ProcessorBase implements ProcessorInterface
// Redirect pages with trailing slash if configured to do so.
$path = $uri->path() ?: '/';
- if ($path !== '/' && $config->get('system.pages.redirect_trailing_slash', false) && Utils::endsWith($path, '/')) {
- $this->container->redirectLangSafe(rtrim($path, '/'));
+ if ($path !== '/'
+ && $config->get('system.pages.redirect_trailing_slash', false)
+ && Utils::endsWith($path, '/')) {
+
+ $redirect = (string) $uri::getCurrentRoute()->toString();
+ $this->container->redirect($redirect);
}
$this->container->setLocale();
+ $this->stopTimer();
+
+ return $handler->handle($request);
}
}
diff --git a/system/src/Grav/Common/Processors/LoggerProcessor.php b/system/src/Grav/Common/Processors/LoggerProcessor.php
new file mode 100644
index 0000000..af3c169
--- /dev/null
+++ b/system/src/Grav/Common/Processors/LoggerProcessor.php
@@ -0,0 +1,50 @@
+startTimer();
+
+ $grav = $this->container;
+
+ /** @var Config $config */
+ $config = $grav['config'];
+
+ switch ($config->get('system.log.handler', 'file')) {
+ case 'syslog':
+ $log = $grav['log'];
+ $log->popHandler();
+
+ $facility = $config->get('system.log.syslog.facility', 'local6');
+ $logHandler = new SyslogHandler('grav', $facility);
+ $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%");
+ $logHandler->setFormatter($formatter);
+
+ $log->pushHandler($logHandler);
+ break;
+ }
+ $this->stopTimer();
+
+ return $handler->handle($request);
+ }
+}
diff --git a/system/src/Grav/Common/Processors/PagesProcessor.php b/system/src/Grav/Common/Processors/PagesProcessor.php
index b2828f7..ede22e5 100644
--- a/system/src/Grav/Common/Processors/PagesProcessor.php
+++ b/system/src/Grav/Common/Processors/PagesProcessor.php
@@ -1,23 +1,29 @@
startTimer();
+
// Dump Cache state
$this->container['debugger']->addMessage($this->container['cache']->getCacheStatus());
@@ -25,20 +31,41 @@ class PagesProcessor extends ProcessorBase implements ProcessorInterface
$this->container->fireEvent('onPagesInitialized', new Event(['pages' => $this->container['pages']]));
$this->container->fireEvent('onPageInitialized', new Event(['page' => $this->container['page']]));
- /** @var Page $page */
+ /** @var PageInterface $page */
$page = $this->container['page'];
if (!$page->routable()) {
// If no page found, fire event
- $event = $this->container->fireEvent('onPageNotFound', new Event(['page' => $page]));
+ $event = new Event(['page' => $page]);
+ $event->page = null;
+ $event = $this->container->fireEvent('onPageNotFound', $event);
if (isset($event->page)) {
unset ($this->container['page']);
- $this->container['page'] = $event->page;
+ $this->container['page'] = $page = $event->page;
} else {
throw new \RuntimeException('Page Not Found', 404);
}
+
+ $this->addMessage("Routed to page {$page->rawRoute()} (type: {$page->template()}) [Not Found fallback]");
+ } else {
+ $this->addMessage("Routed to page {$page->rawRoute()} (type: {$page->template()})");
+
+ $task = $this->container['task'];
+ $action = $this->container['action'];
+ if ($task) {
+ $event = new Event(['task' => $task, 'page' => $page]);
+ $this->container->fireEvent('onPageTask', $event);
+ $this->container->fireEvent('onPageTask.' . $task, $event);
+ } elseif ($action) {
+ $event = new Event(['action' => $action, 'page' => $page]);
+ $this->container->fireEvent('onPageAction', $event);
+ $this->container->fireEvent('onPageAction.' . $action, $event);
+ }
}
+ $this->stopTimer();
+
+ return $handler->handle($request);
}
}
diff --git a/system/src/Grav/Common/Processors/PluginsProcessor.php b/system/src/Grav/Common/Processors/PluginsProcessor.php
index ee56393..9d2943b 100644
--- a/system/src/Grav/Common/Processors/PluginsProcessor.php
+++ b/system/src/Grav/Common/Processors/PluginsProcessor.php
@@ -1,21 +1,32 @@
startTimer();
+ // TODO: remove in 2.0.
+ $this->container['accounts'];
$this->container['plugins']->init();
$this->container->fireEvent('onPluginsInitialized');
+ $this->stopTimer();
+
+ return $handler->handle($request);
}
}
diff --git a/system/src/Grav/Common/Processors/ProcessorBase.php b/system/src/Grav/Common/Processors/ProcessorBase.php
index 056c86d..5ce7bfd 100644
--- a/system/src/Grav/Common/Processors/ProcessorBase.php
+++ b/system/src/Grav/Common/Processors/ProcessorBase.php
@@ -1,25 +1,48 @@
container = $container;
}
+ protected function startTimer($id = null, $title = null)
+ {
+ /** @var Debugger $debugger */
+ $debugger = $this->container['debugger'];
+ $debugger->startTimer($id ?? $this->id, $title ?? $this->title);
+ }
+
+ protected function stopTimer($id = null)
+ {
+ /** @var Debugger $debugger */
+ $debugger = $this->container['debugger'];
+ $debugger->stopTimer($id ?? $this->id);
+ }
+
+ protected function addMessage($message, $label = 'info', $isString = true)
+ {
+ /** @var Debugger $debugger */
+ $debugger = $this->container['debugger'];
+ $debugger->addMessage($message, $label, $isString);
+ }
}
diff --git a/system/src/Grav/Common/Processors/ProcessorInterface.php b/system/src/Grav/Common/Processors/ProcessorInterface.php
index 0e4b169..a8e4aaa 100644
--- a/system/src/Grav/Common/Processors/ProcessorInterface.php
+++ b/system/src/Grav/Common/Processors/ProcessorInterface.php
@@ -1,14 +1,16 @@
startTimer();
+
$container = $this->container;
$output = $container['output'];
- if ($output instanceof \Psr\Http\Message\ResponseInterface) {
- // Support for custom output providers like Slim Framework.
- } else {
- // Use internal Grav output.
- $container->output = $output;
- $container->fireEvent('onOutputGenerated');
-
- // Set the header type
- $container->header();
-
- echo $container->output;
-
- // remove any output
- $container->output = '';
-
- $this->container->fireEvent('onOutputRendered');
+ if ($output instanceof ResponseInterface) {
+ return $output;
}
+
+ ob_start();
+
+ // Use internal Grav output.
+ $container->output = $output;
+ $container->fireEvent('onOutputGenerated');
+
+ echo $container->output;
+
+ // remove any output
+ $container->output = '';
+
+ $this->container->fireEvent('onOutputRendered');
+
+ $html = ob_get_clean();
+
+ /** @var PageInterface $page */
+ $page = $this->container['page'];
+ $this->stopTimer();
+
+ return new Response($page->httpResponseCode(), $page->httpHeaders(), $html);
}
}
diff --git a/system/src/Grav/Common/Processors/RequestProcessor.php b/system/src/Grav/Common/Processors/RequestProcessor.php
new file mode 100644
index 0000000..8cb876a
--- /dev/null
+++ b/system/src/Grav/Common/Processors/RequestProcessor.php
@@ -0,0 +1,51 @@
+startTimer();
+
+ $header = $request->getHeaderLine('Content-Type');
+ $type = trim(strstr($header, ';', true) ?: $header);
+ if ($type === 'application/json') {
+ $request = $request->withParsedBody(json_decode($request->getBody()->getContents(), true));
+ }
+
+ $request = $request
+ ->withAttribute('grav', $this->container)
+ ->withAttribute('time', $_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME)
+ ->withAttribute('route', Uri::getCurrentRoute())
+ ->withAttribute('referrer', $this->container['uri']->referrer());
+
+ $event = new RequestHandlerEvent(['request' => $request, 'handler' => $handler]);
+ /** @var RequestHandlerEvent $event */
+ $event = $this->container->fireEvent('onRequestHandlerInit', $event);
+ $response = $event->getResponse();
+ $this->stopTimer();
+
+ if ($response) {
+ return $response;
+ }
+
+ return $handler->handle($request);
+ }
+}
diff --git a/system/src/Grav/Common/Processors/SchedulerProcessor.php b/system/src/Grav/Common/Processors/SchedulerProcessor.php
new file mode 100644
index 0000000..64f3fba
--- /dev/null
+++ b/system/src/Grav/Common/Processors/SchedulerProcessor.php
@@ -0,0 +1,31 @@
+startTimer();
+ $scheduler = $this->container['scheduler'];
+ $this->container->fireEvent('onSchedulerInitialized', new Event(['scheduler' => $scheduler]));
+ $this->stopTimer();
+
+ return $handler->handle($request);
+ }
+}
diff --git a/system/src/Grav/Common/Processors/SiteSetupProcessor.php b/system/src/Grav/Common/Processors/SiteSetupProcessor.php
deleted file mode 100644
index 1f3f8af..0000000
--- a/system/src/Grav/Common/Processors/SiteSetupProcessor.php
+++ /dev/null
@@ -1,21 +0,0 @@
-container['setup']->init();
- $this->container['streams'];
- }
-}
diff --git a/system/src/Grav/Common/Processors/TasksProcessor.php b/system/src/Grav/Common/Processors/TasksProcessor.php
index d1e7a2f..aaf5cdf 100644
--- a/system/src/Grav/Common/Processors/TasksProcessor.php
+++ b/system/src/Grav/Common/Processors/TasksProcessor.php
@@ -1,23 +1,61 @@
startTimer();
+
$task = $this->container['task'];
- if ($task) {
- $this->container->fireEvent('onTask.' . $task);
+ $action = $this->container['action'];
+ if ($task || $action) {
+ $attributes = $request->getAttribute('controller');
+
+ $controllerClass = $attributes['class'] ?? null;
+ if ($controllerClass) {
+ /** @var RequestHandlerInterface $controller */
+ $controller = new $controllerClass($attributes['path'] ?? '', $attributes['params'] ?? []);
+ try {
+ $response = $controller->handle($request);
+
+ if ($response->getStatusCode() === 418) {
+ $response = $handler->handle($request);
+ }
+
+ $this->stopTimer();
+
+ return $response;
+
+ } catch (NotFoundException $e) {
+ // Task not found: Let it pass through.
+ }
+ }
+
+ if ($task) {
+ $this->container->fireEvent('onTask.' . $task);
+ } elseif ($action) {
+ $this->container->fireEvent('onAction.' . $action);
+ }
}
+ $this->stopTimer();
+
+ return $handler->handle($request);
}
}
diff --git a/system/src/Grav/Common/Processors/ThemesProcessor.php b/system/src/Grav/Common/Processors/ThemesProcessor.php
index c9ea013..d1c6ae5 100644
--- a/system/src/Grav/Common/Processors/ThemesProcessor.php
+++ b/system/src/Grav/Common/Processors/ThemesProcessor.php
@@ -1,20 +1,29 @@
startTimer();
$this->container['themes']->init();
+ $this->stopTimer();
+
+ return $handler->handle($request);
}
}
diff --git a/system/src/Grav/Common/Processors/TwigProcessor.php b/system/src/Grav/Common/Processors/TwigProcessor.php
index 392824f..4ed247d 100644
--- a/system/src/Grav/Common/Processors/TwigProcessor.php
+++ b/system/src/Grav/Common/Processors/TwigProcessor.php
@@ -1,21 +1,29 @@
startTimer();
$this->container['twig']->init();
- }
+ $this->stopTimer();
+ return $handler->handle($request);
+ }
}
diff --git a/system/src/Grav/Common/Scheduler/Cron.php b/system/src/Grav/Common/Scheduler/Cron.php
new file mode 100644
index 0000000..3a42b29
--- /dev/null
+++ b/system/src/Grav/Common/Scheduler/Cron.php
@@ -0,0 +1,595 @@
+ modified for Grav integration
+ * @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
+ * @license MIT License; see LICENSE file for details.
+ */
+
+namespace Grav\Common\Scheduler;
+
+/*
+ * Usage examples :
+ * ----------------
+ *
+ * $cron = new Cron('10-30/5 12 * * *');
+ *
+ * var_dump($cron->getMinutes());
+ * // array(5) {
+ * // [0]=> int(10)
+ * // [1]=> int(15)
+ * // [2]=> int(20)
+ * // [3]=> int(25)
+ * // [4]=> int(30)
+ * // }
+ *
+ * var_dump($cron->getText('fr'));
+ * // string(32) "Chaque jour à 12:10,15,20,25,30"
+ *
+ * var_dump($cron->getText('en'));
+ * // string(30) "Every day at 12:10,15,20,25,30"
+ *
+ * var_dump($cron->getType());
+ * // string(3) "day"
+ *
+ * var_dump($cron->getCronHours());
+ * // string(2) "12"
+ *
+ * var_dump($cron->matchExact(new \DateTime('2012-07-01 13:25:10')));
+ * // bool(false)
+ *
+ * var_dump($cron->matchExact(new \DateTime('2012-07-01 12:15:20')));
+ * // bool(true)
+ *
+ * var_dump($cron->matchWithMargin(new \DateTime('2012-07-01 12:32:50'), -3, 5));
+ * // bool(true)
+ */
+class Cron
+{
+ public const TYPE_UNDEFINED = '';
+ public const TYPE_MINUTE = 'minute';
+ public const TYPE_HOUR = 'hour';
+ public const TYPE_DAY = 'day';
+ public const TYPE_WEEK = 'week';
+ public const TYPE_MONTH = 'month';
+ public const TYPE_YEAR = 'year';
+ /**
+ *
+ * @var array
+ */
+ protected $texts = [
+ 'fr' => [
+ 'empty' => '-tout-',
+ 'name_minute' => 'minute',
+ 'name_hour' => 'heure',
+ 'name_day' => 'jour',
+ 'name_week' => 'semaine',
+ 'name_month' => 'mois',
+ 'name_year' => 'année',
+ 'text_period' => 'Chaque %s',
+ 'text_mins' => 'à %s minutes',
+ 'text_time' => 'à %s:%s',
+ 'text_dow' => 'le %s',
+ 'text_month' => 'de %s',
+ 'text_dom' => 'le %s',
+ 'weekdays' => ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche'],
+ 'months' => ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'],
+ ],
+ 'en' => [
+ 'empty' => '-all-',
+ 'name_minute' => 'minute',
+ 'name_hour' => 'hour',
+ 'name_day' => 'day',
+ 'name_week' => 'week',
+ 'name_month' => 'month',
+ 'name_year' => 'year',
+ 'text_period' => 'Every %s',
+ 'text_mins' => 'at %s minutes past the hour',
+ 'text_time' => 'at %s:%s',
+ 'text_dow' => 'on %s',
+ 'text_month' => 'of %s',
+ 'text_dom' => 'on the %s',
+ 'weekdays' => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
+ 'months' => ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'],
+ ],
+ ];
+
+ /**
+ * min hour dom month dow
+ * @var string
+ */
+ protected $cron = '';
+ /**
+ *
+ * @var array
+ */
+ protected $minutes = [];
+ /**
+ *
+ * @var array
+ */
+ protected $hours = [];
+ /**
+ *
+ * @var array
+ */
+ protected $months = [];
+ /**
+ * 0-7 : sunday, monday, ... saturday, sunday
+ * @var array
+ */
+ protected $dow = [];
+ /**
+ *
+ * @var array
+ */
+ protected $dom = [];
+
+ /**
+ *
+ * @param string|null $cron
+ */
+ public function __construct($cron = null)
+ {
+ if (null !== $cron) {
+ $this->setCron($cron);
+ }
+ }
+
+ /**
+ *
+ * @return string
+ */
+ public function getCron()
+ {
+ return implode(' ', [
+ $this->getCronMinutes(),
+ $this->getCronHours(),
+ $this->getCronDaysOfMonth(),
+ $this->getCronMonths(),
+ $this->getCronDaysOfWeek(),
+ ]);
+ }
+
+ /**
+ *
+ * @param string $lang 'fr' or 'en'
+ * @return string
+ */
+ public function getText($lang)
+ {
+ // check lang
+ if (!isset($this->texts[$lang])) {
+ return $this->getCron();
+ }
+
+ $texts = $this->texts[$lang];
+ // check type
+
+ $type = $this->getType();
+ if ($type === self::TYPE_UNDEFINED) {
+ return $this->getCron();
+ }
+
+ // init
+ $elements = [];
+ $elements[] = sprintf($texts['text_period'], $texts['name_' . $type]);
+
+ // hour
+ if ($type === self::TYPE_HOUR) {
+ $elements[] = sprintf($texts['text_mins'], $this->getCronMinutes());
+ }
+
+ // week
+ if ($type === self::TYPE_WEEK) {
+ $dow = $this->getCronDaysOfWeek();
+ foreach ($texts['weekdays'] as $i => $wd) {
+ $dow = str_replace((string) ($i + 1), $wd, $dow);
+ }
+ $elements[] = sprintf($texts['text_dow'], $dow);
+ }
+
+ // month + year
+ if (\in_array($type, [self::TYPE_MONTH, self::TYPE_YEAR], true)) {
+ $elements[] = sprintf($texts['text_dom'], $this->getCronDaysOfMonth());
+ }
+
+ // year
+ if ($type === self::TYPE_YEAR) {
+ $months = $this->getCronMonths();
+ for ($i = count($texts['months']) - 1; $i >= 0; $i--) {
+ $months = str_replace((string) ($i + 1), $texts['months'][$i], $months);
+ }
+ $elements[] = sprintf($texts['text_month'], $months);
+ }
+
+ // day + week + month + year
+ if (\in_array($type, [self::TYPE_DAY, self::TYPE_WEEK, self::TYPE_MONTH, self::TYPE_YEAR], true)) {
+ $elements[] = sprintf($texts['text_time'], $this->getCronHours(), $this->getCronMinutes());
+ }
+
+ return str_replace('*', $texts['empty'], implode(' ', $elements));
+ }
+
+ /**
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ $mask = preg_replace('/[^\* ]/', '-', $this->getCron());
+ $mask = preg_replace('/-+/', '-', $mask);
+ $mask = preg_replace('/[^-\*]/', '', $mask);
+
+ if ($mask === '*****') {
+ return self::TYPE_MINUTE;
+ }
+
+ if ($mask === '-****') {
+ return self::TYPE_HOUR;
+ }
+
+ if (substr($mask, -3) === '***') {
+ return self::TYPE_DAY;
+ }
+
+ if (substr($mask, -3) === '-**') {
+ return self::TYPE_MONTH;
+ }
+
+ if (substr($mask, -3) === '**-') {
+ return self::TYPE_WEEK;
+ }
+
+ if (substr($mask, -2) === '-*') {
+ return self::TYPE_YEAR;
+ }
+
+ return self::TYPE_UNDEFINED;
+ }
+
+ /**
+ *
+ * @param string $cron
+ * @return Cron
+ */
+ public function setCron($cron)
+ {
+ // sanitize
+ $cron = trim($cron);
+ $cron = preg_replace('/\s+/', ' ', $cron);
+ // explode
+ $elements = explode(' ', $cron);
+ if (\count($elements) !== 5) {
+ throw new \RuntimeException('Bad number of elements');
+ }
+
+ $this->cron = $cron;
+ $this->setMinutes($elements[0]);
+ $this->setHours($elements[1]);
+ $this->setDaysOfMonth($elements[2]);
+ $this->setMonths($elements[3]);
+ $this->setDaysOfWeek($elements[4]);
+
+ return $this;
+ }
+
+ /**
+ *
+ * @return string
+ */
+ public function getCronMinutes()
+ {
+ return $this->arrayToCron($this->minutes);
+ }
+
+ /**
+ *
+ * @return string
+ */
+ public function getCronHours()
+ {
+ return $this->arrayToCron($this->hours);
+ }
+
+ /**
+ *
+ * @return string
+ */
+ public function getCronDaysOfMonth()
+ {
+ return $this->arrayToCron($this->dom);
+ }
+
+ /**
+ *
+ * @return string
+ */
+ public function getCronMonths()
+ {
+ return $this->arrayToCron($this->months);
+ }
+
+ /**
+ *
+ * @return string
+ */
+ public function getCronDaysOfWeek()
+ {
+ return $this->arrayToCron($this->dow);
+ }
+
+ /**
+ *
+ * @return array
+ */
+ public function getMinutes()
+ {
+ return $this->minutes;
+ }
+
+ /**
+ *
+ * @return array
+ */
+ public function getHours()
+ {
+ return $this->hours;
+ }
+
+ /**
+ *
+ * @return array
+ */
+ public function getDaysOfMonth()
+ {
+ return $this->dom;
+ }
+
+ /**
+ *
+ * @return array
+ */
+ public function getMonths()
+ {
+ return $this->months;
+ }
+
+ /**
+ *
+ * @return array
+ */
+ public function getDaysOfWeek()
+ {
+ return $this->dow;
+ }
+
+ /**
+ *
+ * @param string|array $minutes
+ * @return Cron
+ */
+ public function setMinutes($minutes)
+ {
+ $this->minutes = $this->cronToArray($minutes, 0, 59);
+
+ return $this;
+ }
+
+ /**
+ *
+ * @param string|array $hours
+ * @return Cron
+ */
+ public function setHours($hours)
+ {
+ $this->hours = $this->cronToArray($hours, 0, 23);
+
+ return $this;
+ }
+
+ /**
+ *
+ * @param string|array $months
+ * @return Cron
+ */
+ public function setMonths($months)
+ {
+ $this->months = $this->cronToArray($months, 1, 12);
+
+ return $this;
+ }
+
+ /**
+ *
+ * @param string|array $dow
+ * @return Cron
+ */
+ public function setDaysOfWeek($dow)
+ {
+ $this->dow = $this->cronToArray($dow, 0, 7);
+
+ return $this;
+ }
+
+ /**
+ *
+ * @param string|array $dom
+ * @return Cron
+ */
+ public function setDaysOfMonth($dom)
+ {
+ $this->dom = $this->cronToArray($dom, 1, 31);
+
+ return $this;
+ }
+
+ /**
+ *
+ * @param mixed $date
+ * @param int $min
+ * @param int $hour
+ * @param int $day
+ * @param int $month
+ * @param int $weekday
+ * @return \DateTime
+ */
+ protected function parseDate($date, &$min, &$hour, &$day, &$month, &$weekday)
+ {
+ if (is_numeric($date) && (int)$date == $date) {
+ $date = new \DateTime('@' . $date);
+ }
+ elseif (is_string($date)) {
+ $date = new \DateTime('@' . strtotime($date));
+ }
+ if ($date instanceof \DateTime) {
+ $min = (int)$date->format('i');
+ $hour = (int)$date->format('H');
+ $day = (int)$date->format('d');
+ $month = (int)$date->format('m');
+ $weekday = (int)$date->format('w'); // 0-6
+ }
+ else {
+ throw new \RuntimeException('Date format not supported');
+ }
+
+ return new \DateTime($date->format('Y-m-d H:i:sP'));
+ }
+
+ /**
+ *
+ * @param int|string|\DateTime $date
+ */
+ public function matchExact($date)
+ {
+ $date = $this->parseDate($date, $min, $hour, $day, $month, $weekday);
+
+ return
+ (empty($this->minutes) || \in_array($min, $this->minutes, true)) &&
+ (empty($this->hours) || \in_array($hour, $this->hours, true)) &&
+ (empty($this->dom) || \in_array($day, $this->dom, true)) &&
+ (empty($this->months) || \in_array($month, $this->months, true)) &&
+ (empty($this->dow) || \in_array($weekday, $this->dow, true) || ($weekday == 0 && \in_array(7, $this->dow, true)) || ($weekday == 7 && \in_array(0, $this->dow, true))
+ );
+ }
+
+ /**
+ *
+ * @param int|string|\DateTime $date
+ * @param int $minuteBefore
+ * @param int $minuteAfter
+ */
+ public function matchWithMargin($date, $minuteBefore = 0, $minuteAfter = 0)
+ {
+ if ($minuteBefore > 0) {
+ throw new \RuntimeException('MinuteBefore parameter cannot be positive !');
+ }
+ if ($minuteAfter < 0) {
+ throw new \RuntimeException('MinuteAfter parameter cannot be negative !');
+ }
+
+ $date = $this->parseDate($date, $min, $hour, $day, $month, $weekday);
+ $interval = new \DateInterval('PT1M'); // 1 min
+ if ($minuteBefore !== 0) {
+ $date->sub(new \DateInterval('PT' . abs($minuteBefore) . 'M'));
+ }
+ $n = $minuteAfter - $minuteBefore + 1;
+ for ($i = 0; $i < $n; $i++) {
+ if ($this->matchExact($date)) {
+ return true;
+ }
+ $date->add($interval);
+ }
+
+ return false;
+ }
+
+ /**
+ *
+ * @param array $array
+ * @return string
+ */
+ protected function arrayToCron($array)
+ {
+ $n = \count($array);
+ if (!\is_array($array) || $n === 0) {
+ return '*';
+ }
+
+ $cron = [$array[0]];
+ $s = $c = $array[0];
+ for ($i = 1; $i < $n; $i++) {
+ if ($array[$i] == $c + 1) {
+ $c = $array[$i];
+ $cron[\count($cron) - 1] = $s . '-' . $c;
+ }
+ else {
+ $s = $c = $array[$i];
+ $cron[] = $c;
+ }
+ }
+
+ return implode(',', $cron);
+ }
+
+ /**
+ *
+ * @param array|string $string
+ * @param int $min
+ * @param int $max
+ * @return array
+ */
+ protected function cronToArray($string, $min, $max)
+ {
+ $array = [];
+ if (\is_array($string)) {
+ foreach ($string as $val) {
+ if (is_numeric($val) && (int)$val == $val && $val >= $min && $val <= $max) {
+ $array[] = (int)$val;
+ }
+ }
+ } elseif ($string !== '*') {
+ while ($string !== '') {
+ // test "*/n" expression
+ if (preg_match('/^\*\/([0-9]+),?/', $string, $m)) {
+ for ($i = max(0, $min); $i <= min(59, $max); $i += $m[1]) {
+ $array[] = (int)$i;
+ }
+ $string = substr($string, strlen($m[0]));
+ continue;
+ }
+ // test "a-b/n" expression
+ if (preg_match('/^([0-9]+)-([0-9]+)\/([0-9]+),?/', $string, $m)) {
+ for ($i = max($m[1], $min); $i <= min($m[2], $max); $i += $m[3]) {
+ $array[] = (int)$i;
+ }
+ $string = substr($string, strlen($m[0]));
+ continue;
+ }
+ // test "a-b" expression
+ if (preg_match('/^([0-9]+)-([0-9]+),?/', $string, $m)) {
+ for ($i = max($m[1], $min); $i <= min($m[2], $max); $i++) {
+ $array[] = (int)$i;
+ }
+ $string = substr($string, strlen($m[0]));
+ continue;
+ }
+ // test "c" expression
+ if (preg_match('/^([0-9]+),?/', $string, $m)) {
+ if ($m[1] >= $min && $m[1] <= $max) {
+ $array[] = (int)$m[1];
+ }
+ $string = substr($string, strlen($m[0]));
+ continue;
+ }
+
+ // something goes wrong in the expression
+ return [];
+ }
+ }
+ sort($array);
+
+ return $array;
+ }
+}
diff --git a/system/src/Grav/Common/Scheduler/IntervalTrait.php b/system/src/Grav/Common/Scheduler/IntervalTrait.php
new file mode 100644
index 0000000..b382c9d
--- /dev/null
+++ b/system/src/Grav/Common/Scheduler/IntervalTrait.php
@@ -0,0 +1,399 @@
+at = $expression;
+ $this->executionTime = CronExpression::factory($expression);
+
+ return $this;
+ }
+
+ /**
+ * Set the execution time to every minute.
+ *
+ * @return self
+ */
+ public function everyMinute()
+ {
+ return $this->at('* * * * *');
+ }
+
+ /**
+ * Set the execution time to every hour.
+ *
+ * @param int|string $minute
+ * @return self
+ */
+ public function hourly($minute = 0)
+ {
+ $c = $this->validateCronSequence($minute);
+
+ return $this->at("{$c['minute']} * * * *");
+ }
+
+ /**
+ * Set the execution time to once a day.
+ *
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function daily($hour = 0, $minute = 0)
+ {
+ if (\is_string($hour)) {
+ $parts = explode(':', $hour);
+ $hour = $parts[0];
+ $minute = $parts[1] ?? '0';
+ }
+ $c = $this->validateCronSequence($minute, $hour);
+
+ return $this->at("{$c['minute']} {$c['hour']} * * *");
+ }
+
+ /**
+ * Set the execution time to once a week.
+ *
+ * @param int|string $weekday
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function weekly($weekday = 0, $hour = 0, $minute = 0)
+ {
+ if (\is_string($hour)) {
+ $parts = explode(':', $hour);
+ $hour = $parts[0];
+ $minute = $parts[1] ?? '0';
+ }
+ $c = $this->validateCronSequence($minute, $hour, null, null, $weekday);
+
+ return $this->at("{$c['minute']} {$c['hour']} * * {$c['weekday']}");
+ }
+
+ /**
+ * Set the execution time to once a month.
+ *
+ * @param int|string $month
+ * @param int|string $day
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function monthly($month = '*', $day = 1, $hour = 0, $minute = 0)
+ {
+ if (\is_string($hour)) {
+ $parts = explode(':', $hour);
+ $hour = $parts[0];
+ $minute = $parts[1] ?? '0';
+ }
+ $c = $this->validateCronSequence($minute, $hour, $day, $month);
+
+ return $this->at("{$c['minute']} {$c['hour']} {$c['day']} {$c['month']} *");
+ }
+
+ /**
+ * Set the execution time to every Sunday.
+ *
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function sunday($hour = 0, $minute = 0)
+ {
+ return $this->weekly(0, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every Monday.
+ *
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function monday($hour = 0, $minute = 0)
+ {
+ return $this->weekly(1, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every Tuesday.
+ *
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function tuesday($hour = 0, $minute = 0)
+ {
+ return $this->weekly(2, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every Wednesday.
+ *
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function wednesday($hour = 0, $minute = 0)
+ {
+ return $this->weekly(3, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every Thursday.
+ *
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function thursday($hour = 0, $minute = 0)
+ {
+ return $this->weekly(4, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every Friday.
+ *
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function friday($hour = 0, $minute = 0)
+ {
+ return $this->weekly(5, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every Saturday.
+ *
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function saturday($hour = 0, $minute = 0)
+ {
+ return $this->weekly(6, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every January.
+ *
+ * @param int|string $day
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function january($day = 1, $hour = 0, $minute = 0)
+ {
+ return $this->monthly(1, $day, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every February.
+ *
+ * @param int|string $day
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function february($day = 1, $hour = 0, $minute = 0)
+ {
+ return $this->monthly(2, $day, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every March.
+ *
+ * @param int|string $day
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function march($day = 1, $hour = 0, $minute = 0)
+ {
+ return $this->monthly(3, $day, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every April.
+ *
+ * @param int|string $day
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function april($day = 1, $hour = 0, $minute = 0)
+ {
+ return $this->monthly(4, $day, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every May.
+ *
+ * @param int|string $day
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function may($day = 1, $hour = 0, $minute = 0)
+ {
+ return $this->monthly(5, $day, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every June.
+ *
+ * @param int|string $day
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function june($day = 1, $hour = 0, $minute = 0)
+ {
+ return $this->monthly(6, $day, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every July.
+ *
+ * @param int|string $day
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function july($day = 1, $hour = 0, $minute = 0)
+ {
+ return $this->monthly(7, $day, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every August.
+ *
+ * @param int|string $day
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function august($day = 1, $hour = 0, $minute = 0)
+ {
+ return $this->monthly(8, $day, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every September.
+ *
+ * @param int|string $day
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function september($day = 1, $hour = 0, $minute = 0)
+ {
+ return $this->monthly(9, $day, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every October.
+ *
+ * @param int|string $day
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function october($day = 1, $hour = 0, $minute = 0)
+ {
+ return $this->monthly(10, $day, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every November.
+ *
+ * @param int|string $day
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function november($day = 1, $hour = 0, $minute = 0)
+ {
+ return $this->monthly(11, $day, $hour, $minute);
+ }
+
+ /**
+ * Set the execution time to every December.
+ *
+ * @param int|string $day
+ * @param int|string $hour
+ * @param int|string $minute
+ * @return self
+ */
+ public function december($day = 1, $hour = 0, $minute = 0)
+ {
+ return $this->monthly(12, $day, $hour, $minute);
+ }
+
+ /**
+ * Validate sequence of cron expression.
+ *
+ * @param int|string $minute
+ * @param int|string $hour
+ * @param int|string $day
+ * @param int|string $month
+ * @param int|string $weekday
+ * @return array
+ */
+ private function validateCronSequence($minute = null, $hour = null, $day = null, $month = null, $weekday = null)
+ {
+ return [
+ 'minute' => $this->validateCronRange($minute, 0, 59),
+ 'hour' => $this->validateCronRange($hour, 0, 23),
+ 'day' => $this->validateCronRange($day, 1, 31),
+ 'month' => $this->validateCronRange($month, 1, 12),
+ 'weekday' => $this->validateCronRange($weekday, 0, 6),
+ ];
+ }
+
+ /**
+ * Validate sequence of cron expression.
+ *
+ * @param int|string $value
+ * @param int $min
+ * @param int $max
+ * @return mixed
+ */
+ private function validateCronRange($value, $min, $max)
+ {
+ if ($value === null || $value === '*') {
+ return '*';
+ }
+
+ if (! is_numeric($value) ||
+ ! ($value >= $min && $value <= $max)
+ ) {
+ throw new \InvalidArgumentException(
+ "Invalid value: it should be '*' or between {$min} and {$max}."
+ );
+ }
+
+ return $value;
+ }
+}
+
diff --git a/system/src/Grav/Common/Scheduler/Job.php b/system/src/Grav/Common/Scheduler/Job.php
new file mode 100644
index 0000000..6250f1d
--- /dev/null
+++ b/system/src/Grav/Common/Scheduler/Job.php
@@ -0,0 +1,522 @@
+id = Grav::instance()['inflector']->hyphenize($id);
+ } else {
+ if (is_string($command)) {
+ $this->id = md5($command);
+ } else {
+ /* @var object $command */
+ $this->id = spl_object_hash($command);
+ }
+ }
+ $this->creationTime = new \DateTime('now');
+ // initialize the directory path for lock files
+ $this->tempDir = sys_get_temp_dir();
+ $this->command = $command;
+ $this->args = $args;
+ // Set enabled state
+ $status = Grav::instance()['config']->get('scheduler.status');
+ $this->enabled = !(isset($status[$id]) && $status[$id] === 'disabled');
+ }
+
+ /**
+ * Get the command
+ *
+ * @return string
+ */
+ public function getCommand()
+ {
+ return $this->command;
+ }
+
+ /**
+ * Get the cron 'at' syntax for this job
+ *
+ * @return string
+ */
+ public function getAt()
+ {
+ return $this->at;
+ }
+
+ /**
+ * Get the status of this job
+ *
+ * @return bool
+ */
+ public function getEnabled()
+ {
+ return $this->enabled;
+ }
+
+ /**
+ * Get optional arguments
+ *
+ * @return string|null
+ */
+ public function getArguments()
+ {
+ if (\is_string($this->args)) {
+ return $this->args;
+ }
+
+ return null;
+ }
+
+ public function getCronExpression()
+ {
+ return CronExpression::factory($this->at);
+ }
+
+ /**
+ * Get the status of the last run for this job
+ *
+ * @return bool
+ */
+ public function isSuccessful()
+ {
+ return $this->successful;
+ }
+
+ /**
+ * Get the Job id.
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * Check if the Job is due to run.
+ * It accepts as input a DateTime used to check if
+ * the job is due. Defaults to job creation time.
+ * It also default the execution time if not previously defined.
+ *
+ * @param \DateTime $date
+ * @return bool
+ */
+ public function isDue(\DateTime $date = null)
+ {
+ // The execution time is being defaulted if not defined
+ if (!$this->executionTime) {
+ $this->at('* * * * *');
+ }
+
+ $date = $date ?? $this->creationTime;
+
+ return $this->executionTime->isDue($date);
+ }
+
+ /**
+ * Check if the Job is overlapping.
+ *
+ * @return bool
+ */
+ public function isOverlapping()
+ {
+ return $this->lockFile &&
+ file_exists($this->lockFile) &&
+ call_user_func($this->whenOverlapping, filemtime($this->lockFile)) === false;
+ }
+
+ /**
+ * Force the Job to run in foreground.
+ *
+ * @return $this
+ */
+ public function inForeground()
+ {
+ $this->runInBackground = false;
+
+ return $this;
+ }
+
+ /**
+ * Sets/Gets an option backlink
+ *
+ * @param string $link
+ *
+ * @return null|string
+ */
+ public function backlink($link = null)
+ {
+ if ($link) {
+ $this->backlink = $link;
+ }
+ return $this->backlink;
+ }
+
+
+ /**
+ * Check if the Job can run in background.
+ *
+ * @return bool
+ */
+ public function runInBackground()
+ {
+ return !(is_callable($this->command) || $this->runInBackground === false);
+ }
+
+ /**
+ * This will prevent the Job from overlapping.
+ * It prevents another instance of the same Job of
+ * being executed if the previous is still running.
+ * The job id is used as a filename for the lock file.
+ *
+ * @param string $tempDir The directory path for the lock files
+ * @param callable $whenOverlapping A callback to ignore job overlapping
+ * @return self
+ */
+ public function onlyOne($tempDir = null, callable $whenOverlapping = null)
+ {
+ if ($tempDir === null || !is_dir($tempDir)) {
+ $tempDir = $this->tempDir;
+ }
+ $this->lockFile = implode('/', [
+ trim($tempDir),
+ trim($this->id) . '.lock',
+ ]);
+ if ($whenOverlapping) {
+ $this->whenOverlapping = $whenOverlapping;
+ } else {
+ $this->whenOverlapping = function () {
+ return false;
+ };
+ }
+
+ return $this;
+ }
+
+ /**
+ * Configure the job.
+ *
+ * @param array $config
+ * @return self
+ */
+ public function configure(array $config = [])
+ {
+ // Check if config has defined a tempDir
+ if (isset($config['tempDir']) && is_dir($config['tempDir'])) {
+ $this->tempDir = $config['tempDir'];
+ }
+
+ return $this;
+ }
+
+ /**
+ * Truth test to define if the job should run if due.
+ *
+ * @param callable $fn
+ * @return self
+ */
+ public function when(callable $fn)
+ {
+ $this->truthTest = $fn();
+
+ return $this;
+ }
+
+ /**
+ * Run the job.
+ *
+ * @return bool
+ */
+ public function run()
+ {
+ // If the truthTest failed, don't run
+ if ($this->truthTest !== true) {
+ return false;
+ }
+
+ // If overlapping, don't run
+ if ($this->isOverlapping()) {
+ return false;
+ }
+
+ // Write lock file if necessary
+ $this->createLockFile();
+
+ // Call before if required
+ if (is_callable($this->before)) {
+ call_user_func($this->before);
+ }
+
+ // If command is callable...
+ if (is_callable($this->command)) {
+ $this->output = $this->exec();
+ } else {
+ $args = \is_string($this->args) ? $this->args : implode(' ', $this->args);
+ $command = $this->command . ' ' . $args;
+ $process = new Process($command);
+
+ $this->process = $process;
+
+ if ($this->runInBackground()) {
+ $process->start();
+ } else {
+ $process->run();
+ $this->finalize();
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Finish up processing the job
+ *
+ * @return void
+ */
+ public function finalize()
+ {
+ /** @var Process $process */
+ $process = $this->process;
+
+ if ($process) {
+ $process->wait();
+
+ if ($process->isSuccessful()) {
+ $this->successful = true;
+ $this->output = $process->getOutput();
+ } else {
+ $this->successful = false;
+ $this->output = $process->getErrorOutput();
+ }
+
+ $this->postRun();
+
+ unset($this->process);
+ }
+ }
+
+ /**
+ * Things to run after job has run
+ */
+ private function postRun()
+ {
+ if (count($this->outputTo) > 0) {
+ foreach ($this->outputTo as $file) {
+ $output_mode = $this->outputMode === 'append' ? FILE_APPEND | LOCK_EX : LOCK_EX;
+ file_put_contents($file, $this->output, $output_mode);
+ }
+ }
+
+ // Send output to email
+ $this->emailOutput();
+
+ // Call any callback defined
+ if (is_callable($this->after)) {
+ call_user_func($this->after, $this->output, $this->returnCode);
+ }
+
+ $this->removeLockFile();
+ }
+
+ /**
+ * Create the job lock file.
+ *
+ * @param mixed $content
+ * @return void
+ */
+ private function createLockFile($content = null)
+ {
+ if ($this->lockFile) {
+ if ($content === null || !\is_string($content)) {
+ $content = $this->getId();
+ }
+ file_put_contents($this->lockFile, $content);
+ }
+ }
+
+ /**
+ * Remove the job lock file.
+ *
+ * @return void
+ */
+ private function removeLockFile()
+ {
+ if ($this->lockFile && file_exists($this->lockFile)) {
+ unlink($this->lockFile);
+ }
+ }
+
+ /**
+ * Execute a callable job.
+ *
+ * @throws \RuntimeException
+ * @return string
+ */
+ private function exec()
+ {
+ $return_data = '';
+ ob_start();
+ try {
+ $return_data = call_user_func_array($this->command, $this->args);
+ $this->successful = true;
+ } catch (\RuntimeException $e) {
+ $this->successful = false;
+ }
+ $this->output = ob_get_clean() . (is_string($return_data) ? $return_data : '');
+
+ $this->postRun();
+ }
+
+ /**
+ * Set the file/s where to write the output of the job.
+ *
+ * @param string|array $filename
+ * @param bool $append
+ * @return self
+ */
+ public function output($filename, $append = false)
+ {
+ $this->outputTo = is_array($filename) ? $filename : [$filename];
+ $this->outputMode = $append === false ? 'overwrite' : 'append';
+
+ return $this;
+ }
+
+ /**
+ * Get the job output.
+ *
+ * @return mixed
+ */
+ public function getOutput()
+ {
+ return $this->output;
+ }
+
+ /**
+ * Set the emails where the output should be sent to.
+ * The Job should be set to write output to a file
+ * for this to work.
+ *
+ * @param string|array $email
+ * @return self
+ */
+ public function email($email)
+ {
+ if (!is_string($email) && !is_array($email)) {
+ throw new \InvalidArgumentException('The email can be only string or array');
+ }
+
+ $this->emailTo = is_array($email) ? $email : [$email];
+ // Force the job to run in foreground
+ $this->inForeground();
+
+ return $this;
+ }
+
+ /**
+ * Email the output of the job, if any.
+ *
+ * @return bool
+ */
+ private function emailOutput()
+ {
+ if (!count($this->outputTo) || !count($this->emailTo)) {
+ return false;
+ }
+
+ if (is_callable('Grav\Plugin\Email\Utils::sendEmail')) {
+ $subject ='Grav Scheduled Job [' . $this->getId() . ']';
+ $content = "Output from Job ID: {$this->getId()}
\nCommand: {$this->getCommand()}
\n".$this->getOutput()."\n
";
+ $to = $this->emailTo;
+
+ \Grav\Plugin\Email\Utils::sendEmail($subject, $content, $to);
+ }
+
+ return true;
+ }
+
+ /**
+ * Set function to be called before job execution
+ * Job object is injected as a parameter to callable function.
+ *
+ * @param callable $fn
+ * @return self
+ */
+ public function before(callable $fn)
+ {
+ $this->before = $fn;
+
+ return $this;
+ }
+
+ /**
+ * Set a function to be called after job execution.
+ * By default this will force the job to run in foreground
+ * because the output is injected as a parameter of this
+ * function, but it could be avoided by passing true as a
+ * second parameter. The job will run in background if it
+ * meets all the other criteria.
+ *
+ * @param callable $fn
+ * @param bool $runInBackground
+ * @return self
+ */
+ public function then(callable $fn, $runInBackground = false)
+ {
+ $this->after = $fn;
+ // Force the job to run in foreground
+ if ($runInBackground === false) {
+ $this->inForeground();
+ }
+
+ return $this;
+ }
+}
+
diff --git a/system/src/Grav/Common/Scheduler/Scheduler.php b/system/src/Grav/Common/Scheduler/Scheduler.php
new file mode 100644
index 0000000..df5602e
--- /dev/null
+++ b/system/src/Grav/Common/Scheduler/Scheduler.php
@@ -0,0 +1,361 @@
+get('scheduler.defaults', []);
+ $this->config = $config;
+
+ $this->status_path = Grav::instance()['locator']->findResource('user-data://scheduler', true, true);
+ if (!file_exists($this->status_path)) {
+ Folder::create($this->status_path);
+ }
+
+ }
+
+ /**
+ * Load saved jobs from config/scheduler.yaml file
+ */
+ public function loadSavedJobs()
+ {
+ $this->saved_jobs = [];
+ $saved_jobs = (array) Grav::instance()['config']->get('scheduler.custom_jobs', []);
+
+ foreach ($saved_jobs as $id => $j) {
+ $args = $j['args'] ?? [];
+ $id = Grav::instance()['inflector']->hyphenize($id);
+ $job = $this->addCommand($j['command'], $args, $id);
+
+ if (isset($j['at'])) {
+ $job->at($j['at']);
+ }
+
+ if (isset($j['output'])) {
+ $mode = isset($j['output_mode']) && $j['output_mode'] === 'append' ? true : false;
+ $job->output($j['output'], $mode);
+ }
+
+ if (isset($j['email'])) {
+ $job->email($j['email']);
+ }
+
+ // store in saved_jobs
+ $this->saved_jobs[] = $job;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the queued jobs as background/foreground
+ *
+ * @param bool $all
+ * @return array
+ */
+ public function getQueuedJobs($all = false)
+ {
+ $background = [];
+ $foreground = [];
+ foreach ($this->jobs as $job) {
+ if ($all || $job->getEnabled()) {
+ if ($job->runInBackground()) {
+ $background[] = $job;
+ } else {
+ $foreground[] = $job;
+ }
+ }
+
+ }
+ return [$background, $foreground];
+ }
+
+ /**
+ * Get all jobs if they are disabled or not as one array
+ *
+ * @return array
+ */
+ public function getAllJobs()
+ {
+ list($background, $foreground) = $this->loadSavedJobs()->getQueuedJobs(true);
+
+ return array_merge($background, $foreground);
+ }
+
+ /**
+ * Queues a PHP function execution.
+ *
+ * @param callable $fn The function to execute
+ * @param array $args Optional arguments to pass to the php script
+ * @param string $id Optional custom identifier
+ * @return Job
+ */
+ public function addFunction(callable $fn, $args = [], $id = null)
+ {
+ $job = new Job($fn, $args, $id);
+ $this->queueJob($job->configure($this->config));
+
+ return $job;
+ }
+
+ /**
+ * Queue a raw shell command.
+ *
+ * @param string $command The command to execute
+ * @param array $args Optional arguments to pass to the command
+ * @param string $id Optional custom identifier
+ * @return Job
+ */
+ public function addCommand($command, $args = [], $id = null)
+ {
+ $job = new Job($command, $args, $id);
+ $this->queueJob($job->configure($this->config));
+
+ return $job;
+ }
+
+ /**
+ * Run the scheduler.
+ *
+ * @param \DateTime|null $runTime Optional, run at specific moment
+ */
+ public function run(\DateTime $runTime = null)
+ {
+ $this->loadSavedJobs();
+
+ list($background, $foreground) = $this->getQueuedJobs(false);
+ $alljobs = array_merge($background, $foreground);
+
+ if (null === $runTime) {
+ $runTime = new \DateTime('now');
+ }
+
+ // Star processing jobs
+ foreach ($alljobs as $job) {
+ if ($job->isDue($runTime)) {
+ $job->run();
+ $this->jobs_run[] = $job;
+ }
+ }
+
+ // Finish handling any background jobs
+ foreach($background as $job) {
+ $job->finalize();
+ }
+
+ // Store states
+ $this->saveJobStates();
+ }
+
+ /**
+ * Reset all collected data of last run.
+ *
+ * Call before run() if you call run() multiple times.
+ */
+ public function resetRun()
+ {
+ // Reset collected data of last run
+ $this->executed_jobs = [];
+ $this->failed_jobs = [];
+ $this->output_schedule = [];
+
+ return $this;
+ }
+
+ /**
+ * Get the scheduler verbose output.
+ *
+ * @param string $type Allowed: text, html, array
+ * @return mixed The return depends on the requested $type
+ */
+ public function getVerboseOutput($type = 'text')
+ {
+ switch ($type) {
+ case 'text':
+ return implode("\n", $this->output_schedule);
+ case 'html':
+ return implode('
', $this->output_schedule);
+ case 'array':
+ return $this->output_schedule;
+ default:
+ throw new \InvalidArgumentException('Invalid output type');
+ }
+ }
+
+ /**
+ * Remove all queued Jobs.
+ */
+ public function clearJobs()
+ {
+ $this->jobs = [];
+
+ return $this;
+ }
+
+ /**
+ * Helper to get the full Cron command
+ *
+ * @return string
+ */
+ public function getCronCommand()
+ {
+ $phpBinaryFinder = new PhpExecutableFinder();
+ $php = $phpBinaryFinder->find();
+ $command = 'cd ' . str_replace(' ', '\ ', GRAV_ROOT) . ';' . $php . ' bin/grav scheduler';
+
+ return "(crontab -l; echo \"* * * * * {$command} 1>> /dev/null 2>&1\") | crontab -";
+ }
+
+ /**
+ * Helper to determine if cron job is setup
+ *
+ * @return int
+ */
+ public function isCrontabSetup()
+ {
+ $process = new Process('crontab -l');
+ $process->run();
+
+ if ($process->isSuccessful()) {
+ $output = $process->getOutput();
+
+ return preg_match('$bin\/grav schedule$', $output) ? 1 : 0;
+ }
+
+ $error = $process->getErrorOutput();
+
+ return Utils::startsWith($error, 'crontab: no crontab') ? 0 : 2;
+ }
+
+ /**
+ * Get the Job states file
+ *
+ * @return \RocketTheme\Toolbox\File\FileInterface|YamlFile
+ */
+ public function getJobStates()
+ {
+ return YamlFile::instance($this->status_path . '/status.yaml');
+ }
+
+ /**
+ * Save job states to statys file
+ */
+ private function saveJobStates()
+ {
+ $now = time();
+ $new_states = [];
+
+ foreach ($this->jobs_run as $job) {
+ if ($job->isSuccessful()) {
+ $new_states[$job->getId()] = ['state' => 'success', 'last-run' => $now];
+ $this->pushExecutedJob($job);
+ } else {
+ $new_states[$job->getId()] = ['state' => 'failure', 'last-run' => $now, 'error' => $job->getOutput()];
+ $this->pushFailedJob($job);
+ }
+ }
+
+ $saved_states = $this->getJobStates();
+ $saved_states->save(array_merge($saved_states->content(), $new_states));
+ }
+
+ /**
+ * Queue a job for execution in the correct queue.
+ *
+ * @param Job $job
+ * @return void
+ */
+ private function queueJob(Job $job)
+ {
+ $this->jobs[] = $job;
+
+ // Store jobs
+ }
+
+ /**
+ * Add an entry to the scheduler verbose output array.
+ *
+ * @param string $string
+ * @return void
+ */
+ private function addSchedulerVerboseOutput($string)
+ {
+ $now = '[' . (new \DateTime('now'))->format('c') . '] ';
+ $this->output_schedule[] = $now . $string;
+ // Print to stdoutput in light gray
+ // echo "\033[37m{$string}\033[0m\n";
+ }
+
+ /**
+ * Push a succesfully executed job.
+ *
+ * @param Job $job
+ * @return Job
+ */
+ private function pushExecutedJob(Job $job)
+ {
+ $this->executed_jobs[] = $job;
+ $command = $job->getCommand();
+ $args = $job->getArguments();
+ // If callable, log the string Closure
+ if (is_callable($command)) {
+ $command = \is_string($command) ? $command : 'Closure';
+ }
+ $this->addSchedulerVerboseOutput("Success: {$command} {$args}");
+
+ return $job;
+ }
+
+ /**
+ * Push a failed job.
+ *
+ * @param Job $job
+ * @return Job
+ */
+ private function pushFailedJob(Job $job)
+ {
+ $this->failed_jobs[] = $job;
+ $command = $job->getCommand();
+ // If callable, log the string Closure
+ if (is_callable($command)) {
+ $command = \is_string($command) ? $command : 'Closure';
+ }
+ $output = trim($job->getOutput());
+ $this->addSchedulerVerboseOutput("Error: {$command} → {$output}");
+
+ return $job;
+ }
+}
diff --git a/system/src/Grav/Common/Security.php b/system/src/Grav/Common/Security.php
index 8fa00e1..0f61097 100644
--- a/system/src/Grav/Common/Security.php
+++ b/system/src/Grav/Common/Security.php
@@ -1,17 +1,20 @@
routes();
@@ -20,11 +23,11 @@ class Security
$list = [];
-// // This needs Symfony 4.1 to work
-// $status && $status([
-// 'type' => 'count',
-// 'steps' => count($routes),
-// ]);
+ // This needs Symfony 4.1 to work
+ $status && $status([
+ 'type' => 'count',
+ 'steps' => count($routes),
+ ]);
foreach ($routes as $path) {
@@ -43,7 +46,12 @@ class Security
$results = Security::detectXssFromArray($data);
if (!empty($results)) {
- $list[$page->filePathClean()] = $results;
+ if ($route) {
+ $list[$page->route()] = $results;
+ } else {
+ $list[$page->filePathClean()] = $results;
+ }
+
}
} catch (\Exception $e) {
@@ -85,7 +93,7 @@ class Security
* their content.
*
* @param string $string The string to run XSS detection logic on
- * @return boolean|string Type of XSS vector if the given `$string` may contain XSS, false otherwise.
+ * @return bool|string Type of XSS vector if the given `$string` may contain XSS, false otherwise.
*
* Copies the code from: https://github.com/symphonycms/xssfilter/blob/master/extension.driver.php#L138
*/
@@ -118,9 +126,8 @@ class Security
$config = Grav::instance()['config'];
- $dangerous_tags = $config->get('security.xss_dangerous_tags');
- $dangerous_tags = array_map('preg_quote', array_map("trim", $dangerous_tags));
-
+ $dangerous_tags = array_map('preg_quote', array_map("trim", $config->get('security.xss_dangerous_tags')));
+ $invalid_protocols = array_map('preg_quote', array_map("trim", $config->get('security.xss_invalid_protocols')));
$enabled_rules = $config->get('security.xss_enabled');
// Set the patterns we'll test against
@@ -129,7 +136,7 @@ class Security
'on_events' => '#(<[^>]+[[a-z\x00-\x20\"\'\/])(\son|\sxmlns)[a-z].*=>?#iUu',
// Match javascript:, livescript:, vbscript:, mocha:, feed: and data: protocols
- 'invalid_protocols' => '#((java|live|vb)script|mocha|feed|data):.*?#iUu',
+ 'invalid_protocols' => '#(' . implode('|', $invalid_protocols) . '):.*?#iUu',
// Match -moz-bindings
'moz_binding' => '#-moz-binding[a-z\x00-\x20]*:#u',
@@ -138,7 +145,7 @@ class Security
'html_inline_styles' => '#(<[^>]+[a-z\x00-\x20\"\'\/])(style=[^>]*(url\:|x\:expression).*)>?#iUu',
// Match potentially dangerous tags
- 'dangerous_tags' => '#*(' . implode('|', $dangerous_tags ) . ')[^>]*>?#ui'
+ 'dangerous_tags' => '#*(' . implode('|', $dangerous_tags) . ')[^>]*>?#ui'
];
diff --git a/system/src/Grav/Common/Service/AccountsServiceProvider.php b/system/src/Grav/Common/Service/AccountsServiceProvider.php
new file mode 100644
index 0000000..ca15f5a
--- /dev/null
+++ b/system/src/Grav/Common/Service/AccountsServiceProvider.php
@@ -0,0 +1,124 @@
+get('system.accounts.type') === 'flex') {
+ $debugger->addMessage('User Accounts: Flex Directory');
+ return $this->flexAccounts($container);
+ }
+
+ return $this->dataAccounts($container);
+ };
+
+ $container['users'] = $container->factory(function (Container $container) {
+ user_error('Grav::instance()[\'users\'] is deprecated since Grav 1.6, use Grav::instance()[\'accounts\'] instead', E_USER_DEPRECATED);
+
+ return $container['accounts'];
+ });
+ }
+
+ protected function dataAccounts(Container $container)
+ {
+ define('GRAV_USER_INSTANCE', 'DATA');
+
+ // Use User class for backwards compatibility.
+ return new DataUser\UserCollection(User::class);
+ }
+
+ protected function flexAccounts(Container $container)
+ {
+ define('GRAV_USER_INSTANCE', 'FLEX');
+
+ /** @var Config $config */
+ $config = $container['config'];
+
+ $options = [
+ 'enabled' => true,
+ 'data' => [
+ 'object' => User::class, // Use User class for backwards compatibility.
+ 'collection' => FlexUser\UserCollection::class,
+ 'index' => FlexUser\UserIndex::class,
+ 'storage' => $this->getFlexStorage($config->get('system.accounts.storage', 'file')),
+ 'search' => [
+ 'options' => [
+ 'contains' => 1
+ ],
+ 'fields' => [
+ 'key',
+ 'email'
+ ]
+ ]
+ ]
+ ] + ($config->get('plugins.flex-objects.object') ?: []);
+
+ $directory = new FlexDirectory('accounts', 'blueprints://user/accounts.yaml', $options);
+
+ /** @var EventDispatcher $dispatcher */
+ $dispatcher = $container['events'];
+ $dispatcher->addListener('onFlexInit', function (Event $event) use ($directory) {
+ /** @var Flex $flex */
+ $flex = $event['flex'];
+ $flex->addDirectory($directory);
+ });
+
+ return $directory->getIndex();
+ }
+
+ protected function getFlexStorage($config)
+ {
+ if (\is_array($config)) {
+ return $config;
+ }
+
+ if ($config === 'folder') {
+ return [
+ 'class' => FlexUser\Storage\UserFolderStorage::class,
+ 'options' => [
+ 'formatter' => ['class' => YamlFormatter::class],
+ 'folder' => 'account://',
+ 'pattern' => '{FOLDER}/{KEY:2}/{KEY}/user.yaml',
+ 'key' => 'username',
+ 'indexed' => true
+ ],
+ ];
+ }
+
+ return [
+ 'class' => FlexUser\Storage\UserFileStorage::class,
+ 'options' => [
+ 'formatter' => ['class' => YamlFormatter::class],
+ 'folder' => 'account://',
+ 'pattern' => '{FOLDER}/{KEY}.yaml',
+ 'key' => 'storage_key',
+ 'indexed' => true
+ ],
+ ];
+ }
+}
diff --git a/system/src/Grav/Common/Service/AssetsServiceProvider.php b/system/src/Grav/Common/Service/AssetsServiceProvider.php
index 9618013..004da9c 100644
--- a/system/src/Grav/Common/Service/AssetsServiceProvider.php
+++ b/system/src/Grav/Common/Service/AssetsServiceProvider.php
@@ -1,8 +1,9 @@
setup();
+
+ return $backups;
+ };
+ }
+}
diff --git a/system/src/Grav/Common/Service/ConfigServiceProvider.php b/system/src/Grav/Common/Service/ConfigServiceProvider.php
index 379c042..c874441 100644
--- a/system/src/Grav/Common/Service/ConfigServiceProvider.php
+++ b/system/src/Grav/Common/Service/ConfigServiceProvider.php
@@ -1,8 +1,9 @@
init();
+
+ return $setup;
};
$container['blueprints'] = function ($c) {
@@ -45,11 +50,10 @@ class ConfigServiceProvider implements ServiceProviderInterface
$container['languages'] = function ($c) {
return static::languages($c);
};
- }
- public static function setup(Container $container)
- {
- return new Setup($container);
+ $container['language'] = function ($c) {
+ return new Language($c);
+ };
}
public static function blueprints(Container $container)
@@ -93,12 +97,15 @@ class ConfigServiceProvider implements ServiceProviderInterface
$paths = $locator->findResources('plugins://');
$files += (new ConfigFileFinder)->setBase('plugins')->locateInFolders($paths);
- $config = new CompiledConfig($cache, $files, GRAV_ROOT);
- $config->setBlueprints(function() use ($container) {
+ $compiled = new CompiledConfig($cache, $files, GRAV_ROOT);
+ $compiled->setBlueprints(function() use ($container) {
return $container['blueprints'];
});
- return $config->name("master-{$setup->environment}")->load();
+ $config = $compiled->name("master-{$setup->environment}")->load();
+ $config->environment = $setup->environment;
+
+ return $config;
}
public static function languages(Container $container)
@@ -133,8 +140,8 @@ class ConfigServiceProvider implements ServiceProviderInterface
/**
* Find specific paths in plugins
*
- * @param $plugins
- * @param $folder_path
+ * @param array $plugins
+ * @param string $folder_path
* @return array
*/
private static function pluginFolderPaths($plugins, $folder_path)
diff --git a/system/src/Grav/Common/Service/ErrorServiceProvider.php b/system/src/Grav/Common/Service/ErrorServiceProvider.php
index 139de54..fce69ff 100644
--- a/system/src/Grav/Common/Service/ErrorServiceProvider.php
+++ b/system/src/Grav/Common/Service/ErrorServiceProvider.php
@@ -1,8 +1,9 @@
findResource('log://grav.log', true, true);
-
$log->pushHandler(new StreamHandler($log_file, Logger::DEBUG));
return $log;
diff --git a/system/src/Grav/Common/Service/OutputServiceProvider.php b/system/src/Grav/Common/Service/OutputServiceProvider.php
index 89322ee..a9b2877 100644
--- a/system/src/Grav/Common/Service/OutputServiceProvider.php
+++ b/system/src/Grav/Common/Service/OutputServiceProvider.php
@@ -1,14 +1,15 @@
processSite($page->templateFormat());
diff --git a/system/src/Grav/Common/Service/PageServiceProvider.php b/system/src/Grav/Common/Service/PagesServiceProvider.php
similarity index 92%
rename from system/src/Grav/Common/Service/PageServiceProvider.php
rename to system/src/Grav/Common/Service/PagesServiceProvider.php
index ccfa580..0a37e83 100644
--- a/system/src/Grav/Common/Service/PageServiceProvider.php
+++ b/system/src/Grav/Common/Service/PagesServiceProvider.php
@@ -1,8 +1,9 @@
fromGlobals();
+ };
+
+ $container['route'] = function() {
+ return Uri::getCurrentRoute();
+ };
+ }
+}
diff --git a/system/src/Grav/Common/Service/SchedulerServiceProvider.php b/system/src/Grav/Common/Service/SchedulerServiceProvider.php
new file mode 100644
index 0000000..1df4bbc
--- /dev/null
+++ b/system/src/Grav/Common/Service/SchedulerServiceProvider.php
@@ -0,0 +1,24 @@
+hyphenize($config->get('system.session.name', 'grav_site')) . '-' . substr(md5(GRAV_ROOT), 0, 7);
+ $session_prefix = $c['inflector']->hyphenize($config->get('system.session.name', 'grav-site'));
+ $session_uniqueness = $config->get('system.session.uniqueness', 'path') === 'path' ? substr(md5(GRAV_ROOT), 0, 7) : md5($config->get('security.salt'));
+
+ $session_name = $session_prefix . '-' . $session_uniqueness;
+
if ($is_admin && $config->get('system.session.split', true)) {
$session_name .= '-admin';
}
diff --git a/system/src/Grav/Common/Service/StreamsServiceProvider.php b/system/src/Grav/Common/Service/StreamsServiceProvider.php
index a2ebcc6..3bbbea1 100644
--- a/system/src/Grav/Common/Service/StreamsServiceProvider.php
+++ b/system/src/Grav/Common/Service/StreamsServiceProvider.php
@@ -1,8 +1,9 @@
initializeLocator($locator);
return $locator;
};
- $container['streams'] = function($c) {
+ $container['streams'] = function(Container $container) {
/** @var Setup $setup */
- $setup = $c['setup'];
+ $setup = $container['setup'];
/** @var UniformResourceLocator $locator */
- $locator = $c['locator'];
+ $locator = $container['locator'];
// Set locator to both streams.
Stream::setLocator($locator);
diff --git a/system/src/Grav/Common/Service/TaskServiceProvider.php b/system/src/Grav/Common/Service/TaskServiceProvider.php
index 40b9696..c22121a 100644
--- a/system/src/Grav/Common/Service/TaskServiceProvider.php
+++ b/system/src/Grav/Common/Service/TaskServiceProvider.php
@@ -1,8 +1,9 @@
param('task');
+ $container['task'] = function (Grav $c) {
+ $task = $_POST['task'] ?? $c['uri']->param('task');
+ if (null !== $task) {
+ $task = filter_var($task, FILTER_SANITIZE_STRING);
+ }
+
+ return $task ?: null;
+ };
+
+ $container['action'] = function (Grav $c) {
+ $action = $_POST['action'] ?? $c['uri']->param('action');
+ if (null !== $action) {
+ $action = filter_var($action, FILTER_SANITIZE_STRING);
+ }
+
+ return $action ?: null;
};
}
}
diff --git a/system/src/Grav/Common/Session.php b/system/src/Grav/Common/Session.php
index d617f46..cd9b2c1 100644
--- a/system/src/Grav/Common/Session.php
+++ b/system/src/Grav/Common/Session.php
@@ -1,13 +1,16 @@
getInstance() method instead.
*/
public static function instance()
{
- user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getInstance() method instead', E_USER_DEPRECATED);
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use ->getInstance() method instead', E_USER_DEPRECATED);
return static::getInstance();
}
@@ -31,7 +34,7 @@ class Session extends \Grav\Framework\Session\Session
*/
public function init()
{
- if ($this->autoStart) {
+ if ($this->autoStart && !$this->isStarted()) {
$this->start();
$this->autoStart = false;
@@ -53,11 +56,11 @@ class Session extends \Grav\Framework\Session\Session
* Returns attributes.
*
* @return array Attributes
- * @deprecated 1.5 Use getAll() method instead
+ * @deprecated 1.5 Use ->getAll() method instead.
*/
public function all()
{
- user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getAll() method instead', E_USER_DEPRECATED);
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use ->getAll() method instead', E_USER_DEPRECATED);
return $this->getAll();
}
@@ -66,11 +69,11 @@ class Session extends \Grav\Framework\Session\Session
* Checks if the session was started.
*
* @return Boolean
- * @deprecated 1.5 Use isStarted() method instead
+ * @deprecated 1.5 Use ->isStarted() method instead.
*/
public function started()
{
- user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use isStarted() method instead', E_USER_DEPRECATED);
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use ->isStarted() method instead', E_USER_DEPRECATED);
return $this->isStarted();
}
@@ -84,7 +87,7 @@ class Session extends \Grav\Framework\Session\Session
*/
public function setFlashObject($name, $object)
{
- $this->{$name} = serialize($object);
+ $this->__set($name, serialize($object));
return $this;
}
@@ -97,9 +100,34 @@ class Session extends \Grav\Framework\Session\Session
*/
public function getFlashObject($name)
{
- $object = unserialize($this->{$name});
+ $serialized = $this->__get($name);
- $this->{$name} = null;
+ $object = \is_string($serialized) ? unserialize($serialized, ['allowed_classes' => true]) : $serialized;
+
+ $this->__unset($name);
+
+ if ($name === 'files-upload') {
+ $grav = Grav::instance();
+
+ // Make sure that Forms 3.0+ has been installed.
+ if (null === $object && isset($grav['forms'])) {
+ user_error(
+ __CLASS__ . '::' . __FUNCTION__ . '(\'files-upload\') is deprecated since Grav 1.6, use $form->getFlash()->getLegacyFiles() instead',
+ E_USER_DEPRECATED
+ );
+
+ /** @var Uri $uri */
+ $uri = $grav['uri'];
+ /** @var Grav\Plugin\Form\Forms $form */
+ $form = $grav['forms']->getActiveForm();
+
+ $sessionField = base64_encode($uri->url);
+
+ /** @var FormFlash $flash */
+ $flash = $form ? $form->getFlash() : null;
+ $object = $flash ? [$sessionField => $flash->getLegacyFiles()] : null;
+ }
+ }
return $object;
}
diff --git a/system/src/Grav/Common/Taxonomy.php b/system/src/Grav/Common/Taxonomy.php
index dda2844..e323647 100644
--- a/system/src/Grav/Common/Taxonomy.php
+++ b/system/src/Grav/Common/Taxonomy.php
@@ -1,8 +1,9 @@
taxonomy();
}
- if (!$page->published() || empty($page_taxonomy)) {
+ if (empty($page_taxonomy) || !$page->published()) {
return;
}
@@ -99,7 +100,7 @@ class Taxonomy
}
}
- if (strtolower($operator) == 'or') {
+ if (strtolower($operator) === 'or') {
foreach ($matches as $match) {
$results = array_merge($results, $match);
}
@@ -136,14 +137,8 @@ class Taxonomy
*
* @return array keys of this taxonomy
*/
- public function getTaxonomyItemKeys($taxonomy) {
- if (isset($this->taxonomy_map[$taxonomy])) {
-
- $results = array_keys($this->taxonomy_map[$taxonomy]);
-
- return $results;
- }
-
- return [];
+ public function getTaxonomyItemKeys($taxonomy)
+ {
+ return isset($this->taxonomy_map[$taxonomy]) ? array_keys($this->taxonomy_map[$taxonomy]) : [];
}
}
diff --git a/system/src/Grav/Common/Theme.php b/system/src/Grav/Common/Theme.php
index 537df11..dbc589f 100644
--- a/system/src/Grav/Common/Theme.php
+++ b/system/src/Grav/Common/Theme.php
@@ -1,14 +1,15 @@
getFilename();
- $result = self::get($theme);
+ $result = $this->get($theme);
if ($result) {
$list[$theme] = $result;
@@ -143,7 +144,7 @@ class Themes extends Iterator
$blueprint->set('thumbnail', $this->grav['base_url'] . '/' . $path);
}
- $obj = new Data($file->content(), $blueprint);
+ $obj = new Data((array)$file->content(), $blueprint);
// Override with user configuration.
$obj->merge($this->config->get('themes.' . $name) ?: []);
@@ -249,7 +250,7 @@ class Themes extends Iterator
}
}
- if (in_array($scheme, $registered)) {
+ if (\in_array($scheme, $registered, true)) {
stream_wrapper_unregister($scheme);
}
$type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
@@ -289,12 +290,12 @@ class Themes extends Iterator
$locator = $this->grav['locator'];
if ($config->get('system.languages.translations', true)) {
- $language_file = $locator->findResource("theme://languages" . YAML_EXT);
+ $language_file = $locator->findResource('theme://languages' . YAML_EXT);
if ($language_file) {
$language = CompiledYamlFile::instance($language_file)->content();
$this->grav['languages']->mergeRecursive($language);
}
- $languages_folder = $locator->findResource("theme://languages/");
+ $languages_folder = $locator->findResource('theme://languages');
if (file_exists($languages_folder)) {
$languages = [];
$iterator = new \DirectoryIterator($languages_folder);
diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeMarkdown.php b/system/src/Grav/Common/Twig/Node/TwigNodeMarkdown.php
index 0aa12ea..4d238da 100644
--- a/system/src/Grav/Common/Twig/Node/TwigNodeMarkdown.php
+++ b/system/src/Grav/Common/Twig/Node/TwigNodeMarkdown.php
@@ -1,25 +1,36 @@
$body], [], $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
- * @param \Twig_Compiler A Twig_Compiler instance
+ * @param Compiler $compiler A Twig_Compiler instance
*/
- public function compile(\Twig_Compiler $compiler)
+ public function compile(Compiler $compiler)
{
$compiler
->addDebugInfo($this)
diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeRender.php b/system/src/Grav/Common/Twig/Node/TwigNodeRender.php
new file mode 100644
index 0000000..753adf0
--- /dev/null
+++ b/system/src/Grav/Common/Twig/Node/TwigNodeRender.php
@@ -0,0 +1,78 @@
+ $object, 'layout' => $layout, 'context' => $context], [], $lineno, $tag);
+ }
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Compiler $compiler A Twig_Compiler instance
+ * @throws \LogicException
+ */
+ public function compile(Compiler $compiler)
+ {
+ $compiler->addDebugInfo($this);
+ $compiler->write('$object = ')->subcompile($this->getNode('object'))->raw(';' . PHP_EOL);
+
+ $layout = $this->getNode('layout');
+ if ($layout) {
+ $compiler->write('$layout = ')->subcompile($layout)->raw(';' . PHP_EOL);
+ } else {
+ $compiler->write('$layout = null;' . PHP_EOL);
+ }
+
+ $context = $this->getNode('context');
+ if ($context) {
+ $compiler->write('$attributes = ')->subcompile($context)->raw(';' . PHP_EOL);
+ } else {
+ $compiler->write('$attributes = null;' . PHP_EOL);
+ }
+
+ $compiler
+ ->write('$html = $object->render($layout, $attributes ?? []);' . PHP_EOL)
+ ->write('$block = $context[\'block\'] ?? null;' . PHP_EOL)
+ ->write('if ($block instanceof \Grav\Framework\ContentBlock\ContentBlock && $html instanceof \Grav\Framework\ContentBlock\ContentBlock) {' . PHP_EOL)
+ ->indent()
+ ->write('$block->addBlock($html);' . PHP_EOL)
+ ->write('echo $html->getToken();' . PHP_EOL)
+ ->outdent()
+ ->write('} else {' . PHP_EOL)
+ ->indent()
+ ->write('echo (string)$html;' . PHP_EOL)
+ ->outdent()
+ ->write('}' . PHP_EOL)
+ ;
+ }
+}
diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeScript.php b/system/src/Grav/Common/Twig/Node/TwigNodeScript.php
index 23f16a3..4f6f776 100644
--- a/system/src/Grav/Common/Twig/Node/TwigNodeScript.php
+++ b/system/src/Grav/Common/Twig/Node/TwigNodeScript.php
@@ -1,33 +1,39 @@
addDebugInfo($this);
+ $compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];\n");
+
if ($this->getNode('attributes') !== null) {
$compiler
->write('$attributes = ')
->subcompile($this->getNode('attributes'))
->raw(";\n")
- ->write("if (\$attributes !== null && !is_array(\$attributes)) {\n")
+ ->write("if (!is_array(\$attributes)) {\n")
->indent()
->write("throw new UnexpectedValueException('{% {$this->tagName} with x %}: x is not an array');\n")
->outdent()
@@ -60,43 +68,34 @@ class TwigNodeScript extends \Twig_Node implements \Twig_NodeCaptureInterface
if ($this->getNode('group') !== null) {
$compiler
- ->write('$group = ')
+ ->write("\$attributes['group'] = ")
->subcompile($this->getNode('group'))
->raw(";\n")
- ->write("if (\$group !== null && !is_string(\$group)) {\n")
+ ->write("if (!is_string(\$attributes['group'])) {\n")
->indent()
->write("throw new UnexpectedValueException('{% {$this->tagName} in x %}: x is not a string');\n")
->outdent()
->write("}\n");
- } else {
- $compiler->write('$group = null;' . "\n");
}
if ($this->getNode('priority') !== null) {
$compiler
- ->write('$priority = (int)(')
+ ->write("\$attributes['priority'] = (int)(")
->subcompile($this->getNode('priority'))
->raw(");\n");
- } else {
- $compiler->write('$priority = null;' . "\n");
}
- $compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];\n");
-
if ($this->getNode('file') !== null) {
$compiler
- ->write('$file = ')
+ ->write('$assets->addJs(')
->subcompile($this->getNode('file'))
- ->write(";\n")
- ->write("\$pipeline = !empty(\$attributes['pipeline']);\n")
- ->write("\$loading = !empty(\$attributes['defer']) ? 'defer' : (!empty(\$attributes['async']) ? 'async' : null);\n")
- ->write("\$assets->addJs(\$file, \$priority, \$pipeline, \$loading, \$group);\n");
+ ->raw(", \$attributes);\n");
} else {
$compiler
->write("ob_start();\n")
->subcompile($this->getNode('body'))
- ->write("\$content = ob_get_clean();")
- ->write("\$assets->addInlineJs(\$content, \$priority, \$group, \$attributes);\n");
+ ->write('$content = ob_get_clean();' . "\n")
+ ->write("\$assets->addInlineJs(\$content, \$attributes);\n");
}
}
}
diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeStyle.php b/system/src/Grav/Common/Twig/Node/TwigNodeStyle.php
index ae30e43..22f3302 100644
--- a/system/src/Grav/Common/Twig/Node/TwigNodeStyle.php
+++ b/system/src/Grav/Common/Twig/Node/TwigNodeStyle.php
@@ -1,30 +1,39 @@
addDebugInfo($this);
+ $compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];\n");
+
if ($this->getNode('attributes') !== null) {
$compiler
->write('$attributes = ')
->subcompile($this->getNode('attributes'))
->raw(";\n")
- ->write("if (\$attributes !== null && !is_array(\$attributes)) {\n")
+ ->write("if (!is_array(\$attributes)) {\n")
->indent()
->write("throw new UnexpectedValueException('{% {$this->tagName} with x %}: x is not an array');\n")
->outdent()
@@ -57,42 +68,34 @@ class TwigNodeStyle extends \Twig_Node implements \Twig_NodeCaptureInterface
if ($this->getNode('group') !== null) {
$compiler
- ->write('$group = ')
+ ->write("\$attributes['group'] = ")
->subcompile($this->getNode('group'))
->raw(";\n")
- ->write("if (\$group !== null && !is_string(\$group)) {\n")
+ ->write("if (!is_string(\$attributes['group'])) {\n")
->indent()
->write("throw new UnexpectedValueException('{% {$this->tagName} in x %}: x is not a string');\n")
->outdent()
->write("}\n");
- } else {
- $compiler->write('$group = null;' . "\n");
}
if ($this->getNode('priority') !== null) {
$compiler
- ->write('$priority = (int)(')
+ ->write("\$attributes['priority'] = (int)(")
->subcompile($this->getNode('priority'))
->raw(");\n");
- } else {
- $compiler->write('$priority = null;' . "\n");
}
- $compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];\n");
-
if ($this->getNode('file') !== null) {
$compiler
- ->write('$file = ')
+ ->write('$assets->addCss(')
->subcompile($this->getNode('file'))
- ->write(";\n")
- ->write("\$pipeline = !empty(\$attributes['pipeline']);\n")
- ->write("\$assets->addCss(\$file, \$priority, \$pipeline, \$group);\n");
+ ->raw(", \$attributes);\n");
} else {
$compiler
->write("ob_start();\n")
->subcompile($this->getNode('body'))
- ->write("\$content = ob_get_clean();")
- ->write("\$assets->addInlineCss(\$content, \$priority, \$group);\n");
+ ->write('$content = ob_get_clean();' . "\n")
+ ->write("\$assets->addInlineCss(\$content, \$attributes);\n");
}
}
}
diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeSwitch.php b/system/src/Grav/Common/Twig/Node/TwigNodeSwitch.php
index a03faa5..f29ed6d 100644
--- a/system/src/Grav/Common/Twig/Node/TwigNodeSwitch.php
+++ b/system/src/Grav/Common/Twig/Node/TwigNodeSwitch.php
@@ -1,19 +1,31 @@
addDebugInfo($this)
diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeThrow.php b/system/src/Grav/Common/Twig/Node/TwigNodeThrow.php
new file mode 100644
index 0000000..5bb5899
--- /dev/null
+++ b/system/src/Grav/Common/Twig/Node/TwigNodeThrow.php
@@ -0,0 +1,51 @@
+ $message], ['code' => $code], $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Compiler $compiler A Twig_Compiler instance
+ * @throws \LogicException
+ */
+ public function compile(Compiler $compiler)
+ {
+ $compiler->addDebugInfo($this);
+
+ $compiler
+ ->write('throw new \RuntimeException(')
+ ->subcompile($this->getNode('message'))
+ ->write(', ')
+ ->write($this->getAttribute('code') ?: 500)
+ ->write(");\n");
+ }
+}
diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeTryCatch.php b/system/src/Grav/Common/Twig/Node/TwigNodeTryCatch.php
index ecfcdab..4028e74 100644
--- a/system/src/Grav/Common/Twig/Node/TwigNodeTryCatch.php
+++ b/system/src/Grav/Common/Twig/Node/TwigNodeTryCatch.php
@@ -1,32 +1,43 @@
$try, 'catch' => $catch), array(), $lineno, $tag);
+ parent::__construct(['try' => $try, 'catch' => $catch], [], $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
- * @param \Twig_Compiler $compiler A Twig_Compiler instance
+ * @param Compiler $compiler A Twig_Compiler instance
* @throws \LogicException
*/
- public function compile(\Twig_Compiler $compiler)
+ public function compile(Compiler $compiler)
{
$compiler->addDebugInfo($this);
diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserMarkdown.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserMarkdown.php
index a1d4135..7a1d139 100644
--- a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserMarkdown.php
+++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserMarkdown.php
@@ -1,14 +1,17 @@
getLine();
- $this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
+ $this->parser->getStream()->expect(Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideMarkdownEnd'), true);
- $this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
+ $this->parser->getStream()->expect(Token::BLOCK_END_TYPE);
return new TwigNodeMarkdown($body, $lineno, $this->getTag());
}
/**
* Decide if current token marks end of Markdown block.
*
- * @param \Twig_Token $token
+ * @param Token $token
* @return bool
*/
- public function decideMarkdownEnd(\Twig_Token $token)
+ public function decideMarkdownEnd(Token $token)
{
return $token->test('endmarkdown');
}
diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserRender.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserRender.php
new file mode 100644
index 0000000..811705d
--- /dev/null
+++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserRender.php
@@ -0,0 +1,75 @@
+getLine();
+
+ [$object, $layout, $context] = $this->parseArguments($token);
+
+ return new TwigNodeRender($object, $layout, $context, $lineno, $this->getTag());
+ }
+
+ /**
+ * @param Token $token
+ * @return array
+ */
+ protected function parseArguments(Token $token)
+ {
+ $stream = $this->parser->getStream();
+
+ $object = $this->parser->getExpressionParser()->parseExpression();
+
+ $layout = null;
+ if ($stream->nextIf(Token::NAME_TYPE, 'layout')) {
+ $stream->expect(Token::PUNCTUATION_TYPE, ':');
+ $layout = $this->parser->getExpressionParser()->parseExpression();
+ }
+
+ $context = null;
+ if ($stream->nextIf(Token::NAME_TYPE, 'with')) {
+ $context = $this->parser->getExpressionParser()->parseExpression();
+ }
+
+ $stream->expect(Token::BLOCK_END_TYPE);
+
+ return [$object, $layout, $context];
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'render';
+ }
+}
diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserScript.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserScript.php
index fd87b1b..bb04707 100644
--- a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserScript.php
+++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserScript.php
@@ -1,14 +1,18 @@
getLine();
$stream = $this->parser->getStream();
@@ -39,51 +43,51 @@ class TwigTokenParserScript extends \Twig_TokenParser
$content = null;
if ($file === null) {
$content = $this->parser->subparse([$this, 'decideBlockEnd'], true);
- $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+ $stream->expect(Token::BLOCK_END_TYPE);
}
return new TwigNodeScript($content, $file, $group, $priority, $attributes, $lineno, $this->getTag());
}
/**
- * @param \Twig_Token $token
+ * @param Token $token
* @return array
*/
- protected function parseArguments(\Twig_Token $token)
+ protected function parseArguments(Token $token)
{
$stream = $this->parser->getStream();
$file = null;
- if (!$stream->test(\Twig_Token::NAME_TYPE) && !$stream->test(\Twig_Token::OPERATOR_TYPE) && !$stream->test(\Twig_Token::BLOCK_END_TYPE)) {
+ if (!$stream->test(Token::NAME_TYPE) && !$stream->test(Token::OPERATOR_TYPE) && !$stream->test(Token::BLOCK_END_TYPE)) {
$file = $this->parser->getExpressionParser()->parseExpression();
}
$group = null;
- if ($stream->nextIf(\Twig_Token::OPERATOR_TYPE, 'in')) {
+ if ($stream->nextIf(Token::OPERATOR_TYPE, 'in')) {
$group = $this->parser->getExpressionParser()->parseExpression();
}
$priority = null;
- if ($stream->nextIf(\Twig_Token::NAME_TYPE, 'priority')) {
- $stream->expect(\Twig_Token::PUNCTUATION_TYPE, ':');
+ if ($stream->nextIf(Token::NAME_TYPE, 'priority')) {
+ $stream->expect(Token::PUNCTUATION_TYPE, ':');
$priority = $this->parser->getExpressionParser()->parseExpression();
}
$attributes = null;
- if ($stream->nextIf(\Twig_Token::NAME_TYPE, 'with')) {
+ if ($stream->nextIf(Token::NAME_TYPE, 'with')) {
$attributes = $this->parser->getExpressionParser()->parseExpression();
}
- $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+ $stream->expect(Token::BLOCK_END_TYPE);
return [$file, $group, $priority, $attributes];
}
/**
- * @param \Twig_Token $token
+ * @param Token $token
* @return bool
*/
- public function decideBlockEnd(\Twig_Token $token)
+ public function decideBlockEnd(Token $token)
{
return $token->test('endscript');
}
diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserStyle.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserStyle.php
index 0c09ed4..5a5beb6 100644
--- a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserStyle.php
+++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserStyle.php
@@ -1,14 +1,18 @@
getLine();
$stream = $this->parser->getStream();
@@ -38,51 +42,51 @@ class TwigTokenParserStyle extends \Twig_TokenParser
$content = null;
if (!$file) {
$content = $this->parser->subparse([$this, 'decideBlockEnd'], true);
- $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+ $stream->expect(Token::BLOCK_END_TYPE);
}
return new TwigNodeStyle($content, $file, $group, $priority, $attributes, $lineno, $this->getTag());
}
/**
- * @param \Twig_Token $token
+ * @param Token $token
* @return array
*/
- protected function parseArguments(\Twig_Token $token)
+ protected function parseArguments(Token $token)
{
$stream = $this->parser->getStream();
$file = null;
- if (!$stream->test(\Twig_Token::NAME_TYPE) && !$stream->test(\Twig_Token::OPERATOR_TYPE) && !$stream->test(\Twig_Token::BLOCK_END_TYPE)) {
+ if (!$stream->test(Token::NAME_TYPE) && !$stream->test(Token::OPERATOR_TYPE) && !$stream->test(Token::BLOCK_END_TYPE)) {
$file = $this->parser->getExpressionParser()->parseExpression();
}
$group = null;
- if ($stream->nextIf(\Twig_Token::OPERATOR_TYPE, 'in')) {
+ if ($stream->nextIf(Token::OPERATOR_TYPE, 'in')) {
$group = $this->parser->getExpressionParser()->parseExpression();
}
$priority = null;
- if ($stream->nextIf(\Twig_Token::NAME_TYPE, 'priority')) {
- $stream->expect(\Twig_Token::PUNCTUATION_TYPE, ':');
+ if ($stream->nextIf(Token::NAME_TYPE, 'priority')) {
+ $stream->expect(Token::PUNCTUATION_TYPE, ':');
$priority = $this->parser->getExpressionParser()->parseExpression();
}
$attributes = null;
- if ($stream->nextIf(\Twig_Token::NAME_TYPE, 'with')) {
+ if ($stream->nextIf(Token::NAME_TYPE, 'with')) {
$attributes = $this->parser->getExpressionParser()->parseExpression();
}
- $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+ $stream->expect(Token::BLOCK_END_TYPE);
return [$file, $group, $priority, $attributes];
}
/**
- * @param \Twig_Token $token
+ * @param Token $token
* @return bool
*/
- public function decideBlockEnd(\Twig_Token $token)
+ public function decideBlockEnd(Token $token)
{
return $token->test('endstyle');
}
diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserSwitch.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserSwitch.php
index 0768b30..7bd3ba9 100644
--- a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserSwitch.php
+++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserSwitch.php
@@ -1,8 +1,9 @@
getLine();
$stream = $this->parser->getStream();
$name = $this->parser->getExpressionParser()->parseExpression();
- $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+ $stream->expect(Token::BLOCK_END_TYPE);
// There can be some whitespace between the {% switch %} and first {% case %} tag.
- while ($stream->getCurrent()->getType() === \Twig_Token::TEXT_TYPE && trim($stream->getCurrent()->getValue()) === '') {
+ while ($stream->getCurrent()->getType() === Token::TEXT_TYPE && trim($stream->getCurrent()->getValue()) === '') {
$stream->next();
}
- $stream->expect(\Twig_Token::BLOCK_START_TYPE);
+ $stream->expect(Token::BLOCK_START_TYPE);
$expressionParser = $this->parser->getExpressionParser();
@@ -59,23 +64,23 @@ class TwigTokenParserSwitch extends \Twig_TokenParser
while (true) {
$values[] = $expressionParser->parsePrimaryExpression();
// Multiple allowed values?
- if ($stream->test(\Twig_Token::OPERATOR_TYPE, 'or')) {
+ if ($stream->test(Token::OPERATOR_TYPE, 'or')) {
$stream->next();
} else {
break;
}
}
- $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+ $stream->expect(Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideIfFork'));
- $cases[] = new \Twig_Node([
- 'values' => new \Twig_Node($values),
+ $cases[] = new Node([
+ 'values' => new Node($values),
'body' => $body
]);
break;
case 'default':
- $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+ $stream->expect(Token::BLOCK_END_TYPE);
$default = $this->parser->subparse(array($this, 'decideIfEnd'));
break;
@@ -84,22 +89,22 @@ class TwigTokenParserSwitch extends \Twig_TokenParser
break;
default:
- throw new \Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "case", "default", or "endswitch" to close the "switch" block started at line %d)', $lineno), -1);
+ throw new SyntaxError(sprintf('Unexpected end of template. Twig was looking for the following tags "case", "default", or "endswitch" to close the "switch" block started at line %d)', $lineno), -1);
}
}
- $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+ $stream->expect(Token::BLOCK_END_TYPE);
- return new TwigNodeSwitch($name, new \Twig_Node($cases), $default, $lineno, $this->getTag());
+ return new TwigNodeSwitch($name, new Node($cases), $default, $lineno, $this->getTag());
}
/**
* Decide if current token marks switch logic.
*
- * @param \Twig_Token $token
+ * @param Token $token
* @return bool
*/
- public function decideIfFork(\Twig_Token $token)
+ public function decideIfFork(Token $token)
{
return $token->test(array('case', 'default', 'endswitch'));
}
@@ -107,10 +112,10 @@ class TwigTokenParserSwitch extends \Twig_TokenParser
/**
* Decide if current token marks end of swtich block.
*
- * @param \Twig_Token $token
+ * @param Token $token
* @return bool
*/
- public function decideIfEnd(\Twig_Token $token)
+ public function decideIfEnd(Token $token)
{
return $token->test(array('endswitch'));
}
diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserThrow.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserThrow.php
new file mode 100644
index 0000000..c0d6de9
--- /dev/null
+++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserThrow.php
@@ -0,0 +1,54 @@
+
+ * {% throw 404 'Not Found' %}
+ *
+ */
+class TwigTokenParserThrow extends AbstractTokenParser
+{
+ /**
+ * Parses a token and returns a node.
+ *
+ * @param Token $token A Twig_Token instance
+ *
+ * @return Node A Twig_Node instance
+ */
+ public function parse(Token $token)
+ {
+ $lineno = $token->getLine();
+ $stream = $this->parser->getStream();
+
+ $code = $stream->expect(Token::NUMBER_TYPE)->getValue();
+ $message = $this->parser->getExpressionParser()->parseExpression();
+ $stream->expect(Token::BLOCK_END_TYPE);
+
+ return new TwigNodeThrow($code, $message, $lineno, $this->getTag());
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'throw';
+ }
+}
diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserTryCatch.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserTryCatch.php
index b205861..95adef3 100644
--- a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserTryCatch.php
+++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserTryCatch.php
@@ -1,14 +1,18 @@
*/
-class TwigTokenParserTryCatch extends \Twig_TokenParser
+class TwigTokenParserTryCatch extends AbstractTokenParser
{
/**
* Parses a token and returns a node.
*
- * @param \Twig_Token $token A Twig_Token instance
+ * @param Token $token A Twig_Token instance
*
- * @return \Twig_Node A Twig_Node instance
+ * @return Node A Twig_Node instance
*/
- public function parse(\Twig_Token $token)
+ public function parse(Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
- $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+ $stream->expect(Token::BLOCK_END_TYPE);
$try = $this->parser->subparse([$this, 'decideCatch']);
$stream->next();
- $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+ $stream->expect(Token::BLOCK_END_TYPE);
$catch = $this->parser->subparse([$this, 'decideEnd']);
$stream->next();
- $stream->expect(\Twig_Token::BLOCK_END_TYPE);
+ $stream->expect(Token::BLOCK_END_TYPE);
return new TwigNodeTryCatch($try, $catch, $lineno, $this->getTag());
}
- public function decideCatch(\Twig_Token $token)
+ public function decideCatch(Token $token)
{
return $token->test(array('catch'));
}
- public function decideEnd(\Twig_Token $token)
+ public function decideEnd(Token $token)
{
return $token->test(array('endtry')) || $token->test(array('endcatch'));
}
diff --git a/system/src/Grav/Common/Twig/Twig.php b/system/src/Grav/Common/Twig/Twig.php
index 567b18c..5c9008f 100644
--- a/system/src/Grav/Common/Twig/Twig.php
+++ b/system/src/Grav/Common/Twig/Twig.php
@@ -1,8 +1,9 @@
twig)) {
+ if (null === $this->twig) {
/** @var Config $config */
$config = $this->grav['config'];
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
-
/** @var Language $language */
$language = $this->grav['language'];
@@ -98,7 +99,8 @@ class Twig
$this->grav->fireEvent('onTwigTemplatePaths');
// Add Grav core templates location
- $this->twig_paths = array_merge($this->twig_paths, $locator->findResources('system://templates'));
+ $core_templates = array_merge($locator->findResources('system://templates'), $locator->findResources('system://templates/testing'));
+ $this->twig_paths = array_merge($this->twig_paths, $core_templates);
$this->loader = new \Twig_Loader_Filesystem($this->twig_paths);
@@ -174,13 +176,16 @@ class Twig
// set default date format if set in config
if ($config->get('system.pages.dateformat.long')) {
- $this->twig->getExtension('Twig_Extension_Core')->setDateFormat($config->get('system.pages.dateformat.long'));
+ /** @var \Twig_Extension_Core $extension */
+ $extension = $this->twig->getExtension('Twig_Extension_Core');
+ $extension->setDateFormat($config->get('system.pages.dateformat.long'));
}
// enable the debug extension if required
if ($config->get('system.twig.debug')) {
$this->twig->addExtension(new \Twig_Extension_Debug());
}
$this->twig->addExtension(new TwigExtension());
+ $this->twig->addExtension(new DeferredExtension());
$this->grav->fireEvent('onTwigExtensions');
@@ -209,6 +214,8 @@ class Twig
'language_codes' => new LanguageCodes,
];
}
+
+ return $this;
}
/**
@@ -243,15 +250,15 @@ class Twig
* 1) Handles modular pages by rendering a specific page based on its modular twig template
* 2) Renders individual page items for twig processing before the site rendering
*
- * @param Page $item The page item to render
+ * @param PageInterface $item The page item to render
* @param string $content Optional content override
*
* @return string The rendered output
* @throws \Twig_Error_Loader
*/
- public function processPage(Page $item, $content = null)
+ public function processPage(PageInterface $item, $content = null)
{
- $content = $content !== null ? $content : $item->content();
+ $content = $content ?? $item->content();
// override the twig header vars for local resolution
$this->grav->fireEvent('onTwigPageVariables', new Event(['page' => $item]));
@@ -261,8 +268,9 @@ class Twig
$twig_vars['media'] = $item->media();
$twig_vars['header'] = $item->header();
- $local_twig = clone($this->twig);
+ $local_twig = clone $this->twig;
+ $output = '';
try {
// Process Modular Twig
if ($item->modularTwig()) {
@@ -365,7 +373,7 @@ class Twig
$twig_vars['header'] = $page->header();
$twig_vars['media'] = $page->media();
$twig_vars['content'] = $content;
- $ext = '.' . ($format ? $format : 'html') . TWIG_EXT;
+ $ext = '.' . ($format ?: 'html') . TWIG_EXT;
// determine if params are set, if so disable twig cache
$params = $this->grav['uri']->params(null, true);
@@ -381,7 +389,7 @@ class Twig
} catch (\Twig_Error_Loader $e) {
$error_msg = $e->getMessage();
// Try html version of this template if initial template was NOT html
- if ($ext != '.html' . TWIG_EXT) {
+ if ($ext !== '.html' . TWIG_EXT) {
try {
$page->templateFormat('html');
$output = $this->twig->render($page->template() . '.html' . TWIG_EXT, $vars + $twig_vars);
@@ -398,8 +406,8 @@ class Twig
/**
* Wraps the Twig_Loader_Filesystem addPath method (should be used only in `onTwigLoader()` event
- * @param $template_path
- * @param null $namespace
+ * @param string $template_path
+ * @param string $namespace
*/
public function addPath($template_path, $namespace = '__main__')
{
@@ -408,8 +416,8 @@ class Twig
/**
* Wraps the Twig_Loader_Filesystem prependPath method (should be used only in `onTwigLoader()` event
- * @param $template_path
- * @param null $namespace
+ * @param string $template_path
+ * @param string $namespace
*/
public function prependPath($template_path, $namespace = '__main__')
{
@@ -426,18 +434,14 @@ class Twig
*/
public function template($template)
{
- if (isset($this->template)) {
- return $this->template;
- } else {
- return $template;
- }
+ return $this->template ?? $template;
}
/**
* Overrides the autoescape setting
*
- * @param boolean $state
- * @deprecated 1.5
+ * @param bool $state
+ * @deprecated 1.5 Auto-escape should always be turned on to protect against XSS issues (can be disabled per template file).
*/
public function setAutoescape($state)
{
diff --git a/system/src/Grav/Common/Twig/TwigEnvironment.php b/system/src/Grav/Common/Twig/TwigEnvironment.php
index 66ca8bf..54ef24f 100644
--- a/system/src/Grav/Common/Twig/TwigEnvironment.php
+++ b/system/src/Grav/Common/Twig/TwigEnvironment.php
@@ -1,8 +1,9 @@
grav = Grav::instance();
- $this->debugger = isset($this->grav['debugger']) ? $this->grav['debugger'] : null;
+ $this->debugger = $this->grav['debugger'] ?? null;
$this->config = $this->grav['config'];
}
@@ -98,9 +109,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFilter('print_r', 'print_r'),
new \Twig_SimpleFilter('yaml_encode', [$this, 'yamlEncodeFilter']),
new \Twig_SimpleFilter('yaml_decode', [$this, 'yamlDecodeFilter']),
+ new \Twig_SimpleFilter('nicecron', [$this, 'niceCronFilter']),
// Translations
- new \Twig_SimpleFilter('t', [$this, 'translate']),
+ new \Twig_SimpleFilter('t', [$this, 'translate'], ['needs_environment' => true]),
new \Twig_SimpleFilter('tl', [$this, 'translateLanguage']),
new \Twig_SimpleFilter('ta', [$this, 'translateArray']),
@@ -110,6 +122,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFilter('bool', [$this, 'boolFilter']),
new \Twig_SimpleFilter('float', [$this, 'floatFilter'], ['is_safe' => ['all']]),
new \Twig_SimpleFilter('array', [$this, 'arrayFilter']),
+
+ // Object Types
+ new \Twig_SimpleFilter('get_type', [$this, 'getTypeFunc']),
+ new \Twig_SimpleFilter('of_type', [$this, 'ofTypeFunc'])
];
}
@@ -126,7 +142,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFunction('array_key_exists', 'array_key_exists'),
new \Twig_SimpleFunction('array_unique', 'array_unique'),
new \Twig_SimpleFunction('array_intersect', [$this, 'arrayIntersectFunc']),
- new \Twig_simpleFunction('authorize', [$this, 'authorize']),
+ new \Twig_SimpleFunction('authorize', [$this, 'authorize']),
new \Twig_SimpleFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('dump', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('vardump', [$this, 'vardumpFunc']),
@@ -137,7 +153,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFunction('gist', [$this, 'gistFunc']),
new \Twig_SimpleFunction('nonce_field', [$this, 'nonceFieldFunc']),
new \Twig_SimpleFunction('pathinfo', 'pathinfo'),
- new \Twig_simpleFunction('random_string', [$this, 'randomStringFunc']),
+ new \Twig_SimpleFunction('random_string', [$this, 'randomStringFunc']),
new \Twig_SimpleFunction('repeat', [$this, 'repeatFunc']),
new \Twig_SimpleFunction('regex_replace', [$this, 'regexReplace']),
new \Twig_SimpleFunction('regex_filter', [$this, 'regexFilter']),
@@ -157,12 +173,18 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFunction('nicenumber', [$this, 'niceNumberFunc']),
new \Twig_SimpleFunction('nicefilesize', [$this, 'niceFilesizeFunc']),
new \Twig_SimpleFunction('nicetime', [$this, 'nicetimeFunc']),
+ new \Twig_SimpleFunction('cron', [$this, 'cronFunc']),
new \Twig_SimpleFunction('xss', [$this, 'xssFunc']),
+
// Translations
- new \Twig_simpleFunction('t', [$this, 'translate']),
- new \Twig_simpleFunction('tl', [$this, 'translateLanguage']),
- new \Twig_simpleFunction('ta', [$this, 'translateArray']),
+ new \Twig_SimpleFunction('t', [$this, 'translate'], ['needs_environment' => true]),
+ new \Twig_SimpleFunction('tl', [$this, 'translateLanguage']),
+ new \Twig_SimpleFunction('ta', [$this, 'translateArray']),
+
+ // Object Types
+ new \Twig_SimpleFunction('get_type', [$this, 'getTypeFunc']),
+ new \Twig_SimpleFunction('of_type', [$this, 'ofTypeFunc'])
];
}
@@ -172,6 +194,8 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
public function getTokenParsers()
{
return [
+ new TwigTokenParserRender(),
+ new TwigTokenParserThrow(),
new TwigTokenParserTryCatch(),
new TwigTokenParserScript(),
new TwigTokenParserStyle(),
@@ -204,16 +228,13 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
public function safeEmailFilter($str)
{
$email = '';
- for ( $i = 0, $len = strlen( $str ); $i < $len; $i++ ) {
- $j = mt_rand( 0, 1);
- if ( $j === 0 ) {
- $email .= '' . ord( $str[$i] ) . ';';
- } elseif ( $j === 1 ) {
- $email .= $str[$i];
- }
+ for ($i = 0, $len = strlen($str); $i < $len; $i++) {
+ $j = random_int(0, 1);
+
+ $email .= $j === 0 ? '' . ord($str[$i]) . ';' : $str[$i];
}
- return str_replace( '@', '@', $email );
+ return str_replace('@', '@', $email);
}
/**
@@ -226,7 +247,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
*/
public function randomizeFilter($original, $offset = 0)
{
- if (!is_array($original)) {
+ if (!\is_array($original)) {
return $original;
}
@@ -238,7 +259,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
$random = array_slice($original, $offset);
shuffle($random);
- $sizeOf = count($original);
+ $sizeOf = \count($original);
for ($x = 0; $x < $sizeOf; $x++) {
if ($x < $offset) {
$sorted[] = $original[$x];
@@ -261,19 +282,15 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
*/
public function modulusFilter($number, $divider, $items = null)
{
- if (is_string($number)) {
+ if (\is_string($number)) {
$number = strlen($number);
}
$remainder = $number % $divider;
- if (is_array($items)) {
- if (isset($items[$remainder])) {
- return $items[$remainder];
- }
-
- return $items[0];
- }
+ if (\is_array($items)) {
+ return $items[$remainder] ?? $items[0];
+ }
return $remainder;
}
@@ -295,11 +312,11 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
* @param string $data
* @param int $count
*
- * @return mixed
+ * @return string
*/
public function inflectorFilter($action, $data, $count = null)
{
- $action = $action . 'ize';
+ $action .= 'ize';
$inflector = $this->grav['inflector'];
@@ -308,18 +325,14 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
['titleize', 'camelize', 'underscorize', 'hyphenize', 'humanize', 'ordinalize', 'monthize'],
true
)) {
- return $inflector->$action($data);
+ return $inflector->{$action}($data);
}
if (\in_array($action, ['pluralize', 'singularize'], true)) {
- if ($count) {
- return $inflector->$action($data, $count);
- }
+ return $count ? $inflector->{$action}($data, $count) : $inflector->{$action}($data);
+ }
- return $inflector->$action($data);
- }
-
- return $data;
+ return $data;
}
/**
@@ -337,7 +350,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Return Base32 encoded string
*
- * @param $str
+ * @param string $str
* @return string
*/
public function base32EncodeFilter($str)
@@ -348,7 +361,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Return Base32 decoded string
*
- * @param $str
+ * @param string $str
* @return bool|string
*/
public function base32DecodeFilter($str)
@@ -359,7 +372,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Return Base64 encoded string
*
- * @param $str
+ * @param string $str
* @return string
*/
public function base64EncodeFilter($str)
@@ -370,7 +383,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Return Base64 decoded string
*
- * @param $str
+ * @param string $str
* @return bool|string
*/
public function base64DecodeFilter($str)
@@ -414,8 +427,8 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Wrapper for chunk_split() function
*
- * @param $value
- * @param $chars
+ * @param string $value
+ * @param int $chars
* @param string $split
* @return string
*/
@@ -427,60 +440,88 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* determine if a string contains another
*
- * @param String $haystack
- * @param String $needle
+ * @param string $haystack
+ * @param string $needle
*
- * @return boolean
+ * @return bool
*/
public function containsFilter($haystack, $needle)
{
- return (strpos($haystack, $needle) !== false);
+ if (empty($needle)) {
+ return $haystack;
+ }
+
+ return (strpos($haystack, (string) $needle) !== false);
+ }
+
+ /**
+ * Gets a human readable output for cron sytnax
+ *
+ * @param $at
+ * @return string
+ */
+ public function niceCronFilter($at)
+ {
+ $cron = new Cron($at);
+ return $cron->getText('en');
+ }
+
+ /**
+ * Get Cron object for a crontab 'at' format
+ *
+ * @param string $at
+ * @return CronExpression
+ */
+ public function cronFunc($at)
+ {
+ return CronExpression::factory($at);
}
/**
* displays a facebook style 'time ago' formatted date/time
*
- * @param $date
- * @param $long_strings
+ * @param string $date
+ * @param bool $long_strings
*
- * @return boolean
+ * @param bool $show_tense
+ * @return bool
*/
- public function nicetimeFunc($date, $long_strings = true)
+ public function nicetimeFunc($date, $long_strings = true, $show_tense = true)
{
if (empty($date)) {
- return $this->grav['language']->translate('NICETIME.NO_DATE_PROVIDED', null, true);
+ return $this->grav['language']->translate('GRAV.NICETIME.NO_DATE_PROVIDED', null, true);
}
if ($long_strings) {
$periods = [
- "NICETIME.SECOND",
- "NICETIME.MINUTE",
- "NICETIME.HOUR",
- "NICETIME.DAY",
- "NICETIME.WEEK",
- "NICETIME.MONTH",
- "NICETIME.YEAR",
- "NICETIME.DECADE"
+ 'NICETIME.SECOND',
+ 'NICETIME.MINUTE',
+ 'NICETIME.HOUR',
+ 'NICETIME.DAY',
+ 'NICETIME.WEEK',
+ 'NICETIME.MONTH',
+ 'NICETIME.YEAR',
+ 'NICETIME.DECADE'
];
} else {
$periods = [
- "NICETIME.SEC",
- "NICETIME.MIN",
- "NICETIME.HR",
- "NICETIME.DAY",
- "NICETIME.WK",
- "NICETIME.MO",
- "NICETIME.YR",
- "NICETIME.DEC"
+ 'NICETIME.SEC',
+ 'NICETIME.MIN',
+ 'NICETIME.HR',
+ 'NICETIME.DAY',
+ 'NICETIME.WK',
+ 'NICETIME.MO',
+ 'NICETIME.YR',
+ 'NICETIME.DEC'
];
}
- $lengths = ["60", "60", "24", "7", "4.35", "12", "10"];
+ $lengths = ['60', '60', '24', '7', '4.35', '12', '10'];
$now = time();
// check if unix timestamp
- if ((string)(int)$date == $date) {
+ if ((string)(int)$date === (string)$date) {
$unix_date = $date;
} else {
$unix_date = strtotime($date);
@@ -488,21 +529,21 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
// check validity of date
if (empty($unix_date)) {
- return $this->grav['language']->translate('NICETIME.BAD_DATE', null, true);
+ return $this->grav['language']->translate('GRAV.NICETIME.BAD_DATE', null, true);
}
// is it future date or past date
if ($now > $unix_date) {
$difference = $now - $unix_date;
- $tense = $this->grav['language']->translate('NICETIME.AGO', null, true);
+ $tense = $this->grav['language']->translate('GRAV.NICETIME.AGO', null, true);
- } else if ($now == $unix_date) {
+ } elseif ($now == $unix_date) {
$difference = $now - $unix_date;
- $tense = $this->grav['language']->translate('NICETIME.JUST_NOW', null, false);
+ $tense = $this->grav['language']->translate('GRAV.NICETIME.JUST_NOW', null, false);
} else {
$difference = $unix_date - $now;
- $tense = $this->grav['language']->translate('NICETIME.FROM_NOW', null, true);
+ $tense = $this->grav['language']->translate('GRAV.NICETIME.FROM_NOW', null, true);
}
for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths) - 1; $j++) {
@@ -523,38 +564,40 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
}
}
- $periods[$j] = $this->grav['language']->translate($periods[$j], null, true);
+ $periods[$j] = $this->grav['language']->translate('GRAV.'.$periods[$j], null, true);
if ($now == $unix_date) {
- return "{$tense}";
+ return $tense;
}
- return "$difference $periods[$j] {$tense}";
+ $time = "{$difference} {$periods[$j]}";
+ $time .= $show_tense ? " {$tense}" : '';
+
+ return $time;
}
/**
* Allow quick check of a string for XSS Vulnerabilities
*
- * @param $string
+ * @param string|array $data
* @return bool|string|array
*/
public function xssFunc($data)
{
- if (is_array($data)) {
- $results = Security::detectXssFromArray($data);
- } else {
+ if (!\is_array($data)) {
return Security::detectXss($data);
}
+ $results = Security::detectXssFromArray($data);
$results_parts = array_map(function($value, $key) {
return $key.': \''.$value . '\'';
}, array_values($results), array_keys($results));
- return implode(', ', $results_parts);
+ return implode(', ', $results_parts);
}
/**
- * @param $string
+ * @param string $string
*
* @return mixed
*/
@@ -568,36 +611,19 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
}
/**
- * @param $string
+ * @param string $string
*
* @param bool $block Block or Line processing
* @return mixed|string
*/
public function markdownFunction($string, $block = true)
{
- $page = $this->grav['page'];
- $defaults = $this->config->get('system.pages.markdown');
-
- // Initialize the preferred variant of Parsedown
- if ($defaults['extra']) {
- $parsedown = new ParsedownExtra($page, $defaults);
- } else {
- $parsedown = new Parsedown($page, $defaults);
- }
-
- if ($block) {
- $string = $parsedown->text($string);
- } else {
- $string = $parsedown->line($string);
- }
-
-
- return $string;
+ return Utils::processMarkdown($string, $block);
}
/**
- * @param $haystack
- * @param $needle
+ * @param string $haystack
+ * @param string $needle
*
* @return bool
*/
@@ -607,8 +633,8 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
}
/**
- * @param $haystack
- * @param $needle
+ * @param string $haystack
+ * @param string $needle
*
* @return bool
*/
@@ -618,7 +644,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
}
/**
- * @param $value
+ * @param mixed $value
* @param null $default
*
* @return null
@@ -629,7 +655,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
}
/**
- * @param $value
+ * @param string $value
* @param null $chars
*
* @return string
@@ -640,7 +666,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
}
/**
- * @param $value
+ * @param string $value
* @param null $chars
*
* @return string
@@ -707,37 +733,62 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
}
/**
- * @return mixed
+ * @return string
*/
- public function translate()
+ public function translate(\Twig_Environment $twig)
{
- return $this->grav['language']->translate(func_get_args());
+ // shift off the environment
+ $args = func_get_args();
+ array_shift($args);
+
+ // If admin and tu filter provided, use it
+ if (isset($this->grav['admin'])) {
+ $numargs = count($args);
+ $lang = null;
+
+ if (($numargs === 3 && is_array($args[1])) || ($numargs === 2 && !is_array($args[1]))) {
+ $lang = array_pop($args);
+ } elseif ($numargs === 2 && is_array($args[1])) {
+ $subs = array_pop($args);
+ $args = array_merge($args, $subs);
+ }
+
+ return $this->grav['admin']->translate($args, $lang);
+ }
+
+ // else use the default grav translate functionality
+ return $this->grav['language']->translate($args);
}
/**
* Translate Strings
*
- * @param $args
+ * @param string|array $args
* @param array|null $languages
* @param bool $array_support
* @param bool $html_out
- * @return mixed
+ * @return string
*/
public function translateLanguage($args, array $languages = null, $array_support = false, $html_out = false)
{
- return $this->grav['language']->translate($args, $languages, $array_support, $html_out);
+ /** @var Language $language */
+ $language = $this->grav['language'];
+
+ return $language->translate($args, $languages, $array_support, $html_out);
}
/**
- * @param $key
- * @param $index
- * @param null $lang
- *
- * @return mixed
+ * @param string $key
+ * @param string $index
+ * @param array|null $lang
+ * @return string
*/
public function translateArray($key, $index, $lang = null)
{
- return $this->grav['language']->translateArray($key, $index, $lang);
+ /** @var Language $language */
+ $language = $this->grav['language'];
+
+ return $language->translateArray($key, $index, $lang);
}
/**
@@ -781,14 +832,15 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
$env = new \Twig_Environment($loader);
$template = $env->createTemplate($twig);
+
return $template->render($context);
}
/**
* This function will evaluate a $string through the $environment, and return its results.
*
- * @param $context
- * @param $string
+ * @param array $context
+ * @param string $string
* @return mixed
*/
public function evaluateStringFunc($context, $string )
@@ -802,7 +854,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
* (c) 2011 Fabien Potencier
*
* @param \Twig_Environment $env
- * @param $context
+ * @param string $context
*/
public function dump(\Twig_Environment $env, $context)
{
@@ -864,14 +916,14 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Pad a string to a certain length with another string
*
- * @param $input
- * @param $pad_length
+ * @param string $input
+ * @param int $pad_length
* @param string $pad_string
* @param int $pad_type
*
* @return string
*/
- public static function padFilter($input, $pad_length, $pad_string = " ", $pad_type = STR_PAD_RIGHT)
+ public static function padFilter($input, $pad_length, $pad_string = ' ', $pad_type = STR_PAD_RIGHT)
{
return str_pad($input, (int)$pad_length, $pad_string, $pad_type);
}
@@ -892,15 +944,16 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
return array($key => $val);
}
- $current_array[$key] = $val;
- return $current_array;
- }
+ $current_array[$key] = $val;
+
+ return $current_array;
+ }
/**
* Wrapper for array_intersect() method
*
- * @param $array1
- * @param $array2
+ * @param array $array1
+ * @param array $array2
* @return array
*/
public function arrayIntersectFunc($array1, $array2)
@@ -915,7 +968,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Returns a string from a value. If the value is array, return it json encoded
*
- * @param $value
+ * @param array|string $value
*
* @return string
*/
@@ -925,8 +978,8 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
return json_encode($value);
}
- return $value;
- }
+ return $value;
+ }
/**
* Translate a string
@@ -951,7 +1004,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
*/
public function authorize($action)
{
- /** @var User $user */
+ /** @var UserInterface $user */
$user = $this->grav['user'];
if (!$user->authenticated || (isset($user->authorized) && !$user->authorized)) {
@@ -994,8 +1047,8 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
*
* @param string $str
* @param bool $assoc
- * @param int $depth
- * @param int $options
+ * @param int $depth
+ * @param int $options
* @return array
*/
public function jsonDecodeFilter($str, $assoc = false, $depth = 512, $options = 0)
@@ -1023,7 +1076,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
* @param mixed $replace the replacement value either as a string or an array of replacements
* @param int $limit the maximum possible replacements for each pattern in each subject
*
- * @return mixed the resulting content
+ * @return string|string[]|null the resulting content
*/
public function regexReplace($subject, $pattern, $replace, $limit = -1)
{
@@ -1033,12 +1086,13 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Twig wrapper for PHP's preg_grep method
*
- * @param $array
- * @param $regex
+ * @param array $array
+ * @param string $regex
* @param int $flags
* @return array
*/
- public function regexFilter($array, $regex, $flags = 0) {
+ public function regexFilter($array, $regex, $flags = 0)
+ {
return preg_grep($regex, $array, $flags);
}
@@ -1072,7 +1126,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
* Check if HTTP_X_REQUESTED_WITH has been set to xmlhttprequest,
* in which case we may unsafely assume ajax. Non critical use only.
*
- * @return true if HTTP_X_REQUESTED_WITH exists and has been set to xmlhttprequest
+ * @return bool True if HTTP_X_REQUESTED_WITH exists and has been set to xmlhttprequest
*/
public function isAjaxFunc()
{
@@ -1084,14 +1138,13 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Get's the Exif data for a file
*
- * @param $image
+ * @param string $image
* @param bool $raw
* @return mixed
*/
public function exifFunc($image, $raw = false)
{
if (isset($this->grav['exif'])) {
-
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
@@ -1101,7 +1154,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
$exif_reader = $this->grav['exif']->getReader();
- if (file_exists($image) && $this->config->get('system.media.auto_metadata_exif') && $exif_reader) {
+ if ($image & file_exists($image) && $this->config->get('system.media.auto_metadata_exif') && $exif_reader) {
$exif_data = $exif_reader->read($image);
@@ -1110,10 +1163,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
return $exif_data->getRawData();
}
- return $exif_data->getData();
- }
+ return $exif_data->getData();
}
}
+ }
return null;
}
@@ -1121,7 +1174,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Simple function to read a file based on a filepath and output it
*
- * @param $filepath
+ * @param string $filepath
* @return bool|string
*/
public function readFileFunc($filepath)
@@ -1133,7 +1186,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
$filepath = $locator->findResource($filepath);
}
- if (file_exists($filepath)) {
+ if ($filepath && file_exists($filepath)) {
return file_get_contents($filepath);
}
@@ -1143,7 +1196,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Process a folder as Media and return a media object
*
- * @param $media_dir
+ * @param string $media_dir
* @return Media|null
*/
public function mediaDirFunc($media_dir)
@@ -1155,7 +1208,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
$media_dir = $locator->findResource($media_dir);
}
- if (file_exists($media_dir)) {
+ if ($media_dir && file_exists($media_dir)) {
return new Media($media_dir);
}
@@ -1165,7 +1218,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Dump a variable to the browser
*
- * @param $var
+ * @param mixed $var
*/
public function vardumpFunc($var)
{
@@ -1175,68 +1228,50 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Returns a nicer more readable filesize based on bytes
*
- * @param $bytes
+ * @param int $bytes
* @return string
*/
public function niceFilesizeFunc($bytes)
{
- if ($bytes >= 1073741824)
- {
- $bytes = number_format($bytes / 1073741824, 2) . ' GB';
- }
- elseif ($bytes >= 1048576)
- {
- $bytes = number_format($bytes / 1048576, 2) . ' MB';
- }
- elseif ($bytes >= 1024)
- {
- $bytes = number_format($bytes / 1024, 1) . ' KB';
- }
- elseif ($bytes > 1)
- {
- $bytes = $bytes . ' bytes';
- }
- elseif ($bytes == 1)
- {
- $bytes = $bytes . ' byte';
- }
- else
- {
- $bytes = '0 bytes';
- }
-
- return $bytes;
+ return Utils::prettySize($bytes);
}
-
/**
* Returns a nicer more readable number
*
- * @param int|float $n
- * @return bool|string
+ * @param int|float|string $n
+ * @return string|bool
*/
public function niceNumberFunc($n)
{
- // first strip any formatting;
- $n = 0 + str_replace(',', '', $n);
+ if (!\is_float($n) && !\is_int($n)) {
+ if (!\is_string($n) || $n === '') {
+ return false;
+ }
- // is this a number?
- if (!is_numeric($n)) {
- return false;
+ // Strip any thousand formatting and find the first number.
+ $list = array_filter(preg_split("/\D+/", str_replace(',', '', $n)));
+ $n = reset($list);
+
+ if (!\is_numeric($n)) {
+ return false;
+ }
+
+ $n = (float)$n;
}
// now filter it;
if ($n > 1000000000000) {
- return round(($n/1000000000000), 2).' t';
+ return round($n/1000000000000, 2).' t';
}
if ($n > 1000000000) {
- return round(($n/1000000000), 2).' b';
+ return round($n/1000000000, 2).' b';
}
if ($n > 1000000) {
- return round(($n/1000000), 2).' m';
+ return round($n/1000000, 2).' m';
}
if ($n > 1000) {
- return round(($n/1000), 2).' k';
+ return round($n/1000, 2).' k';
}
return number_format($n);
@@ -1245,14 +1280,15 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Get a theme variable
*
- * @param $var
+ * @param string $var
* @param bool $default
* @return string
*/
public function themeVarFunc($var, $default = null)
{
$header = $this->grav['page']->header();
- $header_classes = isset($header->$var) ? $header->$var : null;
+ $header_classes = $header->{$var} ?? null;
+
return $header_classes ?: $this->config->get('theme.' . $var, $default);
}
@@ -1260,14 +1296,14 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
* takes an array of classes, and if they are not set on body_classes
* look to see if they are set in theme config
*
- * @param $classes
+ * @param string|string[] $classes
* @return string
*/
public function bodyClassFunc($classes)
{
$header = $this->grav['page']->header();
- $body_classes = isset($header->body_classes) ? $header->body_classes : '';
+ $body_classes = $header->body_classes ?? '';
foreach ((array)$classes as $class) {
if (!empty($body_classes) && Utils::contains($body_classes, $class)) {
@@ -1284,8 +1320,8 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Look for a page header variable in an array of pages working its way through until a value is found
*
- * @param $var
- * @param null $pages
+ * @param string $var
+ * @param string|string[]|null $pages
* @return mixed
*/
public function pageHeaderVarFunc($var, $pages = null)
@@ -1295,20 +1331,20 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
}
// Make sure pages are an array
- if (!is_array($pages)) {
- $pages = array($pages);
+ if (!\is_array($pages)) {
+ $pages = [$pages];
}
// Loop over pages and look for header vars
foreach ($pages as $page) {
- if (is_string($page)) {
+ if (\is_string($page)) {
$page = $this->grav['pages']->find($page);
}
if ($page) {
$header = $page->header();
- if (isset($header->$var)) {
- return $header->$var;
+ if (isset($header->{$var})) {
+ return $header->{$var};
}
}
}
@@ -1319,9 +1355,9 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Dump/Encode data into YAML format
*
- * @param $data
- * @param $inline integer number of levels of inline syntax
- * @return mixed
+ * @param array $data
+ * @param int $inline integer number of levels of inline syntax
+ * @return string
*/
public function yamlEncodeFilter($data, $inline = 10)
{
@@ -1331,11 +1367,77 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Decode/Parse data from YAML format
*
- * @param $data
- * @return mixed
+ * @param string $data
+ * @return array
*/
public function yamlDecodeFilter($data)
{
return Yaml::parse($data);
}
+
+ /**
+ * Function/Filter to return the type of variable
+ *
+ * @param mixed $var
+ * @return string
+ */
+ public function getTypeFunc($var)
+ {
+ return gettype($var);
+ }
+
+ /**
+ * Function/Filter to test type of variable
+ *
+ * @param mixed $var
+ * @param string|null $typeTest
+ * @param string|null $className
+ * @return bool
+ */
+ public function ofTypeFunc($var, $typeTest=null, $className=null)
+ {
+
+ switch ($typeTest)
+ {
+ default:
+ return false;
+ break;
+
+ case 'array':
+ return is_array($var);
+ break;
+
+ case 'bool':
+ return is_bool($var);
+ break;
+
+ case 'class':
+ return is_object($var) === true && get_class($var) === $className;
+ break;
+
+ case 'float':
+ return is_float($var);
+ break;
+
+ case 'int':
+ return is_int($var);
+ break;
+
+ case 'numeric':
+ return is_numeric($var);
+ break;
+
+ case 'object':
+ return is_object($var);
+ break;
+
+ case 'scalar':
+ return is_scalar($var);
+ break;
+
+ case 'string':
+ return is_string($var);
+ break;
+ }
+ }
}
diff --git a/system/src/Grav/Common/Twig/WriteCacheFileTrait.php b/system/src/Grav/Common/Twig/WriteCacheFileTrait.php
index c413ca6..f87ff67 100644
--- a/system/src/Grav/Common/Twig/WriteCacheFileTrait.php
+++ b/system/src/Grav/Common/Twig/WriteCacheFileTrait.php
@@ -1,13 +1,15 @@
createFromString($env);
} else {
- $this->createFromEnvironment(is_array($env) ? $env : $_SERVER);
+ $this->createFromEnvironment(\is_array($env) ? $env : $_SERVER);
}
}
@@ -143,7 +144,7 @@ class Uri
} else {
$this->root = $this->base . $this->root_path;
}
- $this->uri = Utils::replaceFirstOccurrence($orig_root_path, $this->root_path, $this->uri);
+ $this->uri = Utils::replaceFirstOccurrence($orig_root_path, $this->root_path, $this->uri);
} else {
$this->root = $this->base . $this->root_path;
}
@@ -173,7 +174,7 @@ class Uri
}
// Get the path. If there's no path, make sure pathinfo() still returns dirname variable
- $path = isset($bits['path']) ? $bits['path'] : '/';
+ $path = $bits['path'] ?? '/';
// remove the extension if there is one set
$parts = pathinfo($path);
@@ -186,11 +187,9 @@ class Uri
$this->extension = $parts['extension'];
}
- $valid_page_types = implode('|', $config->get('system.pages.types'));
-
// Strip the file extension for valid page types
- if (preg_match('/\.(' . $valid_page_types . ')$/', $parts['basename'])) {
- $path = rtrim(str_replace(DIRECTORY_SEPARATOR, DS, $parts['dirname']), DS) . '/' . $parts['filename'];
+ if ($this->isValidExtension($this->extension)) {
+ $path = Utils::replaceLastOccurrence(".{$this->extension}", '', $path);
}
// set the new url
@@ -213,7 +212,7 @@ class Uri
/**
* Return URI path.
*
- * @param string $id
+ * @param string $id
*
* @return string|string[]
*/
@@ -229,8 +228,8 @@ class Uri
/**
* Return route to the current URI. By default route doesn't include base path.
*
- * @param bool $absolute True to include full path.
- * @param bool $domain True to include domain. Works only if first parameter is also true.
+ * @param bool $absolute True to include full path.
+ * @param bool $domain True to include domain. Works only if first parameter is also true.
*
* @return string
*/
@@ -242,15 +241,15 @@ class Uri
/**
* Return full query string or a single query attribute.
*
- * @param string $id Optional attribute. Get a single query attribute if set
- * @param bool $raw If true and $id is not set, return the full query array. Otherwise return the query string
+ * @param string $id Optional attribute. Get a single query attribute if set
+ * @param bool $raw If true and $id is not set, return the full query array. Otherwise return the query string
*
* @return string|array Returns an array if $id = null and $raw = true
*/
public function query($id = null, $raw = false)
{
if ($id !== null) {
- return isset($this->queries[$id]) ? $this->queries[$id] : null;
+ return $this->queries[$id] ?? null;
}
if ($raw) {
@@ -267,8 +266,8 @@ class Uri
/**
* Return all or a single query parameter as a URI compatible string.
*
- * @param string $id Optional parameter name.
- * @param boolean $array return the array format or not
+ * @param string $id Optional parameter name.
+ * @param boolean $array return the array format or not
*
* @return null|string|array
*/
@@ -300,7 +299,7 @@ class Uri
/**
* Get URI parameter.
*
- * @param string $id
+ * @param string $id
*
* @return bool|string
*/
@@ -331,7 +330,7 @@ class Uri
/**
* Return URL.
*
- * @param bool $include_host Include hostname.
+ * @param bool $include_host Include hostname.
*
* @return string
*/
@@ -522,7 +521,7 @@ class Uri
/**
* Return root URL to the site.
*
- * @param bool $include_host Include hostname.
+ * @param bool $include_host Include hostname.
*
* @return mixed
*/
@@ -542,7 +541,7 @@ class Uri
*/
public function currentPage()
{
- return isset($this->params['page']) ? $this->params['page'] : 1;
+ return $this->params['page'] ?? 1;
}
/**
@@ -555,7 +554,7 @@ class Uri
*/
public function referrer($default = null, $attributes = null)
{
- $referrer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
+ $referrer = $_SERVER['HTTP_REFERER'] ?? null;
// Check that referrer came from our site.
$root = $this->rootUrl(true);
@@ -583,15 +582,28 @@ class Uri
return static::buildUrl($this->toArray());
}
- public function toArray()
+ public function toOriginalString()
{
+ return static::buildUrl($this->toArray(true));
+ }
+
+ public function toArray($full = false)
+ {
+ if ($full === true) {
+ $root_path = $this->root_path ?? '';
+ $extension = isset($this->extension) && $this->isValidExtension($this->extension) ? '.' . $this->extension : '';
+ $path = $root_path . $this->path . $extension;
+ } else {
+ $path = $this->path;
+ }
+
return [
'scheme' => $this->scheme,
'host' => $this->host,
'port' => $this->port,
'user' => $this->user,
'pass' => $this->password,
- 'path' => $this->path,
+ 'path' => $path,
'params' => $this->params,
'query' => $this->query,
'fragment' => $this->fragment
@@ -672,7 +684,7 @@ class Uri
*/
public static function isExternal($url)
{
- return Utils::startsWith($url, 'http');
+ return (0 === strpos($url, 'http://') || 0 === strpos($url, 'https://') || 0 === strpos($url, '//'));
}
/**
@@ -686,12 +698,12 @@ class Uri
{
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . ':' : '';
$authority = isset($parsed_url['host']) ? '//' : '';
- $host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
+ $host = $parsed_url['host'] ?? '';
$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
- $user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
+ $user = $parsed_url['user'] ?? '';
$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
$pass = ($user || $pass) ? "{$pass}@" : '';
- $path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
+ $path = $parsed_url['path'] ?? '';
$path = !empty($parsed_url['params']) ? rtrim($path, '/') . static::buildParams($parsed_url['params']) : $path;
$query = !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
$fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
@@ -723,14 +735,14 @@ class Uri
/**
* Converts links from absolute '/' or relative (../..) to a Grav friendly format
*
- * @param Page $page the current page to use as reference
+ * @param PageInterface $page the current page to use as reference
* @param string|array $url the URL as it was written in the markdown
* @param string $type the type of URL, image | link
* @param bool $absolute if null, will use system default, if true will use absolute links internally
* @param bool $route_only only return the route, not full URL path
- * @return string the more friendly formatted url
+ * @return string|array the more friendly formatted url
*/
- public static function convertUrl(Page $page, $url, $type = 'link', $absolute = false, $route_only = false)
+ public static function convertUrl(PageInterface $page, $url, $type = 'link', $absolute = false, $route_only = false)
{
$grav = Grav::instance();
@@ -766,7 +778,7 @@ class Uri
$normalized_path = Utils::normalizePath($pages_dir . $url_path);
} else {
$page_route = ($page->home() && !empty($url_path)) ? $page->rawRoute() : $page->route();
- $normalized_url = $base_url . Utils::normalizePath($page_route . '/' . $url_path);
+ $normalized_url = $base_url . Utils::normalizePath(rtrim($page_route, '/') . '/' . $url_path);
$normalized_path = Utils::normalizePath($page->path() . '/' . $url_path);
}
@@ -806,7 +818,7 @@ class Uri
// get page instances and try to find one that fits
$instances = $grav['pages']->instances();
if (isset($instances[$page_path])) {
- /** @var Page $target */
+ /** @var PageInterface $target */
$target = $instances[$page_path];
$url_bits['path'] = $base_url . rtrim($target->route(), '/') . $filename;
@@ -821,7 +833,7 @@ class Uri
}
// handle absolute URLs
- if (is_array($url) && !$external && ($absolute === true || $grav['config']->get('system.absolute_urls', false))) {
+ if (\is_array($url) && !$external && ($absolute === true || $grav['config']->get('system.absolute_urls', false))) {
$url['scheme'] = $uri->scheme(true);
$url['host'] = $uri->host();
@@ -921,7 +933,7 @@ class Uri
foreach ($matches as $match) {
$param = explode($delimiter, $match[1]);
- if (count($param) === 2) {
+ if (\count($param) === 2) {
$plain_var = filter_var(rawurldecode($param[1]), FILTER_SANITIZE_STRING);
$params[$param[0]] = $plain_var;
$uri = str_replace($match[0], '', $uri);
@@ -935,14 +947,14 @@ class Uri
/**
* Converts links from absolute '/' or relative (../..) to a Grav friendly format
*
- * @param Page $page the current page to use as reference
+ * @param PageInterface $page the current page to use as reference
* @param string $markdown_url the URL as it was written in the markdown
* @param string $type the type of URL, image | link
* @param null $relative if null, will use system default, if true will use relative links internally
*
* @return string the more friendly formatted url
*/
- public static function convertUrlOld(Page $page, $markdown_url, $type = 'link', $relative = null)
+ public static function convertUrlOld(PageInterface $page, $markdown_url, $type = 'link', $relative = null)
{
$grav = Grav::instance();
@@ -1022,7 +1034,7 @@ class Uri
// get page instances and try to find one that fits
$instances = $grav['pages']->instances();
if (isset($instances[$page_path])) {
- /** @var Page $target */
+ /** @var PageInterface $target */
$target = $instances[$page_path];
$url_bits['path'] = $base_url . rtrim($target->route(), '/') . $filename;
@@ -1043,7 +1055,7 @@ class Uri
*/
public static function addNonce($url, $action, $nonceParamName = 'nonce')
{
- $fake = $url && $url[0] === '/';
+ $fake = $url && strpos($url, '/') === 0;
if ($fake) {
$url = 'http://domain.com' . $url;
@@ -1051,7 +1063,7 @@ class Uri
$uri = new static($url);
$parts = $uri->toArray();
$nonce = Utils::getNonce($action);
- $parts['params'] = (isset($parts['params']) ? $parts['params'] : []) + [$nonceParamName => $nonce];
+ $parts['params'] = ($parts['params'] ?? []) + [$nonceParamName => $nonce];
if ($fake) {
unset($parts['scheme'], $parts['host']);
@@ -1063,7 +1075,7 @@ class Uri
/**
* Is the passed in URL a valid URL?
*
- * @param $url
+ * @param string $url
* @return bool
*/
public static function isValidUrl($url)
@@ -1079,7 +1091,7 @@ class Uri
/**
* Removes extra double slashes and fixes back-slashes
*
- * @param $path
+ * @param string $path
* @return mixed|string
*/
public static function cleanPath($path)
@@ -1145,13 +1157,13 @@ class Uri
} elseif (isset($env['REQUEST_SCHEME'])) {
$this->scheme = $env['REQUEST_SCHEME'];
} else {
- $https = isset($env['HTTPS']) ? $env['HTTPS'] : '';
+ $https = $env['HTTPS'] ?? '';
$this->scheme = (empty($https) || strtolower($https) === 'off') ? 'http' : 'https';
}
// Build user and password.
- $this->user = isset($env['PHP_AUTH_USER']) ? $env['PHP_AUTH_USER'] : null;
- $this->password = isset($env['PHP_AUTH_PW']) ? $env['PHP_AUTH_PW'] : null;
+ $this->user = $env['PHP_AUTH_USER'] ?? null;
+ $this->password = $env['PHP_AUTH_PW'] ?? null;
// Build host.
$hostname = 'localhost';
@@ -1185,11 +1197,11 @@ class Uri
}
// Build path.
- $request_uri = isset($env['REQUEST_URI']) ? $env['REQUEST_URI'] : '';
+ $request_uri = $env['REQUEST_URI'] ?? '';
$this->path = rawurldecode(parse_url('http://example.com' . $request_uri, PHP_URL_PATH));
// Build query string.
- $this->query = isset($env['QUERY_STRING']) ? $env['QUERY_STRING'] : '';
+ $this->query = $env['QUERY_STRING'] ?? '';
if ($this->query === '') {
$this->query = parse_url('http://example.com' . $request_uri, PHP_URL_QUERY);
}
@@ -1220,7 +1232,7 @@ class Uri
*/
protected function hasStandardPort()
{
- return ($this->scheme === 'http' && $this->port === 80) || ($this->scheme === 'https' && $this->port === 443);
+ return ($this->port === 80 || $this->port === 443);
}
/**
@@ -1233,14 +1245,14 @@ class Uri
if ($parts === false) {
throw new \RuntimeException('Malformed URL: ' . $url);
}
- $this->scheme = isset($parts['scheme']) ? $parts['scheme'] : null;
- $this->user = isset($parts['user']) ? $parts['user'] : null;
- $this->password = isset($parts['pass']) ? $parts['pass'] : null;
- $this->host = isset($parts['host']) ? $parts['host'] : null;
+ $this->scheme = $parts['scheme'] ?? null;
+ $this->user = $parts['user'] ?? null;
+ $this->password = $parts['pass'] ?? null;
+ $this->host = $parts['host'] ?? null;
$this->port = isset($parts['port']) ? (int)$parts['port'] : null;
- $this->path = isset($parts['path']) ? $parts['path'] : '';
- $this->query = isset($parts['query']) ? $parts['query'] : '';
- $this->fragment = isset($parts['fragment']) ? $parts['fragment'] : null;
+ $this->path = $parts['path'] ?? '';
+ $this->query = $parts['query'] ?? '';
+ $this->fragment = $parts['fragment'] ?? null;
// Validate the hostname
if ($this->host) {
@@ -1313,7 +1325,7 @@ class Uri
* @param bool $short
* @return null|string
*/
- private function getContentType($short = true)
+ public function getContentType($short = true)
{
if (isset($_SERVER['CONTENT_TYPE'])) {
$content_type = $_SERVER['CONTENT_TYPE'];
@@ -1325,6 +1337,38 @@ class Uri
return null;
}
+ /**
+ * Check if this is a valid Grav extension
+ *
+ * @param $extension
+ * @return bool
+ */
+ public function isValidExtension($extension)
+ {
+ $valid_page_types = implode('|', Grav::instance()['config']->get('system.pages.types'));
+
+ // Strip the file extension for valid page types
+ if (preg_match('/(' . $valid_page_types . ')/', $extension)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Allow overriding of any element (be careful!)
+ *
+ * @param $data
+ * @return Uri
+ */
+ public function setUriProperties($data)
+ {
+ foreach (get_object_vars($this) as $property => $default) {
+ if (!array_key_exists($property, $data)) continue;
+ $this->{$property} = $data[$property]; // assign value to object
+ }
+ return $this;
+ }
+
/**
* Get the base URI with port if needed
*
@@ -1362,7 +1406,7 @@ class Uri
/**
* Process any params based in this URL, supports any valid delimiter
*
- * @param $uri
+ * @param string $uri
* @param string $delimiter
*
* @return string
diff --git a/system/src/Grav/Common/User/Authentication.php b/system/src/Grav/Common/User/Authentication.php
index b46750c..2447732 100644
--- a/system/src/Grav/Common/User/Authentication.php
+++ b/system/src/Grav/Common/User/Authentication.php
@@ -1,8 +1,9 @@
get('user/account');
+ }
+
+ parent::__construct($items, $blueprints);
+ }
+
+ /**
+ * @param string $offset
+ * @return bool
+ */
+ public function offsetExists($offset)
+ {
+ $value = parent::offsetExists($offset);
+
+ // Handle special case where user was logged in before 'authorized' was added to the user object.
+ if (false === $value && $offset === 'authorized') {
+ $value = $this->offsetExists('authenticated');
+ }
+
+ return $value;
+ }
+
+ /**
+ * @param string $offset
+ * @return mixed
+ */
+ public function offsetGet($offset)
+ {
+ $value = parent::offsetGet($offset);
+
+ // Handle special case where user was logged in before 'authorized' was added to the user object.
+ if (null === $value && $offset === 'authorized') {
+ $value = $this->offsetGet('authenticated');
+ $this->offsetSet($offset, $value);
+ }
+
+ return $value;
+ }
+
+ public function isValid(): bool
+ {
+ return $this->items !== null;
+ }
+
+ /**
+ * Update object with data
+ *
+ * @param array $data
+ * @param array $files
+ * @return $this
+ */
+ public function update(array $data, array $files = [])
+ {
+ // Note: $this->merge() would cause infinite loop as it calls this method.
+ parent::merge($data);
+
+ return $this;
+ }
+
+ /**
+ * Save user without the username
+ */
+ public function save()
+ {
+ /** @var CompiledYamlFile $file */
+ $file = $this->file();
+ if (!$file || !$file->filename()) {
+ user_error(__CLASS__ . ': calling \$user = new ' . __CLASS__ . "() is deprecated since Grav 1.6, use \$grav['accounts']->load(\$username) or \$grav['accounts']->load('') instead", E_USER_DEPRECATED);
+ }
+
+ if ($file) {
+ $username = $this->get('username');
+
+ if (!$file->filename()) {
+ $locator = Grav::instance()['locator'];
+ $file->filename($locator->findResource('account://' . mb_strtolower($username) . YAML_EXT, true, true));
+ }
+
+ // if plain text password, hash it and remove plain text
+ $password = $this->get('password');
+ if ($password) {
+ $this->set('hashed_password', Authentication::create($password));
+ $this->undef('password');
+ }
+
+ $data = $this->items;
+ unset($data['username'], $data['authenticated'], $data['authorized']);
+
+ $file->save($data);
+ }
+ }
+
+ public function getMedia()
+ {
+ if (null === $this->_media) {
+ // Media object should only contain avatar, nothing else.
+ $media = new Media($this->getMediaFolder(), $this->getMediaOrder(), false);
+
+ $path = $this->getAvatarFile();
+ if ($path && is_file($path)) {
+ $medium = MediumFactory::fromFile($path);
+ if ($medium) {
+ $media->add(basename($path), $medium);
+ }
+ }
+
+ $this->_media = $media;
+ }
+
+ return $this->_media;
+ }
+
+ public function getMediaFolder()
+ {
+ return $this->blueprints()->fields()['avatar']['destination'] ?? 'user://accounts/avatars';
+ }
+
+ public function getMediaOrder()
+ {
+ return [];
+ }
+
+ /**
+ * Serialize user.
+ */
+ public function __sleep()
+ {
+ return [
+ 'items',
+ 'storage'
+ ];
+ }
+
+ /**
+ * Unserialize user.
+ */
+ public function __wakeup()
+ {
+ $this->gettersVariable = 'items';
+ $this->nestedSeparator = '.';
+
+ if (null === $this->items) {
+ $this->items = [];
+ }
+
+ // Always set blueprints.
+ if (null === $this->blueprints) {
+ $this->blueprints = (new Blueprints)->get('user/account');
+ }
+ }
+
+ /**
+ * Merge two configurations together.
+ *
+ * @param array $data
+ * @return $this
+ * @deprecated 1.6 Use `->update($data)` instead (same but with data validation & filtering, file upload support).
+ */
+ public function merge(array $data)
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->update($data) method instead', E_USER_DEPRECATED);
+
+ return $this->update($data);
+ }
+
+ /**
+ * Return media object for the User's avatar.
+ *
+ * @return ImageMedium|null
+ * @deprecated 1.6 Use ->getAvatarImage() method instead.
+ */
+ public function getAvatarMedia()
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use getAvatarImage() method instead', E_USER_DEPRECATED);
+
+ return $this->getAvatarImage();
+ }
+
+ /**
+ * Return the User's avatar URL
+ *
+ * @return string
+ * @deprecated 1.6 Use ->getAvatarUrl() method instead.
+ */
+ public function avatarUrl()
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use getAvatarUrl() method instead', E_USER_DEPRECATED);
+
+ return $this->getAvatarUrl();
+ }
+
+ /**
+ * Checks user authorization to the action.
+ * Ensures backwards compatibility
+ *
+ * @param string $action
+ *
+ * @return bool
+ * @deprecated 1.5 Use ->authorize() method instead.
+ */
+ public function authorise($action)
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use authorize() method instead', E_USER_DEPRECATED);
+
+ return $this->authorize($action);
+ }
+
+ /**
+ * Implements Countable interface.
+ *
+ * @return int
+ * @deprecated 1.6 Method makes no sense for user account.
+ */
+ public function count()
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED);
+
+ return parent::count();
+ }
+
+ protected function getAvatarFile(): ?string
+ {
+ $avatars = $this->get('avatar');
+ if (\is_array($avatars) && $avatars) {
+ $avatar = array_shift($avatars);
+ return $avatar['path'] ?? null;
+ }
+
+ return null;
+ }
+}
diff --git a/system/src/Grav/Common/User/DataUser/UserCollection.php b/system/src/Grav/Common/User/DataUser/UserCollection.php
new file mode 100644
index 0000000..0970ce7
--- /dev/null
+++ b/system/src/Grav/Common/User/DataUser/UserCollection.php
@@ -0,0 +1,121 @@
+className = $className;
+ }
+
+ /**
+ * Load user account.
+ *
+ * Always creates user object. To check if user exists, use $this->exists().
+ *
+ * @param string $username
+ * @return UserInterface
+ */
+ public function load($username): UserInterface
+ {
+ $grav = Grav::instance();
+ /** @var UniformResourceLocator $locator */
+ $locator = $grav['locator'];
+
+ // force lowercase of username
+ $username = mb_strtolower($username);
+
+ $filename = 'account://' . $username . YAML_EXT;
+ $path = $locator->findResource($filename) ?: $locator->findResource($filename, true, true);
+ $file = CompiledYamlFile::instance($path);
+ $content = (array)$file->content() + ['username' => $username, 'state' => 'enabled'];
+
+ $userClass = $this->className;
+ $callable = function() {
+ $blueprints = new Blueprints;
+
+ return $blueprints->get('user/account');
+ };
+
+ /** @var UserInterface $user */
+ $user = new $userClass($content, $callable);
+ $user->file($file);
+
+ return $user;
+ }
+
+ /**
+ * Find a user by username, email, etc
+ *
+ * @param string $query the query to search for
+ * @param array $fields the fields to search
+ * @return UserInterface
+ */
+ public function find($query, $fields = ['username', 'email']): UserInterface
+ {
+ $fields = (array)$fields;
+
+ $account_dir = Grav::instance()['locator']->findResource('account://');
+ $files = $account_dir ? array_diff(scandir($account_dir), ['.', '..']) : [];
+
+ // Try with username first, you never know!
+ if (in_array('username', $fields, true)) {
+ $user = $this->load($query);
+ unset($fields[array_search('username', $fields, true)]);
+ } else {
+ $user = $this->load('');
+ }
+
+ // If not found, try the fields
+ if (!$user->exists()) {
+ foreach ($files as $file) {
+ if (Utils::endsWith($file, YAML_EXT)) {
+ $find_user = $this->load(trim(pathinfo($file, PATHINFO_FILENAME)));
+ foreach ($fields as $field) {
+ if ($find_user[$field] === $query) {
+ return $find_user;
+ }
+ }
+ }
+ }
+ }
+ return $user;
+ }
+
+ /**
+ * Remove user account.
+ *
+ * @param string $username
+ *
+ * @return bool True if the action was performed
+ */
+ public function delete($username): bool
+ {
+ $file_path = Grav::instance()['locator']->findResource('account://' . $username . YAML_EXT);
+
+ return $file_path && unlink($file_path);
+ }
+}
diff --git a/system/src/Grav/Common/User/FlexUser/Storage/UserFileStorage.php b/system/src/Grav/Common/User/FlexUser/Storage/UserFileStorage.php
new file mode 100644
index 0000000..3156fc4
--- /dev/null
+++ b/system/src/Grav/Common/User/FlexUser/Storage/UserFileStorage.php
@@ -0,0 +1,16 @@
+ false,
+ 'find' => false,
+ 'remove' => false,
+ 'get' => true,
+ 'set' => false,
+ 'undef' => false,
+ 'def' => false,
+ ] + parent::getCachedMethods();
+ }
+
+ public function __construct(array $elements, $key, FlexDirectory $directory, bool $validate = false)
+ {
+ // User can only be authenticated via login.
+ unset($elements['authenticated'], $elements['authorized']);
+
+ parent::__construct($elements, $key, $directory, $validate);
+
+ // Define username and state if they aren't set.
+ $this->defProperty('username', $key);
+ $this->defProperty('state', 'enabled');
+ }
+
+ /**
+ * Get value by using dot notation for nested arrays/objects.
+ *
+ * @example $value = $this->get('this.is.my.nested.variable');
+ *
+ * @param string $name Dot separated path to the requested value.
+ * @param mixed $default Default value (or null).
+ * @param string $separator Separator, defaults to '.'
+ * @return mixed Value.
+ */
+ public function get($name, $default = null, $separator = null)
+ {
+ return $this->getNestedProperty($name, $default, $separator);
+ }
+
+ /**
+ * Set value by using dot notation for nested arrays/objects.
+ *
+ * @example $data->set('this.is.my.nested.variable', $value);
+ *
+ * @param string $name Dot separated path to the requested value.
+ * @param mixed $value New value.
+ * @param string $separator Separator, defaults to '.'
+ * @return $this
+ */
+ public function set($name, $value, $separator = null)
+ {
+ $this->setNestedProperty($name, $value, $separator);
+
+ return $this;
+ }
+
+ /**
+ * Unset value by using dot notation for nested arrays/objects.
+ *
+ * @example $data->undef('this.is.my.nested.variable');
+ *
+ * @param string $name Dot separated path to the requested value.
+ * @param string $separator Separator, defaults to '.'
+ * @return $this
+ */
+ public function undef($name, $separator = null)
+ {
+ $this->unsetNestedProperty($name, $separator);
+
+ return $this;
+ }
+
+ /**
+ * Set default value by using dot notation for nested arrays/objects.
+ *
+ * @example $data->def('this.is.my.nested.variable', 'default');
+ *
+ * @param string $name Dot separated path to the requested value.
+ * @param mixed $default Default value (or null).
+ * @param string $separator Separator, defaults to '.'
+ * @return $this
+ */
+ public function def($name, $default = null, $separator = null)
+ {
+ $this->defNestedProperty($name, $default, $separator);
+
+ return $this;
+ }
+
+ /**
+ * Get value from a page variable (used mostly for creating edit forms).
+ *
+ * @param string $name Variable name.
+ * @param mixed $default
+ * @param string|null $separator
+ * @return mixed
+ */
+ public function getFormValue(string $name, $default = null, string $separator = null)
+ {
+ $value = parent::getFormValue($name, null, $separator);
+
+ if ($name === 'avatar') {
+ return $this->parseFileProperty($value);
+ }
+
+ if (null === $value) {
+ if ($name === 'media_order') {
+ return implode(',', $this->getMediaOrder());
+ }
+ }
+
+ return $value ?? $default;
+ }
+
+ /**
+ * @param string $property
+ * @param mixed $default
+ * @return mixed
+ */
+ public function getProperty($property, $default = null)
+ {
+ $value = parent::getProperty($property, $default);
+
+ if ($property === 'avatar') {
+ $value = $this->parseFileProperty($value);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Convert object into an array.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $array = $this->jsonSerialize();
+ $array['avatar'] = $this->parseFileProperty($array['avatar'] ?? null);
+
+ return $array;
+ }
+
+ /**
+ * Convert object into YAML string.
+ *
+ * @param int $inline The level where you switch to inline YAML.
+ * @param int $indent The amount of spaces to use for indentation of nested nodes.
+ *
+ * @return string A YAML string representing the object.
+ */
+ public function toYaml($inline = 5, $indent = 2)
+ {
+ $yaml = new YamlFormatter(['inline' => $inline, 'indent' => $indent]);
+
+ return $yaml->encode($this->toArray());
+ }
+
+ /**
+ * Convert object into JSON string.
+ *
+ * @return string
+ */
+ public function toJson()
+ {
+ $json = new JsonFormatter();
+
+ return $json->encode($this->toArray());
+ }
+
+ /**
+ * Join nested values together by using blueprints.
+ *
+ * @param string $name Dot separated path to the requested value.
+ * @param mixed $value Value to be joined.
+ * @param string $separator Separator, defaults to '.'
+ * @return $this
+ * @throws \RuntimeException
+ */
+ public function join($name, $value, $separator = null)
+ {
+ $separator = $separator ?? '.';
+ $old = $this->get($name, null, $separator);
+ if ($old !== null) {
+ if (!\is_array($old)) {
+ throw new \RuntimeException('Value ' . $old);
+ }
+
+ if (\is_object($value)) {
+ $value = (array) $value;
+ } elseif (!\is_array($value)) {
+ throw new \RuntimeException('Value ' . $value);
+ }
+
+ $value = $this->getBlueprint()->mergeData($old, $value, $name, $separator);
+ }
+
+ $this->set($name, $value, $separator);
+
+ return $this;
+ }
+
+ /**
+ * Get nested structure containing default values defined in the blueprints.
+ *
+ * Fields without default value are ignored in the list.
+
+ * @return array
+ */
+ public function getDefaults()
+ {
+ return $this->getBlueprint()->getDefaults();
+ }
+
+ /**
+ * Set default values by using blueprints.
+ *
+ * @param string $name Dot separated path to the requested value.
+ * @param mixed $value Value to be joined.
+ * @param string $separator Separator, defaults to '.'
+ * @return $this
+ */
+ public function joinDefaults($name, $value, $separator = null)
+ {
+ if (\is_object($value)) {
+ $value = (array) $value;
+ }
+
+ $old = $this->get($name, null, $separator);
+ if ($old !== null) {
+ $value = $this->getBlueprint()->mergeData($value, $old, $name, $separator);
+ }
+
+ $this->setNestedProperty($name, $value, $separator);
+
+ return $this;
+ }
+
+ /**
+ * Get value from the configuration and join it with given data.
+ *
+ * @param string $name Dot separated path to the requested value.
+ * @param array|object $value Value to be joined.
+ * @param string $separator Separator, defaults to '.'
+ * @return array
+ * @throws \RuntimeException
+ */
+ public function getJoined($name, $value, $separator = null)
+ {
+ if (\is_object($value)) {
+ $value = (array) $value;
+ } elseif (!\is_array($value)) {
+ throw new \RuntimeException('Value ' . $value);
+ }
+
+ $old = $this->get($name, null, $separator);
+
+ if ($old === null) {
+ // No value set; no need to join data.
+ return $value;
+ }
+
+ if (!\is_array($old)) {
+ throw new \RuntimeException('Value ' . $old);
+ }
+
+ // Return joined data.
+ return $this->getBlueprint()->mergeData($old, $value, $name, $separator);
+ }
+
+ /**
+ * Set default values to the configuration if variables were not set.
+ *
+ * @param array $data
+ * @return $this
+ */
+ public function setDefaults(array $data)
+ {
+ $this->setElements($this->getBlueprint()->mergeData($data, $this->toArray()));
+
+ return $this;
+ }
+
+ /**
+ * Validate by blueprints.
+ *
+ * @return $this
+ * @throws \Exception
+ */
+ public function validate()
+ {
+ $this->getBlueprint()->validate($this->toArray());
+
+ return $this;
+ }
+
+ /**
+ * Filter all items by using blueprints.
+ * @return $this
+ */
+ public function filter()
+ {
+ $this->setElements($this->getBlueprint()->filter($this->toArray()));
+
+ return $this;
+ }
+
+ /**
+ * Get extra items which haven't been defined in blueprints.
+ *
+ * @return array
+ */
+ public function extra()
+ {
+ return $this->getBlueprint()->extra($this->toArray());
+ }
+
+ /**
+ * Return unmodified data as raw string.
+ *
+ * NOTE: This function only returns data which has been saved to the storage.
+ *
+ * @return string
+ */
+ public function raw()
+ {
+ $file = $this->file();
+
+ return $file ? $file->raw() : '';
+ }
+
+ /**
+ * Set or get the data storage.
+ *
+ * @param FileInterface $storage Optionally enter a new storage.
+ * @return FileInterface
+ */
+ public function file(FileInterface $storage = null)
+ {
+ if (null !== $storage) {
+ $this->_storage = $storage;
+ }
+
+ return $this->_storage;
+ }
+
+ public function isValid(): bool
+ {
+ return $this->getProperty('state') !== null;
+ }
+
+ /**
+ * Save user without the username
+ */
+ public function save()
+ {
+ $password = $this->getProperty('password');
+ if (null !== $password) {
+ $this->unsetProperty('password');
+ $this->unsetProperty('password1');
+ $this->unsetProperty('password2');
+ $this->setProperty('hashed_password', Authentication::create($password));
+ }
+
+ return parent::save();
+ }
+
+ /**
+ * @return array
+ */
+ public function prepareStorage(): array
+ {
+ $elements = parent::prepareStorage();
+
+ // Do not save authorization information.
+ unset($elements['authenticated'], $elements['authorized']);
+
+ return $elements;
+ }
+
+ /**
+ * Merge two configurations together.
+ *
+ * @param array $data
+ * @return $this
+ * @deprecated 1.6 Use `->update($data)` instead (same but with data validation & filtering, file upload support).
+ */
+ public function merge(array $data)
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->update($data) method instead', E_USER_DEPRECATED);
+
+ $this->setElements($this->getBlueprint()->mergeData($this->toArray(), $data));
+
+ return $this;
+ }
+
+ /**
+ * Return media object for the User's avatar.
+ *
+ * @return ImageMedium|null
+ * @deprecated 1.6 Use ->getAvatarImage() method instead.
+ */
+ public function getAvatarMedia()
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getAvatarImage() method instead', E_USER_DEPRECATED);
+
+ return $this->getAvatarImage();
+ }
+
+ /**
+ * Return the User's avatar URL
+ *
+ * @return string
+ * @deprecated 1.6 Use ->getAvatarUrl() method instead.
+ */
+ public function avatarUrl()
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getAvatarUrl() method instead', E_USER_DEPRECATED);
+
+ return $this->getAvatarUrl();
+ }
+
+ /**
+ * Checks user authorization to the action.
+ * Ensures backwards compatibility
+ *
+ * @param string $action
+ * @return bool
+ * @deprecated 1.5 Use ->authorize() method instead.
+ */
+ public function authorise($action)
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use ->authorize() method instead', E_USER_DEPRECATED);
+
+ return $this->authorize($action);
+ }
+
+ /**
+ * Implements Countable interface.
+ *
+ * @return int
+ * @deprecated 1.6 Method makes no sense for user account.
+ */
+ public function count()
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED);
+
+ return \count($this->jsonSerialize());
+ }
+
+ /**
+ * Gets the associated media collection (original images).
+ *
+ * @return MediaCollectionInterface Representation of associated media.
+ */
+ protected function getOriginalMedia()
+ {
+ return (new Media($this->getMediaFolder() . '/original', $this->getMediaOrder()))->setTimestamps();
+ }
+
+ /**
+ * @param array $files
+ */
+ protected function setUpdatedMedia(array $files): void
+ {
+ $list = [];
+ $list_original = [];
+ foreach ($files as $field => $group) {
+ foreach ($group as $filename => $file) {
+ if (strpos($field, '/original')) {
+ // Special handling for original images.
+ $list_original[$filename] = $file;
+ continue;
+ }
+
+ $list[$filename] = $file;
+
+ if ($file) {
+ /** @var FormFlashFile $file */
+ $data = $file->jsonSerialize();
+ $path = $file->getClientFilename();
+ unset($data['tmp_name'], $data['path']);
+
+ $this->setNestedProperty("{$field}\n{$path}", $data, "\n");
+ } else {
+ $this->unsetNestedProperty("{$field}\n{$filename}", "\n");
+ }
+ }
+ }
+
+ $this->_uploads = $list;
+ $this->_uploads_original = $list_original;
+ }
+
+ protected function saveUpdatedMedia(): void
+ {
+ // Upload/delete original sized images.
+ /** @var FormFlashFile $file */
+ foreach ($this->_uploads_original ?? [] as $name => $file) {
+ $name = 'original/' . $name;
+ if ($file) {
+ $this->uploadMediaFile($file, $name);
+ } else {
+ $this->deleteMediaFile($name);
+ }
+ }
+
+ /**
+ * @var string $filename
+ * @var UploadedFileInterface $file
+ */
+ foreach ($this->getUpdatedMedia() as $filename => $file) {
+ if ($file) {
+ $this->uploadMediaFile($file, $filename);
+ } else {
+ $this->deleteMediaFile($filename);
+ }
+ }
+
+ $this->setUpdatedMedia([]);
+ }
+
+ /**
+ * @param array $value
+ * @return array
+ */
+ protected function parseFileProperty($value)
+ {
+ if (!\is_array($value)) {
+ return $value;
+ }
+
+ $originalMedia = $this->getOriginalMedia();
+ $resizedMedia = $this->getMedia();
+
+ $list = [];
+ foreach ($value as $filename => $info) {
+ if (!\is_array($info)) {
+ continue;
+ }
+
+ /** @var Medium $thumbFile */
+ $thumbFile = $resizedMedia[$filename];
+ /** @var Medium $imageFile */
+ $imageFile = $originalMedia[$filename] ?? $thumbFile;
+ if ($thumbFile) {
+ $list[$filename] = [
+ 'name' => $filename,
+ 'type' => $info['type'],
+ 'size' => $info['size'],
+ 'image_url' => $imageFile->url(),
+ 'thumb_url' => $thumbFile->url(),
+ 'cropData' => (object)($imageFile->metadata()['upload']['crop'] ?? [])
+ ];
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * @return array
+ */
+ protected function doSerialize(): array
+ {
+ return [
+ 'type' => 'accounts',
+ 'key' => $this->getKey(),
+ 'elements' => $this->jsonSerialize(),
+ 'storage' => $this->getStorage()
+ ];
+ }
+
+ /**
+ * @param array $serialized
+ */
+ protected function doUnserialize(array $serialized): void
+ {
+ $grav = Grav::instance();
+
+ /** @var UserCollection $accounts */
+ $accounts = $grav['accounts'];
+
+ $directory = $accounts->getFlexDirectory();
+ if (!$directory) {
+ throw new \InvalidArgumentException('Internal error');
+ }
+
+ $this->setFlexDirectory($directory);
+ $this->setStorage($serialized['storage']);
+ $this->setKey($serialized['key']);
+ $this->setElements($serialized['elements']);
+ }
+}
diff --git a/system/src/Grav/Common/User/FlexUser/UserCollection.php b/system/src/Grav/Common/User/FlexUser/UserCollection.php
new file mode 100644
index 0000000..0f7a1d1
--- /dev/null
+++ b/system/src/Grav/Common/User/FlexUser/UserCollection.php
@@ -0,0 +1,100 @@
+exists().
+ *
+ * @param string $username
+ * @return User
+ */
+ public function load($username): UserInterface
+ {
+ if ($username !== '') {
+ $key = mb_strtolower($username);
+ $user = $this->get($key);
+ if ($user) {
+ return $user;
+ }
+ } else {
+ $key = '';
+ }
+
+ $directory = $this->getFlexDirectory();
+
+ /** @var User $object */
+ $object = $directory->createObject(
+ [
+ 'username' => $username,
+ 'state' => 'enabled'
+ ],
+ $key
+ );
+
+ return $object;
+ }
+
+ /**
+ * Find a user by username, email, etc
+ *
+ * @param string $query the query to search for
+ * @param array $fields the fields to search
+ * @return User
+ */
+ public function find($query, $fields = ['username', 'email']): UserInterface
+ {
+ foreach ((array)$fields as $field) {
+ if ($field === 'key') {
+ $user = $this->get($query);
+ } elseif ($field === 'storage_key') {
+ $user = $this->withKeyField('storage_key')->get($query);
+ } elseif ($field === 'flex_key') {
+ $user = $this->withKeyField('flex_key')->get($query);
+ } elseif ($field === 'username') {
+ $user = $this->get(mb_strtolower($query));
+ } else {
+ $user = parent::find($query, $field);
+ }
+ if ($user) {
+ return $user;
+ }
+ }
+
+ return $this->load('');
+ }
+
+ /**
+ * Delete user account.
+ *
+ * @param string $username
+ *
+ * @return bool True if user account was found and was deleted.
+ */
+ public function delete($username): bool
+ {
+ $user = $this->load($username);
+
+ $exists = $user->exists();
+ if ($exists) {
+ $user->delete();
+ }
+
+ return $exists;
+ }
+}
diff --git a/system/src/Grav/Common/User/FlexUser/UserIndex.php b/system/src/Grav/Common/User/FlexUser/UserIndex.php
new file mode 100644
index 0000000..f95f51b
--- /dev/null
+++ b/system/src/Grav/Common/User/FlexUser/UserIndex.php
@@ -0,0 +1,133 @@
+exists().
+ *
+ * @param string $username
+ *
+ * @return User
+ */
+ public function load($username): UserInterface
+ {
+ if ($username !== '') {
+ $key = mb_strtolower($username);
+ $user = $this->get($key);
+ if ($user) {
+ return $user;
+ }
+ } else {
+ $key = '';
+ }
+
+ $directory = $this->getFlexDirectory();
+
+ /** @var User $object */
+ $object = $directory->createObject(
+ [
+ 'username' => $username,
+ 'state' => 'enabled'
+ ],
+ $key
+ );
+
+ return $object;
+ }
+
+ /**
+ * Find a user by username, email, etc
+ *
+ * @param string $query the query to search for
+ * @param array $fields the fields to search
+ * @return User
+ */
+ public function find($query, $fields = ['username', 'email']): UserInterface
+ {
+ foreach ((array)$fields as $field) {
+ if ($field === 'key') {
+ $user = $this->get($query);
+ } elseif ($field === 'storage_key') {
+ $user = $this->withKeyField('storage_key')->get($query);
+ } elseif ($field === 'flex_key') {
+ $user = $this->withKeyField('flex_key')->get($query);
+ } elseif ($field === 'email') {
+ $user = $this->withKeyField('email')->get($query);
+ } elseif ($field === 'username') {
+ $user = $this->get(mb_strtolower($query));
+ } else {
+ $user = $this->__call('find', [$query, $field]);
+ }
+ if ($user) {
+ return $user;
+ }
+ }
+
+ return $this->load('');
+ }
+
+ protected static function updateIndexData(array &$entry, array $data)
+ {
+ $entry['key'] = mb_strtolower($entry['key']);
+ $entry['email'] = isset($data['email']) ? mb_strtolower($data['email']) : null;
+ }
+
+ protected static function getIndexFile(FlexStorageInterface $storage)
+ {
+ // Load saved index file.
+ $grav = Grav::instance();
+ $locator = $grav['locator'];
+ $filename = $locator->findResource('user-data://flex/indexes/accounts.yaml', true, true);
+
+ return CompiledYamlFile::instance($filename);
+ }
+
+ protected static function onChanges(array $entries, array $added, array $updated, array $removed)
+ {
+ $message = sprintf('Flex: User index updated, %d objects (%d added, %d updated, %d removed).', \count($entries), \count($added), \count($updated), \count($removed));
+
+ $grav = Grav::instance();
+
+ /** @var Logger $logger */
+ $logger = $grav['log'];
+ $logger->addDebug($message);
+
+ /** @var Debugger $debugger */
+ $debugger = $grav['debugger'];
+ $debugger->addMessage($message, 'debug');
+ }
+}
diff --git a/system/src/Grav/Common/User/Group.php b/system/src/Grav/Common/User/Group.php
index a251326..9815c0e 100644
--- a/system/src/Grav/Common/User/Group.php
+++ b/system/src/Grav/Common/User/Group.php
@@ -1,8 +1,9 @@
$group) {
- $groups[$groupname] = isset($group['readableName']) ? $group['readableName'] : $groupname;
+ $groups[$groupname] = $group['readableName'] ?? $groupname;
}
return $groups;
@@ -66,10 +67,10 @@ class Group extends Data
{
$groups = self::groups();
- $content = isset($groups[$groupname]) ? $groups[$groupname] : [];
+ $content = $groups[$groupname] ?? [];
$content += ['groupname' => $groupname];
- $blueprints = new Blueprints;
+ $blueprints = new Blueprints();
$blueprint = $blueprints->get('user/group');
return new Group($content, $blueprint);
@@ -85,17 +86,17 @@ class Group extends Data
/** @var Config $config */
$config = $grav['config'];
- $blueprints = new Blueprints;
+ $blueprints = new Blueprints();
$blueprint = $blueprints->get('user/group');
- $config->set("groups.{$this->groupname}", []);
+ $config->set("groups.{$this->get('groupname')}", []);
$fields = $blueprint->fields();
foreach ($fields as $field) {
if ($field['type'] === 'text') {
$value = $field['name'];
if (isset($this->items['data'][$value])) {
- $config->set("groups.{$this->groupname}.{$value}", $this->items['data'][$value]);
+ $config->set("groups.{$this->get('groupname')}.{$value}", $this->items['data'][$value]);
}
}
if ($field['type'] === 'array' || $field['type'] === 'permissions') {
@@ -104,14 +105,14 @@ class Group extends Data
if ($arrayValues) {
foreach ($arrayValues as $arrayIndex => $arrayValue) {
- $config->set("groups.{$this->groupname}.{$value}.{$arrayIndex}", $arrayValue);
+ $config->set("groups.{$this->get('groupname')}.{$value}.{$arrayIndex}", $arrayValue);
}
}
}
}
$type = 'groups';
- $blueprints = $this->blueprints("config/{$type}");
+ $blueprints = $this->blueprints();
$filename = CompiledYamlFile::instance($grav['locator']->findResource("config://{$type}.yaml"));
@@ -134,7 +135,7 @@ class Group extends Data
/** @var Config $config */
$config = $grav['config'];
- $blueprints = new Blueprints;
+ $blueprints = new Blueprints();
$blueprint = $blueprints->get('user/group');
$type = 'groups';
diff --git a/system/src/Grav/Common/User/Interfaces/UserCollectionInterface.php b/system/src/Grav/Common/User/Interfaces/UserCollectionInterface.php
new file mode 100644
index 0000000..a8cc7d6
--- /dev/null
+++ b/system/src/Grav/Common/User/Interfaces/UserCollectionInterface.php
@@ -0,0 +1,40 @@
+exists().
+ *
+ * @param string $username
+ * @return UserInterface
+ */
+ public function load($username): UserInterface;
+
+ /**
+ * Find a user by username, email, etc
+ *
+ * @param string $query the query to search for
+ * @param array $fields the fields to search
+ * @return UserInterface
+ */
+ public function find($query, $fields = ['username', 'email']): UserInterface;
+
+ /**
+ * Delete user account.
+ *
+ * @param string $username
+ * @return bool True if user account was found and was deleted.
+ */
+ public function delete($username): bool;
+}
diff --git a/system/src/Grav/Common/User/Interfaces/UserInterface.php b/system/src/Grav/Common/User/Interfaces/UserInterface.php
new file mode 100644
index 0000000..0fba676
--- /dev/null
+++ b/system/src/Grav/Common/User/Interfaces/UserInterface.php
@@ -0,0 +1,198 @@
+get('this.is.my.nested.variable');
+ *
+ * @param string $name Dot separated path to the requested value.
+ * @param mixed $default Default value (or null).
+ * @param string $separator Separator, defaults to '.'
+ * @return mixed Value.
+ */
+ public function get($name, $default = null, $separator = null);
+
+ /**
+ * Set value by using dot notation for nested arrays/objects.
+ *
+ * @example $data->set('this.is.my.nested.variable', $value);
+ *
+ * @param string $name Dot separated path to the requested value.
+ * @param mixed $value New value.
+ * @param string $separator Separator, defaults to '.'
+ * @return $this
+ */
+ public function set($name, $value, $separator = null);
+
+ /**
+ * Unset value by using dot notation for nested arrays/objects.
+ *
+ * @example $data->undef('this.is.my.nested.variable');
+ *
+ * @param string $name Dot separated path to the requested value.
+ * @param string $separator Separator, defaults to '.'
+ * @return $this
+ */
+ public function undef($name, $separator = null);
+
+ /**
+ * Set default value by using dot notation for nested arrays/objects.
+ *
+ * @example $data->def('this.is.my.nested.variable', 'default');
+ *
+ * @param string $name Dot separated path to the requested value.
+ * @param mixed $default Default value (or null).
+ * @param string $separator Separator, defaults to '.'
+ * @return $this
+ */
+ public function def($name, $default = null, $separator = null);
+
+ /**
+ * Join nested values together by using blueprints.
+ *
+ * @param string $name Dot separated path to the requested value.
+ * @param mixed $value Value to be joined.
+ * @param string $separator Separator, defaults to '.'
+ * @return $this
+ * @throws \RuntimeException
+ */
+ public function join($name, $value, $separator = '.');
+
+ /**
+ * Get nested structure containing default values defined in the blueprints.
+ *
+ * Fields without default value are ignored in the list.
+
+ * @return array
+ */
+ public function getDefaults();
+
+ /**
+ * Set default values by using blueprints.
+ *
+ * @param string $name Dot separated path to the requested value.
+ * @param mixed $value Value to be joined.
+ * @param string $separator Separator, defaults to '.'
+ * @return $this
+ */
+ public function joinDefaults($name, $value, $separator = '.');
+
+ /**
+ * Get value from the configuration and join it with given data.
+ *
+ * @param string $name Dot separated path to the requested value.
+ * @param array|object $value Value to be joined.
+ * @param string $separator Separator, defaults to '.'
+ * @return array
+ * @throws \RuntimeException
+ */
+ public function getJoined($name, $value, $separator = '.');
+
+ /**
+ * Set default values to the configuration if variables were not set.
+ *
+ * @param array $data
+ * @return $this
+ */
+ public function setDefaults(array $data);
+
+ /**
+ * Update object with data
+ *
+ * @param array $data
+ * @param array $files
+ * @return $this
+ */
+ public function update(array $data, array $files = []);
+
+ /**
+ * Returns whether the data already exists in the storage.
+ *
+ * NOTE: This method does not check if the data is current.
+ *
+ * @return bool
+ */
+ public function exists();
+
+ /**
+ * Return unmodified data as raw string.
+ *
+ * NOTE: This function only returns data which has been saved to the storage.
+ *
+ * @return string
+ */
+ public function raw();
+
+ /**
+ * Authenticate user.
+ *
+ * If user password needs to be updated, new information will be saved.
+ *
+ * @param string $password Plaintext password.
+ *
+ * @return bool
+ */
+ public function authenticate(string $password): bool;
+
+ /**
+ * Checks user authorization to the action.
+ *
+ * @param string $action
+ * @param string|null $scope
+ * @return bool
+ */
+ public function authorize(string $action, string $scope = null): bool;
+
+ /**
+ * Return media object for the User's avatar.
+ *
+ * Note: if there's no local avatar image for the user, you should call getAvatarUrl() to get the external avatar URL.
+ *
+ * @return ImageMedium|null
+ */
+ public function getAvatarImage(): ?ImageMedium;
+
+ /**
+ * Return the User's avatar URL.
+ *
+ * @return string
+ */
+ public function getAvatarUrl(): string;
+}
diff --git a/system/src/Grav/Common/User/Traits/UserTrait.php b/system/src/Grav/Common/User/Traits/UserTrait.php
new file mode 100644
index 0000000..2b69768
--- /dev/null
+++ b/system/src/Grav/Common/User/Traits/UserTrait.php
@@ -0,0 +1,170 @@
+get('hashed_password');
+
+ $isHashed = null !== $hash;
+ if (!$isHashed) {
+ // If there is no hashed password, fake verify with default hash.
+ $hash = Grav::instance()['config']->get('system.security.default_hash');
+ }
+
+ // Always execute verify() to protect us from timing attacks, but make the test to fail if hashed password wasn't set.
+ $result = Authentication::verify($password, $hash) && $isHashed;
+
+ $plaintext_password = $this->get('password');
+ if (null !== $plaintext_password) {
+ // Plain-text password is still stored, check if it matches.
+ if ($password !== $plaintext_password) {
+ return false;
+ }
+
+ // Force hash update to get rid of plaintext password.
+ $result = 2;
+ }
+
+ if ($result === 2) {
+ // Password needs to be updated, save the user.
+ $this->set('password', $password);
+ $this->undef('hashed_password');
+ $this->save();
+ }
+
+ return (bool)$result;
+ }
+
+ /**
+ * Checks user authorization to the action.
+ *
+ * @param string $action
+ * @param string|null $scope
+ * @return bool
+ */
+ public function authorize(string $action, string $scope = null): bool
+ {
+ if (!$this->get('authenticated')) {
+ return false;
+ }
+
+ if ($this->get('state', 'enabled') !== 'enabled') {
+ return false;
+ }
+
+ if (null !== $scope) {
+ $action = $scope . '.' . $action;
+ }
+
+ $config = Grav::instance()['config'];
+ $authorized = false;
+
+ //Check group access level
+ $groups = (array)$this->get('groups');
+ foreach ($groups as $group) {
+ $permission = $config->get("groups.{$group}.access.{$action}");
+ $authorized = Utils::isPositive($permission);
+ if ($authorized === true) {
+ break;
+ }
+ }
+
+ //Check user access level
+ $access = $this->get('access');
+ if ($access && Utils::getDotNotation($access, $action) !== null) {
+ $permission = $this->get("access.{$action}");
+ $authorized = Utils::isPositive($permission);
+ }
+
+ return $authorized;
+ }
+
+ /**
+ * Return media object for the User's avatar.
+ *
+ * Note: if there's no local avatar image for the user, you should call getAvatarUrl() to get the external avatar URL.
+ *
+ * @return ImageMedium|null
+ */
+ public function getAvatarImage(): ?ImageMedium
+ {
+ $avatars = $this->get('avatar');
+ if (\is_array($avatars) && $avatars) {
+ $avatar = array_shift($avatars);
+
+ $media = $this->getMedia();
+ $name = $avatar['name'] ?? null;
+
+ $image = $name ? $media[$name] : null;
+ if ($image instanceof ImageMedium) {
+ return $image;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the User's avatar URL
+ *
+ * @return string
+ */
+ public function getAvatarUrl(): string
+ {
+ // Try to locate avatar image.
+ $avatar = $this->getAvatarImage();
+ if ($avatar) {
+ return $avatar->url();
+ }
+
+ // Try if avatar is a sting (URL).
+ $avatar = $this->get('avatar');
+ if (\is_string($avatar)) {
+ return $avatar;
+ }
+
+ // Try looking for provider.
+ $provider = $this->get('provider');
+ if (\is_array($provider)) {
+ if (isset($provider['avatar_url']) && \is_string($provider['avatar_url'])) {
+ return $provider['avatar_url'];
+ }
+ if (isset($provider['avatar']) && \is_string($provider['avatar'])) {
+ return $provider['avatar'];
+ }
+ }
+
+ $email = $this->get('email');
+
+ // By default fall back to gravatar image.
+ return $email ? 'https://www.gravatar.com/avatar/' . md5(strtolower(trim($email))) : '';
+ }
+
+ abstract public function get($name, $default = null, $separator = null);
+ abstract public function set($name, $value, $separator = null);
+ abstract public function undef($name, $separator = null);
+ abstract public function save();
+}
diff --git a/system/src/Grav/Common/User/User.php b/system/src/Grav/Common/User/User.php
index c6f5dac..ba1dbdc 100644
--- a/system/src/Grav/Common/User/User.php
+++ b/system/src/Grav/Common/User/User.php
@@ -1,318 +1,146 @@
exists().
- *
- * @param string $username
- * @param bool $setConfig
- *
- * @return User
+ * @deprecated 1.6 Use $grav['accounts'] instead of static calls. In type hints, please use UserInterface.
*/
- public static function load($username)
+ class User extends FlexUser\User
{
- $grav = Grav::instance();
- /** @var UniformResourceLocator $locator */
- $locator = $grav['locator'];
+ /**
+ * Load user account.
+ *
+ * Always creates user object. To check if user exists, use $this->exists().
+ *
+ * @param string $username
+ *
+ * @return UserInterface
+ * @deprecated 1.6 Use $grav['accounts']->load(...) instead.
+ */
+ public static function load($username)
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use $grav[\'accounts\']->' . __FUNCTION__ . '() instead', E_USER_DEPRECATED);
- // force lowercase of username
- $username = strtolower($username);
-
- $blueprints = new Blueprints;
- $blueprint = $blueprints->get('user/account');
-
- $file_path = $locator->findResource('account://' . $username . YAML_EXT);
- $file = CompiledYamlFile::instance($file_path);
- $content = (array)$file->content() + ['username' => $username, 'state' => 'enabled'];
-
- $user = new User($content, $blueprint);
- $user->file($file);
-
- return $user;
- }
-
- /**
- * Find a user by username, email, etc
- *
- * @param string $query the query to search for
- * @param array $fields the fields to search
- * @return User
- */
- public static function find($query, $fields = ['username', 'email'])
- {
- $account_dir = Grav::instance()['locator']->findResource('account://');
- $files = $account_dir ? array_diff(scandir($account_dir), ['.', '..']) : [];
-
- // Try with username first, you never know!
- if (in_array('username', $fields, true)) {
- $user = User::load($query);
- unset($fields[array_search('username', $fields, true)]);
- } else {
- $user = User::load('');
+ return static::getCollection()->load($username);
}
- // If not found, try the fields
- if (!$user->exists()) {
- foreach ($files as $file) {
- if (Utils::endsWith($file, YAML_EXT)) {
- $find_user = User::load(trim(pathinfo($file, PATHINFO_FILENAME)));
- foreach ($fields as $field) {
- if ($find_user[$field] === $query) {
- return $find_user;
- }
- }
- }
- }
- }
- return $user;
- }
+ /**
+ * Find a user by username, email, etc
+ *
+ * Always creates user object. To check if user exists, use $this->exists().
+ *
+ * @param string $query the query to search for
+ * @param array $fields the fields to search
+ * @return UserInterface
+ * @deprecated 1.6 Use $grav['accounts']->find(...) instead.
+ */
+ public static function find($query, $fields = ['username', 'email'])
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use $grav[\'accounts\']->' . __FUNCTION__ . '() instead', E_USER_DEPRECATED);
- /**
- * Remove user account.
- *
- * @param string $username
- *
- * @return bool True if the action was performed
- */
- public static function remove($username)
- {
- $file_path = Grav::instance()['locator']->findResource('account://' . $username . YAML_EXT);
-
- return $file_path && unlink($file_path);
- }
-
- /**
- * @param string $offset
- * @return bool
- */
- public function offsetExists($offset)
- {
- $value = parent::offsetExists($offset);
-
- // Handle special case where user was logged in before 'authorized' was added to the user object.
- if (false === $value && $offset === 'authorized') {
- $value = $this->offsetExists('authenticated');
+ return static::getCollection()->find($query, $fields);
}
- return $value;
- }
+ /**
+ * Remove user account.
+ *
+ * @param string $username
+ * @return bool True if the action was performed
+ * @deprecated 1.6 Use $grav['accounts']->delete(...) instead.
+ */
+ public static function remove($username)
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use $grav[\'accounts\']->delete() instead', E_USER_DEPRECATED);
- /**
- * @param string $offset
- * @return mixed
- */
- public function offsetGet($offset)
- {
- $value = parent::offsetGet($offset);
-
- // Handle special case where user was logged in before 'authorized' was added to the user object.
- if (null === $value && $offset === 'authorized') {
- $value = $this->offsetGet('authenticated');
- $this->offsetSet($offset, $value);
+ return static::getCollection()->delete($username);
}
- return $value;
- }
-
- /**
- * Authenticate user.
- *
- * If user password needs to be updated, new information will be saved.
- *
- * @param string $password Plaintext password.
- *
- * @return bool
- */
- public function authenticate($password)
- {
- $save = false;
-
- // Plain-text is still stored
- if ($this->password) {
- if ($password !== $this->password) {
- // Plain-text passwords do not match, we know we should fail but execute
- // verify to protect us from timing attacks and return false regardless of
- // the result
- Authentication::verify(
- $password,
- Grav::instance()['config']->get('system.security.default_hash')
- );
-
- return false;
- }
-
- // Plain-text does match, we can update the hash and proceed
- $save = true;
-
- $this->hashed_password = Authentication::create($this->password);
- unset($this->password);
-
- }
-
- $result = Authentication::verify($password, $this->hashed_password);
-
- // Password needs to be updated, save the file.
- if ($result === 2) {
- $save = true;
- $this->hashed_password = Authentication::create($password);
- }
-
- if ($save) {
- $this->save();
- }
-
- return (bool)$result;
- }
-
- /**
- * Save user without the username
- */
- public function save()
- {
- $file = $this->file();
-
- if ($file) {
- $username = $this->get('username');
-
- if (!$file->filename()) {
- $locator = Grav::instance()['locator'];
- $file->filename($locator->findResource('account://') . DS . strtolower($username) . YAML_EXT);
- }
-
- // if plain text password, hash it and remove plain text
- if ($this->password) {
- $this->hashed_password = Authentication::create($this->password);
- unset($this->password);
- }
-
- unset($this->username);
- $file->save($this->items);
- $this->set('username', $username);
+ /**
+ * @return UserCollectionInterface
+ */
+ protected static function getCollection()
+ {
+ return Grav::instance()['accounts'];
}
}
-
+} else {
/**
- * Checks user authorization to the action.
- *
- * @param string $action
- *
- * @return bool
+ * @deprecated 1.6 Use $grav['accounts'] instead of static calls. In type hints, use UserInterface.
*/
- public function authorize($action)
+ class User extends DataUser\User
{
- if (empty($this->items)) {
- return false;
+ /**
+ * Load user account.
+ *
+ * Always creates user object. To check if user exists, use $this->exists().
+ *
+ * @param string $username
+ *
+ * @return UserInterface
+ * @deprecated 1.6 Use $grav['accounts']->load(...) instead.
+ */
+ public static function load($username)
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use $grav[\'accounts\']->' . __FUNCTION__ . '() instead', E_USER_DEPRECATED);
+
+ return static::getCollection()->load($username);
}
- if (!$this->authenticated) {
- return false;
+ /**
+ * Find a user by username, email, etc
+ *
+ * Always creates user object. To check if user exists, use $this->exists().
+ *
+ * @param string $query the query to search for
+ * @param array $fields the fields to search
+ * @return UserInterface
+ * @deprecated 1.6 Use $grav['accounts']->find(...) instead.
+ */
+ public static function find($query, $fields = ['username', 'email'])
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use $grav[\'accounts\']->' . __FUNCTION__ . '() instead', E_USER_DEPRECATED);
+
+ return static::getCollection()->find($query, $fields);
}
- if (isset($this->state) && $this->state !== 'enabled') {
- return false;
+ /**
+ * Remove user account.
+ *
+ * @param string $username
+ * @return bool True if the action was performed
+ * @deprecated 1.6 Use $grav['accounts']->delete(...) instead.
+ */
+ public static function remove($username)
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use $grav[\'accounts\']->delete() instead', E_USER_DEPRECATED);
+
+ return static::getCollection()->delete($username);
}
- $return = false;
-
- //Check group access level
- $groups = $this->get('groups');
- if ($groups) {
- foreach ((array)$groups as $group) {
- $permission = Grav::instance()['config']->get("groups.{$group}.access.{$action}");
- $return = Utils::isPositive($permission);
- if ($return === true) {
- break;
- }
- }
- }
-
- //Check user access level
- if ($this->get('access')) {
- if (Utils::getDotNotation($this->get('access'), $action) !== null) {
- $permission = $this->get("access.{$action}");
- $return = Utils::isPositive($permission);
- }
- }
-
- return $return;
- }
-
- /**
- * Checks user authorization to the action.
- * Ensures backwards compatibility
- *
- * @param string $action
- *
- * @deprecated use authorize()
- * @return bool
- */
- public function authorise($action)
- {
- user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use authorize() method instead', E_USER_DEPRECATED);
-
- return $this->authorize($action);
- }
-
- /**
- * Return the User's avatar URL
- *
- * @return string
- */
- public function avatarUrl()
- {
- if ($this->avatar) {
- $avatar = $this->avatar;
- $avatar = array_shift($avatar);
- return Grav::instance()['base_url'] . '/' . $avatar['path'];
- }
-
- return 'https://www.gravatar.com/avatar/' . md5($this->email);
- }
-
- /**
- * Serialize user.
- */
- public function __sleep()
- {
- return [
- 'items',
- 'storage'
- ];
- }
-
- /**
- * Unserialize user.
- */
- public function __wakeup()
- {
- $this->gettersVariable = 'items';
- $this->nestedSeparator = '.';
-
- if (null === $this->items) {
- $this->items = [];
- }
-
- if (null === $this->blueprints) {
- $blueprints = new Blueprints;
- $this->blueprints = $blueprints->get('user/account');
+ /**
+ * @return UserCollectionInterface
+ */
+ protected static function getCollection()
+ {
+ return Grav::instance()['accounts'];
}
}
}
diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php
index b49abdd..c254009 100644
--- a/system/src/Grav/Common/Utils.php
+++ b/system/src/Grav/Common/Utils.php
@@ -1,16 +1,18 @@
get('system.absolute_urls', false)) {
@@ -39,6 +45,12 @@ abstract class Utils
return $input;
}
+ /** @var Uri $uri */
+ $uri = Grav::instance()['uri'];
+
+ $root = $uri->rootUrl();
+ $input = Utils::replaceFirstOccurrence($root, '', $input);
+
$input = ltrim((string)$input, '/');
if (Utils::contains((string)$input, '://')) {
@@ -63,10 +75,9 @@ abstract class Utils
$resource = $input;
}
- /** @var Uri $uri */
- $uri = Grav::instance()['uri'];
- return $resource ? rtrim($uri->rootUrl($domain), '/') . '/' . $resource : null;
+
+ return rtrim($uri->rootUrl($domain), '/') . '/' . ($resource ?? '');
}
/**
@@ -74,15 +85,18 @@ abstract class Utils
*
* @param string $haystack
* @param string|string[] $needle
+ * @param bool $case_sensitive
*
* @return bool
*/
- public static function startsWith($haystack, $needle)
+ public static function startsWith($haystack, $needle, $case_sensitive = true)
{
$status = false;
+ $compare_func = $case_sensitive ? 'mb_strpos' : 'mb_stripos';
+
foreach ((array)$needle as $each_needle) {
- $status = $each_needle === '' || strpos($haystack, $each_needle) === 0;
+ $status = $each_needle === '' || $compare_func($haystack, $each_needle) === 0;
if ($status) {
break;
}
@@ -96,15 +110,19 @@ abstract class Utils
*
* @param string $haystack
* @param string|string[] $needle
+ * @param bool $case_sensitive
*
* @return bool
*/
- public static function endsWith($haystack, $needle)
+ public static function endsWith($haystack, $needle, $case_sensitive = true)
{
$status = false;
+ $compare_func = $case_sensitive ? 'mb_strrpos' : 'mb_strripos';
+
foreach ((array)$needle as $each_needle) {
- $status = $each_needle === '' || substr($haystack, -strlen($each_needle)) === $each_needle;
+ $expectedPosition = mb_strlen($haystack) - mb_strlen($each_needle);
+ $status = $each_needle === '' || $compare_func($haystack, $each_needle, 0) === $expectedPosition;
if ($status) {
break;
}
@@ -118,15 +136,18 @@ abstract class Utils
*
* @param string $haystack
* @param string|string[] $needle
+ * @param bool $case_sensitive
*
* @return bool
*/
- public static function contains($haystack, $needle)
+ public static function contains($haystack, $needle, $case_sensitive = true)
{
$status = false;
+ $compare_func = $case_sensitive ? 'mb_strpos' : 'mb_stripos';
+
foreach ((array)$needle as $each_needle) {
- $status = $each_needle === '' || strpos($haystack, $each_needle) !== false;
+ $status = $each_needle === '' || $compare_func($haystack, $each_needle) !== false;
if ($status) {
break;
}
@@ -135,18 +156,45 @@ abstract class Utils
return $status;
}
+ /**
+ * Function that can match wildcards
+ *
+ * match_wildcard('foo*', $test), // TRUE
+ * match_wildcard('bar*', $test), // FALSE
+ * match_wildcard('*bar*', $test), // TRUE
+ * match_wildcard('**blob**', $test), // TRUE
+ * match_wildcard('*a?d*', $test), // TRUE
+ * match_wildcard('*etc**', $test) // TRUE
+ *
+ * @param string $wildcard_pattern
+ * @param string $haystack
+ * @return false|int
+ */
+ public static function matchWildcard($wildcard_pattern, $haystack) {
+ $regex = str_replace(
+ array("\*", "\?"), // wildcard chars
+ array('.*','.'), // regexp chars
+ preg_quote($wildcard_pattern, '/')
+ );
+
+ return preg_match('/^'.$regex.'$/is', $haystack);
+ }
+
/**
* Returns the substring of a string up to a specified needle. if not found, return the whole haystack
*
- * @param $haystack
- * @param $needle
+ * @param string $haystack
+ * @param string $needle
+ * @param bool $case_sensitive
*
* @return string
*/
- public static function substrToString($haystack, $needle)
+ public static function substrToString($haystack, $needle, $case_sensitive = true)
{
- if (static::contains($haystack, $needle)) {
- return substr($haystack, 0, strpos($haystack, $needle));
+ $compare_func = $case_sensitive ? 'mb_strpos' : 'mb_stripos';
+
+ if (static::contains($haystack, $needle, $case_sensitive)) {
+ return mb_substr($haystack, 0, $compare_func($haystack, $needle, $case_sensitive));
}
return $haystack;
@@ -155,30 +203,34 @@ abstract class Utils
/**
* Utility method to replace only the first occurrence in a string
*
- * @param $search
- * @param $replace
- * @param $subject
- * @return mixed
+ * @param string $search
+ * @param string $replace
+ * @param string $subject
+ *
+ * @return string
*/
public static function replaceFirstOccurrence($search, $replace, $subject)
{
if (!$search) {
return $subject;
}
- $pos = strpos($subject, $search);
+
+ $pos = mb_strpos($subject, $search);
if ($pos !== false) {
- $subject = substr_replace($subject, $replace, $pos, strlen($search));
+ $subject = static::mb_substr_replace($subject, $replace, $pos, mb_strlen($search));
}
+
+
return $subject;
}
/**
* Utility method to replace only the last occurrence in a string
*
- * @param $search
- * @param $replace
- * @param $subject
- * @return mixed
+ * @param string $search
+ * @param string $replace
+ * @param string $subject
+ * @return string
*/
public static function replaceLastOccurrence($search, $replace, $subject)
{
@@ -186,12 +238,29 @@ abstract class Utils
if($pos !== false)
{
- $subject = substr_replace($subject, $replace, $pos, strlen($search));
+ $subject = static::mb_substr_replace($subject, $replace, $pos, mb_strlen($search));
}
return $subject;
}
+ /**
+ * Multibyte compatible substr_replace
+ *
+ * @param string $original
+ * @param string $replacement
+ * @param int $position
+ * @param int $length
+ * @return string
+ */
+ public static function mb_substr_replace($original, $replacement, $position, $length)
+ {
+ $startString = mb_substr($original, 0, $position, "UTF-8");
+ $endString = mb_substr($original, $position + $length, mb_strlen($original), "UTF-8");
+
+ return $startString . $replacement . $endString;
+ }
+
/**
* Merge two objects into one.
*
@@ -208,9 +277,9 @@ abstract class Utils
/**
* Recursive Merge with uniqueness
*
- * @param $array1
- * @param $array2
- * @return mixed
+ * @param array $array1
+ * @param array $array2
+ * @return array
*/
public static function arrayMergeRecursiveUnique($array1, $array2)
{
@@ -229,6 +298,65 @@ abstract class Utils
return $array1;
}
+ /**
+ * Returns an array with the differences between $array1 and $array2
+ *
+ * @param array $array1
+ * @param array $array2
+ * @return array
+ */
+ public static function arrayDiffMultidimensional($array1, $array2)
+ {
+ $result = array();
+ foreach ($array1 as $key => $value) {
+ if (!is_array($array2) || !array_key_exists($key, $array2)) {
+ $result[$key] = $value;
+ continue;
+ }
+ if (is_array($value)) {
+ $recursiveArrayDiff = static::ArrayDiffMultidimensional($value, $array2[$key]);
+ if (count($recursiveArrayDiff)) {
+ $result[$key] = $recursiveArrayDiff;
+ }
+ continue;
+ }
+ if ($value != $array2[$key]) {
+ $result[$key] = $value;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Array combine but supports different array lengths
+ *
+ * @param array $arr1
+ * @param array $arr2
+ * @return array|false
+ */
+ public static function arrayCombine($arr1, $arr2)
+ {
+ $count = min(count($arr1), count($arr2));
+
+ return array_combine(array_slice($arr1, 0, $count), array_slice($arr2, 0, $count));
+ }
+
+ /**
+ * Array is associative or not
+ *
+ * @param array $arr
+ * @return bool
+ */
+ public static function arrayIsAssociative($arr)
+ {
+ if ([] === $arr) {
+ return false;
+ }
+
+ return array_keys($arr) !== range(0, count($arr) - 1);
+ }
+
/**
* Return the Grav date formats allowed
*
@@ -236,7 +364,7 @@ abstract class Utils
*/
public static function dateFormats()
{
- $now = new DateTime();
+ $now = new \DateTime();
$date_formats = [
'd-m-Y H:i' => 'd-m-Y H:i (e.g. '.$now->format('d-m-Y H:i').')',
@@ -253,6 +381,24 @@ abstract class Utils
return $date_formats;
}
+ /**
+ * Get current date/time
+ *
+ * @param string|null $default_format
+ * @return string
+ * @throws \Exception
+ */
+ public static function dateNow($default_format = null)
+ {
+ $now = new \DateTime();
+
+ if (is_null($default_format)) {
+ $default_format = Grav::instance()['config']->get('system.pages.dateformat.default');
+ }
+
+ return $now->format($default_format);
+ }
+
/**
* Truncate text by number of characters but can cut off words.
*
@@ -264,7 +410,7 @@ abstract class Utils
*
* @return string
*/
- public static function truncate($string, $limit = 150, $up_to_break = false, $break = " ", $pad = "…")
+ public static function truncate($string, $limit = 150, $up_to_break = false, $break = ' ', $pad = '…')
{
// return with no change if string is shorter than $limit
if (mb_strlen($string) <= $limit) {
@@ -479,50 +625,24 @@ abstract class Utils
}
/**
- * Return the mimetype based on filename
+ * Get all the mimetypes for an array of extensions
*
- * @param string $filename Filename or path to file
- * @param string $default default value
- *
- * @return string
+ * @param array $extensions
+ * @return array
*/
- public static function getMimeByFilename($filename, $default = 'application/octet-stream')
+ public static function getMimeTypes(array $extensions)
{
- return static::getMimeByExtension(pathinfo($filename, PATHINFO_EXTENSION), $default);
- }
-
- /**
- * Return the mimetype based on existing local file
- *
- * @param string $filename Path to the file
- *
- * @return string|bool
- */
- public static function getMimeByLocalFile($filename, $default = 'application/octet-stream')
- {
- $type = false;
-
- // For local files we can detect type by the file content.
- if (!stream_is_local($filename) || !file_exists($filename)) {
- return false;
- }
-
- // Prefer using finfo if it exists.
- if (\extension_loaded('fileinfo')) {
- $finfo = finfo_open(FILEINFO_SYMLINK | FILEINFO_MIME_TYPE);
- $type = finfo_file($finfo, $filename);
- finfo_close($finfo);
- } else {
- // Fall back to use getimagesize() if it is available (not recommended, but better than nothing)
- $info = @getimagesize($filename);
- if ($info) {
- $type = $info['mime'];
+ $mimetypes = [];
+ foreach ($extensions as $extension) {
+ $mimetype = static::getMimeByExtension($extension, false);
+ if ($mimetype && !in_array($mimetype, $mimetypes)) {
+ $mimetypes[] = $mimetype;
}
}
-
- return $type ?: static::getMimeByFilename($filename, $default);
+ return $mimetypes;
}
+
/**
* Return the mimetype based on filename extension
*
@@ -565,6 +685,71 @@ abstract class Utils
return $default;
}
+ /**
+ * Get all the extensions for an array of mimetypes
+ *
+ * @param array $mimetypes
+ * @return array
+ */
+ public static function getExtensions(array $mimetypes)
+ {
+ $extensions = [];
+ foreach ($mimetypes as $mimetype) {
+ $extension = static::getExtensionByMime($mimetype, false);
+ if ($extension && !\in_array($extension, $extensions, true)) {
+ $extensions[] = $extension;
+ }
+ }
+
+ return $extensions;
+ }
+
+ /**
+ * Return the mimetype based on filename
+ *
+ * @param string $filename Filename or path to file
+ * @param string $default default value
+ *
+ * @return string
+ */
+ public static function getMimeByFilename($filename, $default = 'application/octet-stream')
+ {
+ return static::getMimeByExtension(pathinfo($filename, PATHINFO_EXTENSION), $default);
+ }
+
+ /**
+ * Return the mimetype based on existing local file
+ *
+ * @param string $filename Path to the file
+ *
+ * @return string|bool
+ */
+ public static function getMimeByLocalFile($filename, $default = 'application/octet-stream')
+ {
+ $type = false;
+
+ // For local files we can detect type by the file content.
+ if (!stream_is_local($filename) || !file_exists($filename)) {
+ return false;
+ }
+
+ // Prefer using finfo if it exists.
+ if (\extension_loaded('fileinfo')) {
+ $finfo = finfo_open(FILEINFO_SYMLINK | FILEINFO_MIME_TYPE);
+ $type = finfo_file($finfo, $filename);
+ finfo_close($finfo);
+ } else {
+ // Fall back to use getimagesize() if it is available (not recommended, but better than nothing)
+ $info = @getimagesize($filename);
+ if ($info) {
+ $type = $info['mime'];
+ }
+ }
+
+ return $type ?: static::getMimeByFilename($filename, $default);
+ }
+
+
/**
* Returns true if filename is considered safe.
*
@@ -601,22 +786,47 @@ abstract class Utils
*/
public static function normalizePath($path)
{
- $root = ($path[0] === '/') ? '/' : '';
-
- $segments = explode('/', trim($path, '/'));
- $ret = [];
- foreach ($segments as $segment) {
- if (($segment === '.') || $segment === '') {
- continue;
- }
- if ($segment === '..') {
- array_pop($ret);
- } else {
- $ret[] = $segment;
- }
+ // Resolve any streams
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+ if ($locator->isStream($path)) {
+ $path = $locator->findResource($path);
}
- return $root . implode('/', $ret);
+ // Set root properly for any URLs
+ $root = '';
+ preg_match(self::ROOTURL_REGEX, $path, $matches);
+ if ($matches) {
+ $root = $matches[1];
+ $path = $matches[2];
+ }
+
+ // Strip off leading / to ensure explode is accurate
+ if (Utils::startsWith($path,'/')) {
+ $root .= '/';
+ $path = ltrim($path, '/');
+ }
+
+ // If there are any relative paths (..) handle those
+ if (Utils::contains($path, '..')) {
+ $segments = explode('/', trim($path, '/'));
+ $ret = [];
+ foreach ($segments as $segment) {
+ if (($segment === '.') || $segment === '') {
+ continue;
+ }
+ if ($segment === '..') {
+ array_pop($ret);
+ } else {
+ $ret[] = $segment;
+ }
+ }
+ $path = implode('/', $ret);
+ }
+
+ // Stick everything back together
+ $normalized = $root . $path;
+ return $normalized;
}
/**
@@ -628,7 +838,7 @@ abstract class Utils
*/
public static function isFunctionDisabled($function)
{
- return in_array($function, explode(',', ini_get('disable_functions')), true);
+ return \in_array($function, explode(',', ini_get('disable_functions')), true);
}
/**
@@ -640,7 +850,7 @@ abstract class Utils
{
$timezones = \DateTimeZone::listIdentifiers(\DateTimeZone::ALL);
$offsets = [];
- $testDate = new \DateTime;
+ $testDate = new \DateTime();
foreach ($timezones as $zone) {
$tz = new \DateTimeZone($zone);
@@ -696,7 +906,7 @@ abstract class Utils
public static function arrayFlatten($array)
{
$flatten = array();
- foreach ($array as $key => $inner){
+ foreach ($array as $key => $inner) {
if (is_array($inner)) {
foreach ($inner as $inner_key => $value) {
$flatten[$inner_key] = $value;
@@ -705,9 +915,60 @@ abstract class Utils
$flatten[$key] = $inner;
}
}
+
return $flatten;
}
+ /**
+ * Flatten a multi-dimensional associative array into dot notation
+ *
+ * @param array $array
+ * @param string $prepend
+ * @return array
+ */
+ public static function arrayFlattenDotNotation($array, $prepend = '')
+ {
+ $results = array();
+ foreach ($array as $key => $value) {
+ if (is_array($value)) {
+ $results = array_merge($results, static::arrayFlattenDotNotation($value, $prepend.$key.'.'));
+ } else {
+ $results[$prepend.$key] = $value;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Opposite of flatten, convert flat dot notation array to multi dimensional array
+ *
+ * @param array $array
+ * @param string $separator
+ * @return array
+ */
+ public static function arrayUnflattenDotNotation($array, $separator = '.')
+ {
+ $newArray = [];
+ foreach ($array as $key => $value) {
+ $dots = explode($separator, $key);
+ if (\count($dots) > 1) {
+ $last = &$newArray[$dots[0]];
+ foreach ($dots as $k => $dot) {
+ if ($k === 0) {
+ continue;
+ }
+ $last = &$last[$dot];
+ }
+ $last = $value;
+ } else {
+ $newArray[$key] = $value;
+ }
+ }
+
+ return $newArray;
+ }
+
/**
* Checks if the passed path contains the language code prefix
*
@@ -723,11 +984,7 @@ abstract class Utils
$languages_enabled = Grav::instance()['config']->get('system.languages.supported', []);
- if ($string[0] === '/' && $string[3] === '/' && in_array(substr($string, 1, 2), $languages_enabled)) {
- return true;
- }
-
- return false;
+ return $string[0] === '/' && $string[3] === '/' && \in_array(substr($string, 1, 2), $languages_enabled, true);
}
/**
@@ -745,9 +1002,9 @@ abstract class Utils
// try to use DateTime and default format
if ($dateformat) {
- $datetime = DateTime::createFromFormat($dateformat, $date);
+ $datetime = \DateTime::createFromFormat($dateformat, $date);
} else {
- $datetime = new DateTime($date);
+ $datetime = new \DateTime($date);
}
// fallback to strtotime() if DateTime approach failed
@@ -764,11 +1021,11 @@ abstract class Utils
* @param null $default
* @return mixed
*
- * @deprecated Use getDotNotation() method instead
+ * @deprecated 1.5 Use ->getDotNotation() method instead.
*/
public static function resolve(array $array, $path, $default = null)
{
- user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDotNotation() method instead', E_USER_DEPRECATED);
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use ->getDotNotation() method instead', E_USER_DEPRECATED);
return static::getDotNotation($array, $path, $default);
}
@@ -871,12 +1128,8 @@ abstract class Utils
//Nonce generated 12-24 hours ago
$previousTick = true;
- if ($nonce === self::getNonce($action, $previousTick)) {
- return true;
- }
- //Invalid nonce
- return false;
+ return $nonce === self::getNonce($action, $previousTick);
}
/**
@@ -896,8 +1149,8 @@ abstract class Utils
/**
* Get a portion of an array (passed by reference) with dot-notation key
*
- * @param $array
- * @param $key
+ * @param array $array
+ * @param string|int $key
* @param null $default
* @return mixed
*/
@@ -926,10 +1179,10 @@ abstract class Utils
* Set portion of array (passed by reference) for a dot-notation key
* and set the value
*
- * @param $array
- * @param $key
- * @param $value
- * @param bool $merge
+ * @param array $array
+ * @param string|int $key
+ * @param mixed $value
+ * @param bool $merge
*
* @return mixed
*/
@@ -960,7 +1213,6 @@ abstract class Utils
$array[$key] = array_merge($array[$key], $value);
}
-
return $array;
}
@@ -1005,13 +1257,13 @@ abstract class Utils
/**
* Sort an array by a key value in the array
*
- * @param $array
- * @param $array_key
+ * @param mixed $array
+ * @param string|int $array_key
* @param int $direction
* @param int $sort_flags
* @return array
*/
- public static function sortArrayByKey($array, $array_key, $direction = SORT_DESC, $sort_flags = SORT_REGULAR )
+ public static function sortArrayByKey($array, $array_key, $direction = SORT_DESC, $sort_flags = SORT_REGULAR)
{
$output = [];
@@ -1031,12 +1283,12 @@ abstract class Utils
/**
* Get's path based on a token
*
- * @param $path
- * @param Page|null $page
+ * @param string $path
+ * @param PageInterface|null $page
* @return string
* @throws \RuntimeException
*/
- public static function getPagePathFromToken($path, $page = null)
+ public static function getPagePathFromToken($path, PageInterface $page = null)
{
$path_parts = pathinfo($path);
$grav = Grav::instance();
@@ -1100,10 +1352,52 @@ abstract class Utils
return $max_size;
}
+ /**
+ * Convert bytes to the unit specified by the $to parameter.
+ *
+ * @param int $bytes The filesize in Bytes.
+ * @param string $to The unit type to convert to. Accepts K, M, or G for Kilobytes, Megabytes, or Gigabytes, respectively.
+ * @param int $decimal_places The number of decimal places to return.
+ *
+ * @return int Returns only the number of units, not the type letter. Returns 0 if the $to unit type is out of scope.
+ *
+ */
+ public static function convertSize($bytes, $to, $decimal_places = 1)
+ {
+ $formulas = array(
+ 'K' => number_format($bytes / 1024, $decimal_places),
+ 'M' => number_format($bytes / 1048576, $decimal_places),
+ 'G' => number_format($bytes / 1073741824, $decimal_places)
+ );
+ return $formulas[$to] ?? 0;
+ }
+
+ /**
+ * Return a pretty size based on bytes
+ *
+ * @param int $bytes
+ * @param int $precision
+ * @return string
+ */
+ public static function prettySize($bytes, $precision = 2)
+ {
+ $units = array('B', 'KB', 'MB', 'GB', 'TB');
+
+ $bytes = max($bytes, 0);
+ $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
+ $pow = min($pow, count($units) - 1);
+
+ // Uncomment one of the following alternatives
+ $bytes /= pow(1024, $pow);
+ // $bytes /= (1 << (10 * $pow));
+
+ return round($bytes, $precision) . ' ' . $units[$pow];
+ }
+
/**
* Parse a readable file size and return a value in bytes
*
- * @param $size
+ * @param string|int $size
* @return int
*/
public static function parseSize($size)
@@ -1111,17 +1405,17 @@ abstract class Utils
$unit = preg_replace('/[^bkmgtpezy]/i', '', $size);
$size = preg_replace('/[^0-9\.]/', '', $size);
if ($unit) {
- return (int)($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
+ return round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
+ } else {
+ return round($size);
}
-
- return (int)$size;
}
/**
* Multibyte-safe Parse URL function
*
- * @param $url
- * @return mixed
+ * @param string $url
+ * @return array
* @throws \InvalidArgumentException
*/
public static function multibyteParseUrl($url)
@@ -1146,4 +1440,73 @@ abstract class Utils
return $parts;
}
+
+ /**
+ * Process a string as markdown
+ *
+ * @param string $string
+ *
+ * @param bool $block Block or Line processing
+ * @return string
+ */
+ public static function processMarkdown($string, $block = true)
+ {
+ $page = Grav::instance()['page'] ?? null;
+ $defaults = Grav::instance()['config']->get('system.pages.markdown');
+
+ // Initialize the preferred variant of Parsedown
+ if ($defaults['extra']) {
+ $parsedown = new ParsedownExtra($page, $defaults);
+ } else {
+ $parsedown = new Parsedown($page, $defaults);
+ }
+
+ if ($block) {
+ $string = $parsedown->text($string);
+ } else {
+ $string = $parsedown->line($string);
+ }
+
+ return $string;
+ }
+
+ /**
+ * Find the subnet of an ip with CIDR prefix size
+ *
+ * @param string $ip
+ * @param int $prefix
+ *
+ * @return string
+ * @throws \InvalidArgumentException if provided an invalid IP
+ */
+ public static function getSubnet($ip, $prefix = 64)
+ {
+ if (!filter_var($ip, FILTER_VALIDATE_IP)) {
+ throw new \InvalidArgumentException('Invalid IP: ' . $ip);
+ }
+
+ // Packed representation of IP
+ $ip = inet_pton($ip);
+
+ // Maximum netmask length = same as packed address
+ $len = 8*strlen($ip);
+ if ($prefix > $len) $prefix = $len;
+
+ $mask = str_repeat('f', $prefix>>2);
+
+ switch($prefix & 3)
+ {
+ case 3: $mask .= 'e'; break;
+ case 2: $mask .= 'c'; break;
+ case 1: $mask .= '8'; break;
+ }
+ $mask = str_pad($mask, $len>>2, '0');
+
+ // Packed representation of netmask
+ $mask = pack('H*', $mask);
+ // Bitwise - Take all bits that are both 1 to generate subnet
+ $subnet = inet_ntop($ip & $mask);
+
+ return $subnet;
+ }
}
diff --git a/system/src/Grav/Common/Yaml.php b/system/src/Grav/Common/Yaml.php
index 2c43f73..ad6d81b 100644
--- a/system/src/Grav/Common/Yaml.php
+++ b/system/src/Grav/Common/Yaml.php
@@ -1,8 +1,9 @@
setName("backup")
+ ->setName('backup')
->addArgument(
- 'destination',
+ 'id',
InputArgument::OPTIONAL,
- 'Where to store the backup (/backup is default)'
+ 'The ID of the backup profile to perform without prompting'
)
- ->setDescription("Creates a backup of the Grav instance")
- ->setHelp('The backup creates a zipped backup. Optionally can be saved in a different destination.');
+ ->setDescription('Creates a backup of the Grav instance')
+ ->setHelp('The backup creates a zipped backup.');
$this->source = getcwd();
}
- /**
- * @return int|null|void
- */
protected function serve()
{
$this->progress = new ProgressBar($this->output);
- $this->progress->setFormat('Archiving %current% files [%bar%] %elapsed:6s% %memory:6s%');
+ $this->progress->setFormat('Archiving %current% files [%bar%] %percent:3s%% %elapsed:6s% %message%');
+ $this->progress->setBarWidth(100);
Grav::instance()['config']->init();
- $destination = ($this->input->getArgument('destination')) ? $this->input->getArgument('destination') : null;
- $log = JsonFile::instance(Grav::instance()['locator']->findResource("log://backup.log", true, true));
- $backup = ZipBackup::backup($destination, [$this, 'output']);
+ $io = new SymfonyStyle($this->input, $this->output);
+ $io->title('Grav Backup');
- $log->content([
- 'time' => time(),
- 'location' => $backup
- ]);
- $log->save();
+ /** @var Backups $backups */
+ $backups = Grav::instance()['backups'];
+ $backups_list = $backups->getBackupProfiles();
+ $backups_names = $backups->getBackupNames();
- $this->output->writeln('');
- $this->output->writeln('');
+ $id = null;
+ $inline_id = $this->input->getArgument('id');
+ if (null !== $inline_id && is_numeric($inline_id)) {
+ $id = $inline_id;
+ }
+
+ if (null === $id) {
+ if (\count($backups_list) > 1) {
+ $helper = $this->getHelper('question');
+ $question = new ChoiceQuestion(
+ 'Choose a backup?',
+ $backups_names,
+ 0
+ );
+ $question->setErrorMessage('Option %s is invalid.');
+ $backup_name = $helper->ask($this->input, $this->output, $question);
+ $id = array_search($backup_name, $backups_names, true);
+
+ $io->newLine();
+ $io->note('Selected backup: ' . $backup_name);
+ } else {
+ $id = 0;
+ }
+ }
+
+ $backup = $backups->backup($id, [$this, 'outputProgress']);
+
+ $io->newline(2);
+ $io->success('Backup Successfully Created: ' . $backup);
}
/**
- * @param $args
+ * @param array $args
*/
- public function output($args)
+ public function outputProgress($args)
{
switch ($args['type']) {
+ case 'count':
+ $steps = $args['steps'];
+ $freq = (int)($steps > 100 ? round($steps / 100) : $steps);
+ $this->progress->setMaxSteps($steps);
+ $this->progress->setRedrawFrequency($freq);
+ $this->progress->setMessage('Adding files...');
+ break;
case 'message':
- $this->output->writeln($args['message']);
+ $this->progress->setMessage($args['message']);
+ $this->progress->display();
break;
case 'progress':
- if ($args['complete']) {
+ if (isset($args['complete']) && $args['complete']) {
$this->progress->finish();
} else {
$this->progress->advance();
diff --git a/system/src/Grav/Console/Cli/CleanCommand.php b/system/src/Grav/Console/Cli/CleanCommand.php
index 32e2b75..a905973 100644
--- a/system/src/Grav/Console/Cli/CleanCommand.php
+++ b/system/src/Grav/Console/Cli/CleanCommand.php
@@ -1,8 +1,9 @@
setName("clean")
- ->setDescription("Handles cleaning chores for Grav distribution")
+ ->setName('clean')
+ ->setDescription('Handles cleaning chores for Grav distribution')
->setHelp('The clean clean extraneous folders and data');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
- *
- * @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
diff --git a/system/src/Grav/Console/Cli/ClearCacheCommand.php b/system/src/Grav/Console/Cli/ClearCacheCommand.php
index cb5ffe8..0ab4128 100644
--- a/system/src/Grav/Console/Cli/ClearCacheCommand.php
+++ b/system/src/Grav/Console/Cli/ClearCacheCommand.php
@@ -1,8 +1,9 @@
setName('clear-cache')
- ->setAliases(['clearcache'])
+ ->setName('cache')
+ ->setAliases(['clearcache', 'cache-clear'])
->setDescription('Clears Grav cache')
+ ->addOption('purge', null, InputOption::VALUE_NONE, 'If set purge old caches')
->addOption('all', null, InputOption::VALUE_NONE, 'If set will remove all including compiled, twig, doctrine caches')
->addOption('assets-only', null, InputOption::VALUE_NONE, 'If set will remove only assets/*')
->addOption('images-only', null, InputOption::VALUE_NONE, 'If set will remove only images/*')
->addOption('cache-only', null, InputOption::VALUE_NONE, 'If set will remove only cache/*')
->addOption('tmp-only', null, InputOption::VALUE_NONE, 'If set will remove only tmp/*')
- ->setHelp('The clear-cache deletes all cache files');
+
+ ->setHelp('The cache command allows you to interact with Grav cache');
}
- /**
- * @return int|null|void
- */
protected function serve()
{
$this->cleanPaths();
@@ -45,25 +42,35 @@ class ClearCacheCommand extends ConsoleCommand
private function cleanPaths()
{
$this->output->writeln('');
- $this->output->writeln('Clearing cache');
- $this->output->writeln('');
- if ($this->input->getOption('all')) {
- $remove = 'all';
- } elseif ($this->input->getOption('assets-only')) {
- $remove = 'assets-only';
- } elseif ($this->input->getOption('images-only')) {
- $remove = 'images-only';
- } elseif ($this->input->getOption('cache-only')) {
- $remove = 'cache-only';
- } elseif ($this->input->getOption('tmp-only')) {
- $remove = 'tmp-only';
+
+ if ($this->input->getOption('purge')) {
+ $this->output->writeln('Purging old cache');
+ $this->output->writeln('');
+
+ $msg = Cache::purgeJob();
+ $this->output->writeln($msg);
} else {
- $remove = 'standard';
- }
+ $this->output->writeln('Clearing cache');
+ $this->output->writeln('');
- foreach (Cache::clearCache($remove) as $result) {
- $this->output->writeln($result);
+ if ($this->input->getOption('all')) {
+ $remove = 'all';
+ } elseif ($this->input->getOption('assets-only')) {
+ $remove = 'assets-only';
+ } elseif ($this->input->getOption('images-only')) {
+ $remove = 'images-only';
+ } elseif ($this->input->getOption('cache-only')) {
+ $remove = 'cache-only';
+ } elseif ($this->input->getOption('tmp-only')) {
+ $remove = 'tmp-only';
+ } else {
+ $remove = 'standard';
+ }
+
+ foreach (Cache::clearCache($remove) as $result) {
+ $this->output->writeln($result);
+ }
}
}
}
diff --git a/system/src/Grav/Console/Cli/ComposerCommand.php b/system/src/Grav/Console/Cli/ComposerCommand.php
index 4f9c18f..ffb4e99 100644
--- a/system/src/Grav/Console/Cli/ComposerCommand.php
+++ b/system/src/Grav/Console/Cli/ComposerCommand.php
@@ -1,8 +1,9 @@
setHelp('The composer command updates the composer vendor dependencies needed by Grav');
}
- /**
- * @return int|null|void
- */
protected function serve()
{
$action = $this->input->getOption('install') ? 'install' : ($this->input->getOption('update') ? 'update' : 'install');
diff --git a/system/src/Grav/Console/Cli/InstallCommand.php b/system/src/Grav/Console/Cli/InstallCommand.php
index 198e1d3..05f281e 100644
--- a/system/src/Grav/Console/Cli/InstallCommand.php
+++ b/system/src/Grav/Console/Cli/InstallCommand.php
@@ -1,8 +1,9 @@
setName("install")
+ ->setName('install')
->addOption(
'symlink',
's',
@@ -50,17 +43,14 @@ class InstallCommand extends ConsoleCommand
InputArgument::OPTIONAL,
'Where to install the required bits (default to current project)'
)
- ->setDescription("Installs the dependencies needed by Grav. Optionally can create symbolic links")
+ ->setDescription('Installs the dependencies needed by Grav. Optionally can create symbolic links')
->setHelp('The install command installs the dependencies needed by Grav. Optionally can create symbolic links');
}
- /**
- * @return int|null|void
- */
protected function serve()
{
$dependencies_file = '.dependencies';
- $this->destination = ($this->input->getArgument('destination')) ? $this->input->getArgument('destination') : ROOT_DIR;
+ $this->destination = $this->input->getArgument('destination') ?: ROOT_DIR;
// fix trailing slash
$this->destination = rtrim($this->destination, DS) . DS;
diff --git a/system/src/Grav/Console/Cli/LogViewerCommand.php b/system/src/Grav/Console/Cli/LogViewerCommand.php
new file mode 100644
index 0000000..f9e93a3
--- /dev/null
+++ b/system/src/Grav/Console/Cli/LogViewerCommand.php
@@ -0,0 +1,85 @@
+setName('logviewer')
+ ->addOption(
+ 'file',
+ 'f',
+ InputOption::VALUE_OPTIONAL,
+ 'custom log file location (default = grav.log)'
+ )
+ ->addOption(
+ 'lines',
+ 'l',
+ InputOption::VALUE_OPTIONAL,
+ 'number of lines (default = 10)'
+ )
+ ->setDescription('Display the last few entries of Grav log')
+ ->setHelp("Display the last few entries of Grav log");
+ }
+
+ protected function serve()
+ {
+ $grav = Grav::instance();
+ $grav->setup();
+
+ $file = $this->input->getOption('file') ?? 'grav.log';
+ $lines = $this->input->getOption('lines') ?? 20;
+ $verbose = $this->input->getOption('verbose', false);
+
+ $io = new SymfonyStyle($this->input, $this->output);
+
+ $io->title('Log Viewer');
+
+ $io->writeln(sprintf('viewing last %s entries in %s', $lines, $file));
+ $io->newLine();
+
+ $viewer = new LogViewer();
+
+ $logfile = $grav['locator']->findResource("log://" . $file);
+
+ if ($logfile) {
+ $rows = $viewer->objectTail($logfile, $lines, true);
+ foreach ($rows as $log) {
+ $date = $log['date'];
+ $level_color = LogViewer::levelColor($log['level']);
+
+ if ($date instanceof \DateTime) {
+ $output = "{$log['date']->format('Y-m-d h:i:s')} [<{$level_color}>{$log['level']}{$level_color}>]";
+ if ($log['trace'] && $verbose) {
+ $output .= " {$log['message']}\n";
+ foreach ((array) $log['trace'] as $index => $tracerow) {
+ $output .= "{$index}${tracerow}\n";
+ }
+ } else {
+ $output .= " {$log['message']}";
+ }
+ $io->writeln($output);
+ }
+ }
+ } else {
+ $io->error('cannot find the log file: logs/' . $file);
+ }
+
+ }
+}
diff --git a/system/src/Grav/Console/Cli/NewProjectCommand.php b/system/src/Grav/Console/Cli/NewProjectCommand.php
index 5a8f3d1..90583f0 100644
--- a/system/src/Grav/Console/Cli/NewProjectCommand.php
+++ b/system/src/Grav/Console/Cli/NewProjectCommand.php
@@ -1,8 +1,9 @@
setHelp("The new-project command is a combination of the `setup` and `install` commands.\nCreates a new Grav instance and performs the installation of all the required dependencies.");
}
- /**
- * @return int|null|void
- */
protected function serve()
{
$sandboxCommand = $this->getApplication()->find('sandbox');
@@ -60,6 +55,5 @@ class NewProjectCommand extends ConsoleCommand
$sandboxCommand->run($sandboxArguments, $this->output);
$installCommand->run($installArguments, $this->output);
-
}
}
diff --git a/system/src/Grav/Console/Cli/SandboxCommand.php b/system/src/Grav/Console/Cli/SandboxCommand.php
index 284878e..f961412 100644
--- a/system/src/Grav/Console/Cli/SandboxCommand.php
+++ b/system/src/Grav/Console/Cli/SandboxCommand.php
@@ -1,8 +1,9 @@
'/.gitignore',
+ '/.editorconfig' => '/.editorconfig',
'/CHANGELOG.md' => '/CHANGELOG.md',
'/LICENSE.txt' => '/LICENSE.txt',
'/README.md' => '/README.md',
@@ -60,18 +56,12 @@ class SandboxCommand extends ConsoleCommand
'/webserver-configs' => '/webserver-configs',
];
- /**
- * @var string
- */
-
+ /** @var string */
protected $default_file = "---\ntitle: HomePage\n---\n# HomePage\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque porttitor eu felis sed ornare. Sed a mauris venenatis, pulvinar velit vel, dictum enim. Phasellus ac rutrum velit. Nunc lorem purus, hendrerit sit amet augue aliquet, iaculis ultricies nisl. Suspendisse tincidunt euismod risus, quis feugiat arcu tincidunt eget. Nulla eros mi, commodo vel ipsum vel, aliquet congue odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque velit orci, laoreet at adipiscing eu, interdum quis nibh. Nunc a accumsan purus.";
protected $source;
protected $destination;
- /**
- *
- */
protected function configure()
{
$this
@@ -92,9 +82,6 @@ class SandboxCommand extends ConsoleCommand
$this->source = getcwd();
}
- /**
- * @return int|null|void
- */
protected function serve()
{
$this->destination = $this->input->getArgument('destination');
@@ -121,9 +108,6 @@ class SandboxCommand extends ConsoleCommand
$this->perms();
}
- /**
- *
- */
private function createDirectories()
{
$this->output->writeln('');
@@ -131,14 +115,14 @@ class SandboxCommand extends ConsoleCommand
$dirs_created = false;
if (!file_exists($this->destination)) {
- mkdir($this->destination, 0777, true);
+ Folder::create($this->destination);
}
foreach ($this->directories as $dir) {
if (!file_exists($this->destination . $dir)) {
$dirs_created = true;
$this->output->writeln(' ' . $dir . '');
- mkdir($this->destination . $dir, 0777, true);
+ Folder::create($this->destination . $dir);
}
}
@@ -147,9 +131,6 @@ class SandboxCommand extends ConsoleCommand
}
}
- /**
- *
- */
private function copy()
{
$this->output->writeln('');
@@ -157,7 +138,7 @@ class SandboxCommand extends ConsoleCommand
foreach ($this->mappings as $source => $target) {
- if ((int)$source == $source) {
+ if ((string)(int)$source === (string)$source) {
$source = $target;
}
@@ -169,9 +150,6 @@ class SandboxCommand extends ConsoleCommand
}
}
- /**
- *
- */
private function symlink()
{
$this->output->writeln('');
@@ -179,7 +157,7 @@ class SandboxCommand extends ConsoleCommand
foreach ($this->mappings as $source => $target) {
- if ((int)$source == $source) {
+ if ((string)(int)$source === (string)$source) {
$source = $target;
}
@@ -197,9 +175,6 @@ class SandboxCommand extends ConsoleCommand
}
}
- /**
- *
- */
private function initFiles()
{
$this->check();
@@ -210,7 +185,7 @@ class SandboxCommand extends ConsoleCommand
// Copy files if they do not exist
foreach ($this->files as $source => $target) {
- if ((int)$source == $source) {
+ if ((string)(int)$source === (string)$source) {
$source = $target;
}
@@ -229,9 +204,6 @@ class SandboxCommand extends ConsoleCommand
}
}
- /**
- *
- */
private function pages()
{
$this->output->writeln('');
@@ -241,7 +213,7 @@ class SandboxCommand extends ConsoleCommand
$pages_dir = $this->destination . '/user/pages';
$pages_files = array_diff(scandir($pages_dir), ['..', '.']);
- if (count($pages_files) == 0) {
+ if (\count($pages_files) === 0) {
$destination = $this->source . '/user/pages';
Folder::rcopy($destination, $pages_dir);
$this->output->writeln(' ' . $destination . ' -> Created');
@@ -249,9 +221,6 @@ class SandboxCommand extends ConsoleCommand
}
}
- /**
- *
- */
private function perms()
{
$this->output->writeln('');
@@ -269,9 +238,6 @@ class SandboxCommand extends ConsoleCommand
$this->output->writeln("");
}
- /**
- *
- */
private function check()
{
$success = true;
diff --git a/system/src/Grav/Console/Cli/SchedulerCommand.php b/system/src/Grav/Console/Cli/SchedulerCommand.php
new file mode 100644
index 0000000..eaf45cd
--- /dev/null
+++ b/system/src/Grav/Console/Cli/SchedulerCommand.php
@@ -0,0 +1,179 @@
+setName('scheduler')
+ ->addOption(
+ 'install',
+ 'i',
+ InputOption::VALUE_NONE,
+ 'Show Install Command'
+ )
+ ->addOption(
+ 'jobs',
+ 'j',
+ InputOption::VALUE_NONE,
+ 'Show Jobs Summary'
+ )
+ ->addOption(
+ 'details',
+ 'd',
+ InputOption::VALUE_NONE,
+ 'Show Job Details'
+ )
+ ->setDescription('Run the Grav Scheduler. Best when integrated with system cron')
+ ->setHelp("Running without any options will force the Scheduler to run through it's jobs and process them");
+ }
+
+ protected function serve()
+ {
+// error_reporting(1);
+ $grav = Grav::instance();
+ $grav->setup();
+
+ $grav['uri']->init();
+ $grav['config']->init();
+ $grav['plugins']->init();
+ $grav['themes']->init();
+ $grav['backups']->init();
+
+ // Initialize Plugins
+ $grav->fireEvent('onPluginsInitialized');
+
+ /** @var Scheduler $scheduler */
+ $scheduler = $grav['scheduler'];
+ $grav->fireEvent('onSchedulerInitialized', new Event(['scheduler' => $scheduler]));
+
+ $this->setHelp('foo');
+
+ $io = new SymfonyStyle($this->input, $this->output);
+
+ if ($this->input->getOption('jobs')) {
+ // Show jobs list
+
+ $jobs = $scheduler->getAllJobs();
+ $job_states = $scheduler->getJobStates()->content();
+ $rows = [];
+
+ $table = new Table($this->output);
+ $table->setStyle('box');
+ $headers = ['Job ID', 'Command', 'Run At', 'Status', 'Last Run', 'State'];
+
+ $io->title('Scheduler Jobs Listing');
+
+ foreach ($jobs as $job) {
+ $job_status = ucfirst($job_states[$job->getId()]['state'] ?? 'ready');
+ $last_run = $job_states[$job->getId()]['last-run'] ?? 0;
+ $status = $job_status === 'Failure' ? "{$job_status}" : "{$job_status}";
+ $state = $job->getEnabled() ? 'Enabled' : 'Disabled';
+ $row = [
+ $job->getId(),
+ "{$job->getCommand()}",
+ "{$job->getAt()}",
+ $status,
+ '' . ($last_run === 0 ? 'Never' : date('Y-m-d H:i', $last_run)) . '',
+ $state,
+
+ ];
+ $rows[] = $row;
+ }
+
+ if (!empty($rows)) {
+ $table->setHeaders($headers);
+ $table->setRows($rows);
+ $table->render();
+ } else {
+ $io->text('no jobs found...');
+ }
+
+ $io->newLine();
+ $io->note('For error details run "bin/grav scheduler -d"');
+ $io->newLine();
+ } elseif ($this->input->getOption('details')) {
+ $jobs = $scheduler->getAllJobs();
+ $job_states = $scheduler->getJobStates()->content();
+
+ $io->title('Job Details');
+
+ $table = new Table($this->output);
+ $table->setStyle('box');
+ $table->setHeaders(['Job ID', 'Last Run', 'Next Run', 'Errors']);
+ $rows = [];
+
+ foreach ($jobs as $job) {
+ $job_state = $job_states[$job->getId()];
+ $error = isset($job_state['error']) ? trim($job_state['error']) : false;
+
+ /** @var CronExpression $expression */
+ $expression = $job->getCronExpression();
+ $next_run = $expression->getNextRunDate();
+
+ $row = [];
+ $row[] = $job->getId();
+ if (!is_null($job_state['last-run'])) {
+ $row[] = '' . date('Y-m-d H:i', $job_state['last-run']) . '';
+ } else {
+ $row[] = 'Never';
+ }
+ $row[] = '' . $next_run->format('Y-m-d H:i') . '';
+
+ if ($error) {
+ $row[] = "{$error}";
+ } else {
+ $row[] = 'None';
+ }
+ $rows[] = $row;
+ }
+
+ $table->setRows($rows);
+ $table->render();
+
+ } elseif ($this->input->getOption('install')) {
+ $io->title('Install Scheduler');
+
+ if ($scheduler->isCrontabSetup()) {
+ $io->success('All Ready! You have already set up Grav\'s Scheduler in your crontab');
+ } else {
+ $io->error('You still need to set up Grav\'s Scheduler in your crontab');
+ }
+ if (!Utils::isWindows()) {
+ $io->note('To install, run the following command from your terminal:');
+ $io->newLine();
+ $io->text(trim($scheduler->getCronCommand()));
+ } else {
+ $io->note('To install, create a scheduled task in Windows.');
+ $io->text('Learn more at https://learn.getgrav.org/advanced/scheduler');
+ }
+ } else {
+ // Run scheduler
+ $scheduler->run();
+
+ if ($this->input->getOption('verbose')) {
+ $io->title('Running Scheduled Jobs');
+ $io->text($scheduler->getVerboseOutput());
+ }
+ }
+ }
+}
diff --git a/system/src/Grav/Console/Cli/SecurityCommand.php b/system/src/Grav/Console/Cli/SecurityCommand.php
index 3361d44..7f73086 100644
--- a/system/src/Grav/Console/Cli/SecurityCommand.php
+++ b/system/src/Grav/Console/Cli/SecurityCommand.php
@@ -1,8 +1,9 @@
setName("security")
- ->setDescription("Capable of running various Security checks")
+ ->setName('security')
+ ->setDescription('Capable of running various Security checks')
->setHelp('The security runs various security checks on your Grav site');
$this->source = getcwd();
}
- /**
- * @return int|null|void
- */
protected function serve()
{
-
-
/** @var Grav $grav */
$grav = Grav::instance();
+ $grav->setup();
$grav['uri']->init();
$grav['config']->init();
$grav['debugger']->enabled(false);
- $grav['streams'];
$grav['plugins']->init();
$grav['themes']->init();
-
$grav['twig']->init();
$grav['pages']->init();
- $this->progress = new ProgressBar($this->output, (count($grav['pages']->routes()) - 1));
+ $this->progress = new ProgressBar($this->output, \count($grav['pages']->routes()) - 1);
$this->progress->setFormat('Scanning %current% pages [%bar%] %percent:3s%% %elapsed:6s%');
$this->progress->setBarWidth(100);
$io = new SymfonyStyle($this->input, $this->output);
$io->title('Grav Security Check');
- $output = Security::detectXssFromPages($grav['pages'], [$this, 'outputProgress']);
+ $output = Security::detectXssFromPages($grav['pages'], false, [$this, 'outputProgress']);
$io->newline(2);
@@ -77,7 +70,7 @@ class SecurityCommand extends ConsoleCommand
$io->writeln($counter++ .' - ' . $route . ' → ' . implode(', ', $results_parts) . '');
}
- $io->error('Security Scan complete: ' . count($output) . ' potential XSS issues found...');
+ $io->error('Security Scan complete: ' . \count($output) . ' potential XSS issues found...');
} else {
$io->success('Security Scan complete: No issues found...');
@@ -88,14 +81,14 @@ class SecurityCommand extends ConsoleCommand
}
/**
- * @param $args
+ * @param array $args
*/
public function outputProgress($args)
{
switch ($args['type']) {
case 'count':
$steps = $args['steps'];
- $freq = intval($steps > 100 ? round($steps / 100) : $steps);
+ $freq = (int)($steps > 100 ? round($steps / 100) : $steps);
$this->progress->setMaxSteps($steps);
$this->progress->setRedrawFrequency($freq);
break;
diff --git a/system/src/Grav/Console/Cli/YamlLinterCommand.php b/system/src/Grav/Console/Cli/YamlLinterCommand.php
new file mode 100644
index 0000000..18cdb49
--- /dev/null
+++ b/system/src/Grav/Console/Cli/YamlLinterCommand.php
@@ -0,0 +1,73 @@
+setName('yamllinter')
+ ->addOption(
+ 'env',
+ 'e',
+ InputOption::VALUE_OPTIONAL,
+ 'The environment to trigger a specific configuration. For example: localhost, mysite.dev, www.mysite.com'
+ )
+ ->setDescription('Checks various files for YAML errors')
+ ->setHelp("Checks various files for YAML errors");
+ }
+
+ protected function serve()
+ {
+ $grav = Grav::instance();
+ $grav->setup();
+
+ $io = new SymfonyStyle($this->input, $this->output);
+
+ $io->title('Yaml Linter');
+
+ $io->section('User Configuration');
+ $errors = YamlLinter::lintConfig();
+
+ if (empty($errors)) {
+ $io->success('No YAML Linting issues with configuration');
+ } else {
+ $this->displayErrors($errors, $io);
+ }
+
+
+ $io->section('Pages Frontmatter');
+ $errors = YamlLinter::lintPages();
+
+ if (empty($errors)) {
+ $io->success('No YAML Linting issues with pages');
+ } else {
+ $this->displayErrors($errors, $io);
+ }
+
+ }
+
+ protected function displayErrors($errors, $io)
+ {
+ $io->error("YAML Linting issues found...");
+ foreach ($errors as $path => $error) {
+ $io->writeln("$path - $error");
+ }
+ }
+}
diff --git a/system/src/Grav/Console/ConsoleCommand.php b/system/src/Grav/Console/ConsoleCommand.php
index a9c2217..2c63eaf 100644
--- a/system/src/Grav/Console/ConsoleCommand.php
+++ b/system/src/Grav/Console/ConsoleCommand.php
@@ -1,8 +1,9 @@
output->writeln('');
- $this->output->writeln("ERROR: Destination chosen to install does not appear to be a Grav instance:");
+ $this->output->writeln('ERROR: Destination chosen to install does not appear to be a Grav instance:');
$this->output->writeln(" $path");
$this->output->writeln('');
exit;
@@ -112,6 +112,11 @@ trait ConsoleTrait
return $command->run($input, $this->output);
}
+ public function invalidateCache()
+ {
+ Cache::invalidateCache();
+ }
+
/**
* Load the local config file
*
diff --git a/system/src/Grav/Console/Gpm/DirectInstallCommand.php b/system/src/Grav/Console/Gpm/DirectInstallCommand.php
index 60c5fa8..bd27651 100644
--- a/system/src/Grav/Console/Gpm/DirectInstallCommand.php
+++ b/system/src/Grav/Console/Gpm/DirectInstallCommand.php
@@ -1,13 +1,15 @@
setName("direct-install")
+ ->setName('direct-install')
->setAliases(['directinstall'])
->addArgument(
'package-file',
@@ -47,7 +51,7 @@ class DirectInstallCommand extends ConsoleCommand
'The destination where the package should be installed at. By default this would be where the grav instance has been launched from',
GRAV_ROOT
)
- ->setDescription("Installs Grav, plugin, or theme directly from a file or a URL")
+ ->setDescription('Installs Grav, plugin, or theme directly from a file or a URL')
->setHelp('The direct-install command installs Grav, plugin, or theme directly from a file or a URL');
}
@@ -63,7 +67,7 @@ class DirectInstallCommand extends ConsoleCommand
!Installer::isGravInstance($this->destination) ||
!Installer::isValidDestination($this->destination, [Installer::EXISTS, Installer::IS_LINK])
) {
- $this->output->writeln("ERROR: " . Installer::lastErrorMsg());
+ $this->output->writeln('ERROR: ' . Installer::lastErrorMsg());
exit;
}
@@ -73,12 +77,12 @@ class DirectInstallCommand extends ConsoleCommand
$package_file = $this->input->getArgument('package-file');
$helper = $this->getHelper('question');
- $question = new ConfirmationQuestion('Are you sure you want to direct-install '.$package_file.' [y|N] ', false);
+ $question = new ConfirmationQuestion("Are you sure you want to direct-install {$package_file} [y|N] ", false);
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
if (!$answer) {
- $this->output->writeln("exiting...");
+ $this->output->writeln('exiting...');
$this->output->writeln('');
exit;
}
@@ -86,32 +90,32 @@ class DirectInstallCommand extends ConsoleCommand
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
$tmp_zip = $tmp_dir . '/Grav-' . uniqid();
- $this->output->writeln("");
- $this->output->writeln("Preparing to install " . $package_file . "");
+ $this->output->writeln('');
+ $this->output->writeln("Preparing to install {$package_file}");
if (Response::isRemote($package_file)) {
- $this->output->write(" |- Downloading package... 0%");
+ $this->output->write(' |- Downloading package... 0%');
try {
$zip = GPM::downloadPackage($package_file, $tmp_zip);
} catch (\RuntimeException $e) {
$this->output->writeln('');
- $this->output->writeln(" `- ERROR: " . $e->getMessage() . "");
+ $this->output->writeln(" `- ERROR: {$e->getMessage()}");
$this->output->writeln('');
exit;
}
if ($zip) {
$this->output->write("\x0D");
- $this->output->write(" |- Downloading package... 100%");
+ $this->output->write(' |- Downloading package... 100%');
$this->output->writeln('');
}
} else {
- $this->output->write(" |- Copying package... 0%");
+ $this->output->write(' |- Copying package... 0%');
$zip = GPM::copyPackage($package_file, $tmp_zip);
if ($zip) {
$this->output->write("\x0D");
- $this->output->write(" |- Copying package... 100%");
+ $this->output->write(' |- Copying package... 100%');
$this->output->writeln('');
}
}
@@ -119,19 +123,19 @@ class DirectInstallCommand extends ConsoleCommand
if (file_exists($zip)) {
$tmp_source = $tmp_dir . '/Grav-' . uniqid();
- $this->output->write(" |- Extracting package... ");
+ $this->output->write(' |- Extracting package... ');
$extracted = Installer::unZip($zip, $tmp_source);
if (!$extracted) {
$this->output->write("\x0D");
- $this->output->writeln(" |- Extracting package... failed");
+ $this->output->writeln(' |- Extracting package... failed');
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
exit;
}
$this->output->write("\x0D");
- $this->output->writeln(" |- Extracting package... ok");
+ $this->output->writeln(' |- Extracting package... ok');
$type = GPM::getPackageType($extracted);
@@ -147,26 +151,26 @@ class DirectInstallCommand extends ConsoleCommand
$blueprint = GPM::getBlueprints($extracted);
if ($blueprint) {
if (isset($blueprint['dependencies'])) {
- $depencencies = [];
+ $dependencies = [];
foreach ($blueprint['dependencies'] as $dependency) {
if (is_array($dependency)){
if (isset($dependency['name'])) {
- $depencencies[] = $dependency['name'];
+ $dependencies[] = $dependency['name'];
}
if (isset($dependency['github'])) {
- $depencencies[] = $dependency['github'];
+ $dependencies[] = $dependency['github'];
}
} else {
- $depencencies[] = $dependency;
+ $dependencies[] = $dependency;
}
}
- $this->output->writeln(" |- Dependencies found... [" . implode(',', $depencencies) . "]");
+ $this->output->writeln(' |- Dependencies found... [' . implode(',', $dependencies) . ']');
$question = new ConfirmationQuestion(" | '- Dependencies will not be satisfied. Continue ? [y|N] ", false);
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
if (!$answer) {
- $this->output->writeln("exiting...");
+ $this->output->writeln('exiting...');
$this->output->writeln('');
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
@@ -175,14 +179,14 @@ class DirectInstallCommand extends ConsoleCommand
}
}
- if ($type == 'grav') {
+ if ($type === 'grav') {
- $this->output->write(" |- Checking destination... ");
+ $this->output->write(' |- Checking destination... ');
Installer::isValidDestination(GRAV_ROOT . '/system');
if (Installer::IS_LINK === Installer::lastErrorCode()) {
$this->output->write("\x0D");
- $this->output->writeln(" |- Checking destination... symbolic link");
- $this->output->writeln(" '- ERROR: symlinks found... " . GRAV_ROOT."");
+ $this->output->writeln(' |- Checking destination... symbolic link');
+ $this->output->writeln(" '- ERROR: symlinks found... " . GRAV_ROOT . '');
$this->output->writeln('');
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
@@ -190,15 +194,16 @@ class DirectInstallCommand extends ConsoleCommand
}
$this->output->write("\x0D");
- $this->output->writeln(" |- Checking destination... ok");
+ $this->output->writeln(' |- Checking destination... ok');
- $this->output->write(" |- Installing package... ");
- Installer::install($zip, GRAV_ROOT, ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true], $extracted);
+ $this->output->write(' |- Installing package... ');
+
+ static::upgradeGrav($zip, $extracted);
} else {
$name = GPM::getPackageName($extracted);
if (!$name) {
- $this->output->writeln("ERROR: Name could not be determined. Please specify with --name|-n");
+ $this->output->writeln('ERROR: Name could not be determined. Please specify with --name|-n');
$this->output->writeln('');
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
@@ -208,31 +213,31 @@ class DirectInstallCommand extends ConsoleCommand
$install_path = GPM::getInstallPath($type, $name);
$is_update = file_exists($install_path);
- $this->output->write(" |- Checking destination... ");
+ $this->output->write(' |- Checking destination... ');
Installer::isValidDestination(GRAV_ROOT . DS . $install_path);
if (Installer::lastErrorCode() == Installer::IS_LINK) {
$this->output->write("\x0D");
- $this->output->writeln(" |- Checking destination... symbolic link");
+ $this->output->writeln(' |- Checking destination... symbolic link');
$this->output->writeln(" '- ERROR: symlink found... " . GRAV_ROOT . DS . $install_path . '');
$this->output->writeln('');
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
exit;
- } else {
- $this->output->write("\x0D");
- $this->output->writeln(" |- Checking destination... ok");
}
- $this->output->write(" |- Installing package... ");
+ $this->output->write("\x0D");
+ $this->output->writeln(' |- Checking destination... ok');
+
+ $this->output->write(' |- Installing package... ');
Installer::install(
$zip,
$this->destination,
$options = [
'install_path' => $install_path,
- 'theme' => (($type == 'theme')),
+ 'theme' => (($type === 'theme')),
'is_update' => $is_update
],
$extracted
@@ -244,10 +249,10 @@ class DirectInstallCommand extends ConsoleCommand
$this->output->write("\x0D");
if(Installer::lastErrorCode()) {
- $this->output->writeln(" '- " . Installer::lastErrorMsg() . "");
+ $this->output->writeln(" '- " . Installer::lastErrorMsg() . '');
$this->output->writeln('');
} else {
- $this->output->writeln(" |- Installing package... ok");
+ $this->output->writeln(' |- Installing package... ok');
$this->output->writeln(" '- Success! ");
$this->output->writeln('');
}
@@ -262,6 +267,43 @@ class DirectInstallCommand extends ConsoleCommand
$this->clearCache();
return true;
+ }
+ private function upgradeGrav($zip, $folder, $keepFolder = false)
+ {
+ static $ignores = [
+ 'backup',
+ 'cache',
+ 'images',
+ 'logs',
+ 'tmp',
+ 'user',
+ '.htaccess',
+ 'robots.txt'
+ ];
+
+ if (!is_dir($folder)) {
+ Installer::setError('Invalid source folder');
+ }
+
+ try {
+ $script = $folder . '/system/install.php';
+ /** Install $installer */
+ if ((file_exists($script) && $install = include $script) && is_callable($install)) {
+ $install($zip);
+ } else {
+ Installer::install(
+ $zip,
+ GRAV_ROOT,
+ ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true, 'ignores' => $ignores],
+ $folder,
+ $keepFolder
+ );
+
+ Cache::clearCache();
+ }
+ } catch (\Exception $e) {
+ Installer::setError($e->getMessage());
+ }
}
}
diff --git a/system/src/Grav/Console/Gpm/IndexCommand.php b/system/src/Grav/Console/Gpm/IndexCommand.php
index 8c45706..06d662b 100644
--- a/system/src/Grav/Console/Gpm/IndexCommand.php
+++ b/system/src/Grav/Console/Gpm/IndexCommand.php
@@ -1,14 +1,17 @@
setName("index")
+ ->setName('index')
->addOption(
'force',
'f',
@@ -91,14 +84,11 @@ class IndexCommand extends ConsoleCommand
InputOption::VALUE_NONE,
'Reverses the order of the output.'
)
- ->setDescription("Lists the plugins and themes available for installation")
+ ->setDescription('Lists the plugins and themes available for installation')
->setHelp('The index command lists the plugins and themes available for installation')
;
}
- /**
- * @return int|null|void
- */
protected function serve()
{
$this->options = $this->input->getOptions();
@@ -123,7 +113,7 @@ class IndexCommand extends ConsoleCommand
}
foreach ($data as $type => $packages) {
- $this->output->writeln("" . strtoupper($type) . " [ " . count($packages) . " ]");
+ $this->output->writeln('' . strtoupper($type) . ' [ ' . \count($packages) . ' ]');
$packages = $this->sort($packages);
if (!empty($packages)) {
@@ -134,7 +124,7 @@ class IndexCommand extends ConsoleCommand
foreach ($packages as $slug => $package) {
$row = [
'Count' => $index++ + 1,
- 'Name' => "" . Utils::truncate($package->name, 20, false, ' ', '...') . " ",
+ 'Name' => '' . Utils::truncate($package->name, 20, false, ' ', '...') . ' ',
'Slug' => $slug,
'Version'=> $this->version($package),
'Installed' => $this->installed($package)
@@ -149,55 +139,56 @@ class IndexCommand extends ConsoleCommand
}
$this->output->writeln('You can either get more informations about a package by typing:');
- $this->output->writeln(' ' . $this->argv . ' info ');
+ $this->output->writeln(" {$this->argv} info ");
$this->output->writeln('');
$this->output->writeln('Or you can install a package by typing:');
- $this->output->writeln(' ' . $this->argv . ' install ');
+ $this->output->writeln(" {$this->argv} install ");
$this->output->writeln('');
}
/**
- * @param $package
+ * @param Package $package
*
* @return string
*/
private function version($package)
{
$list = $this->gpm->{'getUpdatable' . ucfirst($package->package_type)}();
- $package = isset($list[$package->slug]) ? $list[$package->slug] : $package;
- $type = ucfirst(preg_replace("/s$/", '', $package->package_type));
+ $package = $list[$package->slug] ?? $package;
+ $type = ucfirst(preg_replace('/s$/', '', $package->package_type));
$updatable = $this->gpm->{'is' . $type . 'Updatable'}($package->slug);
$installed = $this->gpm->{'is' . $type . 'Installed'}($package->slug);
$local = $this->gpm->{'getInstalled' . $type}($package->slug);
if (!$installed || !$updatable) {
$version = $installed ? $local->version : $package->version;
- return "v" . $version . "";
+ return "v{$version}";
}
if ($updatable) {
- return "v" . $package->version . " -> v" . $package->available . "";
+ return "v{$package->version} -> v{$package->available}";
}
return '';
}
/**
- * @param $package
+ * @param Package $package
*
* @return string
*/
private function installed($package)
{
- $package = isset($list[$package->slug]) ? $list[$package->slug] : $package;
- $type = ucfirst(preg_replace("/s$/", '', $package->package_type));
- $installed = $this->gpm->{'is' . $type . 'Installed'}($package->slug);
+ $package = $list[$package->slug] ?? $package;
+ $type = ucfirst(preg_replace('/s$/', '', $package->package_type));
+ $method = 'is' . $type . 'Installed';
+ $installed = $this->gpm->{$method}($package->slug);
return !$installed ? 'not installed' : 'installed';
}
/**
- * @param $data
+ * @param array $data
*
* @return mixed
*/
@@ -218,26 +209,28 @@ class IndexCommand extends ConsoleCommand
$this->options['desc']
];
- if (count(array_filter($filter))) {
+ if (\count(array_filter($filter))) {
foreach ($data as $type => $packages) {
foreach ($packages as $slug => $package) {
$filter = true;
// Filtering by string
if ($this->options['filter']) {
- $filter = preg_grep('/(' . (implode('|', $this->options['filter'])) . ')/i', [$slug, $package->name]);
+ $filter = preg_grep('/(' . implode('|', $this->options['filter']) . ')/i', [$slug, $package->name]);
}
// Filtering updatables only
- if ($this->options['installed-only'] && $filter) {
- $method = ucfirst(preg_replace("/s$/", '', $package->package_type));
- $filter = $this->gpm->{'is' . $method . 'Installed'}($package->slug);
+ if ($filter && $this->options['installed-only']) {
+ $method = ucfirst(preg_replace('/s$/', '', $package->package_type));
+ $function = 'is' . $method . 'Installed';
+ $filter = $this->gpm->{$function}($package->slug);
}
// Filtering updatables only
- if ($this->options['updates-only'] && $filter) {
- $method = ucfirst(preg_replace("/s$/", '', $package->package_type));
- $filter = $this->gpm->{'is' . $method . 'Updatable'}($package->slug);
+ if ($filter && $this->options['updates-only']) {
+ $method = ucfirst(preg_replace('/s$/', '', $package->package_type));
+ $function = 'is' . $method . 'Updatable';
+ $filter = $this->gpm->{$function}($package->slug);
}
if (!$filter) {
@@ -251,7 +244,7 @@ class IndexCommand extends ConsoleCommand
}
/**
- * @param $packages
+ * @param Packages $packages
*/
public function sort($packages)
{
diff --git a/system/src/Grav/Console/Gpm/InfoCommand.php b/system/src/Grav/Console/Gpm/InfoCommand.php
index 8c1d50e..ee89584 100644
--- a/system/src/Grav/Console/Gpm/InfoCommand.php
+++ b/system/src/Grav/Console/Gpm/InfoCommand.php
@@ -1,8 +1,9 @@
setName("info")
+ ->setName('info')
->addOption(
'force',
'f',
@@ -51,7 +50,7 @@ class InfoCommand extends ConsoleCommand
InputArgument::REQUIRED,
'The package of which more informations are desired. Use the "index" command for a list of packages'
)
- ->setDescription("Shows more informations about a package")
+ ->setDescription('Shows more informations about a package')
->setHelp('The info shows more informations about a package');
}
@@ -69,19 +68,19 @@ class InfoCommand extends ConsoleCommand
$foundPackage = $this->gpm->findPackage($this->input->getArgument('package'));
if (!$foundPackage) {
- $this->output->writeln("The package '" . $this->input->getArgument('package') . "' was not found in the Grav repository.");
+ $this->output->writeln("The package '{$this->input->getArgument('package')}' was not found in the Grav repository.");
$this->output->writeln('');
- $this->output->writeln("You can list all the available packages by typing:");
- $this->output->writeln(" " . $this->argv . " index");
+ $this->output->writeln('You can list all the available packages by typing:');
+ $this->output->writeln(" {$this->argv} index");
$this->output->writeln('');
exit;
}
- $this->output->writeln("Found package '" . $this->input->getArgument('package') . "' under the '" . ucfirst($foundPackage->package_type) . "' section");
+ $this->output->writeln("Found package '{$this->input->getArgument('package')}' under the '" . ucfirst($foundPackage->package_type) . "' section");
$this->output->writeln('');
- $this->output->writeln("" . $foundPackage->name . " [" . $foundPackage->slug . "]");
- $this->output->writeln(str_repeat('-', strlen($foundPackage->name) + strlen($foundPackage->slug) + 3));
- $this->output->writeln("" . strip_tags($foundPackage->description_plain) . "");
+ $this->output->writeln("{$foundPackage->name} [{$foundPackage->slug}]");
+ $this->output->writeln(str_repeat('-', \strlen($foundPackage->name) + \strlen($foundPackage->slug) + 3));
+ $this->output->writeln('' . strip_tags($foundPackage->description_plain) . '');
$this->output->writeln('');
$packageURL = '';
@@ -89,8 +88,8 @@ class InfoCommand extends ConsoleCommand
$packageURL = '<' . $foundPackage->author['url'] . '>';
}
- $this->output->writeln("" . str_pad("Author",
- 12) . ": " . $foundPackage->author['name'] . ' <' . $foundPackage->author['email'] . '> ' . $packageURL);
+ $this->output->writeln('' . str_pad('Author',
+ 12) . ': ' . $foundPackage->author['name'] . ' <' . $foundPackage->author['email'] . '> ' . $packageURL);
foreach ([
'version',
@@ -105,21 +104,21 @@ class InfoCommand extends ConsoleCommand
'zipball_url',
'license'
] as $info) {
- if (isset($foundPackage->$info)) {
+ if (isset($foundPackage->{$info})) {
$name = ucfirst($info);
- $data = $foundPackage->$info;
+ $data = $foundPackage->{$info};
- if ($info == 'zipball_url') {
- $name = "Download";
+ if ($info === 'zipball_url') {
+ $name = 'Download';
}
- if ($info == 'date') {
- $name = "Last Update";
+ if ($info === 'date') {
+ $name = 'Last Update';
$data = date('D, j M Y, H:i:s, P ', strtotime('2014-09-16T00:07:16Z'));
}
$name = str_pad($name, 12);
- $this->output->writeln("" . $name . ": " . $data);
+ $this->output->writeln("{$name}: {$data}");
}
}
@@ -131,48 +130,48 @@ class InfoCommand extends ConsoleCommand
if ($installed && $updatable) {
$local = $this->gpm->{'getInstalled'. $type}($foundPackage->slug);
$this->output->writeln('');
- $this->output->writeln("Currently installed version: " . $local->version . "");
+ $this->output->writeln("Currently installed version: {$local->version}");
$this->output->writeln('');
}
// display changelog information
$questionHelper = $this->getHelper('question');
- $question = new ConfirmationQuestion("Would you like to read the changelog? [y|N] ",
+ $question = new ConfirmationQuestion('Would you like to read the changelog? [y|N] ',
false);
$answer = $this->all_yes ? true : $questionHelper->ask($this->input, $this->output, $question);
if ($answer) {
$changelog = $foundPackage->changelog;
- $this->output->writeln("");
+ $this->output->writeln('');
foreach ($changelog as $version => $log) {
$title = $version . ' [' . $log['date'] . ']';
$content = preg_replace_callback('/\d\.\s\[\]\(#(.*)\)/', function ($match) {
- return "\n" . ucfirst($match[1]) . ":";
+ return "\n" . ucfirst($match[1]) . ':';
}, $log['content']);
- $this->output->writeln(''.$title.'');
- $this->output->writeln(str_repeat('-', strlen($title)));
+ $this->output->writeln("{$title}");
+ $this->output->writeln(str_repeat('-', \strlen($title)));
$this->output->writeln($content);
- $this->output->writeln("");
+ $this->output->writeln('');
- $question = new ConfirmationQuestion("Press [ENTER] to continue or [q] to quit ", true);
+ $question = new ConfirmationQuestion('Press [ENTER] to continue or [q] to quit ', true);
$answer = $this->all_yes ? false : $questionHelper->ask($this->input, $this->output, $question);
if (!$answer) {
break;
}
- $this->output->writeln("");
+ $this->output->writeln('');
}
}
$this->output->writeln('');
if ($installed && $updatable) {
- $this->output->writeln("You can update this package by typing:");
- $this->output->writeln(" " . $this->argv . " update " . $foundPackage->slug . "");
+ $this->output->writeln('You can update this package by typing:');
+ $this->output->writeln(" {$this->argv} update {$foundPackage->slug}");
} else {
$this->output->writeln("You can install this package by typing:");
- $this->output->writeln(" " . $this->argv . " install " . $foundPackage->slug . "");
+ $this->output->writeln(" {$this->argv} install {$foundPackage->slug}");
}
$this->output->writeln('');
diff --git a/system/src/Grav/Console/Gpm/InstallCommand.php b/system/src/Grav/Console/Gpm/InstallCommand.php
index 0311428..840ee5b 100644
--- a/system/src/Grav/Console/Gpm/InstallCommand.php
+++ b/system/src/Grav/Console/Gpm/InstallCommand.php
@@ -1,8 +1,9 @@
setName("install")
+ ->setName('install')
->addOption(
'force',
'f',
@@ -82,16 +81,16 @@ class InstallCommand extends ConsoleCommand
InputArgument::IS_ARRAY | InputArgument::REQUIRED,
'Package(s) to install. Use "bin/gpm index" to list packages. Use "bin/gpm direct-install" to install a specific version'
)
- ->setDescription("Performs the installation of plugins and themes")
+ ->setDescription('Performs the installation of plugins and themes')
->setHelp('The install command allows to install plugins and themes');
}
/**
* Allows to set the GPM object, used for testing the class
*
- * @param $gpm
+ * @param GPM $gpm
*/
- public function setGpm($gpm)
+ public function setGpm(GPM $gpm)
{
$this->gpm = $gpm;
}
@@ -117,28 +116,26 @@ class InstallCommand extends ConsoleCommand
!Installer::isGravInstance($this->destination) ||
!Installer::isValidDestination($this->destination, [Installer::EXISTS, Installer::IS_LINK])
) {
- $this->output->writeln("ERROR: " . Installer::lastErrorMsg());
+ $this->output->writeln('ERROR: ' . Installer::lastErrorMsg());
exit;
}
$this->output->writeln('');
if (!$this->data['total']) {
- $this->output->writeln("Nothing to install.");
+ $this->output->writeln('Nothing to install.');
$this->output->writeln('');
exit;
}
- if (count($this->data['not_found'])) {
- $this->output->writeln("These packages were not found on Grav: " . implode(', ',
- array_keys($this->data['not_found'])) . "");
+ if (\count($this->data['not_found'])) {
+ $this->output->writeln('These packages were not found on Grav: ' . implode(', ',
+ array_keys($this->data['not_found'])) . '');
}
- unset($this->data['not_found']);
- unset($this->data['total']);
+ unset($this->data['not_found'], $this->data['total']);
-
- if (isset($this->local_config)) {
+ if (null !== $this->local_config) {
// Symlinks available, ask if Grav should use them
$this->use_symlinks = false;
$helper = $this->getHelper('question');
@@ -149,8 +146,6 @@ class InstallCommand extends ConsoleCommand
if ($answer) {
$this->use_symlinks = true;
}
-
-
}
$this->output->writeln('');
@@ -160,22 +155,22 @@ class InstallCommand extends ConsoleCommand
} catch (\Exception $e) {
//Error out if there are incompatible packages requirements and tell which ones, and what to do
//Error out if there is any error in parsing the dependencies and their versions, and tell which one is broken
- $this->output->writeln("" . $e->getMessage() . "");
+ $this->output->writeln("{$e->getMessage()}");
return false;
}
if ($dependencies) {
try {
- $this->installDependencies($dependencies, 'install', "The following dependencies need to be installed...");
- $this->installDependencies($dependencies, 'update', "The following dependencies need to be updated...");
+ $this->installDependencies($dependencies, 'install', 'The following dependencies need to be installed...');
+ $this->installDependencies($dependencies, 'update', 'The following dependencies need to be updated...');
$this->installDependencies($dependencies, 'ignore', "The following dependencies can be updated as there is a newer version, but it's not mandatory...", false);
} catch (\Exception $e) {
- $this->output->writeln("Installation aborted");
+ $this->output->writeln('Installation aborted');
return false;
}
- $this->output->writeln("Dependencies are OK");
- $this->output->writeln("");
+ $this->output->writeln('Dependencies are OK');
+ $this->output->writeln('');
}
@@ -183,7 +178,7 @@ class InstallCommand extends ConsoleCommand
foreach ($this->data as $data) {
foreach ($data as $package_name => $package) {
if (array_key_exists($package_name, $dependencies)) {
- $this->output->writeln("Package " . $package_name . " already installed as dependency");
+ $this->output->writeln("Package {$package_name} already installed as dependency");
} else {
$is_valid_destination = Installer::isValidDestination($this->destination . DS . $package->install_path);
if ($is_valid_destination || Installer::lastErrorCode() == Installer::NOT_FOUND) {
@@ -195,24 +190,24 @@ class InstallCommand extends ConsoleCommand
$this->askConfirmationIfMajorVersionUpdated($package);
$this->gpm->checkNoOtherPackageNeedsThisDependencyInALowerVersion($package->slug, $package->available, array_keys($data));
} catch (\Exception $e) {
- $this->output->writeln("" . $e->getMessage() . "");
+ $this->output->writeln("{$e->getMessage()}");
return false;
}
$helper = $this->getHelper('question');
- $question = new ConfirmationQuestion("The package $package_name is already installed, overwrite? [y|N] ", false);
+ $question = new ConfirmationQuestion("The package {$package_name} is already installed, overwrite? [y|N] ", false);
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
if ($answer) {
$is_update = true;
$this->processPackage($package, $is_update);
} else {
- $this->output->writeln("Package " . $package_name . " not overwritten");
+ $this->output->writeln("Package {$package_name} not overwritten");
}
} else {
if (Installer::lastErrorCode() == Installer::IS_LINK) {
- $this->output->writeln("Cannot overwrite existing symlink for $package_name");
- $this->output->writeln("");
+ $this->output->writeln("Cannot overwrite existing symlink for {$package_name}");
+ $this->output->writeln('');
}
}
}
@@ -220,7 +215,7 @@ class InstallCommand extends ConsoleCommand
}
}
- if (count($this->demo_processing) > 0) {
+ if (\count($this->demo_processing) > 0) {
foreach ($this->demo_processing as $package) {
$this->installDemoContent($package);
}
@@ -235,27 +230,27 @@ class InstallCommand extends ConsoleCommand
/**
* If the package is updated from an older major release, show warning and ask confirmation
*
- * @param $package
+ * @param Package $package
*/
public function askConfirmationIfMajorVersionUpdated($package)
{
$helper = $this->getHelper('question');
$package_name = $package->name;
- $new_version = $package->available ? $package->available : $this->gpm->getLatestVersionOfPackage($package->slug);
+ $new_version = $package->available ?: $this->gpm->getLatestVersionOfPackage($package->slug);
$old_version = $package->version;
$major_version_changed = explode('.', $new_version)[0] !== explode('.', $old_version)[0];
if ($major_version_changed) {
if ($this->all_yes) {
- $this->output->writeln("The package $package_name will be updated to a new major version $new_version, from $old_version");
+ $this->output->writeln("The package {$package_name} will be updated to a new major version {$new_version}, from {$old_version}");
return;
}
- $question = new ConfirmationQuestion("The package $package_name will be updated to a new major version $new_version, from $old_version. Be sure to read what changed with the new major release. Continue? [y|N] ", false);
+ $question = new ConfirmationQuestion("The package {$package_name} will be updated to a new major version {$new_version}, from {$old_version}. Be sure to read what changed with the new major release. Continue? [y|N] ", false);
if (!$helper->ask($this->input, $this->output, $question)) {
- $this->output->writeln("Package " . $package_name . " not updated");
+ $this->output->writeln("Package {$package_name} not updated");
exit;
}
}
@@ -276,42 +271,42 @@ class InstallCommand extends ConsoleCommand
public function installDependencies($dependencies, $type, $message, $required = true)
{
$packages = array_filter($dependencies, function ($action) use ($type) { return $action === $type; });
- if (count($packages) > 0) {
+ if (\count($packages) > 0) {
$this->output->writeln($message);
foreach ($packages as $dependencyName => $dependencyVersion) {
- $this->output->writeln(" |- Package " . $dependencyName . "");
+ $this->output->writeln(" |- Package {$dependencyName}");
}
- $this->output->writeln("");
+ $this->output->writeln('');
$helper = $this->getHelper('question');
- if ($type == 'install') {
+ if ($type === 'install') {
$questionAction = 'Install';
} else {
$questionAction = 'Update';
}
- if (count($packages) == 1) {
+ if (\count($packages) === 1) {
$questionArticle = 'this';
} else {
$questionArticle = 'these';
}
- if (count($packages) == 1) {
+ if (\count($packages) === 1) {
$questionNoun = 'package';
} else {
$questionNoun = 'packages';
}
- $question = new ConfirmationQuestion("$questionAction $questionArticle $questionNoun? [Y|n] ", true);
+ $question = new ConfirmationQuestion("${questionAction} {$questionArticle} {$questionNoun}? [Y|n] ", true);
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
if ($answer) {
foreach ($packages as $dependencyName => $dependencyVersion) {
$package = $this->gpm->findPackage($dependencyName);
- $this->processPackage($package, ($type == 'update') ? true : false);
+ $this->processPackage($package, $type === 'update');
}
$this->output->writeln('');
} else {
@@ -323,20 +318,20 @@ class InstallCommand extends ConsoleCommand
}
/**
- * @param $package
- * @param bool $is_update True if the package is an update
+ * @param Package $package
+ * @param bool $is_update True if the package is an update
*/
private function processPackage($package, $is_update = false)
{
if (!$package) {
- $this->output->writeln("Package not found on the GPM! ");
+ $this->output->writeln('Package not found on the GPM!');
$this->output->writeln('');
return;
}
$symlink = false;
if ($this->use_symlinks) {
- if ($this->getSymlinkSource($package) || !isset($package->version)) {
+ if (!isset($package->version) || $this->getSymlinkSource($package)) {
$symlink = true;
}
}
@@ -349,7 +344,7 @@ class InstallCommand extends ConsoleCommand
/**
* Add package to the queue to process the demo content, if demo content exists
*
- * @param $package
+ * @param Package $package
*/
private function processDemo($package)
{
@@ -362,7 +357,7 @@ class InstallCommand extends ConsoleCommand
/**
* Prompt to install the demo content of a package
*
- * @param $package
+ * @param Package $package
*/
private function installDemoContent($package)
{
@@ -373,7 +368,7 @@ class InstallCommand extends ConsoleCommand
$pages_dir = $dest_dir . DS . 'pages';
// Demo content exists, prompt to install it.
- $this->output->writeln("Attention: " . $package->name . " contains demo content");
+ $this->output->writeln("Attention: {$package->name} contains demo content");
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Do you wish to install this demo content? [y|N] ', false);
@@ -402,15 +397,15 @@ class InstallCommand extends ConsoleCommand
// backup current pages folder
if (file_exists($dest_dir)) {
if (rename($pages_dir, $dest_dir . DS . $pages_backup)) {
- $this->output->writeln(" |- Backing up pages... ok");
+ $this->output->writeln(' |- Backing up pages... ok');
} else {
- $this->output->writeln(" |- Backing up pages... failed");
+ $this->output->writeln(' |- Backing up pages... failed');
}
}
}
// Confirmation received, copy over the data
- $this->output->writeln(" |- Installing demo content... ok ");
+ $this->output->writeln(' |- Installing demo content... ok ');
Folder::rcopy($demo_dir, $dest_dir);
$this->output->writeln(" '- Success! ");
$this->output->writeln('');
@@ -418,7 +413,7 @@ class InstallCommand extends ConsoleCommand
}
/**
- * @param $package
+ * @param Package $package
*
* @return array|bool
*/
@@ -436,7 +431,7 @@ class InstallCommand extends ConsoleCommand
}
/**
- * @param $package
+ * @param Package $package
*
* @return bool|string
*/
@@ -465,7 +460,7 @@ class InstallCommand extends ConsoleCommand
}
/**
- * @param $package
+ * @param Package $package
*/
private function processSymlink($package)
{
@@ -475,13 +470,13 @@ class InstallCommand extends ConsoleCommand
$to = $this->destination . DS . $package->install_path;
$from = $this->getSymlinkSource($package);
- $this->output->writeln("Preparing to Symlink " . $package->name . "");
- $this->output->write(" |- Checking source... ");
+ $this->output->writeln("Preparing to Symlink {$package->name}");
+ $this->output->write(' |- Checking source... ');
if (file_exists($from)) {
- $this->output->writeln("ok");
+ $this->output->writeln('ok');
- $this->output->write(" |- Checking destination... ");
+ $this->output->write(' |- Checking destination... ');
$checks = $this->checkDestination($package);
if (!$checks) {
@@ -495,7 +490,7 @@ class InstallCommand extends ConsoleCommand
symlink($from, $to);
// extra white spaces to clear out the buffer properly
- $this->output->writeln(" |- Symlinking package... ok ");
+ $this->output->writeln(' |- Symlinking package... ok ');
$this->output->writeln(" '- Success! ");
$this->output->writeln('');
}
@@ -504,13 +499,13 @@ class InstallCommand extends ConsoleCommand
return;
}
- $this->output->writeln("not found!");
+ $this->output->writeln('not found!');
$this->output->writeln(" '- Installation failed or aborted.");
}
/**
- * @param $package
- * @param bool $is_update
+ * @param Package $package
+ * @param bool $is_update
*
* @return bool
*/
@@ -519,9 +514,9 @@ class InstallCommand extends ConsoleCommand
$version = isset($package->available) ? $package->available : $package->version;
$license = Licenses::get($package->slug);
- $this->output->writeln("Preparing to install " . $package->name . " [v" . $version . "]");
+ $this->output->writeln("Preparing to install {$package->name} [v{$version}]");
- $this->output->write(" |- Downloading package... 0%");
+ $this->output->write(' |- Downloading package... 0%');
$this->file = $this->downloadPackage($package, $license);
if (!$this->file) {
@@ -531,14 +526,14 @@ class InstallCommand extends ConsoleCommand
return false;
}
- $this->output->write(" |- Checking destination... ");
+ $this->output->write(' |- Checking destination... ');
$checks = $this->checkDestination($package);
if (!$checks) {
$this->output->writeln(" '- Installation failed or aborted.");
$this->output->writeln('');
} else {
- $this->output->write(" |- Installing package... ");
+ $this->output->write(' |- Installing package... ');
$installation = $this->installPackage($package, $is_update);
if (!$installation) {
$this->output->writeln(" '- Installation failed or aborted.");
@@ -566,10 +561,10 @@ class InstallCommand extends ConsoleCommand
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
$this->tmp = $tmp_dir . '/Grav-' . uniqid();
$filename = $package->slug . basename($package->zipball_url);
- $filename = preg_replace('/[\\\\\/:"*?&<>|]+/mi', '-', $filename);
+ $filename = preg_replace('/[\\\\\/:"*?&<>|]+/m', '-', $filename);
$query = '';
- if ($package->premium) {
+ if (!empty($package->premium)) {
$query = \json_encode(array_merge(
$package->premium,
[
@@ -588,16 +583,16 @@ class InstallCommand extends ConsoleCommand
$error = str_replace("\n", "\n | '- ", $e->getMessage());
$this->output->write("\x0D");
// extra white spaces to clear out the buffer properly
- $this->output->writeln(" |- Downloading package... error ");
+ $this->output->writeln(' |- Downloading package... error ');
$this->output->writeln(" | '- " . $error);
return false;
}
- Folder::mkdir($this->tmp);
+ Folder::create($this->tmp);
$this->output->write("\x0D");
- $this->output->write(" |- Downloading package... 100%");
+ $this->output->write(' |- Downloading package... 100%');
$this->output->writeln('');
file_put_contents($this->tmp . DS . $filename, $output);
@@ -606,7 +601,7 @@ class InstallCommand extends ConsoleCommand
}
/**
- * @param $package
+ * @param Package $package
*
* @return bool
*/
@@ -618,7 +613,7 @@ class InstallCommand extends ConsoleCommand
if (Installer::lastErrorCode() == Installer::IS_LINK) {
$this->output->write("\x0D");
- $this->output->writeln(" |- Checking destination... symbolic link");
+ $this->output->writeln(' |- Checking destination... symbolic link');
if ($this->all_yes) {
$this->output->writeln(" | '- Skipped automatically.");
@@ -634,13 +629,13 @@ class InstallCommand extends ConsoleCommand
$this->output->writeln(" | '- You decided to not delete the symlink automatically.");
return false;
- } else {
- unlink($this->destination . DS . $package->install_path);
}
+
+ unlink($this->destination . DS . $package->install_path);
}
$this->output->write("\x0D");
- $this->output->writeln(" |- Checking destination... ok");
+ $this->output->writeln(' |- Checking destination... ok');
return true;
}
@@ -657,14 +652,14 @@ class InstallCommand extends ConsoleCommand
{
$type = $package->package_type;
- Installer::install($this->file, $this->destination, ['install_path' => $package->install_path, 'theme' => (($type == 'themes')), 'is_update' => $is_update]);
+ Installer::install($this->file, $this->destination, ['install_path' => $package->install_path, 'theme' => $type === 'themes', 'is_update' => $is_update]);
$error_code = Installer::lastErrorCode();
Folder::delete($this->tmp);
if ($error_code) {
$this->output->write("\x0D");
// extra white spaces to clear out the buffer properly
- $this->output->writeln(" |- Installing package... error ");
+ $this->output->writeln(' |- Installing package... error ');
$this->output->writeln(" | '- " . Installer::lastErrorMsg());
return false;
@@ -674,23 +669,23 @@ class InstallCommand extends ConsoleCommand
if ($message) {
$this->output->write("\x0D");
// extra white spaces to clear out the buffer properly
- $this->output->writeln(" |- " . $message);
+ $this->output->writeln(" |- {$message}");
}
$this->output->write("\x0D");
// extra white spaces to clear out the buffer properly
- $this->output->writeln(" |- Installing package... ok ");
+ $this->output->writeln(' |- Installing package... ok ');
return true;
}
/**
- * @param $progress
+ * @param array $progress
*/
public function progress($progress)
{
$this->output->write("\x0D");
- $this->output->write(" |- Downloading package... " . str_pad($progress['percent'], 5, " ",
+ $this->output->write(' |- Downloading package... ' . str_pad($progress['percent'], 5, ' ',
STR_PAD_LEFT) . '%');
}
}
diff --git a/system/src/Grav/Console/Gpm/SelfupgradeCommand.php b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php
index d406a95..1562f4b 100644
--- a/system/src/Grav/Console/Gpm/SelfupgradeCommand.php
+++ b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php
@@ -1,15 +1,18 @@
setName("self-upgrade")
+ ->setName('self-upgrade')
->setAliases(['selfupgrade', 'selfupdate'])
->addOption(
'force',
@@ -78,13 +69,10 @@ class SelfupgradeCommand extends ConsoleCommand
InputOption::VALUE_NONE,
'Option to overwrite packages if they already exist'
)
- ->setDescription("Detects and performs an update of Grav itself when available")
+ ->setDescription('Detects and performs an update of Grav itself when available')
->setHelp('The update command updates Grav itself when a new version is available');
}
- /**
- * @return int|null|void
- */
protected function serve()
{
$this->upgrader = new Upgrader($this->input->getOption('force'));
@@ -100,26 +88,26 @@ class SelfupgradeCommand extends ConsoleCommand
$release = strftime('%c', strtotime($this->upgrader->getReleaseDate()));
if (!$this->upgrader->meetsRequirements()) {
- $this->output->writeln("ATTENTION:");
- $this->output->writeln(" Grav has increased the minimum PHP requirement.");
- $this->output->writeln(" You are currently running PHP " . phpversion() . ", but PHP " . $this->upgrader->minPHPVersion() . " is required.");
- $this->output->writeln(" Additional information: http://getgrav.org/blog/changing-php-requirements");
- $this->output->writeln("");
- $this->output->writeln("Selfupgrade aborted.");
- $this->output->writeln("");
+ $this->output->writeln('ATTENTION:');
+ $this->output->writeln(' Grav has increased the minimum PHP requirement.');
+ $this->output->writeln(' You are currently running PHP ' . phpversion() . ', but PHP ' . $this->upgrader->minPHPVersion() . ' is required.');
+ $this->output->writeln(' Additional information: http://getgrav.org/blog/changing-php-requirements');
+ $this->output->writeln('');
+ $this->output->writeln('Selfupgrade aborted.');
+ $this->output->writeln('');
exit;
}
if (!$this->overwrite && !$this->upgrader->isUpgradable()) {
- $this->output->writeln("You are already running the latest version of Grav (v" . $local . ") released on " . $release);
+ $this->output->writeln("You are already running the latest version of Grav (v{$local}) released on {$release}");
exit;
}
Installer::isValidDestination(GRAV_ROOT . '/system');
if (Installer::IS_LINK === Installer::lastErrorCode()) {
- $this->output->writeln("ATTENTION: Grav is symlinked, cannot upgrade, aborting...");
+ $this->output->writeln('ATTENTION: Grav is symlinked, cannot upgrade, aborting...');
$this->output->writeln('');
- $this->output->writeln("You are currently running a symbolically linked Grav v" . $local . ". Latest available is v". $remote . ".");
+ $this->output->writeln("You are currently running a symbolically linked Grav v{$local}. Latest available is v{$remote}.");
exit;
}
@@ -129,51 +117,51 @@ class SelfupgradeCommand extends ConsoleCommand
$questionHelper = $this->getHelper('question');
- $this->output->writeln("Grav v$remote is now available [release date: $release].");
- $this->output->writeln("You are currently using v" . GRAV_VERSION . ".");
+ $this->output->writeln("Grav v{$remote} is now available [release date: {$release}].");
+ $this->output->writeln('You are currently using v' . GRAV_VERSION . '.');
if (!$this->all_yes) {
- $question = new ConfirmationQuestion("Would you like to read the changelog before proceeding? [y|N] ",
+ $question = new ConfirmationQuestion('Would you like to read the changelog before proceeding? [y|N] ',
false);
$answer = $questionHelper->ask($this->input, $this->output, $question);
if ($answer) {
$changelog = $this->upgrader->getChangelog(GRAV_VERSION);
- $this->output->writeln("");
+ $this->output->writeln('');
foreach ($changelog as $version => $log) {
$title = $version . ' [' . $log['date'] . ']';
$content = preg_replace_callback('/\d\.\s\[\]\(#(.*)\)/', function ($match) {
- return "\n" . ucfirst($match[1]) . ":";
+ return "\n" . ucfirst($match[1]) . ':';
}, $log['content']);
$this->output->writeln($title);
- $this->output->writeln(str_repeat('-', strlen($title)));
+ $this->output->writeln(str_repeat('-', \strlen($title)));
$this->output->writeln($content);
- $this->output->writeln("");
+ $this->output->writeln('');
}
- $question = new ConfirmationQuestion("Press [ENTER] to continue.", true);
+ $question = new ConfirmationQuestion('Press [ENTER] to continue.', true);
$questionHelper->ask($this->input, $this->output, $question);
}
- $question = new ConfirmationQuestion("Would you like to upgrade now? [y|N] ", false);
+ $question = new ConfirmationQuestion('Would you like to upgrade now? [y|N] ', false);
$answer = $questionHelper->ask($this->input, $this->output, $question);
if (!$answer) {
- $this->output->writeln("Aborting...");
+ $this->output->writeln('Aborting...');
exit;
}
}
- $this->output->writeln("");
- $this->output->writeln("Preparing to upgrade to v$remote..");
+ $this->output->writeln('');
+ $this->output->writeln("Preparing to upgrade to v{$remote}..");
- $this->output->write(" |- Downloading upgrade [" . $this->formatBytes($update['size']) . "]... 0%");
+ $this->output->write(" |- Downloading upgrade [{$this->formatBytes($update['size'])}]... 0%");
$this->file = $this->download($update);
- $this->output->write(" |- Installing upgrade... ");
+ $this->output->write(' |- Installing upgrade... ');
$installation = $this->upgrade();
if (!$installation) {
@@ -189,20 +177,20 @@ class SelfupgradeCommand extends ConsoleCommand
}
/**
- * @param $package
+ * @param Package $package
*
* @return string
*/
private function download($package)
{
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
- $this->tmp = $tmp_dir . '/Grav-' . uniqid();
+ $this->tmp = $tmp_dir . '/Grav-' . uniqid('', false);
$output = Response::get($package['download'], [], [$this, 'progress']);
- Folder::mkdir($this->tmp);
+ Folder::create($this->tmp);
$this->output->write("\x0D");
- $this->output->write(" |- Downloading upgrade [" . $this->formatBytes($package['size']) . "]... 100%");
+ $this->output->write(" |- Downloading upgrade [{$this->formatBytes($package['size'])}]... 100%");
$this->output->writeln('');
file_put_contents($this->tmp . DS . $package['name'], $output);
@@ -215,15 +203,24 @@ class SelfupgradeCommand extends ConsoleCommand
*/
private function upgrade()
{
- Installer::install($this->file, GRAV_ROOT,
- ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true]);
+ if ($this->file) {
+ $folder = Installer::unZip($this->file, $this->tmp . '/zip');
+ } else {
+ $folder = false;
+ }
+
+ static::upgradeGrav($this->file, $folder);
+
$errorCode = Installer::lastErrorCode();
- Folder::delete($this->tmp);
+
+ if ($this->tmp) {
+ Folder::delete($this->tmp);
+ }
if ($errorCode & (Installer::ZIP_OPEN_ERROR | Installer::ZIP_EXTRACT_ERROR)) {
$this->output->write("\x0D");
// extra white spaces to clear out the buffer properly
- $this->output->writeln(" |- Installing upgrade... error ");
+ $this->output->writeln(' |- Installing upgrade... error ');
$this->output->writeln(" | '- " . Installer::lastErrorMsg());
return false;
@@ -231,23 +228,23 @@ class SelfupgradeCommand extends ConsoleCommand
$this->output->write("\x0D");
// extra white spaces to clear out the buffer properly
- $this->output->writeln(" |- Installing upgrade... ok ");
+ $this->output->writeln(' |- Installing upgrade... ok ');
return true;
}
/**
- * @param $progress
+ * @param array $progress
*/
public function progress($progress)
{
$this->output->write("\x0D");
- $this->output->write(" |- Downloading upgrade [" . $this->formatBytes($progress["filesize"]) . "]... " . str_pad($progress['percent'],
- 5, " ", STR_PAD_LEFT) . '%');
+ $this->output->write(" |- Downloading upgrade [{$this->formatBytes($progress['filesize']) }]... " . str_pad($progress['percent'],
+ 5, ' ', STR_PAD_LEFT) . '%');
}
/**
- * @param $size
+ * @param int|float $size
* @param int $precision
*
* @return string
@@ -257,6 +254,44 @@ class SelfupgradeCommand extends ConsoleCommand
$base = log($size) / log(1024);
$suffixes = array('', 'k', 'M', 'G', 'T');
- return round(pow(1024, $base - floor($base)), $precision) . $suffixes[(int)floor($base)];
+ return round(1024 ** ($base - floor($base)), $precision) . $suffixes[(int)floor($base)];
+ }
+
+ private function upgradeGrav($zip, $folder, $keepFolder = false)
+ {
+ static $ignores = [
+ 'backup',
+ 'cache',
+ 'images',
+ 'logs',
+ 'tmp',
+ 'user',
+ '.htaccess',
+ 'robots.txt'
+ ];
+
+ if (!is_dir($folder)) {
+ Installer::setError('Invalid source folder');
+ }
+
+ try {
+ $script = $folder . '/system/install.php';
+ /** Install $installer */
+ if ((file_exists($script) && $install = include $script) && is_callable($install)) {
+ $install($zip);
+ } else {
+ Installer::install(
+ $zip,
+ GRAV_ROOT,
+ ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true, 'ignores' => $ignores],
+ $folder,
+ $keepFolder
+ );
+
+ Cache::clearCache();
+ }
+ } catch (\Exception $e) {
+ Installer::setError($e->getMessage());
+ }
}
}
diff --git a/system/src/Grav/Console/Gpm/UninstallCommand.php b/system/src/Grav/Console/Gpm/UninstallCommand.php
index 34b04ee..99fc81a 100644
--- a/system/src/Grav/Console/Gpm/UninstallCommand.php
+++ b/system/src/Grav/Console/Gpm/UninstallCommand.php
@@ -1,8 +1,9 @@
setName("uninstall")
+ ->setName('uninstall')
->addOption(
'all-yes',
'y',
@@ -61,13 +56,10 @@ class UninstallCommand extends ConsoleCommand
InputArgument::IS_ARRAY | InputArgument::REQUIRED,
'The package(s) that are desired to be removed. Use the "index" command for a list of packages'
)
- ->setDescription("Performs the uninstallation of plugins and themes")
+ ->setDescription('Performs the uninstallation of plugins and themes')
->setHelp('The uninstall command allows to uninstall plugins and themes');
}
- /**
- * @return int|null|void
- */
protected function serve()
{
$this->gpm = new GPM();
@@ -77,37 +69,38 @@ class UninstallCommand extends ConsoleCommand
$packages = array_map('strtolower', $this->input->getArgument('package'));
$this->data = ['total' => 0, 'not_found' => []];
+ $total = 0;
foreach ($packages as $package) {
$plugin = $this->gpm->getInstalledPlugin($package);
$theme = $this->gpm->getInstalledTheme($package);
if ($plugin || $theme) {
$this->data[strtolower($package)] = $plugin ?: $theme;
- $this->data['total']++;
+ $total++;
} else {
$this->data['not_found'][] = $package;
}
}
+ $this->data['total'] = $total;
$this->output->writeln('');
if (!$this->data['total']) {
- $this->output->writeln("Nothing to uninstall.");
+ $this->output->writeln('Nothing to uninstall.');
$this->output->writeln('');
exit;
}
if (count($this->data['not_found'])) {
- $this->output->writeln("These packages were not found installed: " . implode(', ',
- $this->data['not_found']) . "");
+ $this->output->writeln('These packages were not found installed: ' . implode(', ',
+ $this->data['not_found']) . '');
}
- unset($this->data['not_found']);
- unset($this->data['total']);
+ unset($this->data['not_found'], $this->data['total']);
foreach ($this->data as $slug => $package) {
- $this->output->writeln("Preparing to uninstall " . $package->name . " [v" . $package->version . "]");
+ $this->output->writeln("Preparing to uninstall {$package->name} [v{$package->version}]");
- $this->output->write(" |- Checking destination... ");
+ $this->output->write(' |- Checking destination... ');
$checks = $this->checkDestination($slug, $package);
if (!$checks) {
@@ -131,8 +124,8 @@ class UninstallCommand extends ConsoleCommand
/**
- * @param $slug
- * @param $package
+ * @param string $slug
+ * @param Package $package
*
* @return bool
*/
@@ -147,12 +140,12 @@ class UninstallCommand extends ConsoleCommand
if (count($dependent_packages) > ($is_dependency ? 1 : 0)) {
$this->output->writeln('');
$this->output->writeln('');
- $this->output->writeln("Uninstallation failed.");
+ $this->output->writeln('Uninstallation failed.');
$this->output->writeln('');
- if (count($dependent_packages) > ($is_dependency ? 2 : 1)) {
- $this->output->writeln("The installed packages " . implode(', ', $dependent_packages) . " depends on this package. Please remove those first.");
+ if (\count($dependent_packages) > ($is_dependency ? 2 : 1)) {
+ $this->output->writeln('The installed packages ' . implode(', ', $dependent_packages) . ' depends on this package. Please remove those first.');
} else {
- $this->output->writeln("The installed package " . implode(', ', $dependent_packages) . " depends on this package. Please remove it first.");
+ $this->output->writeln('The installed package ' . implode(', ', $dependent_packages) . ' depends on this package. Please remove it first.');
}
$this->output->writeln('');
@@ -165,12 +158,12 @@ class UninstallCommand extends ConsoleCommand
if ($is_dependency) {
foreach ($dependencies as $key => $dependency) {
- if (in_array($dependency['name'], $this->dependencies)) {
+ if (\in_array($dependency['name'], $this->dependencies, true)) {
unset($dependencies[$key]);
}
}
} else {
- if (count($dependencies) > 0) {
+ if (\count($dependencies) > 0) {
$this->output->writeln(' `- Dependencies found...');
$this->output->writeln('');
}
@@ -182,7 +175,7 @@ class UninstallCommand extends ConsoleCommand
$this->dependencies[] = $dependency['name'];
- if (is_array($dependency)) {
+ if (\is_array($dependency)) {
$dependency = $dependency['name'];
}
if ($dependency === 'grav' || $dependency === 'php') {
@@ -194,9 +187,9 @@ class UninstallCommand extends ConsoleCommand
$dependency_exists = $this->packageExists($dependency, $dependencyPackage);
if ($dependency_exists == Installer::EXISTS) {
- $this->output->writeln("A dependency on " . $dependencyPackage->name . " [v" . $dependencyPackage->version . "] was found");
+ $this->output->writeln("A dependency on {$dependencyPackage->name} [v{$dependencyPackage->version}] was found");
- $question = new ConfirmationQuestion(" |- Uninstall " . $dependencyPackage->name . "? [y|N] ", false);
+ $question = new ConfirmationQuestion(" |- Uninstall {$dependencyPackage->name}? [y|N] ", false);
$answer = $this->all_yes ? true : $questionHelper->ask($this->input, $this->output, $question);
if ($answer) {
@@ -210,7 +203,7 @@ class UninstallCommand extends ConsoleCommand
}
$this->output->writeln('');
} else {
- $this->output->writeln(" '- You decided not to uninstall " . $dependencyPackage->name . ".");
+ $this->output->writeln(" '- You decided not to uninstall {$dependencyPackage->name}.");
$this->output->writeln('');
}
}
@@ -225,21 +218,21 @@ class UninstallCommand extends ConsoleCommand
$errorCode = Installer::lastErrorCode();
if ($errorCode && $errorCode !== Installer::IS_LINK && $errorCode !== Installer::EXISTS) {
- $this->output->writeln(" |- Uninstalling " . $package->name . " package... error ");
- $this->output->writeln(" | '- " . Installer::lastErrorMsg()."");
+ $this->output->writeln(" |- Uninstalling {$package->name} package... error ");
+ $this->output->writeln(" | '- " . Installer::lastErrorMsg() . '');
return false;
}
$message = Installer::getMessage();
if ($message) {
- $this->output->writeln(" |- " . $message);
+ $this->output->writeln(" |- {$message}");
}
if (!$is_dependency && $this->dependencies) {
- $this->output->writeln("Finishing up uninstalling " . $package->name . "");
+ $this->output->writeln("Finishing up uninstalling {$package->name}");
}
- $this->output->writeln(" |- Uninstalling " . $package->name . " package... ok ");
+ $this->output->writeln(" |- Uninstalling {$package->name} package... ok ");
@@ -247,8 +240,8 @@ class UninstallCommand extends ConsoleCommand
}
/**
- * @param $slug
- * @param $package
+ * @param string $slug
+ * @param Package $package
*
* @return bool
*/
@@ -261,7 +254,7 @@ class UninstallCommand extends ConsoleCommand
if ($exists == Installer::IS_LINK) {
$this->output->write("\x0D");
- $this->output->writeln(" |- Checking destination... symbolic link");
+ $this->output->writeln(' |- Checking destination... symbolic link');
if ($this->all_yes) {
$this->output->writeln(" | '- Skipped automatically.");
@@ -281,7 +274,7 @@ class UninstallCommand extends ConsoleCommand
}
$this->output->write("\x0D");
- $this->output->writeln(" |- Checking destination... ok");
+ $this->output->writeln(' |- Checking destination... ok');
return true;
}
@@ -289,8 +282,8 @@ class UninstallCommand extends ConsoleCommand
/**
* Check if package exists
*
- * @param $slug
- * @param $package
+ * @param string $slug
+ * @param Package $package
* @return int
*/
private function packageExists($slug, $package)
diff --git a/system/src/Grav/Console/Gpm/UpdateCommand.php b/system/src/Grav/Console/Gpm/UpdateCommand.php
index 06913b1..d8ea735 100644
--- a/system/src/Grav/Console/Gpm/UpdateCommand.php
+++ b/system/src/Grav/Console/Gpm/UpdateCommand.php
@@ -1,8 +1,9 @@
setName("update")
+ ->setName('update')
->addOption(
'force',
'f',
@@ -106,27 +93,24 @@ class UpdateCommand extends ConsoleCommand
InputArgument::IS_ARRAY | InputArgument::OPTIONAL,
'The package or packages that is desired to update. By default all available updates will be applied.'
)
- ->setDescription("Detects and performs an update of plugins and themes when available")
+ ->setDescription('Detects and performs an update of plugins and themes when available')
->setHelp('The update command updates plugins and themes when a new version is available');
}
- /**
- * @return int|null|void
- */
protected function serve()
{
$this->upgrader = new Upgrader($this->input->getOption('force'));
$local = $this->upgrader->getLocalVersion();
$remote = $this->upgrader->getRemoteVersion();
if ($local !== $remote) {
- $this->output->writeln("WARNING: A new version of Grav is available. You should update Grav before updating plugins and themes. If you continue without updating Grav, some plugins or themes may stop working.");
- $this->output->writeln("");
+ $this->output->writeln('WARNING: A new version of Grav is available. You should update Grav before updating plugins and themes. If you continue without updating Grav, some plugins or themes may stop working.');
+ $this->output->writeln('');
$questionHelper = $this->getHelper('question');
- $question = new ConfirmationQuestion("Continue with the update process? [Y|n] ", true);
+ $question = new ConfirmationQuestion('Continue with the update process? [Y|n] ', true);
$answer = $questionHelper->ask($this->input, $this->output, $question);
if (!$answer) {
- $this->output->writeln("Update aborted. Exiting...");
+ $this->output->writeln('Update aborted. Exiting...');
exit;
}
}
@@ -141,7 +125,7 @@ class UpdateCommand extends ConsoleCommand
$this->destination = realpath($this->input->getOption('destination'));
if (!Installer::isGravInstance($this->destination)) {
- $this->output->writeln("ERROR: " . Installer::lastErrorMsg());
+ $this->output->writeln('ERROR: ' . Installer::lastErrorMsg());
exit;
}
if ($this->input->getOption('plugins') === false && $this->input->getOption('themes') === false) {
@@ -153,27 +137,26 @@ class UpdateCommand extends ConsoleCommand
if ($this->overwrite) {
$this->data = $this->gpm->getInstallable($list_type);
- $description = " can be overwritten";
+ $description = ' can be overwritten';
} else {
$this->data = $this->gpm->getUpdatable($list_type);
- $description = " need updating";
+ $description = ' need updating';
}
$only_packages = array_map('strtolower', $this->input->getArgument('package'));
if (!$this->overwrite && !$this->data['total']) {
- $this->output->writeln("Nothing to update.");
+ $this->output->writeln('Nothing to update.');
exit;
}
- $this->output->write("Found " . $this->gpm->countInstalled() . " packages installed of which " . $this->data['total'] . "" . $description);
+ $this->output->write("Found {$this->gpm->countInstalled()} packages installed of which {$this->data['total']}{$description}");
$limit_to = $this->userInputPackages($only_packages);
$this->output->writeln('');
- unset($this->data['total']);
- unset($limit_to['total']);
+ unset($this->data['total'], $limit_to['total']);
// updates review
@@ -182,7 +165,7 @@ class UpdateCommand extends ConsoleCommand
$index = 0;
foreach ($this->data as $packages) {
foreach ($packages as $slug => $package) {
- if (count($only_packages) && !array_key_exists($slug, $limit_to)) {
+ if (!array_key_exists($slug, $limit_to) && \count($only_packages)) {
continue;
}
@@ -192,11 +175,11 @@ class UpdateCommand extends ConsoleCommand
$this->output->writeln(
// index
- str_pad($index++ + 1, 2, '0', STR_PAD_LEFT) . ". " .
+ str_pad($index++ + 1, 2, '0', STR_PAD_LEFT) . '. ' .
// name
- "" . str_pad($package->name, 15) . " " .
+ '' . str_pad($package->name, 15) . ' ' .
// version
- "[v" . $package->version . " -> v" . $package->available . "]"
+ "[v{$package->version} -> v{$package->available}]"
);
$slugs[] = $slug;
}
@@ -204,13 +187,13 @@ class UpdateCommand extends ConsoleCommand
if (!$this->all_yes) {
// prompt to continue
- $this->output->writeln("");
+ $this->output->writeln('');
$questionHelper = $this->getHelper('question');
- $question = new ConfirmationQuestion("Continue with the update process? [Y|n] ", true);
+ $question = new ConfirmationQuestion('Continue with the update process? [Y|n] ', true);
$answer = $questionHelper->ask($this->input, $this->output, $question);
if (!$answer) {
- $this->output->writeln("Update aborted. Exiting...");
+ $this->output->writeln('Update aborted. Exiting...');
exit;
}
}
@@ -228,13 +211,13 @@ class UpdateCommand extends ConsoleCommand
$command_exec = $install_command->run($args, $this->output);
if ($command_exec != 0) {
- $this->output->writeln("Error: An error occurred while trying to install the packages");
+ $this->output->writeln('Error: An error occurred while trying to install the packages');
exit;
}
}
/**
- * @param $only_packages
+ * @param array $only_packages
*
* @return array
*/
@@ -243,14 +226,14 @@ class UpdateCommand extends ConsoleCommand
$found = ['total' => 0];
$ignore = [];
- if (!count($only_packages)) {
+ if (!\count($only_packages)) {
$this->output->writeln('');
} else {
foreach ($only_packages as $only_package) {
$find = $this->gpm->findPackage($only_package);
if (!$find || (!$this->overwrite && !$this->gpm->isUpdatable($find->slug))) {
- $name = isset($find->slug) ? $find->slug : $only_package;
+ $name = $find->slug ?? $only_package;
$ignore[$name] = $name;
} else {
$found[$find->slug] = $find;
@@ -264,18 +247,18 @@ class UpdateCommand extends ConsoleCommand
$list = array_keys($list);
if ($found['total'] !== $this->data['total']) {
- $this->output->write(", only " . $found['total'] . " will be updated");
+ $this->output->write(", only {$found['total']} will be updated");
}
$this->output->writeln('');
- $this->output->writeln("Limiting updates for only " . implode(', ',
- $list) . "");
+ $this->output->writeln('Limiting updates for only ' . implode(', ',
+ $list) . '');
}
- if (count($ignore)) {
+ if (\count($ignore)) {
$this->output->writeln('');
- $this->output->writeln("Packages not found or not requiring updates: " . implode(', ',
- $ignore) . "");
+ $this->output->writeln('Packages not found or not requiring updates: ' . implode(', ',
+ $ignore) . '');
}
}
diff --git a/system/src/Grav/Console/Gpm/VersionCommand.php b/system/src/Grav/Console/Gpm/VersionCommand.php
index eb7872e..becc327 100644
--- a/system/src/Grav/Console/Gpm/VersionCommand.php
+++ b/system/src/Grav/Console/Gpm/VersionCommand.php
@@ -1,8 +1,9 @@
setName("version")
+ ->setName('version')
->addOption(
'force',
'f',
@@ -40,7 +36,7 @@ class VersionCommand extends ConsoleCommand
InputArgument::IS_ARRAY | InputArgument::OPTIONAL,
'The package or packages that is desired to know the version of. By default and if not specified this would be grav'
)
- ->setDescription("Shows the version of an installed package. If available also shows pending updates.")
+ ->setDescription('Shows the version of an installed package. If available also shows pending updates.')
->setHelp('The version command displays the current version of a package installed and, if available, the available version of pending updates');
}
@@ -64,13 +60,13 @@ class VersionCommand extends ConsoleCommand
$version = null;
$updatable = false;
- if ($package == 'grav') {
+ if ($package === 'grav') {
$name = 'Grav';
$version = GRAV_VERSION;
$upgrader = new Upgrader();
if ($upgrader->isUpgradable()) {
- $updatable = ' [upgradable: v' . $upgrader->getRemoteVersion() . ']';
+ $updatable = " [upgradable: v{$upgrader->getRemoteVersion()}]";
}
} else {
@@ -99,17 +95,17 @@ class VersionCommand extends ConsoleCommand
$name = $installed->name;
if ($this->gpm->isUpdatable($package)) {
- $updatable = ' [updatable: v' . $installed->available . ']';
+ $updatable = " [updatable: v{$installed->available}]";
}
}
}
$updatable = $updatable ?: '';
- if ($installed || $package == 'grav') {
- $this->output->writeln('You are running ' . $name . ' v' . $version . '' . $updatable);
+ if ($installed || $package === 'grav') {
+ $this->output->writeln("You are running {$name} v{$version}{$updatable}");
} else {
- $this->output->writeln('Package ' . $package . ' not found');
+ $this->output->writeln("Package {$package} not found");
}
}
}
diff --git a/system/src/Grav/Console/TerminalObjects/Table.php b/system/src/Grav/Console/TerminalObjects/Table.php
index c3078a9..4f9df63 100644
--- a/system/src/Grav/Console/TerminalObjects/Table.php
+++ b/system/src/Grav/Console/TerminalObjects/Table.php
@@ -1,8 +1,9 @@
caches = array_values($caches);
- $this->count = count($caches);
+ $this->count = \count($caches);
}
/**
diff --git a/system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php b/system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php
index 3a69c1f..45c9d07 100644
--- a/system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php
+++ b/system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php
@@ -1,8 +1,9 @@
getNamespace();
- $namespace && $doctrineCache->setNamespace($namespace);
+ if ($namespace) {
+ $doctrineCache->setNamespace($namespace);
+ }
$this->driver = $doctrineCache;
}
@@ -96,20 +99,10 @@ class DoctrineCache extends AbstractCache
/**
* @inheritdoc
- * @throws \Psr\SimpleCache\InvalidArgumentException
+ * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
*/
public function doDeleteMultiple($keys)
{
- // TODO: Remove when Doctrine Cache has been updated to support the feature.
- if (!method_exists($this->driver, 'deleteMultiple')) {
- $success = true;
- foreach ($keys as $key) {
- $success = $this->delete($key) && $success;
- }
-
- return $success;
- }
-
return $this->driver->deleteMultiple($keys);
}
diff --git a/system/src/Grav/Framework/Cache/Adapter/FileCache.php b/system/src/Grav/Framework/Cache/Adapter/FileCache.php
index 389b626..a5a60fa 100644
--- a/system/src/Grav/Framework/Cache/Adapter/FileCache.php
+++ b/system/src/Grav/Framework/Cache/Adapter/FileCache.php
@@ -1,8 +1,9 @@
initFileCache($namespace, $folder ?? '');
}
/**
@@ -62,7 +65,7 @@ class FileCache extends AbstractCache
/**
* @inheritdoc
- * @throws \Psr\SimpleCache\CacheException
+ * @throws \Psr\SimpleCache\CacheException|InvalidArgumentException
*/
public function doSet($key, $value, $ttl)
{
@@ -126,8 +129,8 @@ class FileCache extends AbstractCache
$hash = str_replace('/', '-', base64_encode(hash('sha256', static::class . $key, true)));
$dir = $this->directory . $hash[0] . DIRECTORY_SEPARATOR . $hash[1] . DIRECTORY_SEPARATOR;
- if ($mkdir && !file_exists($dir)) {
- @mkdir($dir, 0777, true);
+ if ($mkdir) {
+ $this->mkdir($dir);
}
return $dir . substr($hash, 2, 20);
@@ -136,9 +139,9 @@ class FileCache extends AbstractCache
/**
* @param string $namespace
* @param string $directory
- * @throws \Psr\SimpleCache\InvalidArgumentException
+ * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
*/
- private function init($namespace, $directory)
+ protected function initFileCache($namespace, $directory)
{
if (!isset($directory[0])) {
$directory = sys_get_temp_dir() . '/grav-cache';
@@ -153,9 +156,7 @@ class FileCache extends AbstractCache
$directory .= DIRECTORY_SEPARATOR . $namespace;
}
- if (!file_exists($directory)) {
- @mkdir($directory, 0777, true);
- }
+ $this->mkdir($directory);
$directory .= DIRECTORY_SEPARATOR;
// On Windows the whole path is limited to 258 chars
@@ -192,6 +193,28 @@ class FileCache extends AbstractCache
}
}
+ /**
+ * @param string $dir
+ * @throws \RuntimeException
+ */
+ private function mkdir($dir)
+ {
+ // Silence error for open_basedir; should fail in mkdir instead.
+ if (@is_dir($dir)) {
+ return;
+ }
+
+ $success = @mkdir($dir, 0777, true);
+
+ if (!$success) {
+ // Take yet another look, make sure that the folder doesn't exist.
+ clearstatcache(true, $dir);
+ if (!@is_dir($dir)) {
+ throw new \RuntimeException(sprintf('Unable to create directory: %s', $dir));
+ }
+ }
+ }
+
/**
* @internal
* @throws \ErrorException
diff --git a/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php b/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php
index 84911fb..2d0fe40 100644
--- a/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php
+++ b/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php
@@ -1,8 +1,9 @@
getNamespace()][$key]) ? $_SESSION[$this->getNamespace()][$key] : null;
+ $stored = $_SESSION[$this->getNamespace()][$key] ?? null;
if (isset($stored[self::LIFETIME]) && $stored[self::LIFETIME] < time()) {
unset($_SESSION[$this->getNamespace()][$key]);
diff --git a/system/src/Grav/Framework/Cache/CacheInterface.php b/system/src/Grav/Framework/Cache/CacheInterface.php
index 54af40c..5aefe35 100644
--- a/system/src/Grav/Framework/Cache/CacheInterface.php
+++ b/system/src/Grav/Framework/Cache/CacheInterface.php
@@ -1,8 +1,9 @@
64) {
+ if (\strlen($key) > 64) {
throw new InvalidArgumentException(
- sprintf('Cache key length must be less than 65 characters, key had %s characters', strlen($key))
+ sprintf('Cache key length must be less than 65 characters, key had %s characters', \strlen($key))
);
}
if (strpbrk($key, '{}()/\@:') !== false) {
@@ -308,7 +309,7 @@ trait CacheTrait
/**
* @param array $keys
- * @throws \Psr\SimpleCache\InvalidArgumentException
+ * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
*/
protected function validateKeys($keys)
{
@@ -322,9 +323,9 @@ trait CacheTrait
}
/**
- * @param null|int|\DateInterval $ttl
+ * @param null|int|\DateInterval|mixed $ttl
* @return int|null
- * @throws \Psr\SimpleCache\InvalidArgumentException
+ * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
*/
protected function convertTtl($ttl)
{
@@ -332,18 +333,18 @@ trait CacheTrait
return $this->getDefaultLifetime();
}
- if (is_int($ttl)) {
+ if (\is_int($ttl)) {
return $ttl;
}
if ($ttl instanceof \DateInterval) {
- $ttl = (int) \DateTime::createFromFormat('U', 0)->add($ttl)->format('U');
+ $ttl = (int) \DateTime::createFromFormat('U', '0')->add($ttl)->format('U');
}
throw new InvalidArgumentException(
sprintf(
'Expiration date must be an integer, a DateInterval or null, "%s" given',
- is_object($ttl) ? get_class($ttl) : gettype($ttl)
+ \is_object($ttl) ? \get_class($ttl) : \gettype($ttl)
)
);
}
diff --git a/system/src/Grav/Framework/Cache/Exception/CacheException.php b/system/src/Grav/Framework/Cache/Exception/CacheException.php
index 71caff7..55da2f5 100644
--- a/system/src/Grav/Framework/Cache/Exception/CacheException.php
+++ b/system/src/Grav/Framework/Cache/Exception/CacheException.php
@@ -1,8 +1,9 @@
getOrderings()) {
$next = null;
foreach (array_reverse($orderings) as $field => $ordering) {
- $next = ClosureExpressionVisitor::sortByField($field, $ordering == Criteria::DESC ? -1 : 1, $next);
+ $next = ClosureExpressionVisitor::sortByField($field, $ordering === Criteria::DESC ? -1 : 1, $next);
}
uasort($filtered, $next);
@@ -106,7 +107,7 @@ class AbstractFileCollection extends AbstractLazyCollection implements FileColle
$length = $criteria->getMaxResults();
if ($offset || $length) {
- $filtered = array_slice($filtered, (int)$offset, $length);
+ $filtered = \array_slice($filtered, (int)$offset, $length);
}
return new ArrayCollection($filtered);
diff --git a/system/src/Grav/Framework/Collection/AbstractIndexCollection.php b/system/src/Grav/Framework/Collection/AbstractIndexCollection.php
new file mode 100644
index 0000000..7299c09
--- /dev/null
+++ b/system/src/Grav/Framework/Collection/AbstractIndexCollection.php
@@ -0,0 +1,510 @@
+entries = $entries;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function toArray()
+ {
+ return $this->loadElements($this->entries);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function first()
+ {
+ $value = reset($this->entries);
+ $key = key($this->entries);
+
+ return $this->loadElement($key, $value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function last()
+ {
+ $value = end($this->entries);
+ $key = key($this->entries);
+
+ return $this->loadElement($key, $value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function key()
+ {
+ return key($this->entries);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function next()
+ {
+ $value = next($this->entries);
+ $key = key($this->entries);
+
+ return $this->loadElement($key, $value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function current()
+ {
+ $value = current($this->entries);
+ $key = key($this->entries);
+
+ return $this->loadElement($key, $value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function remove($key)
+ {
+ if (!array_key_exists($key, $this->entries)) {
+ return null;
+ }
+
+ $value = $this->entries[$key];
+ unset($this->entries[$key]);
+
+ return $this->loadElement($key, $value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function removeElement($element)
+ {
+ $key = $this->isAllowedElement($element) ? $element->getKey() : null;
+
+ if (!$key || !isset($this->entries[$key])) {
+ return false;
+ }
+
+ unset($this->entries[$key]);
+
+ return true;
+ }
+
+ /**
+ * Required by interface ArrayAccess.
+ *
+ * {@inheritDoc}
+ */
+ public function offsetExists($offset)
+ {
+ return $this->containsKey($offset);
+ }
+
+ /**
+ * Required by interface ArrayAccess.
+ *
+ * {@inheritDoc}
+ */
+ public function offsetGet($offset)
+ {
+ return $this->get($offset);
+ }
+
+ /**
+ * Required by interface ArrayAccess.
+ *
+ * {@inheritDoc}
+ */
+ public function offsetSet($offset, $value)
+ {
+ if (null === $offset) {
+ $this->add($value);
+ }
+
+ $this->set($offset, $value);
+ }
+
+ /**
+ * Required by interface ArrayAccess.
+ *
+ * {@inheritDoc}
+ */
+ public function offsetUnset($offset)
+ {
+ return $this->remove($offset);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function containsKey($key)
+ {
+ return isset($this->entries[$key]) || array_key_exists($key, $this->entries);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function contains($element)
+ {
+ $key = $this->isAllowedElement($element) ? $element->getKey() : null;
+
+ return $key && isset($this->entries[$key]);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function exists(Closure $p)
+ {
+ return $this->loadCollection($this->entries)->exists($p);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function indexOf($element)
+ {
+ $key = $this->isAllowedElement($element) ? $element->getKey() : null;
+
+ return $key && isset($this->entries[$key]) ? $key : null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function get($key)
+ {
+ if (!isset($this->entries[$key])) {
+ return null;
+ }
+
+ return $this->loadElement($key, $this->entries[$key]);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getKeys()
+ {
+ return array_keys($this->entries);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getValues()
+ {
+ return array_values($this->loadElements($this->entries));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function count()
+ {
+ return \count($this->entries);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function set($key, $value)
+ {
+ if (!$this->isAllowedElement($value)) {
+ throw new \InvalidArgumentException('Invalid argument $value');
+ }
+
+ if ($key !== $value->getKey()) {
+ $value->setKey($key);
+ }
+
+ $this->entries[$key] = $this->getElementMeta($value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function add($element)
+ {
+ if (!$this->isAllowedElement($element)) {
+ throw new \InvalidArgumentException('Invalid argument $element');
+ }
+
+ $this->entries[$element->getKey()] = $this->getElementMeta($element);
+
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function isEmpty()
+ {
+ return empty($this->entries);
+ }
+
+ /**
+ * Required by interface IteratorAggregate.
+ *
+ * {@inheritDoc}
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->loadElements());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function map(Closure $func)
+ {
+ return $this->loadCollection($this->entries)->map($func);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function filter(Closure $p)
+ {
+ return $this->loadCollection($this->entries)->filter($p);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function forAll(Closure $p)
+ {
+ return $this->loadCollection($this->entries)->forAll($p);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function partition(Closure $p)
+ {
+ return $this->loadCollection($this->entries)->partition($p);
+ }
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return __CLASS__ . '@' . spl_object_hash($this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function clear()
+ {
+ $this->entries = [];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function slice($offset, $length = null)
+ {
+ return $this->loadElements(\array_slice($this->entries, $offset, $length, true));
+ }
+
+ /**
+ * @param int $start
+ * @param int|null $limit
+ * @return static
+ */
+ public function limit($start, $limit = null)
+ {
+ return $this->createFrom(\array_slice($this->entries, $start, $limit, true));
+ }
+
+ /**
+ * Reverse the order of the items.
+ *
+ * @return static
+ */
+ public function reverse()
+ {
+ return $this->createFrom(array_reverse($this->entries));
+ }
+
+ /**
+ * Shuffle items.
+ *
+ * @return static
+ */
+ public function shuffle()
+ {
+ $keys = $this->getKeys();
+ shuffle($keys);
+
+ return $this->createFrom(array_replace(array_flip($keys), $this->entries));
+ }
+
+ /**
+ * Select items from collection.
+ *
+ * Collection is returned in the order of $keys given to the function.
+ *
+ * @param array $keys
+ * @return static
+ */
+ public function select(array $keys)
+ {
+ $list = [];
+ foreach ($keys as $key) {
+ if (isset($this->entries[$key])) {
+ $list[$key] = $this->entries[$key];
+ }
+ }
+
+ return $this->createFrom($list);
+ }
+
+ /**
+ * Un-select items from collection.
+ *
+ * @param array $keys
+ * @return static
+ */
+ public function unselect(array $keys)
+ {
+ return $this->select(array_diff($this->getKeys(), $keys));
+ }
+
+ /**
+ * Split collection into chunks.
+ *
+ * @param int $size Size of each chunk.
+ * @return array
+ */
+ public function chunk($size)
+ {
+ return $this->loadCollection($this->entries)->chunk($size);
+ }
+
+ /**
+ * @return string
+ */
+ public function serialize()
+ {
+ return serialize(['entries' => $this->entries]);
+ }
+
+ /**
+ * @param string $serialized
+ */
+ public function unserialize($serialized)
+ {
+ $data = unserialize($serialized, ['allowed_classes' => false]);
+
+ $this->entries = $data['entries'];
+ }
+
+ /**
+ * Implementes JsonSerializable interface.
+ *
+ * @return array
+ */
+ public function jsonSerialize()
+ {
+ return $this->loadCollection()->jsonSerialize();
+ }
+
+ /**
+ * Creates a new instance from the specified elements.
+ *
+ * This method is provided for derived classes to specify how a new
+ * instance should be created when constructor semantics have changed.
+ *
+ * @param array $entries Elements.
+ *
+ * @return static
+ */
+ protected function createFrom(array $entries)
+ {
+ return new static($entries);
+ }
+
+ /**
+ * @return array
+ */
+ protected function getEntries() : array
+ {
+ return $this->entries;
+ }
+
+ /**
+ * @param array $entries
+ */
+ protected function setEntries(array $entries) : void
+ {
+ $this->entries = $entries;
+ }
+
+ /**
+ * @param string $key
+ * @param mixed $value
+ * @return mixed|null
+ */
+ abstract protected function loadElement($key, $value);
+
+ /**
+ * @param array|null $entries
+ * @return array
+ */
+ abstract protected function loadElements(array $entries = null) : array;
+
+ /**
+ * @param array|null $entries
+ * @return CollectionInterface
+ */
+ abstract protected function loadCollection(array $entries = null) : CollectionInterface;
+
+ /**
+ * @param mixed $value
+ * @return bool
+ */
+ abstract protected function isAllowedElement($value) : bool;
+
+ /**
+ * @param mixed $element
+ * @return mixed
+ */
+ abstract protected function getElementMeta($element);
+}
diff --git a/system/src/Grav/Framework/Collection/AbstractLazyCollection.php b/system/src/Grav/Framework/Collection/AbstractLazyCollection.php
index af2f696..0653caa 100644
--- a/system/src/Grav/Framework/Collection/AbstractLazyCollection.php
+++ b/system/src/Grav/Framework/Collection/AbstractLazyCollection.php
@@ -1,8 +1,9 @@
collection->chunk($size);
}
+ /**
+ * {@inheritDoc}
+ */
+ public function select(array $keys)
+ {
+ $this->initialize();
+ return $this->collection->select($keys);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function unselect(array $keys)
+ {
+ $this->initialize();
+ return $this->collection->unselect($keys);
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/system/src/Grav/Framework/Collection/ArrayCollection.php b/system/src/Grav/Framework/Collection/ArrayCollection.php
index 4c922f4..d3a2002 100644
--- a/system/src/Grav/Framework/Collection/ArrayCollection.php
+++ b/system/src/Grav/Framework/Collection/ArrayCollection.php
@@ -1,8 +1,9 @@
toArray(), $size, true);
}
+ /**
+ * Select items from collection.
+ *
+ * Collection is returned in the order of $keys given to the function.
+ *
+ * @param array $keys
+ * @return static
+ */
+ public function select(array $keys)
+ {
+ $list = [];
+ foreach ($keys as $key) {
+ if ($this->containsKey($key)) {
+ $list[$key] = $this->get($key);
+ }
+ }
+
+ return $this->createFrom($list);
+ }
+
+ /**
+ * Un-select items from collection.
+ *
+ * @param array $keys
+ * @return static
+ */
+ public function unselect(array $keys)
+ {
+ return $this->select(array_diff($this->getKeys(), $keys));
+ }
+
/**
* Implementes JsonSerializable interface.
*
diff --git a/system/src/Grav/Framework/Collection/CollectionInterface.php b/system/src/Grav/Framework/Collection/CollectionInterface.php
index 5f5e1fc..9239d4a 100644
--- a/system/src/Grav/Framework/Collection/CollectionInterface.php
+++ b/system/src/Grav/Framework/Collection/CollectionInterface.php
@@ -1,8 +1,9 @@
get_class($this),
+ '_type' => \get_class($this),
'_version' => $this->version,
- 'id' => $this->id
+ 'id' => $this->id,
+ 'cached' => $this->cached
];
if ($this->checksum) {
@@ -163,8 +166,9 @@ class ContentBlock implements ContentBlockInterface
{
$this->checkVersion($serialized);
- $this->id = isset($serialized['id']) ? $serialized['id'] : $this->generateId();
- $this->checksum = isset($serialized['checksum']) ? $serialized['checksum'] : null;
+ $this->id = $serialized['id'] ?? $this->generateId();
+ $this->checksum = $serialized['checksum'] ?? null;
+ $this->cached = $serialized['cached'] ?? null;
if (isset($serialized['content'])) {
$this->setContent($serialized['content']);
@@ -176,6 +180,34 @@ class ContentBlock implements ContentBlockInterface
}
}
+ /**
+ * @return bool
+ */
+ public function isCached()
+ {
+ if (!$this->cached) {
+ return false;
+ }
+
+ foreach ($this->blocks as $block) {
+ if (!$block->isCached()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @return $this
+ */
+ public function disableCache()
+ {
+ $this->cached = false;
+
+ return $this;
+ }
+
/**
* @param string $checksum
* @return $this
diff --git a/system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php b/system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php
index fff8f2e..330901e 100644
--- a/system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php
+++ b/system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php
@@ -1,8 +1,9 @@
(string) $element];
}
if (empty($element['href'])) {
@@ -175,7 +176,7 @@ class HtmlBlock extends ContentBlock implements HtmlBlockInterface
*/
public function addInlineStyle($element, $priority = 0, $location = 'head')
{
- if (!is_array($element)) {
+ if (!\is_array($element)) {
$element = ['content' => (string) $element];
}
if (empty($element['content'])) {
@@ -206,7 +207,7 @@ class HtmlBlock extends ContentBlock implements HtmlBlockInterface
*/
public function addScript($element, $priority = 0, $location = 'head')
{
- if (!is_array($element)) {
+ if (!\is_array($element)) {
$element = ['src' => (string) $element];
}
if (empty($element['src'])) {
@@ -243,7 +244,7 @@ class HtmlBlock extends ContentBlock implements HtmlBlockInterface
*/
public function addInlineScript($element, $priority = 0, $location = 'head')
{
- if (!is_array($element)) {
+ if (!\is_array($element)) {
$element = ['content' => (string) $element];
}
if (empty($element['content'])) {
@@ -274,7 +275,7 @@ class HtmlBlock extends ContentBlock implements HtmlBlockInterface
*/
public function addHtml($html, $priority = 0, $location = 'bottom')
{
- if (empty($html) || !is_string($html)) {
+ if (empty($html) || !\is_string($html)) {
return false;
}
if (!isset($this->html[$location])) {
@@ -302,7 +303,7 @@ class HtmlBlock extends ContentBlock implements HtmlBlockInterface
];
foreach ($this->blocks as $block) {
- if ($block instanceof HtmlBlock) {
+ if ($block instanceof self) {
$blockAssets = $block->getAssetsFast();
$assets['frameworks'] += $blockAssets['frameworks'];
diff --git a/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php b/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php
index 204e199..ee486d2 100644
--- a/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php
+++ b/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php
@@ -1,8 +1,9 @@
offsetGet($id);
+ }
+
+ public function has($id): bool
+ {
+ return $this->offsetExists($id);
+ }
+}
\ No newline at end of file
diff --git a/system/src/Grav/Framework/File/AbstractFile.php b/system/src/Grav/Framework/File/AbstractFile.php
new file mode 100644
index 0000000..0411701
--- /dev/null
+++ b/system/src/Grav/Framework/File/AbstractFile.php
@@ -0,0 +1,425 @@
+filesystem = $filesystem ?? Filesystem::getInstance();
+ $this->setFilepath($filepath);
+ }
+
+ /**
+ * Unlock file when the object gets destroyed.
+ */
+ public function __destruct()
+ {
+ if ($this->isLocked()) {
+ $this->unlock();
+ }
+ }
+
+ public function __clone()
+ {
+ $this->handle = null;
+ $this->locked = false;
+ }
+
+ /**
+ * @return string
+ */
+ public function serialize(): string
+ {
+ return serialize($this->doSerialize());
+ }
+
+ /**
+ * @param string $serialized
+ */
+ public function unserialize($serialized): void
+ {
+ $this->doUnserialize(unserialize($serialized, ['allowed_classes' => false]));
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::getFilePath()
+ */
+ public function getFilePath(): string
+ {
+ return $this->filepath;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::getPath()
+ */
+ public function getPath(): string
+ {
+ if (null === $this->path) {
+ $this->setPathInfo();
+ }
+
+ return $this->path;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::getFilename()
+ */
+ public function getFilename(): string
+ {
+ if (null === $this->filename) {
+ $this->setPathInfo();
+ }
+
+ return $this->filename;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::getBasename()
+ */
+ public function getBasename(): string
+ {
+ if (null === $this->basename) {
+ $this->setPathInfo();
+ }
+
+ return $this->basename;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::getExtension()
+ */
+ public function getExtension(bool $withDot = false): string
+ {
+ if (null === $this->extension) {
+ $this->setPathInfo();
+ }
+
+ return ($withDot ? '.' : '') . $this->extension;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::exists()
+ */
+ public function exists(): bool
+ {
+ return is_file($this->filepath);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::getCreationTime()
+ */
+ public function getCreationTime(): int
+ {
+ return is_file($this->filepath) ? filectime($this->filepath) : time();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::getModificationTime()
+ */
+ public function getModificationTime(): int
+ {
+ return is_file($this->filepath) ? filemtime($this->filepath) : time();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::lock()
+ */
+ public function lock(bool $block = true): bool
+ {
+ if (!$this->handle) {
+ if (!$this->mkdir($this->getPath())) {
+ throw new \RuntimeException('Creating directory failed for ' . $this->filepath);
+ }
+ $this->handle = @fopen($this->filepath, 'cb+');
+ if (!$this->handle) {
+ $error = error_get_last();
+
+ throw new \RuntimeException("Opening file for writing failed on error {$error['message']}");
+ }
+ }
+
+ $lock = $block ? LOCK_EX : LOCK_EX | LOCK_NB;
+
+ // Some filesystems do not support file locks, only fail if another process holds the lock.
+ $this->locked = flock($this->handle, $lock, $wouldblock) || !$wouldblock;
+
+ return $this->locked;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::unlock()
+ */
+ public function unlock(): bool
+ {
+ if (!$this->handle) {
+ return false;
+ }
+
+ if ($this->locked) {
+ flock($this->handle, LOCK_UN);
+ $this->locked = false;
+ }
+
+ fclose($this->handle);
+ $this->handle = null;
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::isLocked()
+ */
+ public function isLocked(): bool
+ {
+ return $this->locked;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::isReadable()
+ */
+ public function isReadable(): bool
+ {
+ return is_readable($this->filepath) && is_file($this->filepath);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::isWritable()
+ */
+ public function isWritable(): bool
+ {
+ if (!file_exists($this->filepath)) {
+ return $this->isWritablePath($this->getPath());
+ }
+
+ return is_writable($this->filepath) && is_file($this->filepath);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::load()
+ */
+ public function load()
+ {
+ return file_get_contents($this->filepath);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::save()
+ */
+ public function save($data): void
+ {
+ $filepath = $this->filepath;
+ $dir = $this->getPath();
+
+ if (!$this->mkdir($dir)) {
+ throw new \RuntimeException('Creating directory failed for ' . $filepath);
+ }
+
+ try {
+ if ($this->handle) {
+ $tmp = true;
+ // As we are using non-truncating locking, make sure that the file is empty before writing.
+ if (@ftruncate($this->handle, 0) === false || @fwrite($this->handle, $data) === false) {
+ // Writing file failed, throw an error.
+ $tmp = false;
+ }
+ } else {
+ // Create file with a temporary name and rename it to make the save action atomic.
+ $tmp = $this->tempname($filepath);
+ if (@file_put_contents($tmp, $data) === false) {
+ $tmp = false;
+ } elseif (@rename($tmp, $filepath) === false) {
+ @unlink($tmp);
+ $tmp = false;
+ }
+ }
+ } catch (\Exception $e) {
+ $tmp = false;
+ }
+
+ if ($tmp === false) {
+ throw new \RuntimeException('Failed to save file ' . $filepath);
+ }
+
+ // Touch the directory as well, thus marking it modified.
+ @touch($dir);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::rename()
+ */
+ public function rename(string $path): bool
+ {
+ if ($this->exists() && !@rename($this->filepath, $path)) {
+ return false;
+ }
+
+ $this->setFilepath($path);
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::delete()
+ */
+ public function delete(): bool
+ {
+ return @unlink($this->filepath);
+ }
+
+ /**
+ * @param string $dir
+ * @return bool
+ * @throws \RuntimeException
+ * @internal
+ */
+ protected function mkdir(string $dir): bool
+ {
+ // Silence error for open_basedir; should fail in mkdir instead.
+ if (@is_dir($dir)) {
+ return true;
+ }
+
+ $success = @mkdir($dir, 0777, true);
+
+ if (!$success) {
+ // Take yet another look, make sure that the folder doesn't exist.
+ clearstatcache(true, $dir);
+ if (!@is_dir($dir)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @return array
+ */
+ protected function doSerialize(): array
+ {
+ return [
+ 'filepath' => $this->filepath
+ ];
+ }
+
+ /**
+ * @param array $serialized
+ */
+ protected function doUnserialize(array $serialized): void
+ {
+ $this->setFilepath($serialized['filepath']);
+ }
+
+ /**
+ * @param string $filepath
+ */
+ protected function setFilepath(string $filepath): void
+ {
+ $this->filepath = $filepath;
+ $this->filename = null;
+ $this->basename = null;
+ $this->path = null;
+ $this->extension = null;
+ }
+
+ protected function setPathInfo(): void
+ {
+ $pathInfo = $this->filesystem->pathinfo($this->filepath);
+
+ $this->filename = $pathInfo['filename'] ?? null;
+ $this->basename = $pathInfo['basename'] ?? null;
+ $this->path = $pathInfo['dirname'] ?? null;
+ $this->extension = $pathInfo['extension'] ?? null;
+ }
+
+ /**
+ * @param string $dir
+ * @return bool
+ * @internal
+ */
+ protected function isWritablePath(string $dir): bool
+ {
+ if ($dir === '') {
+ return false;
+ }
+
+ if (!file_exists($dir)) {
+ // Recursively look up in the directory tree.
+ return $this->isWritablePath($this->filesystem->parent($dir));
+ }
+
+ return is_dir($dir) && is_writable($dir);
+ }
+
+ /**
+ * @param string $filename
+ * @param int $length
+ * @return string
+ */
+ protected function tempname(string $filename, int $length = 5)
+ {
+ do {
+ $test = $filename . substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, $length);
+ } while (file_exists($test));
+
+ return $test;
+ }
+}
diff --git a/system/src/Grav/Framework/File/CsvFile.php b/system/src/Grav/Framework/File/CsvFile.php
new file mode 100644
index 0000000..53af766
--- /dev/null
+++ b/system/src/Grav/Framework/File/CsvFile.php
@@ -0,0 +1,31 @@
+formatter = $formatter;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::load()
+ */
+ public function load()
+ {
+ $raw = parent::load();
+
+ try {
+ return $raw !== false ? $this->formatter->decode($raw) : false;
+ } catch (RuntimeException $e) {
+ throw new RuntimeException(sprintf("Failed to load file '%s': %s", $this->getFilePath(), $e->getMessage()), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileInterface::save()
+ */
+ public function save($data): void
+ {
+ if (\is_string($data)) {
+ // Make sure that the string is valid data.
+ try {
+ $this->formatter->decode($data);
+ } catch (RuntimeException $e) {
+ throw new RuntimeException(sprintf("Failed to save file '%s': %s", $this->getFilePath(), $e->getMessage()), $e->getCode(), $e);
+ }
+ $encoded = $data;
+ } else {
+ $encoded = $this->formatter->encode($data);
+ }
+
+ parent::save($encoded);
+ }
+}
diff --git a/system/src/Grav/Framework/File/File.php b/system/src/Grav/Framework/File/File.php
new file mode 100644
index 0000000..c1a2173
--- /dev/null
+++ b/system/src/Grav/Framework/File/File.php
@@ -0,0 +1,37 @@
+config = $config;
+ }
+
+ /**
+ * @return string
+ */
+ public function serialize(): string
+ {
+ return serialize($this->doSerialize());
+ }
+
+ /**
+ * @param string $serialized
+ */
+ public function unserialize($serialized): void
+ {
+ $this->doUnserialize(unserialize($serialized, ['allowed_classes' => false]));
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileFormatterInterface::getDefaultFileExtension()
+ */
+ public function getDefaultFileExtension(): string
+ {
+ $extensions = $this->getSupportedFileExtensions();
+
+ // Call fails on bad configuration.
+ return reset($extensions);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileFormatterInterface::getSupportedFileExtensions()
+ */
+ public function getSupportedFileExtensions(): array
+ {
+ $extensions = $this->getConfig('file_extension');
+
+ // Call fails on bad configuration.
+ return \is_string($extensions) ? [$extensions] : $extensions;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileFormatterInterface::encode()
+ */
+ abstract public function encode($data): string;
+
+ /**
+ * {@inheritdoc}
+ * @see FileFormatterInterface::decode()
+ */
+ abstract public function decode($data);
+
+ /**
+ * Get either full configuration or a single option.
+ *
+ * @param string|null $name Configuration option (optional)
+ * @return mixed
+ */
+ protected function getConfig(string $name = null)
+ {
+ if (null !== $name) {
+ return $this->config[$name] ?? null;
+ }
+
+ return $this->config;
+ }
+
+ /**
+ * @return array
+ */
+ protected function doSerialize(): array
+ {
+ return ['config' => $this->config];
+ }
+
+ /**
+ * Note: if overridden, make sure you call parent::doUnserialize()
+ *
+ * @param array $serialized
+ */
+ protected function doUnserialize(array $serialized): void
+ {
+ $this->config = $serialized['config'];
+ }
+}
diff --git a/system/src/Grav/Framework/File/Formatter/CsvFormatter.php b/system/src/Grav/Framework/File/Formatter/CsvFormatter.php
new file mode 100644
index 0000000..2a70025
--- /dev/null
+++ b/system/src/Grav/Framework/File/Formatter/CsvFormatter.php
@@ -0,0 +1,90 @@
+ ['.csv', '.tsv'],
+ 'delimiter' => ','
+ ];
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Returns delimiter used to both encode and decode CSV.
+ *
+ * @return string
+ */
+ public function getDelimiter(): string
+ {
+ // Call fails on bad configuration.
+ return $this->getConfig('delimiter');
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileFormatterInterface::encode()
+ */
+ public function encode($data, $delimiter = null): string
+ {
+ if (count($data) === 0) {
+ return '';
+ }
+ $delimiter = $delimiter ?? $this->getDelimiter();
+ $header = array_keys(reset($data));
+
+ // Encode the field names
+ $string = implode($delimiter, $header). "\n";
+
+ // Encode the data
+ foreach ($data as $row) {
+ $string .= implode($delimiter, $row). "\n";
+ }
+
+ return $string;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileFormatterInterface::decode()
+ */
+ public function decode($data, $delimiter = null): array
+ {
+ $delimiter = $delimiter ?? $this->getDelimiter();
+ $lines = preg_split('/\r\n|\r|\n/', $data);
+
+ if ($lines === false) {
+ throw new \RuntimeException('Decoding CSV failed');
+ }
+
+ // Get the field names
+ $header = str_getcsv(array_shift($lines), $delimiter);
+
+ // Get the data
+ $list = [];
+ foreach ($lines as $line) {
+ $list[] = array_combine($header, str_getcsv($line, $delimiter));
+ }
+
+ return $list;
+ }
+}
diff --git a/system/src/Grav/Framework/File/Formatter/FormatterInterface.php b/system/src/Grav/Framework/File/Formatter/FormatterInterface.php
index 273bf78..a8d5bc5 100644
--- a/system/src/Grav/Framework/File/Formatter/FormatterInterface.php
+++ b/system/src/Grav/Framework/File/Formatter/FormatterInterface.php
@@ -1,44 +1,10 @@
config = $config + [
- 'file_extension' => '.ini'
- ];
- }
+ $config += [
+ 'file_extension' => '.ini'
+ ];
- /**
- * @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
- */
- public function getFileExtension()
- {
- user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDefaultFileExtension() method instead', E_USER_DEPRECATED);
-
- return $this->getDefaultFileExtension();
+ parent::__construct($config);
}
/**
* {@inheritdoc}
+ * @see FileFormatterInterface::encode()
*/
- public function getDefaultFileExtension()
- {
- $extensions = $this->getSupportedFileExtensions();
-
- return (string) reset($extensions);
- }
-
- /**
- * {@inheritdoc}
- */
- public function getSupportedFileExtensions()
- {
- return (array) $this->config['file_extension'];
- }
-
- /**
- * {@inheritdoc}
- */
- public function encode($data)
+ public function encode($data): string
{
$string = '';
foreach ($data as $key => $value) {
@@ -71,8 +48,9 @@ class IniFormatter implements FormatterInterface
/**
* {@inheritdoc}
+ * @see FileFormatterInterface::decode()
*/
- public function decode($data)
+ public function decode($data): array
{
$decoded = @parse_ini_string($data);
diff --git a/system/src/Grav/Framework/File/Formatter/JsonFormatter.php b/system/src/Grav/Framework/File/Formatter/JsonFormatter.php
index 4a2d2fa..1ae16ae 100644
--- a/system/src/Grav/Framework/File/Formatter/JsonFormatter.php
+++ b/system/src/Grav/Framework/File/Formatter/JsonFormatter.php
@@ -1,64 +1,83 @@
config = $config + [
+ $config += [
'file_extension' => '.json',
'encode_options' => 0,
- 'decode_assoc' => true
+ 'decode_assoc' => true,
+ 'decode_depth' => 512,
+ 'decode_options' => 0
];
+
+ parent::__construct($config);
}
/**
- * @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
+ * Returns options used in encode() function.
+ *
+ * @return int
*/
- public function getFileExtension()
+ public function getEncodeOptions(): int
{
- user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDefaultFileExtension() method instead', E_USER_DEPRECATED);
+ return $this->getConfig('encode_options');
+ }
- return $this->getDefaultFileExtension();
+ /**
+ * Returns options used in decode() function.
+ *
+ * @return int
+ */
+ public function getDecodeOptions(): int
+ {
+ return $this->getConfig('decode_options');
+ }
+
+ /**
+ * Returns recursion depth used in decode() function.
+ *
+ * @return int
+ */
+ public function getDecodeDepth(): int
+ {
+ return $this->getConfig('decode_depth');
+ }
+
+ /**
+ * Returns true if JSON objects will be converted into associative arrays.
+ *
+ * @return bool
+ */
+ public function getDecodeAssoc(): bool
+ {
+ return $this->getConfig('decode_assoc');
}
/**
* {@inheritdoc}
+ * @see FileFormatterInterface::encode()
*/
- public function getDefaultFileExtension()
+ public function encode($data): string
{
- $extensions = $this->getSupportedFileExtensions();
+ $encoded = @json_encode($data, $this->getEncodeOptions());
- return (string) reset($extensions);
- }
-
- /**
- * {@inheritdoc}
- */
- public function getSupportedFileExtensions()
- {
- return (array) $this->config['file_extension'];
- }
-
- /**
- * {@inheritdoc}
- */
- public function encode($data)
- {
- $encoded = @json_encode($data, $this->config['encode_options']);
-
- if ($encoded === false) {
- throw new \RuntimeException('Encoding JSON failed');
+ if ($encoded === false && json_last_error() !== JSON_ERROR_NONE) {
+ throw new \RuntimeException('Encoding JSON failed: ' . json_last_error_msg());
}
return $encoded;
@@ -66,13 +85,14 @@ class JsonFormatter implements FormatterInterface
/**
* {@inheritdoc}
+ * @see FileFormatterInterface::decode()
*/
public function decode($data)
{
- $decoded = @json_decode($data, $this->config['decode_assoc']);
+ $decoded = @json_decode($data, $this->getDecodeAssoc(), $this->getDecodeDepth(), $this->getDecodeOptions());
- if ($decoded === false) {
- throw new \RuntimeException('Decoding JSON failed');
+ if (null === $decoded && json_last_error() !== JSON_ERROR_NONE) {
+ throw new \RuntimeException('Decoding JSON failed: ' . json_last_error_msg());
}
return $decoded;
diff --git a/system/src/Grav/Framework/File/Formatter/MarkdownFormatter.php b/system/src/Grav/Framework/File/Formatter/MarkdownFormatter.php
index 415f7ce..a2f84e8 100644
--- a/system/src/Grav/Framework/File/Formatter/MarkdownFormatter.php
+++ b/system/src/Grav/Framework/File/Formatter/MarkdownFormatter.php
@@ -1,23 +1,26 @@
config = $config + [
+ $config += [
'file_extension' => '.md',
'header' => 'header',
'body' => 'markdown',
@@ -25,44 +28,59 @@ class MarkdownFormatter implements FormatterInterface
'yaml' => ['inline' => 20]
];
- $this->headerFormatter = $headerFormatter ?: new YamlFormatter($this->config['yaml']);
+ parent::__construct($config);
+
+ $this->headerFormatter = $headerFormatter ?: new YamlFormatter($config['yaml']);
}
/**
- * @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
+ * Returns header field used in both encode() and decode().
+ *
+ * @return string
*/
- public function getFileExtension()
+ public function getHeaderField(): string
{
- user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDefaultFileExtension() method instead', E_USER_DEPRECATED);
+ return $this->getConfig('header');
+ }
- return $this->getDefaultFileExtension();
+ /**
+ * Returns body field used in both encode() and decode().
+ *
+ * @return string
+ */
+ public function getBodyField(): string
+ {
+ return $this->getConfig('body');
+ }
+
+ /**
+ * Returns raw field used in both encode() and decode().
+ *
+ * @return string
+ */
+ public function getRawField(): string
+ {
+ return $this->getConfig('raw');
+ }
+
+ /**
+ * Returns header formatter object used in both encode() and decode().
+ *
+ * @return FileFormatterInterface
+ */
+ public function getHeaderFormatter(): FileFormatterInterface
+ {
+ return $this->headerFormatter;
}
/**
* {@inheritdoc}
+ * @see FileFormatterInterface::encode()
*/
- public function getDefaultFileExtension()
+ public function encode($data): string
{
- $extensions = $this->getSupportedFileExtensions();
-
- return (string) reset($extensions);
- }
-
- /**
- * {@inheritdoc}
- */
- public function getSupportedFileExtensions()
- {
- return (array) $this->config['file_extension'];
- }
-
- /**
- * {@inheritdoc}
- */
- public function encode($data)
- {
- $headerVar = $this->config['header'];
- $bodyVar = $this->config['body'];
+ $headerVar = $this->getHeaderField();
+ $bodyVar = $this->getBodyField();
$header = isset($data[$headerVar]) ? (array) $data[$headerVar] : [];
$body = isset($data[$bodyVar]) ? (string) $data[$bodyVar] : '';
@@ -70,7 +88,7 @@ class MarkdownFormatter implements FormatterInterface
// Create Markdown file with YAML header.
$encoded = '';
if ($header) {
- $encoded = "---\n" . trim($this->headerFormatter->encode($data['header'])) . "\n---\n\n";
+ $encoded = "---\n" . trim($this->getHeaderFormatter()->encode($data['header'])) . "\n---\n\n";
}
$encoded .= $body;
@@ -82,13 +100,15 @@ class MarkdownFormatter implements FormatterInterface
/**
* {@inheritdoc}
+ * @see FileFormatterInterface::decode()
*/
- public function decode($data)
+ public function decode($data): array
{
- $headerVar = $this->config['header'];
- $bodyVar = $this->config['body'];
- $rawVar = $this->config['raw'];
+ $headerVar = $this->getHeaderField();
+ $bodyVar = $this->getBodyField();
+ $rawVar = $this->getRawField();
+ // Define empty content
$content = [
$headerVar => [],
$bodyVar => ''
@@ -109,7 +129,7 @@ class MarkdownFormatter implements FormatterInterface
if ($rawVar) {
$content[$rawVar] = $frontmatter;
}
- $content[$headerVar] = $this->headerFormatter->decode($frontmatter);
+ $content[$headerVar] = $this->getHeaderFormatter()->decode($frontmatter);
$content[$bodyVar] = $matches[2];
}
diff --git a/system/src/Grav/Framework/File/Formatter/SerializeFormatter.php b/system/src/Grav/Framework/File/Formatter/SerializeFormatter.php
index 8830218..73ed812 100644
--- a/system/src/Grav/Framework/File/Formatter/SerializeFormatter.php
+++ b/system/src/Grav/Framework/File/Formatter/SerializeFormatter.php
@@ -1,73 +1,64 @@
config = $config + [
- 'file_extension' => '.ser'
- ];
+ $config += [
+ 'file_extension' => '.ser',
+ 'decode_options' => ['allowed_classes' => [\stdClass::class]]
+ ];
+
+ parent::__construct($config);
}
/**
- * @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
+ * Returns options used in decode().
+ *
+ * By default only allow stdClass class.
+ *
+ * @return array|bool
*/
- public function getFileExtension()
+ public function getOptions()
{
- user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDefaultFileExtension() method instead', E_USER_DEPRECATED);
-
- return $this->getDefaultFileExtension();
+ return $this->getConfig('decode_options');
}
/**
* {@inheritdoc}
+ * @see FileFormatterInterface::encode()
*/
- public function getDefaultFileExtension()
- {
- $extensions = $this->getSupportedFileExtensions();
-
- return (string) reset($extensions);
- }
-
- /**
- * {@inheritdoc}
- */
- public function getSupportedFileExtensions()
- {
- return (array) $this->config['file_extension'];
- }
-
- /**
- * {@inheritdoc}
- */
- public function encode($data)
+ public function encode($data): string
{
return serialize($this->preserveLines($data, ["\n", "\r"], ['\\n', '\\r']));
}
/**
* {@inheritdoc}
+ * @see FileFormatterInterface::decode()
*/
public function decode($data)
{
- $decoded = @unserialize($data);
+ $decoded = @unserialize($data, $this->getOptions());
- if ($decoded === false) {
+ if ($decoded === false && $data !== serialize(false)) {
throw new \RuntimeException('Decoding serialized data failed');
}
@@ -82,11 +73,11 @@ class SerializeFormatter implements FormatterInterface
* @param array $replace
* @return mixed
*/
- protected function preserveLines($data, $search, $replace)
+ protected function preserveLines($data, array $search, array $replace)
{
- if (is_string($data)) {
+ if (\is_string($data)) {
$data = str_replace($search, $replace, $data);
- } elseif (is_array($data)) {
+ } elseif (\is_array($data)) {
foreach ($data as &$value) {
$value = $this->preserveLines($value, $search, $replace);
}
diff --git a/system/src/Grav/Framework/File/Formatter/YamlFormatter.php b/system/src/Grav/Framework/File/Formatter/YamlFormatter.php
index d59f9e7..a519383 100644
--- a/system/src/Grav/Framework/File/Formatter/YamlFormatter.php
+++ b/system/src/Grav/Framework/File/Formatter/YamlFormatter.php
@@ -1,72 +1,80 @@
config = $config + [
+ $config += [
'file_extension' => '.yaml',
'inline' => 5,
'indent' => 2,
'native' => true,
'compat' => true
];
+
+ parent::__construct($config);
}
/**
- * @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
+ * @return int
*/
- public function getFileExtension()
+ public function getInlineOption(): int
{
- user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDefaultFileExtension() method instead', E_USER_DEPRECATED);
+ return $this->getConfig('inline');
+ }
- return $this->getDefaultFileExtension();
+ /**
+ * @return int
+ */
+ public function getIndentOption(): int
+ {
+ return $this->getConfig('indent');
+ }
+
+ /**
+ * @return bool
+ */
+ public function useNativeDecoder(): bool
+ {
+ return $this->getConfig('native');
+ }
+
+ /**
+ * @return bool
+ */
+ public function useCompatibleDecoder(): bool
+ {
+ return $this->getConfig('compat');
}
/**
* {@inheritdoc}
+ * @see FileFormatterInterface::encode()
*/
- public function getDefaultFileExtension()
- {
- $extensions = $this->getSupportedFileExtensions();
-
- return (string) reset($extensions);
- }
-
- /**
- * {@inheritdoc}
- */
- public function getSupportedFileExtensions()
- {
- return (array) $this->config['file_extension'];
- }
-
- /**
- * {@inheritdoc}
- */
- public function encode($data, $inline = null, $indent = null)
+ public function encode($data, $inline = null, $indent = null): string
{
try {
- return (string) YamlParser::dump(
+ return YamlParser::dump(
$data,
- $inline ? (int) $inline : $this->config['inline'],
- $indent ? (int) $indent : $this->config['indent'],
+ $inline ? (int) $inline : $this->getInlineOption(),
+ $indent ? (int) $indent : $this->getIndentOption(),
YamlParser::DUMP_EXCEPTION_ON_INVALID_TYPE
);
} catch (DumpException $e) {
@@ -76,26 +84,27 @@ class YamlFormatter implements FormatterInterface
/**
* {@inheritdoc}
+ * @see FileFormatterInterface::decode()
*/
- public function decode($data)
+ public function decode($data): array
{
// Try native PECL YAML PHP extension first if available.
- if ($this->config['native'] && function_exists('yaml_parse')) {
+ if (\function_exists('yaml_parse') && $this->useNativeDecoder()) {
// Safely decode YAML.
$saved = @ini_get('yaml.decode_php');
- @ini_set('yaml.decode_php', 0);
+ @ini_set('yaml.decode_php', '0');
$decoded = @yaml_parse($data);
@ini_set('yaml.decode_php', $saved);
if ($decoded !== false) {
- return (array) $decoded;
+ return $decoded;
}
}
try {
- return (array) YamlParser::parse($data);
+ return YamlParser::parse($data);
} catch (ParseException $e) {
- if ($this->config['compat']) {
+ if ($this->useCompatibleDecoder()) {
return (array) FallbackYamlParser::parse($data);
}
diff --git a/system/src/Grav/Framework/File/IniFile.php b/system/src/Grav/Framework/File/IniFile.php
new file mode 100644
index 0000000..cc861f9
--- /dev/null
+++ b/system/src/Grav/Framework/File/IniFile.php
@@ -0,0 +1,31 @@
+setNormalization()
+ *
+ * @return Filesystem
+ */
+ public static function getInstance(bool $normalize = null): Filesystem
+ {
+ if ($normalize === true) {
+ $instance = &static::$safe;
+ } elseif ($normalize === false) {
+ $instance = &static::$unsafe;
+ } else {
+ $instance = &static::$default;
+ }
+
+ if (null === $instance) {
+ $instance = new static($normalize);
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Always use Filesystem::getInstance() instead.
+ *
+ * @param bool|null $normalize
+ */
+ protected function __construct(bool $normalize = null)
+ {
+ $this->normalize = $normalize;
+ }
+
+ /**
+ * Set path normalization.
+ *
+ * Default option enables normalization for the streams only, but you can force the normalization to be either
+ * on or off for every path. Disabling path normalization speeds up the calls, but may cause issues if paths were
+ * not normalized.
+ *
+ * @param bool|null $normalize
+ *
+ * @return Filesystem
+ */
+ public function setNormalization(bool $normalize = null): self
+ {
+ return static::getInstance($normalize);
+ }
+
+ /**
+ * Force all paths to be normalized.
+ *
+ * @return static
+ */
+ public function unsafe(): self
+ {
+ return static::getInstance(true);
+ }
+
+ /**
+ * Force all paths not to be normalized (speeds up the calls if given paths are known to be normalized).
+ *
+ * @return static
+ */
+ public function safe(): self
+ {
+ return static::getInstance(false);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FilesystemInterface::parent()
+ */
+ public function parent(string $path, int $levels = 1): string
+ {
+ [$scheme, $path] = $this->getSchemeAndHierarchy($path);
+
+ if ($this->normalize !== false) {
+ $path = $this->normalizePathPart($path);
+ }
+
+ if ($path === '' || $path === '.') {
+ return '';
+ }
+
+ [$scheme, $parent] = $this->dirnameInternal($scheme, $path, $levels);
+
+ return $parent !== $path ? $this->toString($scheme, $parent) : '';
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FilesystemInterface::normalize()
+ */
+ public function normalize(string $path): string
+ {
+ [$scheme, $path] = $this->getSchemeAndHierarchy($path);
+
+ $path = $this->normalizePathPart($path);
+
+ return $this->toString($scheme, $path);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FilesystemInterface::dirname()
+ */
+ public function dirname(string $path, int $levels = 1): string
+ {
+ [$scheme, $path] = $this->getSchemeAndHierarchy($path);
+
+ if ($this->normalize || ($scheme && null === $this->normalize)) {
+ $path = $this->normalizePathPart($path);
+ }
+
+ [$scheme, $path] = $this->dirnameInternal($scheme, $path, $levels);
+
+ return $this->toString($scheme, $path);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FilesystemInterface::pathinfo()
+ */
+ public function pathinfo(string $path, int $options = null)
+ {
+ [$scheme, $path] = $this->getSchemeAndHierarchy($path);
+
+ if ($this->normalize || ($scheme && null === $this->normalize)) {
+ $path = $this->normalizePathPart($path);
+ }
+
+ return $this->pathinfoInternal($scheme, $path, $options);
+ }
+
+ /**
+ * @param string|null $scheme
+ * @param string $path
+ * @param int $levels
+ *
+ * @return array
+ */
+ protected function dirnameInternal(?string $scheme, string $path, int $levels = 1): array
+ {
+ $path = \dirname($path, $levels);
+
+ if (null !== $scheme && $path === '.') {
+ return [$scheme, ''];
+ }
+
+ return [$scheme, $path];
+ }
+
+ /**
+ * @param string|null $scheme
+ * @param string $path
+ * @param int|null $options
+ *
+ * @return array
+ */
+ protected function pathinfoInternal(?string $scheme, string $path, int $options = null)
+ {
+ $info = $options ? \pathinfo($path, $options) : \pathinfo($path);
+
+ if (null !== $scheme) {
+ $info['scheme'] = $scheme;
+ $dirname = isset($info['dirname']) && $info['dirname'] !== '.' ? $info['dirname'] : null;
+
+ if (null !== $dirname) {
+ $info['dirname'] = $scheme . '://' . $dirname;
+ } else {
+ $info = ['dirname' => $scheme . '://'] + $info;
+ }
+ }
+
+ return $info;
+ }
+
+ /**
+ * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> array(file, tmp)).
+ *
+ * @param string $filename
+ *
+ * @return array
+ */
+ protected function getSchemeAndHierarchy(string $filename): array
+ {
+ $components = explode('://', $filename, 2);
+
+ return 2 === \count($components) ? $components : [null, $components[0]];
+ }
+
+ /**
+ * @param string|null $scheme
+ * @param string $path
+ *
+ * @return string
+ */
+ protected function toString(?string $scheme, string $path): string
+ {
+ if ($scheme) {
+ return $scheme . '://' . $path;
+ }
+
+ return $path;
+ }
+
+ /**
+ * @param string $path
+ *
+ * @return string
+ * @throws \RuntimeException
+ */
+ protected function normalizePathPart(string $path): string
+ {
+ // Quick check for empty path.
+ if ($path === '' || $path === '.') {
+ return '';
+ }
+
+ // Quick check for root.
+ if ($path === '/') {
+ return '/';
+ }
+
+ // If the last character is not '/' or any of '\', './', '//' and '..' are not found, path is clean and we're done.
+ if ($path[-1] !== '/' && !preg_match('`(\\\\|\./|//|\.\.)`', $path)) {
+ return $path;
+ }
+
+ // Convert backslashes
+ $path = strtr($path, ['\\' => '/']);
+
+ $parts = explode('/', $path);
+
+ // Keep absolute paths.
+ $root = '';
+ if ($parts[0] === '') {
+ $root = '/';
+ array_shift($parts);
+ }
+
+ $list = [];
+ foreach ($parts as $i => $part) {
+ // Remove empty parts: // and /./
+ if ($part === '' || $part === '.') {
+ continue;
+ }
+
+ // Resolve /../ by removing path part.
+ if ($part === '..') {
+ $test = array_shift($list);
+ if ($test === null) {
+ // Oops, user tried to access something outside of our root folder.
+ throw new \RuntimeException("Bad path {$path}");
+ }
+ }
+
+ $list[] = $part;
+ }
+
+ // Build path back together.
+ return $root . implode('/', $list);
+ }
+}
diff --git a/system/src/Grav/Framework/Filesystem/Interfaces/FilesystemInterface.php b/system/src/Grav/Framework/Filesystem/Interfaces/FilesystemInterface.php
new file mode 100644
index 0000000..7082ebf
--- /dev/null
+++ b/system/src/Grav/Framework/Filesystem/Interfaces/FilesystemInterface.php
@@ -0,0 +1,75 @@
+= 1).
+ *
+ * @return string Returns parent path.
+ * @throws \RuntimeException
+ * @api
+ */
+ public function parent(string $path, int $levels = 1): string;
+
+ /**
+ * Normalize path by cleaning up `\`, `/./`, `//` and `/../`.
+ *
+ * @param string $path A filename or path, does not need to exist as a file.
+ *
+ * @return string Returns normalized path.
+ * @throws \RuntimeException
+ * @api
+ */
+ public function normalize(string $path): string;
+
+ /**
+ * Stream-safe `\dirname()` replacement.
+ *
+ * @see http://php.net/manual/en/function.dirname.php
+ *
+ * @param string $path A filename or path, does not need to exist as a file.
+ * @param int $levels The number of parent directories to go up (>= 1).
+ *
+ * @return string Returns path to the directory.
+ * @throws \RuntimeException
+ * @api
+ */
+ public function dirname(string $path, int $levels = 1): string;
+
+ /**
+ * Stream-safe `\pathinfo()` replacement.
+ *
+ * @see http://php.net/manual/en/function.pathinfo.php
+ *
+ * @param string $path A filename or path, does not need to exist as a file.
+ * @param int $options A PATHINFO_* constant.
+ *
+ * @return array|string
+ * @api
+ */
+ public function pathinfo(string $path, int $options = null);
+}
diff --git a/system/src/Grav/Framework/Flex/Flex.php b/system/src/Grav/Framework/Flex/Flex.php
new file mode 100644
index 0000000..8e71b2a
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/Flex.php
@@ -0,0 +1,311 @@
+ blueprint file, ...]
+ * @param array $config
+ */
+ public function __construct(array $types, array $config)
+ {
+ $this->config = $config;
+ $this->types = [];
+
+ foreach ($types as $type => $blueprint) {
+ $this->addDirectoryType($type, $blueprint);
+ }
+ }
+
+ /**
+ * @param string $type
+ * @param string $blueprint
+ * @param array $config
+ * @return $this
+ */
+ public function addDirectoryType(string $type, string $blueprint, array $config = [])
+ {
+ $config = array_merge_recursive(['enabled' => true], $this->config['object'] ?? [], $config);
+
+ $this->types[$type] = new FlexDirectory($type, $blueprint, $config);
+
+ return $this;
+ }
+
+ /**
+ * @param FlexDirectory $directory
+ * @return $this
+ */
+ public function addDirectory(FlexDirectory $directory)
+ {
+ $this->types[$directory->getFlexType()] = $directory;
+
+ return $this;
+ }
+
+ /**
+ * @param string $type
+ * @return bool
+ */
+ public function hasDirectory(string $type): bool
+ {
+ return isset($this->types[$type]);
+ }
+
+ /**
+ * @param array|string[] $types
+ * @param bool $keepMissing
+ * @return array|FlexDirectory[]
+ */
+ public function getDirectories(array $types = null, bool $keepMissing = false): array
+ {
+ if ($types === null) {
+ return $this->types;
+ }
+
+ // Return the directories in the given order.
+ $directories = [];
+ foreach ($types as $type) {
+ $directories = $this->types[$type] ?? null;
+ }
+
+ return $keepMissing ? $directories : array_filter($directories);
+ }
+
+ /**
+ * @param string $type
+ * @return FlexDirectory|null
+ */
+ public function getDirectory(string $type): ?FlexDirectory
+ {
+ return $this->types[$type] ?? null;
+ }
+
+ /**
+ * @param string $type
+ * @param array|null $keys
+ * @param string|null $keyField
+ * @return FlexCollectionInterface|null
+ */
+ public function getCollection(string $type, array $keys = null, string $keyField = null): ?FlexCollectionInterface
+ {
+ $directory = $type ? $this->getDirectory($type) : null;
+
+ return $directory ? $directory->getCollection($keys, $keyField) : null;
+ }
+
+ /**
+ * @param array $keys
+ * @param array $options In addition to the options in getObjects(), following options can be passed:
+ * collection_class: Class to be used to create the collection. Defaults to ObjectCollection.
+ * @return FlexCollectionInterface
+ * @throws \RuntimeException
+ */
+ public function getMixedCollection(array $keys, array $options = []): FlexCollectionInterface
+ {
+ $collectionClass = $options['collection_class'] ?? ObjectCollection::class;
+ if (!class_exists($collectionClass)) {
+ throw new \RuntimeException(sprintf('Cannot create collection: Class %s does not exist', $collectionClass));
+ }
+
+ $objects = $this->getObjects($keys, $options);
+
+ return new $collectionClass($objects);
+ }
+
+ /**
+ * @param array $keys
+ * @param array $options Following optional options can be passed:
+ * types: List of allowed types.
+ * type: Allowed type if types isn't defined, otherwise acts as default_type.
+ * default_type: Set default type for objects given without type (only used if key_field isn't set).
+ * keep_missing: Set to true if you want to return missing objects as null.
+ * key_field: Key field which is used to match the objects.
+ * @return array
+ */
+ public function getObjects(array $keys, array $options = []): array
+ {
+ $type = $options['type'] ?? null;
+ $defaultType = $options['default_type'] ?? $type ?? null;
+ $keyField = $options['key_field'] ?? 'flex_key';
+
+ // Prepare empty result lists for all requested Flex types.
+ $types = $options['types'] ?? (array)$type ?: null;
+ if ($types) {
+ $types = array_fill_keys($types, []);
+ }
+ $strict = isset($types);
+
+ $guessed = [];
+ if ($keyField === 'flex_key') {
+ // We need to split Flex key lookups into individual directories.
+ $undefined = [];
+ $keyFieldFind = 'storage_key';
+
+ foreach ($keys as $flexKey) {
+ if (!$flexKey) {
+ continue;
+ }
+
+ $flexKey = (string)$flexKey;
+ // Normalize key and type using fallback to default type if it was set.
+ [$key, $type, $guess] = $this->resolveKeyAndType($flexKey, $defaultType);
+
+ if ($type === '' && $types) {
+ // Add keys which are not associated to any Flex type. They will be included to every Flex type.
+ foreach ($types as $type => &$array) {
+ $array[] = $key;
+ $guessed[$key][] = "{$type}.obj:{$key}";
+ }
+ unset($array);
+ } elseif (!$strict || isset($types[$type])) {
+ // Collect keys by their Flex type. If allowed types are defined, only include values from those types.
+ $types[$type][] = $key;
+ if ($guess) {
+ $guessed[$key][] = "{$type}.obj:{$key}";
+ }
+ }
+ }
+ } else {
+ // We are using a specific key field, make every key undefined.
+ $undefined = $keys;
+ $keyFieldFind = $keyField;
+ }
+
+ if (!$types) {
+ return [];
+ }
+
+ $list = [[]];
+ foreach ($types as $type => $typeKeys) {
+ // Also remember to look up keys from undefined Flex types.
+ $lookupKeys = $undefined ? array_merge($typeKeys, $undefined) : $typeKeys;
+
+ $collection = $this->getCollection($type, $lookupKeys, $keyFieldFind);
+ if ($collection && $keyFieldFind !== $keyField) {
+ $collection = $collection->withKeyField($keyField);
+ }
+
+ $list[] = $collection ? $collection->toArray() : [];
+ }
+
+ // Merge objects from individual types back together.
+ $list = array_merge(...$list);
+
+ // Use the original key ordering.
+ if (!$guessed) {
+ $list = array_replace(array_fill_keys($keys, null), $list);
+ } else {
+ // We have mixed keys, we need to map flex keys back to storage keys.
+ $results = [];
+ foreach ($keys as $key) {
+ $flexKey = $guessed[$key] ?? $key;
+ if (\is_array($flexKey)) {
+ $result = null;
+ foreach ($flexKey as $tryKey) {
+ if ($result = $list[$tryKey] ?? null) {
+ // Use the first matching object (conflicting objects will be ignored for now).
+ break;
+ }
+ }
+ } else {
+ $result = $list[$flexKey] ?? null;
+ }
+
+ $results[$key] = $result;
+ }
+
+ $list = $results;
+ }
+
+ // Remove missing objects if not asked to keep them.
+ if (empty($option['keep_missing'])) {
+ $list = array_filter($list);
+ }
+
+ return $list;
+ }
+
+ /**
+ * @param string $key
+ * @param string|null $type
+ * @param string|null $keyField
+ * @return FlexObjectInterface|null
+ */
+ public function getObject(string $key, string $type = null, string $keyField = null): ?FlexObjectInterface
+ {
+ if (null === $type && null === $keyField) {
+ // Special handling for quick Flex key lookups.
+ $keyField = 'storage_key';
+ [$type, $key] = $this->resolveKeyAndType($key, $type);
+ } else {
+ $type = $this->resolveType($type);
+ }
+
+ if ($type === '' || $key === '') {
+ return null;
+ }
+
+ $directory = $this->getDirectory($type);
+
+ return $directory ? $directory->getObject($key, $keyField) : null;
+ }
+
+ /**
+ * @return int
+ */
+ public function count(): int
+ {
+ return \count($this->types);
+ }
+
+ protected function resolveKeyAndType(string $flexKey, string $type = null): array
+ {
+ $guess = false;
+ if (strpos($flexKey, ':') !== false) {
+ [$type, $key] = explode(':', $flexKey, 2);
+
+ $type = $this->resolveType($type);
+ } else {
+ $key = $flexKey;
+ $type = (string)$type;
+ $guess = true;
+ }
+
+ return [$key, $type, $guess];
+ }
+
+ protected function resolveType(string $type = null): string
+ {
+ if (null !== $type && strpos($type, '.') !== false) {
+ return preg_replace('|\.obj$|', '', $type);
+ }
+
+ return $type ?? '';
+ }
+}
diff --git a/system/src/Grav/Framework/Flex/FlexCollection.php b/system/src/Grav/Framework/Flex/FlexCollection.php
new file mode 100644
index 0000000..ac87f7c
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/FlexCollection.php
@@ -0,0 +1,519 @@
+ true,
+ 'getType' => true,
+ 'getFlexDirectory' => true,
+ 'getCacheKey' => true,
+ 'getCacheChecksum' => true,
+ 'getTimestamp' => true,
+ 'hasProperty' => true,
+ 'getProperty' => true,
+ 'hasNestedProperty' => true,
+ 'getNestedProperty' => true,
+ 'orderBy' => true,
+
+ 'render' => false,
+ 'isAuthorized' => 'session',
+ 'search' => true,
+ 'sort' => true,
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::createFromArray()
+ */
+ public static function createFromArray(array $entries, FlexDirectory $directory, string $keyField = null)
+ {
+ $instance = new static($entries, $directory);
+ $instance->setKeyField($keyField);
+
+ return $instance;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::__construct()
+ */
+ public function __construct(array $entries = [], FlexDirectory $directory = null)
+ {
+ parent::__construct($entries);
+
+ if ($directory) {
+ $this->setFlexDirectory($directory)->setKey($directory->getFlexType());
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::search()
+ */
+ public function search(string $search, $properties = null, array $options = null)
+ {
+ $matching = $this->call('search', [$search, $properties, $options]);
+ $matching = array_filter($matching);
+
+ if ($matching) {
+ uksort($matching, function ($a, $b) {
+ return -($a <=> $b);
+ });
+ }
+
+ return $this->select(array_keys($matching));
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::sort()
+ */
+ public function sort(array $order)
+ {
+ $criteria = Criteria::create()->orderBy($order);
+
+ /** @var FlexCollectionInterface $matching */
+ $matching = $this->matching($criteria);
+
+ return $matching;
+ }
+
+ /**
+ * @param array $filters
+ * @return FlexCollectionInterface
+ */
+ public function filterBy(array $filters)
+ {
+ $expr = Criteria::expr();
+ $criteria = Criteria::create();
+
+ foreach ($filters as $key => $value) {
+ $criteria->andWhere($expr->eq($key, $value));
+ }
+
+ return $this->matching($criteria);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getFlexType()
+ */
+ public function getFlexType(): string
+ {
+ return $this->_flexDirectory->getFlexType();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getFlexDirectory()
+ */
+ public function getFlexDirectory(): FlexDirectory
+ {
+ return $this->_flexDirectory;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getTimestamp()
+ */
+ public function getTimestamp(): int
+ {
+ $timestamps = $this->getTimestamps();
+
+ return $timestamps ? max($timestamps) : time();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getFlexDirectory()
+ */
+ public function getCacheKey(): string
+ {
+ return $this->getTypePrefix() . $this->getFlexType() . '.' . sha1(json_encode($this->call('getKey')));
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getFlexDirectory()
+ */
+ public function getCacheChecksum(): string
+ {
+ return sha1(json_encode($this->getTimestamps()));
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getFlexDirectory()
+ */
+ public function getTimestamps(): array
+ {
+ /** @var int[] $timestamps */
+ $timestamps = $this->call('getTimestamp');
+
+ return $timestamps;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getFlexDirectory()
+ */
+ public function getStorageKeys(): array
+ {
+ /** @var string[] $keys */
+ $keys = $this->call('getStorageKey');
+
+ return $keys;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getFlexDirectory()
+ */
+ public function getFlexKeys(): array
+ {
+ /** @var string[] $keys */
+ $keys = $this->call('getFlexKey');
+
+ return $keys;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::withKeyField()
+ */
+ public function withKeyField(string $keyField = null)
+ {
+ $keyField = $keyField ?: 'key';
+ if ($keyField === $this->getKeyField()) {
+ return $this;
+ }
+
+ $entries = [];
+ foreach ($this as $key => $object) {
+ // TODO: remove hardcoded logic
+ if ($keyField === 'storage_key') {
+ $entries[$object->getStorageKey()] = $object;
+ } elseif ($keyField === 'flex_key') {
+ $entries[$object->getFlexKey()] = $object;
+ } elseif ($keyField === 'key') {
+ $entries[$object->getKey()] = $object;
+ }
+ }
+
+ return $this->createFrom($entries, $keyField);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getIndex()
+ */
+ public function getIndex()
+ {
+ return $this->getFlexDirectory()->getIndex($this->getKeys(), $this->getKeyField());
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::render()
+ */
+ public function render(string $layout = null, array $context = [])
+ {
+ if (null === $layout) {
+ $layout = 'default';
+ }
+ $type = $this->getFlexType();
+
+ $grav = Grav::instance();
+
+ /** @var Debugger $debugger */
+ $debugger = $grav['debugger'];
+ $debugger->startTimer('flex-collection-' . ($debugKey = uniqid($type, false)), 'Render Collection ' . $type . ' (' . $layout . ')');
+
+ $cache = $key = null;
+ foreach ($context as $value) {
+ if (!\is_scalar($value)) {
+ $key = false;
+ }
+ }
+
+ if ($key !== false) {
+ $key = md5($this->getCacheKey() . '.' . $layout . json_encode($context));
+ $cache = $this->getCache('render');
+ }
+
+ try {
+ $data = $cache ? $cache->get($key) : null;
+
+ $block = $data ? HtmlBlock::fromArray($data) : null;
+ } catch (InvalidArgumentException $e) {
+ $debugger->addException($e);
+ $block = null;
+ } catch (\InvalidArgumentException $e) {
+ $debugger->addException($e);
+ $block = null;
+ }
+
+ $checksum = $this->getCacheChecksum();
+ if ($block && $checksum !== $block->getChecksum()) {
+ $block = null;
+ }
+
+ if (!$block) {
+ $block = HtmlBlock::create($key);
+ $block->setChecksum($checksum);
+ if ($key === false) {
+ $block->disableCache();
+ }
+
+ $grav->fireEvent('onFlexCollectionRender', new Event([
+ 'collection' => $this,
+ 'layout' => &$layout,
+ 'context' => &$context
+ ]));
+
+ $output = $this->getTemplate($layout)->render(
+ ['grav' => $grav, 'config' => $grav['config'], 'block' => $block, 'collection' => $this, 'layout' => $layout] + $context
+ );
+
+ if ($debugger->enabled()) {
+ $output = "\n\n{$output}\n\n";
+ }
+
+ $block->setContent($output);
+
+ try {
+ $cache && $block->isCached() && $cache->set($key, $block->toArray());
+ } catch (InvalidArgumentException $e) {
+ $debugger->addException($e);
+ }
+ }
+
+ $debugger->stopTimer('flex-collection-' . $debugKey);
+
+ return $block;
+ }
+
+ /**
+ * @param bool $prefix
+ * @return string
+ * @deprecated 1.6 Use `->getFlexType()` instead.
+ */
+ public function getType($prefix = false)
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getFlexType() method instead', E_USER_DEPRECATED);
+
+ $type = $prefix ? $this->getTypePrefix() : '';
+
+ return $type . $this->getFlexType();
+ }
+
+ /**
+ * @param FlexDirectory $type
+ * @return $this
+ */
+ public function setFlexDirectory(FlexDirectory $type)
+ {
+ $this->_flexDirectory = $type;
+
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getMetaData(string $key) : array
+ {
+ $object = $this->get($key);
+
+ return $object instanceof FlexObjectInterface ? $object->getMetaData() : [];
+ }
+
+ /**
+ * @param string|null $namespace
+ * @return CacheInterface
+ */
+ public function getCache(string $namespace = null)
+ {
+ return $this->_flexDirectory->getCache($namespace);
+ }
+
+ /**
+ * @return string
+ */
+ public function getKeyField(): string
+ {
+ return $this->_keyField ?? 'storage_key';
+ }
+
+ /**
+ * @param string $action
+ * @param string|null $scope
+ * @param UserInterface|null $user
+ * @return static
+ */
+ public function isAuthorized(string $action, string $scope = null, UserInterface $user = null)
+ {
+ $list = $this->call('isAuthorized', [$action, $scope, $user]);
+ $list = \array_filter($list);
+
+ return $this->select(array_keys($list));
+ }
+
+ /**
+ * @param string $value
+ * @param string $field
+ * @return FlexObject|null
+ */
+ public function find($value, $field = 'id')
+ {
+ if ($value) foreach ($this as $element) {
+ if (mb_strtolower($element->getProperty($field)) === mb_strtolower($value)) {
+ return $element;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return array
+ */
+ public function jsonSerialize()
+ {
+ $elements = [];
+
+ /**
+ * @var string $key
+ * @var array|FlexObject $object
+ */
+ foreach ($this->getElements() as $key => $object) {
+ $elements[$key] = \is_array($object) ? $object : $object->jsonSerialize();
+ }
+
+ return $elements;
+ }
+
+ public function __debugInfo()
+ {
+ return [
+ 'type:private' => $this->getFlexType(),
+ 'key:private' => $this->getKey(),
+ 'objects_key:private' => $this->getKeyField(),
+ 'objects:private' => $this->getElements()
+ ];
+ }
+
+ /**
+ * Creates a new instance from the specified elements.
+ *
+ * This method is provided for derived classes to specify how a new
+ * instance should be created when constructor semantics have changed.
+ *
+ * @param array $elements Elements.
+ * @param string|null $keyField
+ *
+ * @return static
+ * @throws \InvalidArgumentException
+ */
+ protected function createFrom(array $elements, $keyField = null)
+ {
+ $collection = new static($elements, $this->_flexDirectory);
+ $collection->setKeyField($keyField ?: $this->_keyField);
+
+ return $collection;
+ }
+
+ /**
+ * @return string
+ */
+ protected function getTypePrefix(): string
+ {
+ return 'c.';
+ }
+
+ /**
+ * @param string $layout
+ * @return TemplateWrapper
+ * @throws LoaderError
+ * @throws SyntaxError
+ */
+ protected function getTemplate($layout)
+ {
+ $grav = Grav::instance();
+
+ /** @var Twig $twig */
+ $twig = $grav['twig'];
+
+ try {
+ return $twig->twig()->resolveTemplate(["flex-objects/layouts/{$this->getFlexType()}/collection/{$layout}.html.twig"]);
+ } catch (LoaderError $e) {
+ /** @var Debugger $debugger */
+ $debugger = Grav::instance()['debugger'];
+ $debugger->addException($e);
+
+ return $twig->twig()->resolveTemplate(['flex-objects/layouts/404.html.twig']);
+ }
+ }
+
+ /**
+ * @param string $type
+ * @return FlexDirectory
+ */
+ protected function getRelatedDirectory($type): ?FlexDirectory
+ {
+ /** @var Flex $flex */
+ $flex = Grav::instance()['flex_objects'];
+
+ return $flex->getDirectory($type);
+ }
+
+ protected function setKeyField($keyField = null): void
+ {
+ $this->_keyField = $keyField ?? 'storage_key';
+ }
+}
diff --git a/system/src/Grav/Framework/Flex/FlexDirectory.php b/system/src/Grav/Framework/Flex/FlexDirectory.php
new file mode 100644
index 0000000..7b50a7e
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/FlexDirectory.php
@@ -0,0 +1,663 @@
+type = $type;
+ $this->blueprints = [];
+ $this->blueprint_file = $blueprint_file;
+ $this->defaults = $defaults;
+ $this->enabled = !empty($defaults['enabled']);
+ }
+
+ /**
+ * @return bool
+ */
+ public function isEnabled(): bool
+ {
+ return $this->enabled;
+ }
+
+ /**
+ * @return string
+ * @deprecated 1.6 Use ->getFlexType() method instead.
+ */
+ public function getType(): string
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getFlexType() method instead', E_USER_DEPRECATED);
+
+ return $this->type;
+ }
+
+ /**
+ * @return string
+ */
+ public function getFlexType(): string
+ {
+ return $this->type;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTitle(): string
+ {
+ return $this->getBlueprintInternal()->get('title', ucfirst($this->getFlexType()));
+ }
+
+ /**
+ * @return string
+ */
+ public function getDescription(): string
+ {
+ return $this->getBlueprintInternal()->get('description', '');
+ }
+
+ /**
+ * @param string|null $name
+ * @param mixed $default
+ * @return mixed
+ */
+ public function getConfig(string $name = null, $default = null)
+ {
+ if (null === $this->config) {
+ $this->config = new Config(array_merge_recursive($this->getBlueprintInternal()->get('config', []), $this->defaults));
+ }
+
+ return null === $name ? $this->config : $this->config->get($name, $default);
+ }
+
+ /**
+ * @param string $type
+ * @param string $context
+ * @return Blueprint
+ */
+ public function getBlueprint(string $type = '', string $context = '')
+ {
+ $blueprint = $this->getBlueprintInternal($type, $context);
+
+ if (empty($this->blueprints_init[$type])) {
+ $this->blueprints_init[$type] = true;
+
+ $blueprint->setScope('object');
+ $blueprint->init();
+ if (empty($blueprint->fields())) {
+ throw new RuntimeException(sprintf('Flex: Blueprint for %s is missing', $this->type));
+ }
+ }
+
+ return $blueprint;
+ }
+
+ /**
+ * @param string $view
+ * @return string
+ */
+ public function getBlueprintFile(string $view = ''): string
+ {
+ $file = $this->blueprint_file;
+ if ($view !== '') {
+ $file = preg_replace('/\.yaml/', "/{$view}.yaml", $file);
+ }
+
+ return $file;
+ }
+
+ /**
+ * Get collection. In the site this will be filtered by the default filters (published etc).
+ *
+ * Use $directory->getIndex() if you want unfiltered collection.
+ *
+ * @param array|null $keys Array of keys.
+ * @param string|null $keyField Field to be used as the key.
+ * @return FlexCollectionInterface
+ */
+ public function getCollection(array $keys = null, string $keyField = null): FlexCollectionInterface
+ {
+ // Get all selected entries.
+ $index = $this->getIndex($keys, $keyField);
+
+ if (!Utils::isAdminPlugin()) {
+ // If not in admin, filter the list by using default filters.
+ $filters = (array)$this->getConfig('site.filter', []);
+
+ foreach ($filters as $filter) {
+ $index = $index->{$filter}();
+ }
+ }
+
+ return $index;
+ }
+
+ /**
+ * Get the full collection of all stored objects.
+ *
+ * Use $directory->getCollection() if you want a filtered collection.
+ *
+ * @param array|null $keys Array of keys.
+ * @param string|null $keyField Field to be used as the key.
+ * @return FlexIndexInterface
+ */
+ public function getIndex(array $keys = null, string $keyField = null): FlexIndexInterface
+ {
+ $index = clone $this->loadIndex();
+ $index = $index->withKeyField($keyField);
+
+ if (null !== $keys) {
+ $index = $index->select($keys);
+ }
+
+ return $index->getIndex();
+ }
+
+ /**
+ * Returns an object if it exists.
+ *
+ * Note: It is not safe to use the object without checking if the user can access it.
+ *
+ * @param string $key
+ * @param string|null $keyField Field to be used as the key.
+ * @return FlexObjectInterface|null
+ */
+ public function getObject($key, string $keyField = null): ?FlexObjectInterface
+ {
+ return $this->getIndex(null, $keyField)->get($key);
+ }
+
+ /**
+ * @param array $data
+ * @param string|null $key
+ * @return FlexObjectInterface
+ */
+ public function update(array $data, string $key = null): FlexObjectInterface
+ {
+ $object = null !== $key ? $this->getIndex()->get($key): null;
+
+ $storage = $this->getStorage();
+
+ if (null === $object) {
+ $object = $this->createObject($data, $key, true);
+ $key = $object->getStorageKey();
+
+ if ($key) {
+ $rows = $storage->replaceRows([$key => $object->prepareStorage()]);
+ } else {
+ $rows = $storage->createRows([$object->prepareStorage()]);
+ }
+ } else {
+ $oldKey = $object->getStorageKey();
+ $object->update($data);
+ $newKey = $object->getStorageKey();
+
+ if ($oldKey !== $newKey) {
+ $object->triggerEvent('move');
+ $storage->renameRow($oldKey, $newKey);
+ // TODO: media support.
+ }
+
+ $object->save();
+ }
+
+ try {
+ $this->clearCache();
+ } catch (InvalidArgumentException $e) {
+ /** @var Debugger $debugger */
+ $debugger = Grav::instance()['debugger'];
+ $debugger->addException($e);
+
+ // Caching failed, but we can ignore that for now.
+ }
+
+ return $object;
+ }
+
+ /**
+ * @param string $key
+ * @return FlexObjectInterface|null
+ */
+ public function remove(string $key): ?FlexObjectInterface
+ {
+ $object = $this->getIndex()->get($key);
+ if (!$object) {
+ return null;
+ }
+
+ $object->delete();
+
+ return $object;
+ }
+
+ /**
+ * @param string|null $namespace
+ * @return CacheInterface
+ */
+ public function getCache(string $namespace = null)
+ {
+ $namespace = $namespace ?: 'index';
+ $cache = $this->cache[$namespace] ?? null;
+
+ if (null === $cache) {
+ try {
+ $grav = Grav::instance();
+
+ /** @var Cache $gravCache */
+ $gravCache = $grav['cache'];
+ $config = $this->getConfig('cache.' . $namespace);
+ if (empty($config['enabled'])) {
+ throw new \RuntimeException(sprintf('Flex: %s %s cache not enabled', $this->type, $namespace));
+ }
+ $timeout = $config['timeout'] ?? 60;
+
+ $key = $gravCache->getKey();
+ if (Utils::isAdminPlugin()) {
+ $key = substr($key, 0, -1);
+ }
+ $cache = new DoctrineCache($gravCache->getCacheDriver(), 'flex-objects-' . $this->getFlexType() . $key, $timeout);
+ } catch (\Exception $e) {
+ /** @var Debugger $debugger */
+ $debugger = Grav::instance()['debugger'];
+ $debugger->addException($e);
+
+ $cache = new MemoryCache('flex-objects-' . $this->getFlexType());
+ }
+
+ // Disable cache key validation.
+ $cache->setValidation(false);
+ $this->cache[$namespace] = $cache;
+ }
+
+ return $cache;
+ }
+
+ /**
+ * @return $this
+ */
+ public function clearCache()
+ {
+ $grav = Grav::instance();
+
+ /** @var Debugger $debugger */
+ $debugger = $grav['debugger'];
+ $debugger->addMessage(sprintf('Flex: Clearing all %s cache', $this->type), 'debug');
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $grav['locator'];
+ $locator->clearCache();
+
+ $this->getCache('index')->clear();
+ $this->getCache('object')->clear();
+ $this->getCache('render')->clear();
+
+ $this->index = null;
+
+ return $this;
+ }
+
+ /**
+ * @param string|null $key
+ * @return string
+ */
+ public function getStorageFolder(string $key = null): string
+ {
+ return $this->getStorage()->getStoragePath($key);
+ }
+
+ /**
+ * @param string|null $key
+ * @return string
+ */
+ public function getMediaFolder(string $key = null): string
+ {
+ return $this->getStorage()->getMediaPath($key);
+ }
+
+ /**
+ * @return FlexStorageInterface
+ */
+ public function getStorage(): FlexStorageInterface
+ {
+ if (null === $this->storage) {
+ $this->storage = $this->createStorage();
+ }
+
+ return $this->storage;
+ }
+
+ /**
+ * @param array $data
+ * @param string $key
+ * @param bool $validate
+ * @return FlexObjectInterface
+ */
+ public function createObject(array $data, string $key = '', bool $validate = false): FlexObjectInterface
+ {
+ /** @var string|FlexObjectInterface $className */
+ $className = $this->objectClassName ?: $this->getObjectClass();
+
+ return new $className($data, $key, $this, $validate);
+ }
+
+ /**
+ * @param array $entries
+ * @param string $keyField
+ * @return FlexCollectionInterface
+ */
+ public function createCollection(array $entries, string $keyField = null): FlexCollectionInterface
+ {
+ /** @var string|FlexCollectionInterface $className */
+ $className = $this->collectionClassName ?: $this->getCollectionClass();
+
+ return $className::createFromArray($entries, $this, $keyField);
+ }
+
+ /**
+ * @param array $entries
+ * @param string $keyField
+ * @return FlexIndexInterface
+ */
+ public function createIndex(array $entries, string $keyField = null): FlexIndexInterface
+ {
+ /** @var string|FlexIndexInterface $className */
+ $className = $this->indexClassName ?: $this->getIndexClass();
+
+ return $className::createFromArray($entries, $this, $keyField);
+ }
+
+ /**
+ * @return string
+ */
+ public function getObjectClass(): string
+ {
+ if (!$this->objectClassName) {
+ $this->objectClassName = $this->getConfig('data.object', 'Grav\\Framework\\Flex\\FlexObject');
+ }
+
+ return $this->objectClassName;
+
+ }
+
+ /**
+ * @return string
+ */
+ public function getCollectionClass(): string
+ {
+ if (!$this->collectionClassName) {
+ $this->collectionClassName = $this->getConfig('data.collection', 'Grav\\Framework\\Flex\\FlexCollection');
+ }
+
+ return $this->collectionClassName;
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getIndexClass(): string
+ {
+ if (!$this->indexClassName) {
+ $this->indexClassName = $this->getConfig('data.index', 'Grav\\Framework\\Flex\\FlexIndex');
+ }
+
+ return $this->indexClassName;
+ }
+
+ /**
+ * @param array $entries
+ * @param string $keyField
+ * @return FlexCollectionInterface
+ */
+ public function loadCollection(array $entries, string $keyField = null): FlexCollectionInterface
+ {
+ return $this->createCollection($this->loadObjects($entries), $keyField);
+ }
+
+ /**
+ * @param array $entries
+ * @return FlexObjectInterface[]
+ * @internal
+ */
+ public function loadObjects(array $entries): array
+ {
+ /** @var Debugger $debugger */
+ $debugger = Grav::instance()['debugger'];
+ $debugger->startTimer('flex-objects', sprintf('Flex: Initializing %d %s', \count($entries), $this->type));
+
+ $storage = $this->getStorage();
+ $cache = $this->getCache('object');
+
+ // Get storage keys for the objects.
+ $keys = [];
+ $rows = [];
+ foreach ($entries as $key => $value) {
+ $k = $value['storage_key'];
+ $keys[$k] = $key;
+ $rows[$k] = null;
+ }
+
+ // Fetch rows from the cache.
+ try {
+ $rows = $cache->getMultiple(array_keys($rows));
+ } catch (InvalidArgumentException $e) {
+ $debugger->addException($e);
+ }
+
+ // Read missing rows from the storage.
+ $updated = [];
+ $rows = $storage->readRows($rows, $updated);
+
+ // Store updated rows to the cache.
+ if ($updated) {
+ try {
+ $debugger->addMessage(sprintf('Flex: Caching %d %s: %s', \count($updated), $this->type, implode(', ', array_keys($updated))), 'debug');
+ $cache->setMultiple($updated);
+ } catch (InvalidArgumentException $e) {
+ $debugger->addException($e);
+
+ // TODO: log about the issue.
+ }
+ }
+
+ // Create objects from the rows.
+ $list = [];
+ foreach ($rows as $storageKey => $row) {
+ if ($row === null) {
+ $debugger->addMessage(sprintf('Flex: Object %s was not found from %s storage', $storageKey, $this->type), 'debug');
+ continue;
+ }
+
+ if (isset($row['__error'])) {
+ $message = sprintf('Flex: Object %s is broken in %s storage: %s', $storageKey, $this->type, $row['__error']);
+ $debugger->addException(new \RuntimeException($message));
+ $debugger->addMessage($message, 'error');
+ continue;
+ }
+
+ $usedKey = $keys[$storageKey];
+ $row += [
+ 'storage_key' => $storageKey,
+ 'storage_timestamp' => $entries[$usedKey]['storage_timestamp'],
+ ];
+
+ $key = $entries[$usedKey]['key'] ?? $usedKey;
+ $object = $this->createObject($row, $key, false);
+ $list[$usedKey] = $object;
+ }
+
+ $debugger->stopTimer('flex-objects');
+
+ return $list;
+ }
+
+ /**
+ * @param string $type_view
+ * @param string $context
+ * @return Blueprint
+ */
+ protected function getBlueprintInternal(string $type_view = '', string $context = '')
+ {
+ if (!isset($this->blueprints[$type_view])) {
+ if (!file_exists($this->blueprint_file)) {
+ throw new RuntimeException(sprintf('Flex: Blueprint file for %s is missing', $this->type));
+ }
+
+ $parts = explode('.', rtrim($type_view, '.'), 2);
+ $type = array_shift($parts);
+ $view = array_shift($parts) ?: '';
+
+ $blueprint = new Blueprint($this->getBlueprintFile($view));
+ if ($context) {
+ $blueprint->setContext($context);
+ }
+
+ $blueprint->load($type ?: null);
+ if ($blueprint->get('type') === 'flex-objects' && isset(Grav::instance()['admin'])) {
+ $blueprintBase = (new Blueprint('plugin://flex-objects/blueprints/flex-objects.yaml'))->load();
+ $blueprint->extend($blueprintBase, true);
+ }
+
+ $this->blueprints[$type_view] = $blueprint;
+ }
+
+ return $this->blueprints[$type_view];
+ }
+
+ /**
+ * @return FlexStorageInterface
+ */
+ protected function createStorage(): FlexStorageInterface
+ {
+ $this->collection = $this->createCollection([]);
+
+ $storage = $this->getConfig('data.storage');
+
+ if (!\is_array($storage)) {
+ $storage = ['options' => ['folder' => $storage]];
+ }
+
+ $className = $storage['class'] ?? SimpleStorage::class;
+ $options = $storage['options'] ?? [];
+
+ return new $className($options);
+ }
+
+ /**
+ * @return FlexIndexInterface
+ */
+ protected function loadIndex(): FlexIndexInterface
+ {
+ static $i = 0;
+
+ $index = $this->index;
+
+ if (null === $index) {
+ $i++; $j = $i;
+ /** @var Debugger $debugger */
+ $debugger = Grav::instance()['debugger'];
+ $debugger->startTimer('flex-keys-' . $this->type . $j, "Flex: Loading {$this->type} index");
+
+ $storage = $this->getStorage();
+ $cache = $this->getCache('index');
+
+ try {
+ $keys = $cache->get('__keys');
+ } catch (InvalidArgumentException $e) {
+ $debugger->addException($e);
+ $keys = null;
+ }
+
+ if (null === $keys) {
+ /** @var string|FlexIndexInterface $className */
+ $className = $this->getIndexClass();
+ $keys = $className::loadEntriesFromStorage($storage);
+ $debugger->addMessage(sprintf('Flex: Caching %s index of %d objects', $this->type, \count($keys)), 'debug');
+ try {
+ $cache->set('__keys', $keys);
+ } catch (InvalidArgumentException $e) {
+ $debugger->addException($e);
+ // TODO: log about the issue.
+ }
+ }
+
+ // We need to do this in two steps as orderBy() calls loadIndex() again and we do not want infinite loop.
+ $this->index = $this->createIndex($keys);
+ /** @var FlexCollectionInterface $collection */
+ $collection = $this->index->orderBy($this->getConfig('data.ordering', []));
+ $this->index = $index = $collection->getIndex();
+
+ $debugger->stopTimer('flex-keys-' . $this->type . $j);
+ }
+
+ return $index;
+ }
+}
diff --git a/system/src/Grav/Framework/Flex/FlexForm.php b/system/src/Grav/Framework/Flex/FlexForm.php
new file mode 100644
index 0000000..7faad52
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/FlexForm.php
@@ -0,0 +1,343 @@
+name = $name;
+ $this->form = $form;
+
+ $uniqueId = $object->exists() ? $object->getStorageKey() : "{$object->getFlexType()}:new";
+ $this->setObject($object);
+ $this->setId($this->getName());
+ $this->setUniqueId(md5($uniqueId));
+ $this->messages = [];
+ $this->submitted = false;
+
+ $flash = $this->getFlash();
+ if ($flash->exists()) {
+ $data = $flash->getData();
+ $includeOriginal = (bool)($this->getBlueprint()->form()['images']['original'] ?? null);
+
+ $this->data = $data ? new Data($data, $this->getBlueprint()) : null;
+ $this->files = $flash->getFilesByFields($includeOriginal);
+ } else {
+ $this->data = null;
+ $this->files = [];
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getName(): string
+ {
+ $object = $this->getObject();
+ $name = $this->name ?: 'object';
+
+ return "flex-{$object->getFlexType()}-{$name}";
+ }
+
+ /**
+ * @return Data|FlexObjectInterface
+ */
+ public function getData()
+ {
+ return $this->data ?? $this->getObject();
+ }
+
+ /**
+ * Get a value from the form.
+ *
+ * Note: Used in form fields.
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function getValue(string $name)
+ {
+ // Attempt to get value from the form data.
+ $value = $this->data ? $this->data[$name] : null;
+
+ // Return the form data or fall back to the object property.
+ return $value ?? $this->getObject()->value($name);
+ }
+
+ public function getDefaultValue(string $name)
+ {
+ return $this->object->getDefaultValue($name);
+ }
+
+ /**
+ * @return array
+ */
+ public function getDefaultValues(): array
+ {
+ return $this->object->getDefaultValues();
+ }
+ /**
+ * @return string
+ */
+ public function getFlexType(): string
+ {
+ return $this->object->getFlexType();
+ }
+
+ /**
+ * @return FlexObjectInterface
+ */
+ public function getObject(): FlexObjectInterface
+ {
+ return $this->object;
+ }
+
+ public function updateObject(): FlexObjectInterface
+ {
+ $data = $this->data instanceof Data ? $this->data->toArray() : [];
+ $files = $this->files;
+
+ return $this->getObject()->update($data, $files);
+ }
+
+ /**
+ * @return Blueprint
+ */
+ public function getBlueprint(): Blueprint
+ {
+ if (null === $this->blueprint) {
+ try {
+ $blueprint = $this->getObject()->getBlueprint(Utils::isAdminPlugin() ? '' : $this->name);
+ if ($this->form) {
+ // We have field overrides available.
+ $blueprint->extend(['form' => $this->form], true);
+ $blueprint->init();
+ }
+ } catch (\RuntimeException $e) {
+ if (!isset($this->form['fields'])) {
+ throw $e;
+ }
+
+ // Blueprint is not defined, but we have custom form fields available.
+ $blueprint = new Blueprint(null, ['form' => $this->form]);
+ $blueprint->load();
+ $blueprint->setScope('object');
+ $blueprint->init();
+ }
+
+ $this->blueprint = $blueprint;
+ }
+
+ return $this->blueprint;
+ }
+
+ /**
+ * @return Route|null
+ */
+ public function getFileUploadAjaxRoute(): ?Route
+ {
+ $object = $this->getObject();
+ if (!method_exists($object, 'route')) {
+ return null;
+ }
+
+ return $object->route('/edit.json/task:media.upload');
+ }
+
+ /**
+ * @param string $field
+ * @param string $filename
+ * @return Route|null
+ */
+ public function getFileDeleteAjaxRoute($field, $filename): ?Route
+ {
+ $object = $this->getObject();
+ if (!method_exists($object, 'route')) {
+ return null;
+ }
+
+ return $object->route('/edit.json/task:media.delete');
+ }
+
+ public function getMediaTaskRoute(array $params = [], $extension = null): string
+ {
+ $grav = Grav::instance();
+ /** @var Flex $flex */
+ $flex = $grav['flex_objects'];
+
+ if (method_exists($flex, 'adminRoute')) {
+ return $flex->adminRoute($this->getObject(), $params, $extension ?? 'json');
+ }
+
+ return '';
+ }
+
+ /**
+ * Implements \Serializable::unserialize().
+ *
+ * @param string $data
+ */
+ public function unserialize($data): void
+ {
+ $data = unserialize($data, ['allowed_classes' => [FlexObject::class]]);
+
+ $this->doUnserialize($data);
+ }
+
+ public function __get($name)
+ {
+ $method = "get{$name}";
+ if (method_exists($this, $method)) {
+ return $this->{$method}();
+ }
+
+ $form = $this->getBlueprint()->form();
+
+ return $form[$name] ?? null;
+ }
+
+ public function __set($name, $value)
+ {
+ $method = "set{$name}";
+ if (method_exists($this, $method)) {
+ $this->{$method}($value);
+ }
+ }
+
+ public function __isset($name)
+ {
+ $method = "get{$name}";
+ if (method_exists($this, $method)) {
+ return true;
+ }
+
+ $form = $this->getBlueprint()->form();
+
+ return isset($form[$name]);
+ }
+
+ public function __unset($name)
+ {
+ }
+
+ /**
+ * Note: this method clones the object.
+ *
+ * @param FlexObjectInterface $object
+ * @return $this
+ */
+ protected function setObject(FlexObjectInterface $object): self
+ {
+ $this->object = clone $object;
+
+ return $this;
+ }
+
+ /**
+ * @param string $layout
+ * @return TemplateWrapper
+ * @throws LoaderError
+ * @throws SyntaxError
+ */
+ protected function getTemplate($layout)
+ {
+ $grav = Grav::instance();
+
+ /** @var Twig $twig */
+ $twig = $grav['twig'];
+
+ return $twig->twig()->resolveTemplate(
+ [
+ "flex-objects/layouts/{$this->getFlexType()}/form/{$layout}.html.twig",
+ "flex-objects/layouts/_default/form/{$layout}.html.twig",
+ "forms/{$layout}/form.html.twig",
+ 'forms/default/form.html.twig'
+ ]
+ );
+ }
+
+ /**
+ * @param array $data
+ * @param array $files
+ * @throws \Exception
+ */
+ protected function doSubmit(array $data, array $files)
+ {
+ /** @var FlexObject $object */
+ $object = clone $this->getObject();
+
+ $object->update($data, $files);
+ $object->save();
+
+ $this->setObject($object);
+ $this->reset();
+ }
+
+ protected function doSerialize(): array
+ {
+ return $this->doTraitSerialize() + [
+ 'object' => $this->object,
+ ];
+ }
+
+ protected function doUnserialize(array $data): void
+ {
+ $this->doTraitUnserialize($data);
+
+ $this->object = $data['object'];
+ }
+
+ /**
+ * Filter validated data.
+ *
+ * @param \ArrayAccess $data
+ */
+ protected function filterData(\ArrayAccess $data): void
+ {
+ if ($data instanceof Data) {
+ $data->filter(true, true);
+ }
+ }
+}
diff --git a/system/src/Grav/Framework/Flex/FlexIndex.php b/system/src/Grav/Framework/Flex/FlexIndex.php
new file mode 100644
index 0000000..29ac33b
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/FlexIndex.php
@@ -0,0 +1,703 @@
+getStorage()), $directory);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::createFromArray()
+ */
+ public static function createFromArray(array $entries, FlexDirectory $directory, string $keyField = null)
+ {
+ $instance = new static($entries, $directory);
+ $instance->setKeyField($keyField);
+
+ return $instance;
+ }
+
+ /**
+ * @param FlexStorageInterface $storage
+ * @return array
+ */
+ public static function loadEntriesFromStorage(FlexStorageInterface $storage): array
+ {
+ return $storage->getExistingKeys();
+ }
+
+ /**
+ * Initializes a new FlexIndex.
+ *
+ * @param array $entries
+ * @param FlexDirectory|null $directory
+ */
+ public function __construct(array $entries = [], FlexDirectory $directory = null)
+ {
+ parent::__construct($entries);
+
+ $this->_flexDirectory = $directory;
+ $this->setKeyField(null);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::search()
+ */
+ public function search(string $search, $properties = null, array $options = null)
+ {
+ return $this->__call('search', [$search, $properties, $options]);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::sort()
+ */
+ public function sort(array $orderings)
+ {
+ return $this->orderBy($orderings);
+ }
+
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::filterBy()
+ */
+ public function filterBy(array $filters)
+ {
+ return $this->__call('filterBy', [$filters]);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getFlexType()
+ */
+ public function getFlexType(): string
+ {
+ return $this->_flexDirectory->getFlexType();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getFlexDirectory()
+ */
+ public function getFlexDirectory(): FlexDirectory
+ {
+ return $this->_flexDirectory;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getTimestamp()
+ */
+ public function getTimestamp(): int
+ {
+ $timestamps = $this->getTimestamps();
+
+ return $timestamps ? max($timestamps) : time();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getCacheKey()
+ */
+ public function getCacheKey(): string
+ {
+ return $this->getTypePrefix() . $this->getFlexType() . '.' . sha1(json_encode($this->getKeys()) . $this->_keyField);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getCacheChecksum()
+ */
+ public function getCacheChecksum(): string
+ {
+ return sha1($this->getCacheKey() . json_encode($this->getTimestamps()));
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getTimestamps()
+ */
+ public function getTimestamps(): array
+ {
+ return $this->getIndexMap('storage_timestamp');
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getStorageKeys()
+ */
+ public function getStorageKeys(): array
+ {
+ return $this->getIndexMap('storage_key');
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getFlexKeys()
+ */
+ public function getFlexKeys(): array
+ {
+ // Get storage keys for the objects.
+ $keys = [];
+ $type = $this->_flexDirectory->getFlexType() . '.obj:';
+
+ foreach ($this->getEntries() as $key => $value) {
+ $keys[$key] = $value['flex_key'] ?? $type . $value['storage_key'];
+ }
+
+ return $keys;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexIndexInterface::withKeyField()
+ */
+ public function withKeyField(string $keyField = null)
+ {
+ $keyField = $keyField ?: 'key';
+ if ($keyField === $this->getKeyField()) {
+ return $this;
+ }
+
+ $type = $keyField === 'flex_key' ? $this->_flexDirectory->getFlexType() . '.obj:' : '';
+ $entries = [];
+ foreach ($this->getEntries() as $key => $value) {
+ if (!isset($value['key'])) {
+ $value['key'] = $key;
+ }
+
+ if (isset($value[$keyField])) {
+ $entries[$value[$keyField]] = $value;
+ } elseif ($keyField === 'flex_key') {
+ $entries[$type . $value['storage_key']] = $value;
+ }
+ }
+
+ return $this->createFrom($entries, $keyField);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::getIndex()
+ */
+ public function getIndex()
+ {
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexCollectionInterface::render()
+ */
+ public function render(string $layout = null, array $context = [])
+ {
+ return $this->__call('render', [$layout, $context]);
+ }
+
+ /**
+ * @param bool $prefix
+ * @return string
+ * @deprecated 1.6 Use `->getFlexType()` instead.
+ */
+ public function getType($prefix = false)
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getFlexType() method instead', E_USER_DEPRECATED);
+
+ $type = $prefix ? $this->getTypePrefix() : '';
+
+ return $type . $this->getFlexType();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexIndexInterface::getFlexKeys()
+ */
+ public function getIndexMap(string $indexKey = null)
+ {
+ if (null === $indexKey) {
+ return $this->getEntries();
+ }
+
+ // Get storage keys for the objects.
+ $index = [];
+ foreach ($this->getEntries() as $key => $value) {
+ $index[$key] = $value[$indexKey] ?? null;
+ }
+
+ return $index;
+ }
+
+ /**
+ * @return array
+ */
+ public function getMetaData(string $key): array
+ {
+ return $this->getEntries()[$key] ?? [];
+ }
+
+ /**
+ * @return string
+ */
+ public function getKeyField() : string
+ {
+ return $this->_keyField ?? 'storage_key';
+ }
+
+ /**
+ * @param string|null $namespace
+ * @return CacheInterface
+ */
+ public function getCache(string $namespace = null)
+ {
+ return $this->_flexDirectory->getCache($namespace);
+ }
+
+ /**
+ * @param array $orderings
+ * @return FlexIndex|FlexCollection
+ */
+ public function orderBy(array $orderings)
+ {
+ if (!$orderings || !$this->count()) {
+ return $this;
+ }
+
+ // Check if ordering needs to load the objects.
+ if (array_diff_key($orderings, $this->getIndexKeys())) {
+ return $this->__call('orderBy', [$orderings]);
+ }
+
+ // Ordering can be done by using index only.
+ $previous = null;
+ foreach (array_reverse($orderings) as $field => $ordering) {
+ if ($this->getKeyField() === $field) {
+ $keys = $this->getKeys();
+ $search = array_combine($keys, $keys);
+ } elseif ($field === 'flex_key') {
+ $search = $this->getFlexKeys();
+ } else {
+ $search = $this->getIndexMap($field);
+ }
+
+ // Update current search to match the previous ordering.
+ if (null !== $previous) {
+ $search = array_replace($previous, $search);
+ }
+
+ // Order by current field.
+ if ($ordering === 'DESC') {
+ arsort($search, SORT_NATURAL);
+ } else {
+ asort($search, SORT_NATURAL);
+ }
+
+ $previous = $search;
+ }
+
+ return $this->createFrom(array_replace($previous, $this->getEntries()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function call($method, array $arguments = [])
+ {
+ return $this->__call('call', [$method, $arguments]);
+ }
+
+ public function __call($name, $arguments)
+ {
+ /** @var Debugger $debugger */
+ $debugger = Grav::instance()['debugger'];
+
+ /** @var FlexCollection $className */
+ $className = $this->_flexDirectory->getCollectionClass();
+ $cachedMethods = $className::getCachedMethods();
+
+ $flexType = $this->getFlexType();
+
+ if (!empty($cachedMethods[$name])) {
+ $type = $cachedMethods[$name];
+ if ($type === 'session') {
+ /** @var Session $session */
+ $session = Grav::instance()['session'];
+ $cacheKey = $session->getId() . $session->user->username;
+ } else {
+ $cacheKey = '';
+ }
+ $key = "{$flexType}.idx." . sha1($name . '.' . $cacheKey . json_encode($arguments) . $this->getCacheKey());
+
+ $cache = $this->getCache('object');
+
+ try {
+ $result = $cache->get($key);
+
+ // Make sure the keys aren't changed if the returned type is the same index type.
+ if ($result instanceof self && $flexType === $result->getFlexType()) {
+ $result = $result->withKeyField($this->getKeyField());
+ }
+ } catch (InvalidArgumentException $e) {
+ /** @var Debugger $debugger */
+ $debugger = Grav::instance()['debugger'];
+ $debugger->addException($e);
+ }
+
+ if (!isset($result)) {
+ $collection = $this->loadCollection();
+ $result = $collection->{$name}(...$arguments);
+
+ try {
+ // If flex collection is returned, convert it back to flex index.
+ if ($result instanceof FlexCollection) {
+ $cached = $result->getFlexDirectory()->getIndex($result->getKeys(), $this->getKeyField());
+ } else {
+ $cached = $result;
+ }
+
+ $cache->set($key, $cached);
+ } catch (InvalidArgumentException $e) {
+ $debugger->addException($e);
+
+ // TODO: log error.
+ }
+ }
+ } else {
+ $collection = $this->loadCollection();
+ $result = $collection->{$name}(...$arguments);
+ if (!isset($cachedMethods[$name])) {
+ $class = \get_class($collection);
+ $debugger->addMessage("Call '{$class}:{$name}()' isn't cached", 'debug');
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * @return string
+ */
+ public function serialize()
+ {
+ return serialize(['type' => $this->getFlexType(), 'entries' => $this->getEntries()]);
+ }
+
+ /**
+ * @param string $serialized
+ */
+ public function unserialize($serialized)
+ {
+ $data = unserialize($serialized, ['allowed_classes' => false]);
+
+ $this->_flexDirectory = Grav::instance()['flex_objects']->getDirectory($data['type']);
+ $this->setEntries($data['entries']);
+ }
+
+ /**
+ * @param array $entries
+ * @param string $keyField
+ * @return static
+ */
+ protected function createFrom(array $entries, string $keyField = null)
+ {
+ $index = new static($entries, $this->_flexDirectory);
+ $index->setKeyField($keyField ?? $this->_keyField);
+
+ return $index;
+ }
+
+ /**
+ * @param string|null $keyField
+ */
+ protected function setKeyField(string $keyField = null)
+ {
+ $this->_keyField = $keyField ?? 'storage_key';
+ }
+
+ protected function getIndexKeys()
+ {
+ if (null === $this->_indexKeys) {
+ $entries = $this->getEntries();
+ $first = reset($entries);
+ if ($first) {
+ $keys = array_keys($first);
+ $keys = array_combine($keys, $keys);
+ } else {
+ $keys = [];
+ }
+
+ $this->setIndexKeys($keys);
+ }
+
+ return $this->_indexKeys;
+ }
+
+ /**
+ * @param array $indexKeys
+ */
+ protected function setIndexKeys(array $indexKeys)
+ {
+ // Add defaults.
+ $indexKeys += [
+ 'key' => 'key',
+ 'storage_key' => 'storage_key',
+ 'storage_timestamp' => 'storage_timestamp',
+ 'flex_key' => 'flex_key'
+ ];
+
+
+ $this->_indexKeys = $indexKeys;
+ }
+
+ /**
+ * @return string
+ */
+ protected function getTypePrefix()
+ {
+ return 'i.';
+ }
+
+ /**
+ * @param string $key
+ * @param mixed $value
+ * @return ObjectInterface|null
+ */
+ protected function loadElement($key, $value): ?ObjectInterface
+ {
+ $objects = $this->_flexDirectory->loadObjects([$key => $value]);
+
+ return $objects ? reset($objects): null;
+ }
+
+ /**
+ * @param array|null $entries
+ * @return ObjectInterface[]
+ */
+ protected function loadElements(array $entries = null): array
+ {
+ return $this->_flexDirectory->loadObjects($entries ?? $this->getEntries());
+ }
+
+ /**
+ * @param array|null $entries
+ * @return ObjectCollectionInterface
+ */
+ protected function loadCollection(array $entries = null): CollectionInterface
+ {
+ return $this->_flexDirectory->loadCollection($entries ?? $this->getEntries(), $this->_keyField);
+ }
+
+ /**
+ * @param mixed $value
+ * @return bool
+ */
+ protected function isAllowedElement($value): bool
+ {
+ return $value instanceof FlexObject;
+ }
+
+ /**
+ * @param FlexObjectInterface $object
+ * @return mixed
+ */
+ protected function getElementMeta($object)
+ {
+ return $object->getTimestamp();
+ }
+
+ /**
+ * @param FlexStorageInterface $storage
+ * @param array $index Saved index
+ * @param array $entries Updated index
+ * @return array Compiled list of entries
+ */
+ protected static function updateIndexFile(FlexStorageInterface $storage, array $index, array $entries): array
+ {
+ // Calculate removed objects.
+ $removed = array_diff_key($index, $entries);
+
+ // First get rid of all removed objects.
+ if ($removed) {
+ $index = array_diff_key($index, $removed);
+ }
+
+ if ($entries) {
+ // Calculate difference between saved index and current data.
+ foreach ($index as $key => $entry) {
+ $storage_key = $entry['storage_key'] ?? null;
+ if (isset($entries[$storage_key]) && $entries[$storage_key]['storage_timestamp'] === $entry['storage_timestamp']) {
+ // Entry is up to date, no update needed.
+ unset($entries[$storage_key]);
+ }
+ }
+
+ if (empty($entries) && empty($removed)) {
+ // No objects were added, updated or removed.
+ return $index;
+ }
+ } elseif (!$removed) {
+ // There are no objects and nothing was removed.
+ return [];
+ }
+
+ // Index should be updated, lock the index file for saving.
+ $indexFile = static::getIndexFile($storage);
+ $indexFile->lock();
+
+ // Read all the data rows into an array.
+ $keys = array_fill_keys(array_keys($entries), null);
+ $rows = $storage->readRows($keys);
+
+ $keyField = $storage->getKeyField();
+
+ // Go through all the updated objects and refresh their index data.
+ $updated = $added = [];
+ foreach ($rows as $key => $row) {
+ if (null !== $row) {
+ $entry = ['key' => $key] + $entries[$key];
+ if ($keyField !== 'storage_key' && isset($row[$keyField])) {
+ $entry['key'] = $row[$keyField];
+ }
+ static::updateIndexData($entry, $row);
+ if (isset($row['__error'])) {
+ $entry['__error'] = true;
+ static::onException(new \RuntimeException(sprintf('Object failed to load: %s (%s)', $key, $row['__error'])));
+ }
+ if (isset($index[$key])) {
+ // Update object in the index.
+ $updated[$key] = $entry;
+ } else {
+ // Add object into the index.
+ $added[$key] = $entry;
+ }
+
+ // Either way, update the entry.
+ $index[$key] = $entry;
+ } elseif (isset($index[$key])) {
+ // Remove object from the index.
+ $removed[$key] = $index[$key];
+ unset($index[$key]);
+ }
+ }
+
+ // Sort the index before saving it.
+ ksort($index, SORT_NATURAL);
+
+ static::onChanges($index, $added, $updated, $removed);
+
+ $indexFile->save(['count' => \count($index), 'index' => $index]);
+ $indexFile->unlock();
+
+ return $index;
+ }
+
+ protected static function updateIndexData(array &$entry, array $data)
+ {
+ }
+
+ protected static function loadEntriesFromIndex(FlexStorageInterface $storage)
+ {
+ $indexFile = static::getIndexFile($storage);
+
+ $data = [];
+ try {
+ $data = (array)$indexFile->content();
+ } catch (\Exception $e) {
+ $e = new \RuntimeException(sprintf('Index failed to load: %s', $e->getMessage()), $e->getCode(), $e);
+
+ static::onException($e);
+ }
+
+ return $data['index'] ?? [];
+ }
+
+ protected static function getIndexFile(FlexStorageInterface $storage)
+ {
+ // Load saved index file.
+ $grav = Grav::instance();
+ $locator = $grav['locator'];
+ $filename = $locator->findResource($storage->getStoragePath() . '/index.yaml', true, true);
+
+ return CompiledYamlFile::instance($filename);
+ }
+
+ protected static function onException(\Exception $e)
+ {
+ $grav = Grav::instance();
+
+ /** @var Logger $logger */
+ $logger = $grav['log'];
+ $logger->addAlert($e->getMessage());
+
+ /** @var Debugger $debugger */
+ $debugger = $grav['debugger'];
+ $debugger->addException($e);
+ $debugger->addMessage($e, 'error');
+ }
+
+ protected static function onChanges(array $entries, array $added, array $updated, array $removed)
+ {
+ $message = sprintf('Index updated, %d objects (%d added, %d updated, %d removed).', \count($entries), \count($added), \count($updated), \count($removed));
+
+ $grav = Grav::instance();
+
+ /** @var Logger $logger */
+ $logger = $grav['log'];
+ $logger->addDebug($message);
+
+ /** @var Debugger $debugger */
+ $debugger = $grav['debugger'];
+ $debugger->addMessage($message, 'debug');
+ }
+
+ public function __debugInfo()
+ {
+ return [
+ 'type:private' => $this->getFlexType(),
+ 'key:private' => $this->getKey(),
+ 'entries_key:private' => $this->getKeyField(),
+ 'entries:private' => $this->getEntries()
+ ];
+ }
+}
diff --git a/system/src/Grav/Framework/Flex/FlexObject.php b/system/src/Grav/Framework/Flex/FlexObject.php
new file mode 100644
index 0000000..0db2cab
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/FlexObject.php
@@ -0,0 +1,855 @@
+ true,
+ 'getType' => true,
+ 'getFlexType' => true,
+ 'getFlexDirectory' => true,
+ 'getCacheKey' => true,
+ 'getCacheChecksum' => true,
+ 'getTimestamp' => true,
+ 'value' => true,
+ 'exists' => true,
+ 'hasProperty' => true,
+ 'getProperty' => true,
+
+ // FlexAclTrait
+ 'isAuthorized' => 'session',
+ ];
+ }
+
+ public static function createFromStorage(array $elements, array $storage, FlexDirectory $directory, bool $validate = false)
+ {
+ $instance = new static($elements, $storage['key'], $directory, $validate);
+ $instance->setStorage($storage);
+
+ return $instance;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::__construct()
+ */
+ public function __construct(array $elements, $key, FlexDirectory $directory, bool $validate = false)
+ {
+ $this->_flexDirectory = $directory;
+
+ if ($validate) {
+ $blueprint = $this->getFlexDirectory()->getBlueprint();
+
+ $blueprint->validate($elements);
+
+ $elements = $blueprint->filter($elements);
+ }
+
+ $this->filterElements($elements);
+
+ $this->objectConstruct($elements, $key);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::getFlexType()
+ */
+ public function getFlexType(): string
+ {
+ return $this->_flexDirectory->getFlexType();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::getFlexDirectory()
+ */
+ public function getFlexDirectory(): FlexDirectory
+ {
+ return $this->_flexDirectory;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::getTimestamp()
+ */
+ public function getTimestamp(): int
+ {
+ return $this->_storage['storage_timestamp'] ?? 0;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::getCacheKey()
+ */
+ public function getCacheKey(): string
+ {
+ return $this->getTypePrefix() . $this->getFlexType() . '.' . $this->getStorageKey();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::getCacheChecksum()
+ */
+ public function getCacheChecksum(): string
+ {
+ return (string)$this->getTimestamp();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::search()
+ */
+ public function search(string $search, $properties = null, array $options = null): float
+ {
+ $options = $options ?? $this->getFlexDirectory()->getConfig('data.search.options', []);
+ $properties = $properties ?? $this->getFlexDirectory()->getConfig('data.search.fields', []);
+
+ $weight = 0;
+ foreach ((array)$properties as $property) {
+ $weight += $this->searchNestedProperty($property, $search, $options);
+ }
+
+ return $weight > 0 ? min($weight, 1) : 0;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see ObjectInterface::getFlexKey()
+ */
+ public function getKey()
+ {
+ return $this->_key ?: $this->getFlexType() . '@@' . spl_object_hash($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::getFlexKey()
+ */
+ public function getFlexKey(): string
+ {
+ return $this->_storage['flex_key'] ?? $this->_flexDirectory->getFlexType() . '.obj:' . $this->getStorageKey();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::getStorageKey()
+ */
+ public function getStorageKey(): string
+ {
+ return $this->_storage['storage_key'] ?? $this->getTypePrefix() . $this->getFlexType() . '@@' . spl_object_hash($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::getMetaData()
+ */
+ public function getMetaData(): array
+ {
+ return $this->getStorage();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::exists()
+ */
+ public function exists(): bool
+ {
+ $key = $this->getStorageKey();
+
+ return $key && $this->getFlexDirectory()->getStorage()->hasKey($key);
+ }
+
+ /**
+ * @param string $property
+ * @param string $search
+ * @param array|null $options
+ * @return float
+ */
+ public function searchProperty(string $property, string $search, array $options = null): float
+ {
+ $options = $options ?? $this->getFlexDirectory()->getConfig('data.search.options', []);
+ $value = $this->getProperty($property);
+
+ return $this->searchValue($property, $value, $search, $options);
+ }
+
+ /**
+ * @param string $property
+ * @param string $search
+ * @param array|null $options
+ * @return float
+ */
+ public function searchNestedProperty(string $property, string $search, array $options = null): float
+ {
+ $options = $options ?? $this->getFlexDirectory()->getConfig('data.search.options', []);
+ $value = $this->getNestedProperty($property);
+
+ return $this->searchValue($property, $value, $search, $options);
+ }
+
+ /**
+ * @param string $name
+ * @param mixed $value
+ * @param string $search
+ * @param array|null $options
+ * @return float
+ */
+ protected function searchValue(string $name, $value, string $search, array $options = null): float
+ {
+ $search = trim($search);
+
+ if ($search === '') {
+ return 0;
+ }
+
+ if (!\is_string($value) || $value === '') {
+ return 0;
+ }
+
+ $tested = false;
+ if (($tested |= !empty($options['starts_with'])) && Utils::startsWith($value, $search, $options['case_sensitive'] ?? false)) {
+ return (float)$options['starts_with'];
+ }
+ if (($tested |= !empty($options['ends_with'])) && Utils::endsWith($value, $search, $options['case_sensitive'] ?? false)) {
+ return (float)$options['ends_with'];
+ }
+ if ((!$tested || !empty($options['contains'])) && Utils::contains($value, $search, $options['case_sensitive'] ?? false)) {
+ return (float)$options['contains'];
+ }
+
+ return 0;
+ }
+
+ /**
+ * Get any changes based on data sent to update
+ *
+ * @return array
+ */
+ public function getChanges(): array
+ {
+ return $this->_changes ?? [];
+ }
+
+ /**
+ * @return string
+ */
+ protected function getTypePrefix(): string
+ {
+ return 'o.';
+ }
+
+ /**
+ * @param bool $prefix
+ * @return string
+ * @deprecated 1.6 Use `->getFlexType()` instead.
+ */
+ public function getType($prefix = false)
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getFlexType() method instead', E_USER_DEPRECATED);
+
+ $type = $prefix ? $this->getTypePrefix() : '';
+
+ return $type . $this->getFlexType();
+ }
+
+ /**
+ * Alias of getBlueprint()
+ *
+ * @return Blueprint
+ * @deprecated 1.6 Admin compatibility
+ */
+ public function blueprints()
+ {
+ return $this->getBlueprint();
+ }
+
+ /**
+ * @param string|null $namespace
+ * @return CacheInterface
+ */
+ public function getCache(string $namespace = null)
+ {
+ return $this->_flexDirectory->getCache($namespace);
+ }
+
+ /**
+ * @param string|null $key
+ * @return $this
+ */
+ public function setStorageKey($key = null)
+ {
+ $this->_storage['storage_key'] = $key;
+
+ return $this;
+ }
+
+ /**
+ * @param int $timestamp
+ * @return $this
+ */
+ public function setTimestamp($timestamp = null)
+ {
+ $this->_storage['storage_timestamp'] = $timestamp ?? time();
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::render()
+ */
+ public function render(string $layout = null, array $context = [])
+ {
+ if (null === $layout) {
+ $layout = 'default';
+ }
+
+ $type = $this->getFlexType();
+
+ $grav = Grav::instance();
+
+ /** @var Debugger $debugger */
+ $debugger = $grav['debugger'];
+ $debugger->startTimer('flex-object-' . ($debugKey = uniqid($type, false)), 'Render Object ' . $type . ' (' . $layout . ')');
+
+ $cache = $key = null;
+ foreach ($context as $value) {
+ if (!\is_scalar($value)) {
+ $key = false;
+ }
+ }
+
+ if ($key !== false) {
+ $key = md5($this->getCacheKey() . '.' . $layout . json_encode($context));
+ $cache = $this->getCache('render');
+ }
+
+ try {
+ $data = $cache ? $cache->get($key) : null;
+
+ $block = $data ? HtmlBlock::fromArray($data) : null;
+ } catch (InvalidArgumentException $e) {
+ $debugger->addException($e);
+
+ $block = null;
+ } catch (\InvalidArgumentException $e) {
+ $debugger->addException($e);
+
+ $block = null;
+ }
+
+ $checksum = $this->getCacheChecksum();
+ if ($block && $checksum !== $block->getChecksum()) {
+ $block = null;
+ }
+
+ if (!$block) {
+ $block = HtmlBlock::create($key);
+ $block->setChecksum($checksum);
+ if ($key === false) {
+ $block->disableCache();
+ }
+
+ $grav->fireEvent('onFlexObjectRender', new Event([
+ 'object' => $this,
+ 'layout' => &$layout,
+ 'context' => &$context
+ ]));
+
+ $output = $this->getTemplate($layout)->render(
+ ['grav' => $grav, 'config' => $grav['config'], 'block' => $block, 'object' => $this, 'layout' => $layout] + $context
+ );
+
+ if ($debugger->enabled()) {
+ $name = $this->getKey() . ' (' . $type . ')';
+ $output = "\n\n{$output}\n\n";
+ }
+
+ $block->setContent($output);
+
+ try {
+ $cache && $block->isCached() && $cache->set($key, $block->toArray());
+ } catch (InvalidArgumentException $e) {
+ $debugger->addException($e);
+ }
+ }
+
+ $debugger->stopTimer('flex-object-' . $debugKey);
+
+ return $block;
+ }
+
+ /**
+ * @return array
+ */
+ public function jsonSerialize()
+ {
+ return $this->getElements();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::prepareStorage()
+ */
+ public function prepareStorage(): array
+ {
+ return $this->getElements();
+ }
+
+ /**
+ * @param string $name
+ * @return $this
+ */
+ public function triggerEvent($name)
+ {
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::update()
+ */
+ public function update(array $data, array $files = [])
+ {
+ if ($data) {
+ $blueprint = $this->getBlueprint();
+
+ // Process updated data through the object filters.
+ $this->filterElements($data);
+
+ // Get currently stored data.
+ $elements = $this->getElements();
+
+ // Merge existing object to the test data to be validated.
+ $test = $blueprint->mergeData($elements, $data);
+
+ // Validate and filter elements and throw an error if any issues were found.
+ $blueprint->validate($test + ['storage_key' => $this->getStorageKey(), 'timestamp' => $this->getTimestamp()]);
+ $data = $blueprint->filter($data, false, true);
+
+ // Finally update the object.
+ foreach ($blueprint->flattenData($data) as $key => $value) {
+ if ($value === null) {
+ $this->unsetNestedProperty($key);
+ } else {
+ $this->setNestedProperty($key, $value);
+ }
+ }
+
+ // Store the changes
+ $this->_changes = Utils::arrayDiffMultidimensional($this->getElements(), $elements);
+ }
+
+ if ($files && method_exists($this, 'setUpdatedMedia')) {
+ $this->setUpdatedMedia($files);
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::create()
+ */
+ public function create(string $key = null)
+ {
+ if ($key) {
+ $this->setStorageKey($key);
+ }
+
+ if ($this->exists()) {
+ throw new \RuntimeException('Cannot create new object (Already exists)');
+ }
+
+ return $this->save();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::save()
+ */
+ public function save()
+ {
+ $this->triggerEvent('onBeforeSave');
+
+ $result = $this->getFlexDirectory()->getStorage()->replaceRows([$this->getStorageKey() => $this->prepareStorage()]);
+
+ $value = reset($result);
+ $storageKey = key($result);
+ if ($value && $storageKey) {
+ $this->setStorageKey($storageKey);
+ if (!$this->hasKey()) {
+ $this->setKey($storageKey);
+ }
+ }
+
+ if (method_exists($this, 'saveUpdatedMedia')) {
+ $this->saveUpdatedMedia();
+ }
+
+ try {
+ $this->getFlexDirectory()->clearCache();
+ if (method_exists($this, 'clearMediaCache')) {
+ $this->clearMediaCache();
+ }
+ } catch (InvalidArgumentException $e) {
+ /** @var Debugger $debugger */
+ $debugger = Grav::instance()['debugger'];
+ $debugger->addException($e);
+
+ // Caching failed, but we can ignore that for now.
+ }
+
+ $this->triggerEvent('onAfterSave');
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::delete()
+ */
+ public function delete()
+ {
+ $this->triggerEvent('onBeforeDelete');
+
+ $this->getFlexDirectory()->getStorage()->deleteRows([$this->getStorageKey() => $this->prepareStorage()]);
+
+ try {
+ $this->getFlexDirectory()->clearCache();
+ if (method_exists($this, 'clearMediaCache')) {
+ $this->clearMediaCache();
+ }
+ } catch (InvalidArgumentException $e) {
+ /** @var Debugger $debugger */
+ $debugger = Grav::instance()['debugger'];
+ $debugger->addException($e);
+
+ // Caching failed, but we can ignore that for now.
+ }
+
+ $this->triggerEvent('onAfterDelete');
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::getBlueprint()
+ */
+ public function getBlueprint(string $name = '')
+ {
+ return $this->_flexDirectory->getBlueprint($name ? '.' . $name : $name);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::getForm()
+ */
+ public function getForm(string $name = '', array $form = null)
+ {
+ if (!isset($this->_forms[$name])) {
+ $this->_forms[$name] = $this->createFormObject($name, $form);
+ }
+
+ return $this->_forms[$name];
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::getDefaultValue()
+ */
+ public function getDefaultValue(string $name, string $separator = null)
+ {
+ $separator = $separator ?: '.';
+ $path = explode($separator, $name) ?: [];
+ $offset = array_shift($path) ?? '';
+
+ $current = $this->getDefaultValues();
+
+ if (!isset($current[$offset])) {
+ return null;
+ }
+
+ $current = $current[$offset];
+
+ while ($path) {
+ $offset = array_shift($path);
+
+ if ((\is_array($current) || $current instanceof \ArrayAccess) && isset($current[$offset])) {
+ $current = $current[$offset];
+ } elseif (\is_object($current) && isset($current->{$offset})) {
+ $current = $current->{$offset};
+ } else {
+ return null;
+ }
+ };
+
+ return $current;
+ }
+
+ /**
+ * @return array
+ */
+ public function getDefaultValues(): array
+ {
+ return $this->getBlueprint()->getDefaults();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexObjectInterface::getFormValue()
+ */
+ public function getFormValue(string $name, $default = null, string $separator = null)
+ {
+ if ($name === 'storage_key') {
+ return $this->getStorageKey();
+ }
+ if ($name === 'storage_timestamp') {
+ return $this->getTimestamp();
+ }
+
+ return $this->getNestedProperty($name, $default, $separator);
+ }
+
+ /**
+ * @param string $name
+ * @param mixed|null $default
+ * @param string|null $separator
+ * @return mixed
+ *
+ * @deprecated 1.6 Use ->getFormValue() method instead.
+ */
+ public function value($name, $default = null, $separator = null)
+ {
+ return $this->getFormValue($name, $default, $separator);
+ }
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getFlexKey();
+ }
+
+ public function __debugInfo()
+ {
+ return [
+ 'type:private' => $this->getFlexType(),
+ 'key:private' => $this->getKey(),
+ 'elements:private' => $this->getElements(),
+ 'storage:private' => $this->getStorage()
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ protected function doSerialize(): array
+ {
+ return [
+ 'type' => $this->getFlexType(),
+ 'key' => $this->getKey(),
+ 'elements' => $this->getElements(),
+ 'storage' => $this->getStorage()
+ ];
+ }
+
+ /**
+ * @param array $serialized
+ */
+ protected function doUnserialize(array $serialized): void
+ {
+ $type = $serialized['type'] ?? 'unknown';
+
+ if (!isset($serialized['key'], $serialized['type'], $serialized['elements'])) {
+ throw new \InvalidArgumentException("Cannot unserialize '{$type}': Bad data");
+ }
+
+ $grav = Grav::instance();
+ /** @var Flex $flex */
+ $flex = $grav['flex_objects'] ?? null;
+ $directory = $flex ? $flex->getDirectory($type) : null;
+ if (!$directory) {
+ throw new \InvalidArgumentException("Cannot unserialize '{$type}': Not found");
+ }
+ $this->setFlexDirectory($directory);
+ $this->setStorage($serialized['storage']);
+ $this->setKey($serialized['key']);
+ $this->setElements($serialized['elements']);
+ }
+
+ /**
+ * @param FlexDirectory $directory
+ */
+ public function setFlexDirectory(FlexDirectory $directory): void
+ {
+ $this->_flexDirectory = $directory;
+ }
+ /**
+ * @param array $storage
+ */
+ protected function setStorage(array $storage) : void
+ {
+ $this->_storage = $storage;
+ }
+
+ /**
+ * @return array
+ */
+ protected function getStorage() : array
+ {
+ return $this->_storage ?? [];
+ }
+
+ /**
+ * @param string $type
+ * @param string $property
+ * @return FlexCollectionInterface
+ */
+ protected function getCollectionByProperty($type, $property)
+ {
+ $directory = $this->getRelatedDirectory($type);
+ $collection = $directory->getCollection();
+ $list = $this->getNestedProperty($property) ?: [];
+
+ /** @var FlexCollection $collection */
+ $collection = $collection->filter(function ($object) use ($list) { return \in_array($object->id, $list, true); });
+
+ return $collection;
+ }
+
+ /**
+ * @param string $type
+ * @return FlexDirectory
+ * @throws \RuntimeException
+ */
+ protected function getRelatedDirectory($type): FlexDirectory
+ {
+ /** @var Flex $flex */
+ $flex = Grav::instance()['flex_objects'];
+ $directory = $flex->getDirectory($type);
+ if (!$directory) {
+ throw new \RuntimeException(ucfirst($type). ' directory does not exist!');
+ }
+
+ return $directory;
+ }
+
+ /**
+ * @param string $layout
+ * @return TemplateWrapper
+ * @throws LoaderError
+ * @throws SyntaxError
+ */
+ protected function getTemplate($layout)
+ {
+ $grav = Grav::instance();
+
+ /** @var Twig $twig */
+ $twig = $grav['twig'];
+
+ try {
+ return $twig->twig()->resolveTemplate(["flex-objects/layouts/{$this->getFlexType()}/object/{$layout}.html.twig"]);
+ } catch (LoaderError $e) {
+ /** @var Debugger $debugger */
+ $debugger = Grav::instance()['debugger'];
+ $debugger->addException($e);
+
+ return $twig->twig()->resolveTemplate(['flex-objects/layouts/404.html.twig']);
+ }
+ }
+
+ /**
+ * Filter data coming to constructor or $this->update() request.
+ *
+ * NOTE: The incoming data can be an arbitrary array so do not assume anything from its content.
+ *
+ * @param array $elements
+ */
+ protected function filterElements(array &$elements): void
+ {
+ if (!empty($elements['storage_key'])) {
+ $this->_storage['storage_key'] = trim($elements['storage_key']);
+ }
+ if (!empty($elements['storage_timestamp'])) {
+ $this->_storage['storage_timestamp'] = (int)$elements['storage_timestamp'];
+ }
+
+ unset ($elements['storage_key'], $elements['storage_timestamp'], $elements['_post_entries_save']);
+ }
+
+ /**
+ * This methods allows you to override form objects in child classes.
+ *
+ * @param string $name Form name
+ * @param array|null $form Form fields
+ * @return FlexFormInterface
+ */
+ protected function createFormObject(string $name, array $form = null)
+ {
+ return new FlexForm($name, $this, $form);
+ }
+}
diff --git a/system/src/Grav/Framework/Flex/Interfaces/FlexAuthorizeInterface.php b/system/src/Grav/Framework/Flex/Interfaces/FlexAuthorizeInterface.php
new file mode 100644
index 0000000..cfcb8a9
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/Interfaces/FlexAuthorizeInterface.php
@@ -0,0 +1,32 @@
+ 'ASC'|'DESC', ...].
+ *
+ * @return FlexCollectionInterface Returns a sorted version from the collection.
+ */
+ public function sort(array $orderings);
+
+ /**
+ * Filter collection by filter array with keys and values.
+ *
+ * @param array $filters
+ * @return FlexCollectionInterface
+ */
+ public function filterBy(array $filters);
+
+ /**
+ * Get timestamps from all the objects in the collection.
+ *
+ * This method can be used for example in caching.
+ *
+ * @return int[] Returns [key => timestamp, ...] pairs.
+ */
+ public function getTimestamps(): array;
+
+ /**
+ * Get storage keys from all the objects in the collection.
+ *
+ * @see FlexDirectory::getObject() If you want to get Flex Object from the Flex Directory.
+ *
+ * @return string[] Returns [key => storage_key, ...] pairs.
+ */
+ public function getStorageKeys(): array;
+
+ /**
+ * Get Flex keys from all the objects in the collection.
+ *
+ * @see Flex::getObjects() If you want to get list of Flex Objects from any Flex Directory.
+ *
+ * @return string[] Returns[key => flex_key, ...] pairs.
+ */
+ public function getFlexKeys(): array;
+
+ /**
+ * Return new collection with a different key.
+ *
+ * @param string|null $keyField Switch key field of the collection.
+ *
+ * @return FlexCollectionInterface Returns a new Flex Collection with new key field.
+ * @api
+ */
+ public function withKeyField(string $keyField = null);
+
+ /**
+ * Get Flex Index from the Flex Collection.
+ *
+ * @return FlexIndexInterface Returns a Flex Index from the current collection.
+ */
+ public function getIndex();
+}
diff --git a/system/src/Grav/Framework/Flex/Interfaces/FlexCommonInterface.php b/system/src/Grav/Framework/Flex/Interfaces/FlexCommonInterface.php
new file mode 100644
index 0000000..482aef2
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/Interfaces/FlexCommonInterface.php
@@ -0,0 +1,64 @@
+ [storage_key => xxx, storage_timestamp => 123456, ...]]
+ */
+ public static function loadEntriesFromStorage(FlexStorageInterface $storage): array;
+
+ /**
+ * Return new collection with a different key.
+ *
+ * @param string|null $keyField Switch key field of the collection.
+ *
+ * @return FlexIndexInterface Returns a new Flex Collection with new key field.
+ * @api
+ */
+ public function withKeyField(string $keyField = null);
+
+ /**
+ * @param string $indexKey
+ * @return array
+ */
+ public function getIndexMap(string $indexKey = null);
+}
diff --git a/system/src/Grav/Framework/Flex/Interfaces/FlexObjectInterface.php b/system/src/Grav/Framework/Flex/Interfaces/FlexObjectInterface.php
new file mode 100644
index 0000000..a10ee84
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/Interfaces/FlexObjectInterface.php
@@ -0,0 +1,208 @@
+ [storage_key => key, storage_timestamp => timestamp], ...]`.
+ */
+ public function getExistingKeys(): array;
+
+ /**
+ * Check if the key exists in the storage.
+ *
+ * @param string $key Storage key of an object.
+ *
+ * @return bool Returns `true` if the key exists in the storage, `false` otherwise.
+ */
+ public function hasKey(string $key): bool;
+
+ /**
+ * Check if the key exists in the storage.
+ *
+ * @param string[] $keys Storage key of an object.
+ *
+ * @return bool[] Returns keys with `true` if the key exists in the storage, `false` otherwise.
+ */
+ public function hasKeys(array $keys): array;
+
+ /**
+ * Create new rows into the storage.
+ *
+ * New keys will be assigned when the objects are created.
+ *
+ * @param array $rows List of rows as `[row, ...]`.
+ *
+ * @return array Returns created rows as `[key => row, ...] pairs.
+ */
+ public function createRows(array $rows): array;
+
+ /**
+ * Read rows from the storage.
+ *
+ * If you pass object or array as value, that value will be used to save I/O.
+ *
+ * @param array $rows Array of `[key => row, ...]` pairs.
+ * @param array $fetched Optional reference to store only fetched items.
+ *
+ * @return array Returns rows. Note that non-existing rows will have `null` as their value.
+ */
+ public function readRows(array $rows, array &$fetched = null): array;
+
+ /**
+ * Update existing rows in the storage.
+ *
+ * @param array $rows Array of `[key => row, ...]` pairs.
+ *
+ * @return array Returns updated rows. Note that non-existing rows will not be saved and have `null` as their value.
+ */
+ public function updateRows(array $rows): array;
+
+ /**
+ * Delete rows from the storage.
+ *
+ * @param array $rows Array of `[key => row, ...]` pairs.
+ *
+ * @return array Returns deleted rows. Note that non-existing rows have `null` as their value.
+ */
+ public function deleteRows(array $rows): array;
+
+ /**
+ * Replace rows regardless if they exist or not.
+ *
+ * All rows should have a specified key for replace to work properly.
+ *
+ * @param array $rows Array of `[key => row, ...]` pairs.
+ *
+ * @return array Returns both created and updated rows.
+ */
+ public function replaceRows(array $rows): array;
+
+ /**
+ * @param string $src
+ * @param string $dst
+ *
+ * @return bool
+ */
+ public function renameRow(string $src, string $dst): bool;
+
+ /**
+ * Get filesystem path for the collection or object storage.
+ *
+ * @param string|null $key Optional storage key.
+ *
+ * @return string Path in the filesystem. Can be URI.
+ */
+ public function getStoragePath(string $key = null): string;
+
+ /**
+ * Get filesystem path for the collection or object media.
+ *
+ * @param string|null $key Optional storage key.
+ *
+ * @return string Path in the filesystem. Can be URI.
+ */
+ public function getMediaPath(string $key = null): string;
+}
diff --git a/system/src/Grav/Framework/Flex/Storage/AbstractFilesystemStorage.php b/system/src/Grav/Framework/Flex/Storage/AbstractFilesystemStorage.php
new file mode 100644
index 0000000..b547cc2
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/Storage/AbstractFilesystemStorage.php
@@ -0,0 +1,154 @@
+hasKey((string)$key);
+ }
+
+ return $list;
+ }
+
+ /**
+ * @return string
+ */
+ public function getKeyField(): string
+ {
+ return $this->keyField;
+ }
+
+ protected function initDataFormatter($formatter): void
+ {
+ // Initialize formatter.
+ if (!\is_array($formatter)) {
+ $formatter = ['class' => $formatter];
+ }
+ $formatterClassName = $formatter['class'] ?? JsonFormatter::class;
+ $formatterOptions = $formatter['options'] ?? [];
+
+ $this->dataFormatter = new $formatterClassName($formatterOptions);
+ }
+
+ /**
+ * @param string $filename
+ * @return null|string
+ */
+ protected function detectDataFormatter(string $filename): ?string
+ {
+ if (preg_match('|(\.[a-z0-9]*)$|ui', $filename, $matches)) {
+ switch ($matches[1]) {
+ case '.json':
+ return JsonFormatter::class;
+ case '.yaml':
+ return YamlFormatter::class;
+ case '.md':
+ return MarkdownFormatter::class;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @param string $filename
+ * @return File
+ */
+ protected function getFile(string $filename)
+ {
+ $filename = $this->resolvePath($filename);
+
+ switch ($this->dataFormatter->getDefaultFileExtension()) {
+ case '.json':
+ $file = CompiledJsonFile::instance($filename);
+ break;
+ case '.yaml':
+ $file = CompiledYamlFile::instance($filename);
+ break;
+ case '.md':
+ $file = CompiledMarkdownFile::instance($filename);
+ break;
+ default:
+ throw new RuntimeException('Unknown extension type ' . $this->dataFormatter->getDefaultFileExtension());
+ }
+
+ return $file;
+ }
+
+ /**
+ * @param string $path
+ * @return string
+ */
+ protected function resolvePath(string $path): string
+ {
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+
+ if (!$locator->isStream($path)) {
+ return $path;
+ }
+
+ return (string) $locator->findResource($path) ?: $locator->findResource($path, true, true);
+ }
+
+ /**
+ * Generates a random, unique key for the row.
+ *
+ * @return string
+ */
+ protected function generateKey(): string
+ {
+ return substr(hash('sha256', random_bytes(32)), 0, 32);
+ }
+
+ /**
+ * Checks if a key is valid.
+ *
+ * @param string $key
+ * @return bool
+ */
+ protected function validateKey(string $key): bool
+ {
+ return (bool) preg_match('/^[^\\/\\?\\*:;{}\\\\\\n]+$/u', $key);
+ }
+}
diff --git a/system/src/Grav/Framework/Flex/Storage/FileStorage.php b/system/src/Grav/Framework/Flex/Storage/FileStorage.php
new file mode 100644
index 0000000..e263e6c
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/Storage/FileStorage.php
@@ -0,0 +1,82 @@
+dataPattern = '{FOLDER}/{KEY}';
+
+ if (!isset($options['formatter']) && isset($options['pattern'])) {
+ $options['formatter'] = $this->detectDataFormatter($options['pattern']);
+ }
+
+ parent::__construct($options);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::getMediaPath()
+ */
+ public function getMediaPath(string $key = null): string
+ {
+ return $key ? \dirname($this->getStoragePath($key)) . '/' . $key : $this->getStoragePath();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getKeyFromPath(string $path): string
+ {
+ return basename($path, $this->dataFormatter->getDefaultFileExtension());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function buildIndex(): array
+ {
+ if (!file_exists($this->getStoragePath())) {
+ return [];
+ }
+
+ $flags = \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS;
+ $iterator = new \FilesystemIterator($this->getStoragePath(), $flags);
+ $list = [];
+ /** @var \SplFileInfo $info */
+ foreach ($iterator as $filename => $info) {
+ if (!$info->isFile() || !($key = $this->getKeyFromPath($filename)) || strpos($info->getFilename(), '.') === 0) {
+ continue;
+ }
+
+ $list[$key] = [
+ 'storage_key' => $key,
+ 'storage_timestamp' => $info->getMTime()
+ ];
+ }
+
+ ksort($list, SORT_NATURAL);
+
+ return $list;
+ }
+}
diff --git a/system/src/Grav/Framework/Flex/Storage/FolderStorage.php b/system/src/Grav/Framework/Flex/Storage/FolderStorage.php
new file mode 100644
index 0000000..2304e80
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/Storage/FolderStorage.php
@@ -0,0 +1,468 @@
+initDataFormatter($options['formatter'] ?? []);
+ $this->initOptions($options);
+
+ // Make sure that the data folder exists.
+ $folder = $this->resolvePath($this->dataFolder);
+ if (!file_exists($folder)) {
+ try {
+ Folder::create($folder);
+ } catch (\RuntimeException $e) {
+ throw new \RuntimeException(sprintf('Flex: %s', $e->getMessage()));
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::getExistingKeys()
+ */
+ public function getExistingKeys(): array
+ {
+ return $this->buildIndex();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::hasKey()
+ */
+ public function hasKey(string $key): bool
+ {
+ return $key && strpos($key, '@@') === false && file_exists($this->getPathFromKey($key));
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::createRows()
+ */
+ public function createRows(array $rows): array
+ {
+ $list = [];
+ foreach ($rows as $key => $row) {
+ // Create new file and save it.
+ $key = $this->getNewKey();
+ $path = $this->getPathFromKey($key);
+ $file = $this->getFile($path);
+ $list[$key] = $this->saveFile($file, $row);
+ }
+
+ return $list;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::readRows()
+ */
+ public function readRows(array $rows, array &$fetched = null): array
+ {
+ $list = [];
+ foreach ($rows as $key => $row) {
+ if (null === $row || (!\is_object($row) && !\is_array($row))) {
+ // Only load rows which haven't been loaded before.
+ $key = (string)$key;
+ if (!$this->hasKey($key)) {
+ $list[$key] = null;
+ } else {
+ $path = $this->getPathFromKey($key);
+ $file = $this->getFile($path);
+ $list[$key] = $this->loadFile($file);
+ }
+ if (null !== $fetched) {
+ $fetched[$key] = $list[$key];
+ }
+ } else {
+ // Keep the row if it has been loaded.
+ $list[$key] = $row;
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::updateRows()
+ */
+ public function updateRows(array $rows): array
+ {
+ $list = [];
+ foreach ($rows as $key => $row) {
+ $key = (string)$key;
+ if (!$this->hasKey($key)) {
+ $list[$key] = null;
+ } else {
+ $path = $this->getPathFromKey($key);
+ $file = $this->getFile($path);
+ $list[$key] = $this->saveFile($file, $row);
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::deleteRows()
+ */
+ public function deleteRows(array $rows): array
+ {
+ $list = [];
+ foreach ($rows as $key => $row) {
+ $key = (string)$key;
+ if (!$this->hasKey($key)) {
+ $list[$key] = null;
+ } else {
+ $path = $this->getPathFromKey($key);
+ $file = $this->getFile($path);
+ $list[$key] = $this->deleteFile($file);
+
+ $storage = $this->getStoragePath($key);
+ $media = $this->getMediaPath($key);
+
+ $this->deleteFolder($storage, true);
+ $media && $this->deleteFolder($media, true);
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::replaceRows()
+ */
+ public function replaceRows(array $rows): array
+ {
+ $list = [];
+ foreach ($rows as $key => $row) {
+ $key = (string)$key;
+ if (strpos($key, '@@')) {
+ $key = $this->getNewKey();
+ }
+ $path = $this->getPathFromKey($key);
+ $file = $this->getFile($path);
+ $list[$key] = $this->saveFile($file, $row);
+ }
+
+ return $list;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::renameRow()
+ */
+ public function renameRow(string $src, string $dst): bool
+ {
+ if ($this->hasKey($dst)) {
+ throw new \RuntimeException("Cannot rename object: key '{$dst}' is already taken");
+ }
+
+ if (!$this->hasKey($src)) {
+ return false;
+ }
+
+ return $this->moveFolder($this->getMediaPath($src), $this->getMediaPath($dst));
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::getStoragePath()
+ */
+ public function getStoragePath(string $key = null): string
+ {
+ if (null === $key) {
+ $path = $this->dataFolder;
+ } else {
+ $path = sprintf($this->dataPattern, $this->dataFolder, $key, substr($key, 0, 2));
+ }
+
+ return $path;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::getMediaPath()
+ */
+ public function getMediaPath(string $key = null): string
+ {
+ return null !== $key ? \dirname($this->getStoragePath($key)) : $this->getStoragePath();
+ }
+
+ /**
+ * Get filesystem path from the key.
+ *
+ * @param string $key
+ * @return string
+ */
+ public function getPathFromKey(string $key): string
+ {
+ return sprintf($this->dataPattern, $this->dataFolder, $key, substr($key, 0, 2));
+ }
+
+ /**
+ * @param File $file
+ * @return array|null
+ */
+ protected function loadFile(File $file): ?array
+ {
+ if (!$file->exists()) {
+ return null;
+ }
+
+ try {
+ $content = (array)$file->content();
+ if (isset($content[0])) {
+ throw new \RuntimeException('Broken object file.');
+ }
+ } catch (\RuntimeException $e) {
+ $content = ['__error' => $e->getMessage()];
+ }
+
+ return $content;
+ }
+
+ /**
+ * @param File $file
+ * @param array $data
+ * @return array
+ */
+ protected function saveFile(File $file, array $data): array
+ {
+ try {
+ $file->save($data);
+
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+ if ($locator->isStream($file->filename())) {
+ $locator->clearCache($file->filename());
+ }
+ } catch (\RuntimeException $e) {
+ throw new \RuntimeException(sprintf('Flex saveFile(%s): %s', $file->filename(), $e->getMessage()));
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param File $file
+ * @return array|string
+ */
+ protected function deleteFile(File $file)
+ {
+ try {
+ $data = $file->content();
+ $file->delete();
+
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+ if ($locator->isStream($file->filename())) {
+ $locator->clearCache($file->filename());
+ }
+ } catch (\RuntimeException $e) {
+ throw new \RuntimeException(sprintf('Flex deleteFile(%s): %s', $file->filename(), $e->getMessage()));
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param string $src
+ * @param string $dst
+ * @return bool
+ */
+ protected function moveFolder(string $src, string $dst): bool
+ {
+ try {
+ Folder::move($this->resolvePath($src), $this->resolvePath($dst));
+
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+ if ($locator->isStream($src) || $locator->isStream($dst)) {
+ $locator->clearCache();
+ }
+ } catch (\RuntimeException $e) {
+ throw new \RuntimeException(sprintf('Flex moveFolder(%s, %s): %s', $src, $dst, $e->getMessage()));
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $path
+ * @param bool $include_target
+ * @return bool
+ */
+ protected function deleteFolder(string $path, bool $include_target = false): bool
+ {
+ try {
+ $success = Folder::delete($this->resolvePath($path), $include_target);
+
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+ if ($locator->isStream($path)) {
+ $locator->clearCache();
+ }
+
+ return $success;
+ } catch (\RuntimeException $e) {
+ throw new \RuntimeException(sprintf('Flex deleteFolder(%s): %s', $path, $e->getMessage()));
+ }
+ }
+
+ /**
+ * Get key from the filesystem path.
+ *
+ * @param string $path
+ * @return string
+ */
+ protected function getKeyFromPath(string $path): string
+ {
+ return basename($path);
+ }
+
+ /**
+ * Returns list of all stored keys in [key => timestamp] pairs.
+ *
+ * @return array
+ */
+ protected function buildIndex(): array
+ {
+ $path = $this->getStoragePath();
+ if (!file_exists($path)) {
+ return [];
+ }
+
+ if ($this->prefixed) {
+ $list = $this->buildPrefixedIndexFromFilesystem($path);
+ } else {
+ $list = $this->buildIndexFromFilesystem($path);
+ }
+
+ ksort($list, SORT_NATURAL);
+
+ return $list;
+ }
+
+ protected function buildIndexFromFilesystem($path)
+ {
+ $flags = \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS;
+
+ $iterator = new \FilesystemIterator($path, $flags);
+ $list = [];
+ /** @var \SplFileInfo $info */
+ foreach ($iterator as $filename => $info) {
+ if (!$info->isDir()) {
+ continue;
+ }
+
+ $key = $this->getKeyFromPath($filename);
+ $filename = $this->getPathFromKey($key);
+ $modified = is_file($filename) ? filemtime($filename) : null;
+ if (null === $modified) {
+ continue;
+ }
+
+ $list[$key] = [
+ 'storage_key' => $key,
+ 'storage_timestamp' => $modified
+ ];
+ }
+
+ return $list;
+ }
+
+ protected function buildPrefixedIndexFromFilesystem($path)
+ {
+ $flags = \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS;
+
+ $iterator = new \FilesystemIterator($path, $flags);
+ $list = [];
+ /** @var \SplFileInfo $info */
+ foreach ($iterator as $filename => $info) {
+ if (!$info->isDir() || strpos($info->getFilename(), '.') === 0) {
+ continue;
+ }
+
+ $list[] = $this->buildIndexFromFilesystem($filename);
+ }
+
+ if (!$list) {
+ return [];
+ }
+
+ return \count($list) > 1 ? array_merge(...$list) : $list[0];
+ }
+
+ /**
+ * @return string
+ */
+ protected function getNewKey(): string
+ {
+ // Make sure that the file doesn't exist.
+ do {
+ $key = $this->generateKey();
+ } while (file_exists($this->getPathFromKey($key)));
+
+ return $key;
+ }
+
+ /**
+ * @param array $options
+ */
+ protected function initOptions(array $options): void
+ {
+ $extension = $this->dataFormatter->getDefaultFileExtension();
+ $pattern = !empty($options['pattern']) ? $options['pattern'] : $this->dataPattern;
+
+ $this->dataFolder = $options['folder'];
+ $this->prefixed = (bool)($options['prefixed'] ?? strpos($pattern, '/{KEY:2}/'));
+ $this->indexed = (bool)($options['indexed'] ?? false);
+ $this->keyField = $options['key'] ?? 'storage_key';
+
+ $pattern = preg_replace(['/{FOLDER}/', '/{KEY}/', '/{KEY:2}/'], ['%1$s', '%2$s', '%3$s'], $pattern);
+ $this->dataPattern = \dirname($pattern) . '/' . basename($pattern, $extension) . $extension;
+ }
+}
diff --git a/system/src/Grav/Framework/Flex/Storage/SimpleStorage.php b/system/src/Grav/Framework/Flex/Storage/SimpleStorage.php
new file mode 100644
index 0000000..7f37b1a
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/Storage/SimpleStorage.php
@@ -0,0 +1,315 @@
+detectDataFormatter($options['folder']);
+ $this->initDataFormatter($formatter);
+
+ $extension = $this->dataFormatter->getDefaultFileExtension();
+ $pattern = basename($options['folder']);
+
+ $this->dataPattern = basename($pattern, $extension) . $extension;
+ $this->dataFolder = \dirname($options['folder']);
+
+ // Make sure that the data folder exists.
+ if (!file_exists($this->dataFolder)) {
+ try {
+ Folder::create($this->dataFolder);
+ } catch (\RuntimeException $e) {
+ throw new \RuntimeException(sprintf('Flex: %s', $e->getMessage()));
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::getExistingKeys()
+ */
+ public function getExistingKeys(): array
+ {
+ return $this->buildIndex();
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::hasKey()
+ */
+ public function hasKey(string $key): bool
+ {
+ if (null === $this->data) {
+ $this->buildIndex();
+ }
+
+ return $key && strpos($key, '@@') === false && isset($this->data[$key]);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::createRows()
+ */
+ public function createRows(array $rows): array
+ {
+ if (null === $this->data) {
+ $this->buildIndex();
+ }
+
+ $list = [];
+ foreach ($rows as $key => $row) {
+ $key = $this->getNewKey();
+ $this->data[$key] = $list[$key] = $row;
+ }
+
+ if ($list) {
+ $this->save();
+ }
+
+ return $list;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::readRows()
+ */
+ public function readRows(array $rows, array &$fetched = null): array
+ {
+ if (null === $this->data) {
+ $this->buildIndex();
+ }
+
+ $list = [];
+ foreach ($rows as $key => $row) {
+ if (null === $row || (!\is_object($row) && !\is_array($row))) {
+ // Only load rows which haven't been loaded before.
+ $key = (string)$key;
+ $list[$key] = $this->hasKey($key) ? $this->data[$key] : null;
+ if (null !== $fetched) {
+ $fetched[$key] = $list[$key];
+ }
+ } else {
+ // Keep the row if it has been loaded.
+ $list[$key] = $row;
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::updateRows()
+ */
+ public function updateRows(array $rows): array
+ {
+ if (null === $this->data) {
+ $this->buildIndex();
+ }
+
+ $list = [];
+ foreach ($rows as $key => $row) {
+ $key = (string)$key;
+ if ($this->hasKey($key)) {
+ $this->data[$key] = $list[$key] = $row;
+ }
+ }
+
+ if ($list) {
+ $this->save();
+ }
+
+ return $list;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::deleteRows()
+ */
+ public function deleteRows(array $rows): array
+ {
+ if (null === $this->data) {
+ $this->buildIndex();
+ }
+
+ $list = [];
+ foreach ($rows as $key => $row) {
+ $key = (string)$key;
+ if ($this->hasKey($key)) {
+ unset($this->data[$key]);
+ $list[$key] = $row;
+ }
+ }
+
+ if ($list) {
+ $this->save();
+ }
+
+ return $list;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::replaceRows()
+ */
+ public function replaceRows(array $rows): array
+ {
+ if (null === $this->data) {
+ $this->buildIndex();
+ }
+
+ $list = [];
+ foreach ($rows as $key => $row) {
+ $this->data[$key] = $list[$key] = $row;
+ }
+
+ if ($list) {
+ $this->save();
+ }
+
+ return $list;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::renameRow()
+ */
+ public function renameRow(string $src, string $dst): bool
+ {
+ if (null === $this->data) {
+ $this->buildIndex();
+ }
+
+ if ($this->hasKey($dst)) {
+ throw new \RuntimeException("Cannot rename object: key '{$dst}' is already taken");
+ }
+
+ if (!$this->hasKey($src)) {
+ return false;
+ }
+
+ // Change single key in the array without changing the order or value.
+ $keys = array_keys($this->data);
+ $keys[array_search($src, $keys, true)] = $dst;
+
+ $this->data = array_combine($keys, $this->data);
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::getStoragePath()
+ */
+ public function getStoragePath(string $key = null): string
+ {
+ return $this->dataFolder . '/' . $this->dataPattern;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FlexStorageInterface::getMediaPath()
+ */
+ public function getMediaPath(string $key = null): string
+ {
+ return sprintf('%s/%s/%s', $this->dataFolder, basename($this->dataPattern, $this->dataFormatter->getDefaultFileExtension()), $key);
+ }
+
+ protected function save() : void
+ {
+ if (null === $this->data) {
+ $this->buildIndex();
+ }
+
+ try {
+ $file = $this->getFile($this->getStoragePath());
+ $file->save($this->data);
+ $file->free();
+ } catch (\RuntimeException $e) {
+ throw new \RuntimeException(sprintf('Flex save(): %s', $e->getMessage()));
+ }
+ }
+
+ /**
+ * Get key from the filesystem path.
+ *
+ * @param string $path
+ * @return string
+ */
+ protected function getKeyFromPath(string $path): string
+ {
+ return basename($path);
+ }
+
+ /**
+ * Returns list of all stored keys in [key => timestamp] pairs.
+ *
+ * @return array
+ */
+ protected function buildIndex(): array
+ {
+ $file = $this->getFile($this->getStoragePath());
+ $modified = $file->modified();
+
+ $this->data = (array) $file->content();
+
+ $list = [];
+ foreach ($this->data as $key => $info) {
+ $list[$key] = [
+ 'storage_key' => $key,
+ 'storage_timestamp' => $modified
+ ];
+ }
+
+ return $list;
+ }
+
+ /**
+ * @return string
+ */
+ protected function getNewKey(): string
+ {
+ if (null === $this->data) {
+ $this->buildIndex();
+ }
+
+ // Make sure that the key doesn't exist.
+ do {
+ $key = $this->generateKey();
+ } while (isset($this->data[$key]));
+
+ return $key;
+ }
+}
diff --git a/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php b/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php
new file mode 100644
index 0000000..66a467d
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php
@@ -0,0 +1,54 @@
+isAuthorizedAction($user, $action, $scope) || $this->isAuthorizedSuperAdmin($user);
+ }
+
+ protected function isAuthorizedSuperAdmin(UserInterface $user): bool
+ {
+ return $user->authorize('admin.super');
+ }
+
+ protected function isAuthorizedAction(UserInterface $user, string $action, string $scope = null) : bool
+ {
+ $scope = $scope ?? isset(Grav::instance()['admin']) ? 'admin' : 'site';
+
+ if ($action === 'save' && $this instanceof FlexObjectInterface) {
+ $action = $this->exists() ? 'update' : 'create';
+ }
+
+ return $user->authorize(sprintf($this->_authorize, $scope, $action));
+ }
+
+ protected function setAuthorizeRule(string $authorize) : void
+ {
+ $this->_authorize = $authorize;
+ }
+}
diff --git a/system/src/Grav/Framework/Flex/Traits/FlexMediaTrait.php b/system/src/Grav/Framework/Flex/Traits/FlexMediaTrait.php
new file mode 100644
index 0000000..d95774f
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/Traits/FlexMediaTrait.php
@@ -0,0 +1,337 @@
+ $this->getUpdatedMedia()
+ ];
+ }
+
+ /**
+ * @return string
+ */
+ public function getStorageFolder()
+ {
+ return $this->getFlexDirectory()->getStorageFolder($this->getStorageKey());
+ }
+
+ /**
+ * @return string
+ */
+ public function getMediaFolder()
+ {
+ return $this->getFlexDirectory()->getMediaFolder($this->getStorageKey());
+ }
+
+ /**
+ * @return MediaCollectionInterface
+ */
+ public function getMedia()
+ {
+ if ($this->media === null) {
+ /** @var AbstractMedia $media */
+ $media = $this->getExistingMedia();
+
+ // Include uploaded media to the object media.
+ /** @var FormFlashFile $upload */
+ foreach ($this->getUpdatedMedia() as $filename => $upload) {
+ // Just make sure we do not include removed or moved media.
+ if ($upload && $upload->getError() === \UPLOAD_ERR_OK && !$upload->isMoved()) {
+ $media->add($filename, MediumFactory::fromUploadedFile($upload));
+ }
+ }
+
+ $media->setTimestamps();
+ }
+
+ return $this->media;
+ }
+
+ public function checkUploadedMediaFile(UploadedFileInterface $uploadedFile)
+ {
+ $grav = Grav::instance();
+ $language = $grav['language'];
+
+ switch ($uploadedFile->getError()) {
+ case UPLOAD_ERR_OK:
+ break;
+ case UPLOAD_ERR_NO_FILE:
+ if ($uploadedFile instanceof FormFlashFile) {
+ break;
+ }
+ throw new RuntimeException($language->translate('PLUGIN_ADMIN.NO_FILES_SENT'), 400);
+ case UPLOAD_ERR_INI_SIZE:
+ case UPLOAD_ERR_FORM_SIZE:
+ throw new RuntimeException($language->translate('PLUGIN_ADMIN.EXCEEDED_FILESIZE_LIMIT'), 400);
+ case UPLOAD_ERR_NO_TMP_DIR:
+ throw new RuntimeException($language->translate('PLUGIN_ADMIN.UPLOAD_ERR_NO_TMP_DIR'), 400);
+ default:
+ throw new RuntimeException($language->translate('PLUGIN_ADMIN.UNKNOWN_ERRORS'), 400);
+ }
+
+ $filename = $uploadedFile->getClientFilename();
+
+ if (!Utils::checkFilename($filename)) {
+ throw new RuntimeException(sprintf($language->translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD'), $filename, 'Bad filename'), 400);
+ }
+
+ $grav_limit = Utils::getUploadLimit();
+ if ($grav_limit > 0 && $uploadedFile->getSize() > $grav_limit) {
+ throw new RuntimeException($language->translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT'), 400);
+ }
+
+ $this->checkMediaFilename($filename);
+ }
+
+ public function checkMediaFilename(string $filename)
+ {
+ // Check the file extension.
+ $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
+
+ $grav = Grav::instance();
+
+ /** @var Config $config */
+ $config = $grav['config'];
+
+ // If not a supported type, return
+ if (!$extension || !$config->get("media.types.{$extension}")) {
+ $language = $grav['language'];
+ throw new RuntimeException($language->translate('PLUGIN_ADMIN.UNSUPPORTED_FILE_TYPE') . ': ' . $extension, 400);
+ }
+ }
+
+ public function uploadMediaFile(UploadedFileInterface $uploadedFile, string $filename = null): void
+ {
+ $this->checkUploadedMediaFile($uploadedFile);
+
+ if ($filename) {
+ $this->checkMediaFilename(basename($filename));
+ } else {
+ $filename = $uploadedFile->getClientFilename();
+ }
+
+ $media = $this->getMedia();
+ $grav = Grav::instance();
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $grav['locator'];
+ $path = $media->getPath();
+ if ($locator->isStream($path)) {
+ $path = $locator->findResource($path, true, true);
+ $locator->clearCache($path);
+ }
+
+ try {
+ // Upload it
+ $filepath = sprintf('%s/%s', $path, $filename);
+ Folder::create(\dirname($filepath));
+ if ($uploadedFile instanceof FormFlashFile) {
+ $metadata = $uploadedFile->getMetaData();
+ if ($metadata) {
+ $file = YamlFile::instance($filepath . '.meta.yaml');
+ $file->save(['upload' => $metadata]);
+ }
+ if ($uploadedFile->getError() === \UPLOAD_ERR_OK) {
+ $uploadedFile->moveTo($filepath);
+ } elseif (!file_exists($filepath) && $pos = strpos($filename, '/')) {
+ $origpath = sprintf('%s/%s', $path, substr($filename, $pos));
+ if (file_exists($origpath)) {
+ copy($origpath, $filepath);
+ }
+ }
+ } else {
+ $uploadedFile->moveTo($filepath);
+ }
+ } catch (\Exception $e) {
+ $language = $grav['language'];
+
+ throw new RuntimeException($language->translate('PLUGIN_ADMIN.FAILED_TO_MOVE_UPLOADED_FILE'), 400);
+ }
+
+ $this->clearMediaCache();
+ }
+
+ public function deleteMediaFile(string $filename): void
+ {
+ $grav = Grav::instance();
+ $language = $grav['language'];
+
+ $basename = basename($filename);
+ $dirname = dirname($filename);
+ $dirname = $dirname === '.' ? '' : '/' . $dirname;
+
+ if (!Utils::checkFilename($basename)) {
+ throw new RuntimeException($language->translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': Bad filename: ' . $filename, 400);
+ }
+
+ $media = $this->getMedia();
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $grav['locator'];
+
+ $targetPath = $media->getPath() . '/' . $dirname;
+ $targetFile = $media->getPath() . '/' . $filename;
+ if ($locator->isStream($targetFile)) {
+ $targetPath = $locator->findResource($targetPath, true, true);
+ $targetFile = $locator->findResource($targetFile, true, true);
+ $locator->clearCache($targetPath);
+ $locator->clearCache($targetFile);
+ }
+
+ $fileParts = pathinfo($basename);
+
+ if (!file_exists($targetPath)) {
+ return;
+ }
+
+ if (file_exists($targetFile)) {
+ $result = unlink($targetFile);
+ if (!$result) {
+ throw new RuntimeException($language->translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . $filename, 500);
+ }
+ }
+
+ // Remove Extra Files
+
+ foreach (scandir($targetPath, SCANDIR_SORT_NONE) as $file) {
+ $preg_name = preg_quote($fileParts['filename'], '`');
+ $preg_ext =preg_quote($fileParts['extension'], '`');
+ $preg_filename = preg_quote($basename, '`');
+
+ if (preg_match("`({$preg_name}@\d+x\.{$preg_ext}(?:\.meta\.yaml)?$|{$preg_filename}\.meta\.yaml)$`", $file)) {
+ $testPath = $targetPath . '/' . $file;
+ if ($locator->isStream($testPath)) {
+ $testPath = $locator->findResource($testPath, true, true);
+ $locator->clearCache($testPath);
+ }
+
+ if (is_file($testPath)) {
+ $result = unlink($testPath);
+ if (!$result) {
+ throw new RuntimeException($language->translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . $filename, 500);
+ }
+ }
+ }
+ }
+
+ $this->clearMediaCache();
+ }
+
+ /**
+ * @param array $files
+ */
+ protected function setUpdatedMedia(array $files): void
+ {
+ $list = [];
+ foreach ($files as $field => $group) {
+ if ($field === '' || \strpos($field, '/', true)) {
+ continue;
+ }
+ foreach ($group as $filename => $file) {
+ $list[$filename] = $file;
+ }
+ }
+
+ $this->_uploads = $list;
+ }
+
+ /**
+ * @return array
+ */
+ protected function getUpdatedMedia(): array
+ {
+ return $this->_uploads ?? [];
+ }
+
+ protected function saveUpdatedMedia(): void
+ {
+ /**
+ * @var string $filename
+ * @var UploadedFileInterface $file
+ */
+ foreach ($this->getUpdatedMedia() as $filename => $file) {
+ if ($file) {
+ $this->uploadMediaFile($file, $filename);
+ } else {
+ $this->deleteMediaFile($filename);
+ }
+ }
+
+ $this->setUpdatedMedia([]);
+ }
+
+ /**
+ * @param string $uri
+ * @return Medium|null
+ */
+ protected function createMedium($uri)
+ {
+ $grav = Grav::instance();
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $grav['locator'];
+
+ $file = $uri && $locator->isStream($uri) ? $locator->findResource($uri) : $uri;
+
+ return $file && file_exists($file) ? MediumFactory::fromFile($file) : null;
+ }
+
+ /**
+ * @return Cache
+ */
+ protected function getMediaCache()
+ {
+ return $this->getCache('object');
+ }
+
+ protected function offsetLoad_media()
+ {
+ return $this->getMedia();
+ }
+
+ protected function offsetSerialize_media()
+ {
+ return null;
+ }
+
+ abstract public function getFlexDirectory(): FlexDirectory;
+
+ abstract public function getStorageKey();
+}
diff --git a/system/src/Grav/Framework/Form/FormFlash.php b/system/src/Grav/Framework/Form/FormFlash.php
new file mode 100644
index 0000000..5060782
--- /dev/null
+++ b/system/src/Grav/Framework/Form/FormFlash.php
@@ -0,0 +1,432 @@
+sessionId = $sessionId;
+ $this->uniqueId = $uniqueId;
+
+ $file = $this->getTmpIndex();
+ $this->exists = $file->exists();
+
+ if ($this->exists) {
+ try {
+ $data = (array)$file->content();
+ } catch (\Exception $e) {
+ $data = [];
+ }
+ $this->formName = null !== $formName ? $content['form'] ?? '' : '';
+ $this->url = $data['url'] ?? '';
+ $this->user = $data['user'] ?? null;
+ $this->data = $data['data'] ?? null;
+ $this->files = $data['files'] ?? [];
+ } else {
+ $this->formName = $formName;
+ $this->url = '';
+ $this->files = [];
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getFormName(): string
+ {
+ return $this->formName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUniqieId(): string
+ {
+ return $this->uniqueId;
+ }
+
+ /**
+ * @return bool
+ */
+ public function exists(): bool
+ {
+ return $this->exists;
+ }
+
+ /**
+ * @return $this
+ */
+ public function save(): self
+ {
+ $file = $this->getTmpIndex();
+ $file->save($this->jsonSerialize());
+ $this->exists = true;
+
+ return $this;
+ }
+
+ public function delete(): self
+ {
+ $this->removeTmpDir();
+ $this->files = [];
+ $this->exists = false;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUrl(): string
+ {
+ return $this->url;
+ }
+
+ /**
+ * @param string $url
+ * @return $this
+ */
+ public function setUrl(string $url): self
+ {
+ $this->url = $url;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUsername(): string
+ {
+ return $this->user['username'] ?? '';
+ }
+
+ /**
+ * @return string
+ */
+ public function getUserEmail(): string
+ {
+ return $this->user['email'] ?? '';
+ }
+
+ /**
+ * @param string|null $username
+ * @return $this
+ */
+ public function setUserName(string $username = null): self
+ {
+ $this->user['username'] = $username;
+
+ return $this;
+ }
+
+ /**
+ * @param string|null $email
+ * @return $this
+ */
+ public function setUserEmail(string $email = null): self
+ {
+ $this->user['email'] = $email;
+
+ return $this;
+ }
+
+ public function getData(): ?array
+ {
+ return $this->data;
+ }
+
+ public function setData(?array $data): void
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * @param string $field
+ * @return array
+ */
+ public function getFilesByField(string $field): array
+ {
+ if (!isset($this->uploadObjects[$field])) {
+ $objects = [];
+ foreach ($this->files[$field] ?? [] as $name => $upload) {
+ $objects[$name] = $upload ? new FormFlashFile($field, $upload, $this) : null;
+ }
+ $this->uploadedFiles[$field] = $objects;
+ }
+
+ return $this->uploadedFiles[$field];
+ }
+
+ /**
+ * @param bool $includeOriginal
+ * @return array
+ */
+ public function getFilesByFields($includeOriginal = false): array
+ {
+ $list = [];
+ foreach ($this->files as $field => $values) {
+ if (!$includeOriginal && strpos($field, '/')) {
+ continue;
+ }
+ $list[$field] = $this->getFilesByField($field);
+ }
+
+ return $list;
+ }
+
+ /**
+ * Add uploaded file to the form flash.
+ *
+ * @param UploadedFileInterface $upload
+ * @param string|null $field
+ * @param array|null $crop
+ * @return string Return name of the file
+ */
+ public function addUploadedFile(UploadedFileInterface $upload, string $field = null, array $crop = null): string
+ {
+ $tmp_dir = $this->getTmpDir();
+ $tmp_name = Utils::generateRandomString(12);
+ $name = $upload->getClientFilename();
+
+ // Prepare upload data for later save
+ $data = [
+ 'name' => $name,
+ 'type' => $upload->getClientMediaType(),
+ 'size' => $upload->getSize(),
+ 'tmp_name' => $tmp_name
+ ];
+
+ Folder::create($tmp_dir);
+ $upload->moveTo("{$tmp_dir}/{$tmp_name}");
+
+ $this->addFileInternal($field, $name, $data, $crop);
+
+ return $name;
+ }
+
+
+ /**
+ * Add existing file to the form flash.
+ *
+ * @param string $filename
+ * @param string $field
+ * @param array $crop
+ * @return bool
+ */
+ public function addFile(string $filename, string $field, array $crop = null): bool
+ {
+ if (!file_exists($filename)) {
+ throw new \RuntimeException("File not found: {$filename}");
+ }
+
+ // Prepare upload data for later save
+ $data = [
+ 'name' => basename($filename),
+ 'type' => Utils::getMimeByLocalFile($filename),
+ 'size' => filesize($filename),
+ ];
+
+ $this->addFileInternal($field, $data['name'], $data, $crop);
+
+ return true;
+ }
+
+ /**
+ * Remove any file from form flash.
+ *
+ * @param string $name
+ * @param string $field
+ * @return bool
+ */
+ public function removeFile(string $name, string $field = null): bool
+ {
+ if (!$name) {
+ return false;
+ }
+
+ $field = $field ?: 'undefined';
+
+ $upload = $this->files[$field][$name] ?? null;
+ if (null !== $upload) {
+ $this->removeTmpFile($upload['tmp_name'] ?? '');
+ }
+ $upload = $this->files[$field . '/original'][$name] ?? null;
+ if (null !== $upload) {
+ $this->removeTmpFile($upload['tmp_name'] ?? '');
+ }
+
+ // Mark file as deleted.
+ $this->files[$field][$name] = null;
+ $this->files[$field . '/original'][$name] = null;
+
+ unset(
+ $this->uploadedFiles[$field][$name],
+ $this->uploadedFiles[$field . '/original'][$name]
+ );
+
+ return true;
+ }
+
+ /**
+ * Clear form flash from all uploaded files.
+ */
+ public function clearFiles()
+ {
+ foreach ($this->files as $field => $files) {
+ foreach ($files as $name => $upload) {
+ $this->removeTmpFile($upload['tmp_name'] ?? '');
+ }
+ }
+
+ $this->files = [];
+ }
+
+ /**
+ * @return array
+ */
+ public function jsonSerialize(): array
+ {
+ return [
+ 'form' => $this->formName,
+ 'unique_id' => $this->uniqueId,
+ 'url' => $this->url,
+ 'user' => $this->user,
+ 'data' => $this->data,
+ 'files' => $this->files
+ ];
+ }
+
+ /**
+ * @return string
+ */
+ public function getTmpDir(): string
+ {
+ return static::getSessionTmpDir($this->sessionId) . '/' . $this->uniqueId;
+ }
+
+ /**
+ * @return YamlFile
+ */
+ protected function getTmpIndex(): YamlFile
+ {
+ // Do not use CompiledYamlFile as the file can change multiple times per second.
+ return YamlFile::instance($this->getTmpDir() . '/index.yaml');
+ }
+
+ /**
+ * @param string $name
+ */
+ protected function removeTmpFile(string $name): void
+ {
+ $filename = $this->getTmpDir() . '/' . $name;
+ if ($name && is_file($filename)) {
+ unlink($filename);
+ }
+ }
+
+ protected function removeTmpDir(): void
+ {
+ $tmpDir = $this->getTmpDir();
+ if (file_exists($tmpDir)) {
+ Folder::delete($tmpDir);
+ }
+ }
+
+ /**
+ * @param string $field
+ * @param string $name
+ * @param array $data
+ * @param array|null $crop
+ */
+ protected function addFileInternal(?string $field, string $name, array $data, array $crop = null): void
+ {
+ $field = $field ?: 'undefined';
+ if (!isset($this->files[$field])) {
+ $this->files[$field] = [];
+ }
+
+ $oldUpload = $this->files[$field][$name] ?? null;
+
+ if ($crop) {
+ // Deal with crop upload
+ if ($oldUpload) {
+ $originalUpload = $this->files[$field . '/original'][$name] ?? null;
+ if ($originalUpload) {
+ // If there is original file already present, remove the modified file
+ $this->files[$field . '/original'][$name]['crop'] = $crop;
+ $this->removeTmpFile($oldUpload['tmp_name'] ?? '');
+ } else {
+ // Otherwise make the previous file as original
+ $oldUpload['crop'] = $crop;
+ $this->files[$field . '/original'][$name] = $oldUpload;
+ }
+ } else {
+ $this->files[$field . '/original'][$name] = [
+ 'name' => $name,
+ 'type' => $data['type'],
+ 'crop' => $crop
+ ];
+ }
+ } else {
+ // Deal with replacing upload
+ $originalUpload = $this->files[$field . '/original'][$name] ?? null;
+ $this->files[$field . '/original'][$name] = null;
+
+ $this->removeTmpFile($oldUpload['tmp_name'] ?? '');
+ $this->removeTmpFile($originalUpload['tmp_name'] ?? '');
+ }
+
+ // Prepare data to be saved later
+ $this->files[$field][$name] = $data;
+ }
+}
diff --git a/system/src/Grav/Framework/Form/FormFlashFile.php b/system/src/Grav/Framework/Form/FormFlashFile.php
new file mode 100644
index 0000000..9183689
--- /dev/null
+++ b/system/src/Grav/Framework/Form/FormFlashFile.php
@@ -0,0 +1,159 @@
+field = $field;
+ $this->upload = $upload;
+ $this->flash = $flash;
+
+ $tmpFile = $this->getTmpFile();
+ if (!$tmpFile && $this->isOk()) {
+ $this->upload['error'] = \UPLOAD_ERR_NO_FILE;
+ }
+
+ if (!isset($this->upload['size'])) {
+ $this->upload['size'] = $tmpFile && $this->isOk() ? filesize($tmpFile) : 0;
+ }
+ }
+
+ /**
+ * @return StreamInterface
+ */
+ public function getStream()
+ {
+ $this->validateActive();
+
+ $resource = \fopen($this->getTmpFile(), 'rb');
+
+ return Stream::create($resource);
+ }
+
+ public function moveTo($targetPath)
+ {
+ $this->validateActive();
+
+ if (!\is_string($targetPath) || empty($targetPath)) {
+ throw new \InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string');
+ }
+
+ $this->moved = \copy($this->getTmpFile(), $targetPath);
+
+ if (false === $this->moved) {
+ throw new \RuntimeException(\sprintf('Uploaded file could not be moved to %s', $targetPath));
+ }
+
+ $this->flash->removeFile($this->getClientFilename(), $this->field);
+ }
+
+ public function getSize()
+ {
+ return $this->upload['size'];
+ }
+
+ public function getError()
+ {
+ return $this->upload['error'] ?? \UPLOAD_ERR_OK;
+ }
+
+ public function getClientFilename()
+ {
+ return $this->upload['name'] ?? 'unknown';
+ }
+
+ public function getClientMediaType()
+ {
+ return $this->upload['type'] ?? 'application/octet-stream';
+ }
+
+ public function isMoved() : bool
+ {
+ return $this->moved;
+ }
+
+ public function getMetaData() : array
+ {
+ if (isset($this->upload['crop'])) {
+ return ['crop' => $this->upload['crop']];
+ }
+
+ return [];
+ }
+
+ public function getDestination()
+ {
+ return $this->upload['path'] ?? '';
+ }
+
+ public function jsonSerialize()
+ {
+ return $this->upload;
+ }
+
+ public function getTmpFile() : ?string
+ {
+ $tmpName = $this->upload['tmp_name'] ?? null;
+
+ if (!$tmpName) {
+ return null;
+ }
+
+ $tmpFile = $this->flash->getTmpDir() . '/' . $tmpName;
+
+ return file_exists($tmpFile) ? $tmpFile : null;
+ }
+
+ public function __debugInfo()
+ {
+ return [
+ 'field:private' => $this->field,
+ 'moved:private' => $this->moved,
+ 'upload:private' => $this->upload,
+ ];
+ }
+
+ /**
+ * @throws \RuntimeException if is moved or not ok
+ */
+ private function validateActive(): void
+ {
+ if (!$this->isOk()) {
+ throw new \RuntimeException('Cannot retrieve stream due to upload error');
+ }
+
+ if ($this->moved) {
+ throw new \RuntimeException('Cannot retrieve stream after it has already been moved');
+ }
+
+ if (!$this->getTmpFile()) {
+ throw new \RuntimeException('Cannot retrieve stream as the file is missing');
+ }
+ }
+
+ /**
+ * @return bool return true if there is no upload error
+ */
+ private function isOk(): bool
+ {
+ return \UPLOAD_ERR_OK === $this->getError();
+ }
+}
diff --git a/system/src/Grav/Framework/Form/Interfaces/FormFactoryInterface.php b/system/src/Grav/Framework/Form/Interfaces/FormFactoryInterface.php
new file mode 100644
index 0000000..fb57db1
--- /dev/null
+++ b/system/src/Grav/Framework/Form/Interfaces/FormFactoryInterface.php
@@ -0,0 +1,38 @@
+id;
+ }
+
+ public function setId(string $id): void
+ {
+ $this->id = $id;
+ }
+
+ public function getUniqueId(): string
+ {
+ return $this->uniqueid;
+ }
+
+ public function setUniqueId(string $uniqueId): void
+ {
+ $this->uniqueid = $uniqueId;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function getFormName(): string
+ {
+ return $this->name;
+ }
+
+ public function getNonceName(): string
+ {
+ return 'form-nonce';
+ }
+
+ public function getNonceAction(): string
+ {
+ return 'form';
+ }
+
+ public function getNonce(): string
+ {
+ return Utils::getNonce($this->getNonceAction());
+ }
+
+ public function getAction(): string
+ {
+ return '';
+ }
+
+ public function getTask(): string
+ {
+ return $this->getBlueprint()->get('form/task') ?? '';
+ }
+
+ public function getData(string $name = null)
+ {
+ return null !== $name ? $this->data[$name] : $this->data;
+ }
+
+ /**
+ * @return array|UploadedFileInterface[]
+ */
+ public function getFiles(): array
+ {
+ return $this->files ?? [];
+ }
+
+ public function getValue(string $name)
+ {
+ return $this->data[$name] ?? null;
+ }
+
+ public function getDefaultValue(string $name)
+ {
+ $path = explode('.', $name) ?: [];
+ $offset = array_shift($path) ?? '';
+
+ $current = $this->getDefaultValues();
+
+ if (!isset($current[$offset])) {
+ return null;
+ }
+
+ $current = $current[$offset];
+
+ while ($path) {
+ $offset = array_shift($path);
+
+ if ((\is_array($current) || $current instanceof \ArrayAccess) && isset($current[$offset])) {
+ $current = $current[$offset];
+ } elseif (\is_object($current) && isset($current->{$offset})) {
+ $current = $current->{$offset};
+ } else {
+ return null;
+ }
+ };
+
+ return $current;
+ }
+
+ /**
+ * @return array
+ */
+ public function getDefaultValues(): array
+ {
+ return $this->getBlueprint()->getDefaults();
+ }
+
+ /**
+ * @param ServerRequestInterface $request
+ * @return FormInterface|$this
+ */
+ public function handleRequest(ServerRequestInterface $request): FormInterface
+ {
+ // Set current form to be active.
+ $grav = Grav::instance();
+ $forms = $grav['forms'] ?? null;
+ if ($forms) {
+ $forms->setActiveForm($this);
+
+ /** @var Twig $twig */
+ $twig = $grav['twig'];
+ $twig->twig_vars['form'] = $this;
+
+ }
+
+ try {
+ [$data, $files] = $this->parseRequest($request);
+
+ $this->submit($data, $files);
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param ServerRequestInterface $request
+ * @return FormInterface|$this
+ */
+ public function setRequest(ServerRequestInterface $request): FormInterface
+ {
+ [$data, $files] = $this->parseRequest($request);
+
+ $this->data = new Data($data, $this->getBlueprint());
+ $this->files = $files;
+
+ return $this;
+ }
+
+ public function isValid(): bool
+ {
+ return $this->status === 'success';
+ }
+
+ public function getError(): ?string
+ {
+ return !$this->isValid() ? $this->message : null;
+ }
+
+ public function getErrors(): array
+ {
+ return !$this->isValid() ? $this->messages : [];
+ }
+
+ public function isSubmitted(): bool
+ {
+ return $this->submitted;
+ }
+
+ public function validate(): bool
+ {
+ if (!$this->isValid()) {
+ return false;
+ }
+
+ try {
+ $this->validateData($this->data);
+ $this->validateUploads($this->getFiles());
+ } catch (ValidationException $e) {
+ $this->setErrors($e->getMessages());
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+ }
+
+ $this->filterData($this->data);
+
+ return $this->isValid();
+ }
+
+ /**
+ * @param array $data
+ * @param UploadedFileInterface[] $files
+ * @return FormInterface|$this
+ */
+ public function submit(array $data, array $files = null): FormInterface
+ {
+ try {
+ if ($this->isSubmitted()) {
+ throw new \RuntimeException('Form has already been submitted');
+ }
+
+ $this->data = new Data($data, $this->getBlueprint());
+ $this->files = $files ?? [];
+
+ if (!$this->validate()) {
+ return $this;
+ }
+
+ $this->doSubmit($this->data->toArray(), $this->files);
+
+ $this->submitted = true;
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+ }
+
+ return $this;
+ }
+
+ public function reset(): void
+ {
+ // Make sure that the flash object gets deleted.
+ $this->getFlash()->delete();
+
+ $this->data = null;
+ $this->files = [];
+ $this->status = 'success';
+ $this->message = null;
+ $this->messages = [];
+ $this->submitted = false;
+ $this->flash = null;
+ }
+
+ public function getFields(): array
+ {
+ return $this->getBlueprint()->fields();
+ }
+
+ public function getButtons(): array
+ {
+ return $this->getBlueprint()['form']['buttons'] ?? [];
+ }
+
+ public function getTasks(): array
+ {
+ return $this->getBlueprint()['form']['tasks'] ?? [];
+ }
+
+ abstract public function getBlueprint(): Blueprint;
+
+ /**
+ * Implements \Serializable::serialize().
+ *
+ * @return string
+ */
+ public function serialize(): string
+ {
+ return serialize($this->doSerialize());
+ }
+
+ /**
+ * Implements \Serializable::unserialize().
+ *
+ * @param string $serialized
+ */
+ public function unserialize($serialized): void
+ {
+ $data = unserialize($serialized, ['allowed_classes' => false]);
+
+ $this->doUnserialize($data);
+ }
+
+ /**
+ * Get form flash object.
+ *
+ * @return FormFlash
+ */
+ public function getFlash(): FormFlash
+ {
+ if (null === $this->flash) {
+ /** @var Grav $grav */
+ $grav = Grav::instance();
+ $id = null;
+
+ $user = $grav['user'] ?? null;
+ if (isset($user)) {
+ $rememberState = $this->getBlueprint()->get('form/remember_state');
+ if ($rememberState === 'user') {
+ $id = $user->username;
+ }
+ }
+
+ // Session Required for flash form
+ $session = $grav['session'] ?? null;
+ if (isset($session)) {
+ // By default store flash by the session id.
+ if (null === $id) {
+ $id = $session->getId();
+ }
+
+
+ $this->flash = new FormFlash($id, $this->getUniqueId(), $this->getName());
+ $this->flash->setUrl($grav['uri']->url)->setUser($user);
+ }
+ }
+
+ return $this->flash;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FormInterface::render()
+ */
+ public function render(string $layout = null, array $context = [])
+ {
+ if (null === $layout) {
+ $layout = 'default';
+ }
+
+ $grav = Grav::instance();
+
+ $block = HtmlBlock::create();
+ $block->disableCache();
+
+ $output = $this->getTemplate($layout)->render(
+ ['grav' => $grav, 'config' => $grav['config'], 'block' => $block, 'form' => $this, 'layout' => $layout] + $context
+ );
+
+ $block->setContent($output);
+
+ return $block;
+ }
+
+ protected function unsetFlash(): void
+ {
+ $this->flash = null;
+ }
+
+ /**
+ * Set a single error.
+ *
+ * @param string $error
+ */
+ protected function setError(string $error): void
+ {
+ $this->status = 'error';
+ $this->message = $error;
+ }
+
+ /**
+ * Set all errors.
+ *
+ * @param array $errors
+ */
+ protected function setErrors(array $errors): void
+ {
+ $this->status = 'error';
+ $this->messages = $errors;
+ }
+
+ /**
+ * @param string $layout
+ * @return TemplateWrapper
+ * @throws LoaderError
+ * @throws SyntaxError
+ */
+ protected function getTemplate($layout)
+ {
+ $grav = Grav::instance();
+
+ /** @var Twig $twig */
+ $twig = $grav['twig'];
+
+ return $twig->twig()->resolveTemplate(
+ [
+ "forms/{$layout}/form.html.twig",
+ 'forms/default/form.html.twig'
+ ]
+ );
+ }
+
+ /**
+ * Parse PSR-7 ServerRequest into data and files.
+ *
+ * @param ServerRequestInterface $request
+ * @return array
+ */
+ protected function parseRequest(ServerRequestInterface $request): array
+ {
+ $method = $request->getMethod();
+ if (!\in_array($method, ['PUT', 'POST', 'PATCH'])) {
+ throw new \RuntimeException(sprintf('FlexForm: Bad HTTP method %s', $method));
+ }
+
+ $body = $request->getParsedBody();
+ $data = isset($body['data']) ? $this->decodeData($body['data']) : null;
+
+ $flash = $this->getFlash();
+ /*
+ if (null !== $data) {
+ $flash->setData($data);
+ $flash->save();
+ }
+ */
+
+ $blueprint = $this->getBlueprint();
+ $includeOriginal = (bool)($blueprint->form()['images']['original'] ?? null);
+ $files = $flash->getFilesByFields($includeOriginal);
+
+ $data = $blueprint->processForm($data ?? [], $body['toggleable_data'] ?? []);
+
+ return [
+ $data,
+ $files ?? []
+ ];
+ }
+
+ /**
+ * Form submit logic goes here.
+ *
+ * @param array $data
+ * @param array $files
+ * @return mixed
+ */
+ abstract protected function doSubmit(array $data, array $files);
+
+ /**
+ * Validate data and throw validation exceptions if validation fails.
+ *
+ * @param \ArrayAccess $data
+ * @throws ValidationException
+ * @throws \Exception
+ */
+ protected function validateData(\ArrayAccess $data): void
+ {
+ if ($data instanceof Data) {
+ $data->validate();
+ }
+ }
+
+ /**
+ * Filter validated data.
+ *
+ * @param \ArrayAccess $data
+ */
+ protected function filterData(\ArrayAccess $data): void
+ {
+ if ($data instanceof Data) {
+ $data->filter();
+ }
+ }
+
+ /**
+ * Validate all uploaded files.
+ *
+ * @param array $files
+ */
+ protected function validateUploads(array $files): void
+ {
+ foreach ($files as $file) {
+ if (null === $file) {
+ continue;
+ }
+ if ($file instanceof UploadedFileInterface) {
+ $this->validateUpload($file);
+ } else {
+ $this->validateUploads($file);
+ }
+ }
+ }
+
+ /**
+ * Validate uploaded file.
+ *
+ * @param UploadedFileInterface $file
+ */
+ protected function validateUpload(UploadedFileInterface $file): void
+ {
+ // Handle bad filenames.
+ $filename = $file->getClientFilename();
+
+ if (!Utils::checkFilename($filename)) {
+ $grav = Grav::instance();
+ throw new \RuntimeException(
+ sprintf($grav['language']->translate('PLUGIN_FORM.FILEUPLOAD_UNABLE_TO_UPLOAD', null, true), $filename, 'Bad filename')
+ );
+ }
+ }
+
+ /**
+ * Decode POST data
+ *
+ * @param array $data
+ * @return array
+ */
+ protected function decodeData($data): array
+ {
+ if (!\is_array($data)) {
+ return [];
+ }
+
+ // Decode JSON encoded fields and merge them to data.
+ if (isset($data['_json'])) {
+ $data = array_replace_recursive($data, $this->jsonDecode($data['_json']));
+ unset($data['_json']);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Recursively JSON decode POST data.
+ *
+ * @param array $data
+ * @return array
+ */
+ protected function jsonDecode(array $data): array
+ {
+ foreach ($data as $key => &$value) {
+ if (\is_array($value)) {
+ $value = $this->jsonDecode($value);
+ } elseif ($value === '') {
+ unset($data[$key]);
+ } else {
+ $value = json_decode($value, true);
+ if ($value === null && json_last_error() !== JSON_ERROR_NONE) {
+ unset($data[$key]);
+ $this->setError("Badly encoded JSON data (for {$key}) was sent to the form");
+ }
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * @return array
+ */
+ protected function doSerialize(): array
+ {
+ $data = $this->data instanceof Data ? $this->data->toArray() : null;
+
+ return [
+ 'name' => $this->name,
+ 'id' => $this->id,
+ 'uniqueid' => $this->uniqueid,
+ 'submitted' => $this->submitted,
+ 'status' => $this->status,
+ 'message' => $this->message,
+ 'messages' => $this->messages,
+ 'data' => $data,
+ 'files' => $this->files,
+ ];
+ }
+
+ /**
+ * @param array $data
+ */
+ protected function doUnserialize(array $data): void
+ {
+ $this->name = $data['name'];
+ $this->id = $data['id'];
+ $this->uniqueid = $data['uniqueid'];
+ $this->submitted = $data['submitted'] ?? false;
+ $this->status = $data['status'] ?? 'success';
+ $this->message = $data['message'] ?? null;
+ $this->messages = $data['messages'] ?? [];
+ $this->data = isset($data['data']) ? new Data($data['data'], $this->getBlueprint()) : null;
+ $this->files = $data['files'] ?? [];
+ }
+}
diff --git a/system/src/Grav/Framework/Interfaces/RenderInterface.php b/system/src/Grav/Framework/Interfaces/RenderInterface.php
new file mode 100644
index 0000000..9185c17
--- /dev/null
+++ b/system/src/Grav/Framework/Interfaces/RenderInterface.php
@@ -0,0 +1,38 @@
+render('custom', ['variable' => 'value']);
+ * @example {% render object layout 'custom' with { variable: 'value' } %}
+ *
+ * @param string|null $layout Layout to be used.
+ * @param array|null $context Extra context given to the renderer.
+ *
+ * @return ContentBlockInterface|HtmlBlock Returns `HtmlBlock` containing the rendered output.
+ * @api
+ */
+ public function render(string $layout = null, array $context = []);
+}
diff --git a/system/src/Grav/Framework/Media/Interfaces/MediaCollectionInterface.php b/system/src/Grav/Framework/Media/Interfaces/MediaCollectionInterface.php
new file mode 100644
index 0000000..e834bc8
--- /dev/null
+++ b/system/src/Grav/Framework/Media/Interfaces/MediaCollectionInterface.php
@@ -0,0 +1,17 @@
+hasProperty($offset)) {
return $default;
@@ -57,9 +58,9 @@ trait NestedPropertyTrait
$offset = array_shift($path);
- if ((is_array($current) || is_a($current, 'ArrayAccess')) && isset($current[$offset])) {
+ if ((\is_array($current) || is_a($current, 'ArrayAccess')) && isset($current[$offset])) {
$current = $current[$offset];
- } elseif (is_object($current) && isset($current->{$offset})) {
+ } elseif (\is_object($current) && isset($current->{$offset})) {
$current = $current->{$offset};
} else {
return $default;
@@ -80,8 +81,8 @@ trait NestedPropertyTrait
public function setNestedProperty($property, $value, $separator = null)
{
$separator = $separator ?: '.';
- $path = explode($separator, $property);
- $offset = array_shift($path);
+ $path = explode($separator, $property) ?: [];
+ $offset = array_shift($path) ?? '';
if (!$path) {
$this->setProperty($offset, $value);
@@ -97,12 +98,12 @@ trait NestedPropertyTrait
// Handle arrays and scalars.
if ($current === null) {
$current = [$offset => []];
- } elseif (is_array($current)) {
+ } elseif (\is_array($current)) {
if (!isset($current[$offset])) {
$current[$offset] = [];
}
} else {
- throw new \RuntimeException('Cannot set nested property on non-array value');
+ throw new \RuntimeException("Cannot set nested property {$property} on non-array value");
}
$current = &$current[$offset];
@@ -122,8 +123,8 @@ trait NestedPropertyTrait
public function unsetNestedProperty($property, $separator = null)
{
$separator = $separator ?: '.';
- $path = explode($separator, $property);
- $offset = array_shift($path);
+ $path = explode($separator, $property) ?: [];
+ $offset = array_shift($path) ?? '';
if (!$path) {
$this->unsetProperty($offset);
@@ -141,12 +142,12 @@ trait NestedPropertyTrait
if ($current === null) {
return $this;
}
- if (is_array($current)) {
+ if (\is_array($current)) {
if (!isset($current[$offset])) {
return $this;
}
} else {
- throw new \RuntimeException('Cannot set nested property on non-array value');
+ throw new \RuntimeException("Cannot unset nested property {$property} on non-array value");
}
$current = &$current[$offset];
@@ -159,7 +160,7 @@ trait NestedPropertyTrait
/**
* @param string $property Object property to be updated.
- * @param string $default Default value.
+ * @param mixed $default Default value.
* @param string $separator Separator, defaults to '.'
* @return $this
* @throws \RuntimeException
diff --git a/system/src/Grav/Framework/Object/Access/OverloadedPropertyTrait.php b/system/src/Grav/Framework/Object/Access/OverloadedPropertyTrait.php
index 1524bd6..029dbba 100644
--- a/system/src/Grav/Framework/Object/Access/OverloadedPropertyTrait.php
+++ b/system/src/Grav/Framework/Object/Access/OverloadedPropertyTrait.php
@@ -1,8 +1,9 @@
getIterator() as $key => $value) {
- $list[$key] = is_object($value) ? clone $value : $value;
+ $list[$key] = \is_object($value) ? clone $value : $value;
}
return $this->createFrom($list);
@@ -45,7 +46,7 @@ trait ObjectCollectionTrait
/**
* @param string $property Object property to be matched.
- * @return array Key/Value pairs of the properties.
+ * @return bool[] Key/Value pairs of the properties.
*/
public function doHasProperty($property)
{
@@ -62,7 +63,7 @@ trait ObjectCollectionTrait
/**
* @param string $property Object property to be fetched.
* @param mixed $default Default value if not set.
- * @return array Key/Value pairs of the properties.
+ * @return mixed[] Key/Value pairs of the properties.
*/
public function doGetProperty($property, $default = null)
{
@@ -78,7 +79,7 @@ trait ObjectCollectionTrait
/**
* @param string $property Object property to be updated.
- * @param string $value New value.
+ * @param mixed $value New value.
* @return $this
*/
public function doSetProperty($property, $value)
@@ -107,7 +108,7 @@ trait ObjectCollectionTrait
/**
* @param string $property Object property to be updated.
- * @param string $default Default value.
+ * @param mixed $default Default value.
* @return $this
*/
public function doDefProperty($property, $default)
@@ -123,15 +124,19 @@ trait ObjectCollectionTrait
/**
* @param string $method Method name.
* @param array $arguments List of arguments passed to the function.
- * @return array Return values.
+ * @return mixed[] Return values.
*/
public function call($method, array $arguments = [])
{
$list = [];
+ /**
+ * @var string|int $id
+ * @var ObjectInterface $element
+ */
foreach ($this->getIterator() as $id => $element) {
- $list[$id] = method_exists($element, $method)
- ? call_user_func_array([$element, $method], $arguments) : null;
+ $callable = method_exists($element, $method) ? [$element, $method] : null;
+ $list[$id] = $callable ? \call_user_func_array($callable, $arguments) : null;
}
return $list;
diff --git a/system/src/Grav/Framework/Object/Base/ObjectTrait.php b/system/src/Grav/Framework/Object/Base/ObjectTrait.php
index c736381..baeeb2b 100644
--- a/system/src/Grav/Framework/Object/Base/ObjectTrait.php
+++ b/system/src/Grav/Framework/Object/Base/ObjectTrait.php
@@ -1,8 +1,9 @@
_key ?: $this->getType() . '@' . spl_object_hash($this);
+ return $this->_key ?: $this->getType() . '@@' . spl_object_hash($this);
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasKey()
+ {
+ return !empty($this->_key);
}
/**
* @param string $property Object property name.
- * @return bool True if property has been defined (can be null).
+ * @return bool|bool[] True if property has been defined (can be null).
*/
public function hasProperty($property)
{
@@ -67,7 +76,7 @@ trait ObjectTrait
/**
* @param string $property Object property to be fetched.
* @param mixed $default Default value if property has not been set.
- * @return mixed Property value.
+ * @return mixed|mixed[] Property value.
*/
public function getProperty($property, $default = null)
{
@@ -76,7 +85,7 @@ trait ObjectTrait
/**
* @param string $property Object property to be updated.
- * @param string $value New value.
+ * @param mixed $value New value.
* @return $this
*/
public function setProperty($property, $value)
@@ -139,7 +148,7 @@ trait ObjectTrait
*/
protected function doSerialize()
{
- return $this->jsonSerialize();
+ return ['key' => $this->getKey(), 'type' => $this->getType(), 'elements' => $this->getElements()];
}
/**
@@ -162,7 +171,7 @@ trait ObjectTrait
*/
public function jsonSerialize()
{
- return ['key' => $this->getKey(), 'type' => $this->getType(), 'elements' => $this->getElements()];
+ return $this->doSerialize();
}
/**
diff --git a/system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php b/system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php
index 5ad3897..0eefb78 100644
--- a/system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php
+++ b/system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php
@@ -1,8 +1,9 @@
setElements($elements));
- $this->setKey($key);
+ $this->setKey($key ?? '');
+ }
+
+ /**
+ * @param array $ordering
+ * @return Collection|static
+ */
+ public function orderBy(array $ordering)
+ {
+ $criteria = Criteria::create()->orderBy($ordering);
+
+ return $this->matching($criteria);
+ }
+
+ /**
+ * @param int $start
+ * @param int|null $limit
+ * @return static
+ */
+ public function limit($start, $limit = null)
+ {
+ return $this->createFrom($this->slice($start, $limit));
}
/**
@@ -54,18 +77,24 @@ class ObjectCollection extends ArrayCollection implements ObjectCollectionInterf
if ($orderings = $criteria->getOrderings()) {
$next = null;
+ /**
+ * @var string $field
+ * @var string $ordering
+ */
foreach (array_reverse($orderings) as $field => $ordering) {
- $next = ObjectExpressionVisitor::sortByField($field, $ordering == Criteria::DESC ? -1 : 1, $next);
+ $next = ObjectExpressionVisitor::sortByField($field, $ordering === Criteria::DESC ? -1 : 1, $next);
}
- uasort($filtered, $next);
+ if ($next) {
+ uasort($filtered, $next);
+ }
}
$offset = $criteria->getFirstResult();
$length = $criteria->getMaxResults();
if ($offset || $length) {
- $filtered = array_slice($filtered, (int)$offset, $length);
+ $filtered = \array_slice($filtered, (int)$offset, $length);
}
return $this->createFrom($filtered);
diff --git a/system/src/Grav/Framework/Object/ObjectIndex.php b/system/src/Grav/Framework/Object/ObjectIndex.php
new file mode 100644
index 0000000..4ba9b75
--- /dev/null
+++ b/system/src/Grav/Framework/Object/ObjectIndex.php
@@ -0,0 +1,251 @@
+getTypePrefix() : '';
+
+ if (static::$type) {
+ return $type . static::$type;
+ }
+
+ $class = \get_class($this);
+ return $type . strtolower(substr($class, strrpos($class, '\\') + 1));
+ }
+
+ /**
+ * @return string
+ */
+ public function getKey()
+ {
+ return $this->_key ?: $this->getType() . '@@' . spl_object_hash($this);
+ }
+
+ /**
+ * @param string $key
+ * @return $this
+ */
+ public function setKey($key)
+ {
+ $this->_key = $key;
+
+ return $this;
+ }
+
+ /**
+ * @param string $property Object property name.
+ * @return array True if property has been defined (can be null).
+ */
+ public function hasProperty($property)
+ {
+ return $this->__call('hasProperty', [$property]);
+ }
+
+ /**
+ * @param string $property Object property to be fetched.
+ * @param mixed $default Default value if property has not been set.
+ * @return array Property values.
+ */
+ public function getProperty($property, $default = null)
+ {
+ return $this->__call('getProperty', [$property, $default]);
+ }
+
+ /**
+ * @param string $property Object property to be updated.
+ * @param string $value New value.
+ * @return ObjectCollectionInterface
+ */
+ public function setProperty($property, $value)
+ {
+ return $this->__call('setProperty', [$property, $value]);
+ }
+
+ /**
+ * @param string $property Object property to be defined.
+ * @param mixed $default Default value.
+ * @return ObjectCollectionInterface
+ */
+ public function defProperty($property, $default)
+ {
+ return $this->__call('defProperty', [$property, $default]);
+ }
+
+ /**
+ * @param string $property Object property to be unset.
+ * @return ObjectCollectionInterface
+ */
+ public function unsetProperty($property)
+ {
+ return $this->__call('unsetProperty', [$property]);
+ }
+
+ /**
+ * @param string $property Object property name.
+ * @param string $separator Separator, defaults to '.'
+ * @return bool True if property has been defined (can be null).
+ */
+ public function hasNestedProperty($property, $separator = null)
+ {
+ return $this->__call('hasNestedProperty', [$property, $separator]);
+ }
+
+ /**
+ * @param string $property Object property to be fetched.
+ * @param mixed $default Default value if property has not been set.
+ * @param string $separator Separator, defaults to '.'
+ * @return mixed Property value.
+ */
+ public function getNestedProperty($property, $default = null, $separator = null)
+ {
+ return $this->__call('getNestedProperty', [$property, $default, $separator]);
+ }
+
+ /**
+ * @param string $property Object property to be updated.
+ * @param string $value New value.
+ * @param string $separator Separator, defaults to '.'
+ * @return ObjectCollectionInterface
+ */
+ public function setNestedProperty($property, $value, $separator = null)
+ {
+ return $this->__call('setNestedProperty', [$property, $value, $separator]);
+ }
+
+ /**
+ * @param string $property Object property to be defined.
+ * @param mixed $default Default value.
+ * @param string $separator Separator, defaults to '.'
+ * @return ObjectCollectionInterface
+ */
+ public function defNestedProperty($property, $default, $separator = null)
+ {
+ return $this->__call('defNestedProperty', [$property, $default, $separator]);
+ }
+
+ /**
+ * @param string $property Object property to be unset.
+ * @return ObjectCollectionInterface
+ */
+ public function unsetNestedProperty($property, $separator = null)
+ {
+ return $this->__call('unsetNestedProperty', [$property, $separator]);
+ }
+
+ /**
+ * Create a copy from this collection by cloning all objects in the collection.
+ *
+ * @return static
+ */
+ public function copy()
+ {
+ $list = [];
+ foreach ($this->getIterator() as $key => $value) {
+ $list[$key] = \is_object($value) ? clone $value : $value;
+ }
+
+ return $this->createFrom($list);
+ }
+
+ /**
+ * @return array
+ */
+ public function getObjectKeys()
+ {
+ return $this->getKeys();
+ }
+
+ /**
+ * @param array $ordering
+ * @return ObjectCollectionInterface
+ */
+ public function orderBy(array $ordering)
+ {
+ return $this->__call('orderBy', [$ordering]);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function call($method, array $arguments = [])
+ {
+ return $this->__call('call', [$method, $arguments]);
+ }
+
+ /**
+ * Group items in the collection by a field and return them as associated array.
+ *
+ * @param string $property
+ * @return array
+ */
+ public function group($property)
+ {
+ return $this->__call('group', [$property]);
+ }
+
+ /**
+ * Group items in the collection by a field and return them as associated array of collections.
+ *
+ * @param string $property
+ * @return ObjectCollectionInterface[]
+ */
+ public function collectionGroup($property)
+ {
+ return $this->__call('collectionGroup', [$property]);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function matching(Criteria $criteria)
+ {
+ /** @var ObjectCollectionInterface $collection */
+ $collection = $this->loadCollection($this->getEntries());
+
+ return $collection->matching($criteria);
+ }
+
+ abstract public function __call($name, $arguments);
+
+ /**
+ * @return string
+ */
+ protected function getTypePrefix()
+ {
+ return '';
+ }
+}
diff --git a/system/src/Grav/Framework/Object/Property/ArrayPropertyTrait.php b/system/src/Grav/Framework/Object/Property/ArrayPropertyTrait.php
index 400b163..7092e15 100644
--- a/system/src/Grav/Framework/Object/Property/ArrayPropertyTrait.php
+++ b/system/src/Grav/Framework/Object/Property/ArrayPropertyTrait.php
@@ -1,8 +1,9 @@
setElements($elements);
- $this->setKey($key);
+ $this->setKey($key ?? '');
}
/**
@@ -94,7 +95,7 @@ trait ArrayPropertyTrait
*/
protected function getElements()
{
- return $this->_elements;
+ return array_filter($this->_elements, function ($val) { return $val !== null; });
}
/**
diff --git a/system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php b/system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php
index 28b9432..1066b99 100644
--- a/system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php
+++ b/system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php
@@ -1,8 +1,9 @@
initObjectProperties();
$this->setElements($elements);
- $this->setKey($key);
+ $this->setKey($key ?? '');
}
/**
@@ -110,7 +111,7 @@ trait ObjectPropertyTrait
if ($doCreate === true) {
$this->_definedProperties[$property] = true;
$this->{$property} = null;
- } elseif (is_callable($doCreate)) {
+ } elseif (\is_callable($doCreate)) {
$this->_definedProperties[$property] = true;
$this->{$property} = $this->offsetLoad($property, $doCreate());
} else {
@@ -152,7 +153,7 @@ trait ObjectPropertyTrait
protected function initObjectProperties()
{
$this->_definedProperties = [];
- foreach (get_object_vars($this) as $property => $value) {
+ foreach (\get_object_vars($this) as $property => $value) {
if ($property[0] !== '_') {
$this->_definedProperties[$property] = ($value !== null);
}
@@ -182,7 +183,10 @@ trait ObjectPropertyTrait
$elements = [];
foreach ($properties as $offset => $value) {
- $elements[$offset] = $this->offsetSerialize($offset, $value);
+ $serialized = $this->offsetSerialize($offset, $value);
+ if ($serialized !== null) {
+ $elements[$offset] = $this->offsetSerialize($offset, $value);
+ }
}
return $elements;
diff --git a/system/src/Grav/Framework/Object/PropertyObject.php b/system/src/Grav/Framework/Object/PropertyObject.php
index 99f7e7f..f740fc6 100644
--- a/system/src/Grav/Framework/Object/PropertyObject.php
+++ b/system/src/Grav/Framework/Object/PropertyObject.php
@@ -1,8 +1,9 @@
'page',
+ 'limit' => 10,
+ 'display' => 5,
+ 'opening' => 0,
+ 'ending' => 0,
+ 'url' => null
+ ];
+
+ /** @var array */
+ private $items;
+
+ public function isEnabled(): bool
+ {
+ return $this->count() > 1;
+ }
+
+ public function getOptions(): array
+ {
+ return $this->options;
+ }
+
+ public function getRoute(): ?Route
+ {
+ return $this->route;
+ }
+
+ public function getTotalPages(): int
+ {
+ return $this->pages;
+ }
+
+ public function getPageNumber(): int
+ {
+ return $this->page;
+ }
+
+ public function getPrevNumber(int $count = 1): ?int
+ {
+ $page = $this->page - $count;
+
+ return $page >= 1 ? $page : null;
+ }
+
+ public function getNextNumber(int $count = 1): ?int
+ {
+ $page = $this->page + $count;
+
+ return $page <= $this->pages ? $page : null;
+ }
+
+ public function getPage(int $page, string $label = null): ?PaginationPage
+ {
+ if ($page < 1 || $page > $this->pages) {
+ return null;
+ }
+
+ $start = ($page - 1) * $this->limit;
+ if ($this->getOptions()['type'] === 'page') {
+ $name = 'page';
+ $offset = $page;
+ } else {
+ $name = 'start';
+ $offset = $start;
+ }
+
+ return new PaginationPage(
+ [
+ 'label' => $label ?? (string)$page,
+ 'number' => $page,
+ 'offset_start' => $start,
+ 'offset_end' => min($start + $this->limit, $this->total) - 1,
+ 'enabled' => $page !== $this->page || $this->viewAll,
+ 'active' => $page === $this->page,
+ 'route' => $this->route->withGravParam($name, $offset)
+ ]
+ );
+ }
+
+ public function getFirstPage(string $label = null, int $count = 0): ?PaginationPage
+ {
+ return $this->getPage(1 + $count, $label ?? $this->getOptions()['label_first'] ?? null);
+ }
+
+ public function getPrevPage(string $label = null, int $count = 1): ?PaginationPage
+ {
+ return $this->getPage($this->page - $count, $label ?? $this->getOptions()['label_prev'] ?? null);
+ }
+
+ public function getNextPage(string $label = null, int $count = 1): ?PaginationPage
+ {
+ return $this->getPage($this->page + $count, $label ?? $this->getOptions()['label_next'] ?? null);
+ }
+
+ public function getLastPage(string $label = null, int $count = 0): ?PaginationPage
+ {
+ return $this->getPage($this->pages - $count, $label ?? $this->getOptions()['label_last'] ?? null);
+ }
+
+ public function getStart(): int
+ {
+ return $this->start;
+ }
+
+ public function getLimit(): int
+ {
+ return $this->limit;
+ }
+
+ public function getTotal(): int
+ {
+ return $this->total;
+ }
+
+ public function count(): int
+ {
+ $this->loadItems();
+
+ return \count($this->items);
+ }
+
+ public function getIterator()
+ {
+ $this->loadItems();
+
+ return new \ArrayIterator($this->items);
+ }
+
+ public function getPages(): array
+ {
+ $this->loadItems();
+
+ return $this->items;
+ }
+
+ protected function loadItems()
+ {
+ $this->calculateRange();
+
+ // Make list like: 1 ... 4 5 6 ... 10
+ $range = range($this->pagesStart, $this->pagesStop);
+ //$range[] = 1;
+ //$range[] = $this->pages;
+ natsort($range);
+ $range = array_unique($range);
+
+ $this->items = [];
+ foreach ($range as $i) {
+ $this->items[$i] = $this->getPage($i);
+ }
+ }
+
+ protected function setRoute(Route $route)
+ {
+ $this->route = $route;
+
+ return $this;
+ }
+
+ protected function setOptions(array $options = null)
+ {
+ $this->options = $options ? array_merge($this->defaultOptions, $options) : $this->defaultOptions;
+
+ return $this;
+ }
+
+ protected function setPage(int $page = null)
+ {
+ $this->page = (int)max($page, 1);
+ $this->start = null;
+
+ return $this;
+ }
+
+ /**
+ * @param int $start
+ * @return $this
+ */
+ protected function setStart(int $start = null)
+ {
+ $this->start = (int)max($start, 0);
+ $this->page = null;
+
+ return $this;
+ }
+
+ /**
+ * @param int|null $limit
+ * @return $this
+ */
+ protected function setLimit(int $limit = null)
+ {
+ $this->limit = (int)max($limit ?? $this->getOptions()['limit'], 0);
+
+ // No limit, display all records in a single page.
+ $this->viewAll = !$limit;
+
+ return $this;
+ }
+
+ /**
+ * @param int $total
+ * @return $this
+ */
+ protected function setTotal(int $total)
+ {
+ $this->total = (int)max($total, 0);
+
+ return $this;
+ }
+
+ protected function initialize(Route $route, int $total, int $pos = null, int $limit = null, array $options = null)
+ {
+ $this->setRoute($route);
+ $this->setOptions($options);
+ $this->setTotal($total);
+ if ($this->getOptions()['type'] === 'start') {
+ $this->setStart($pos);
+ } else {
+ $this->setPage($pos);
+ }
+ $this->setLimit($limit);
+ $this->calculateLimits();
+ }
+
+ protected function calculateLimits()
+ {
+ $limit = $this->limit;
+ $total = $this->total;
+
+ if (!$limit || $limit > $total) {
+ // All records fit into a single page.
+ $this->start = 0;
+ $this->page = 1;
+ $this->pages = 1;
+
+ return;
+ }
+
+ if (null === $this->start) {
+ // If we are using page, convert it to start.
+ $this->start = (int)(($this->page - 1) * $limit);
+ }
+
+ if ($this->start > $total - $limit) {
+ // If start is greater than total count (i.e. we are asked to display records that don't exist)
+ // then set start to display the last natural page of results.
+ $this->start = (int)max(0, (ceil($total / $limit) - 1) * $limit);
+ }
+
+ // Set the total pages and current page values.
+ $this->page = (int)ceil(($this->start + 1) / $limit);
+ $this->pages = (int)ceil($total / $limit);
+ }
+
+ protected function calculateRange()
+ {
+ $options = $this->getOptions();
+ $displayed = $options['display'];
+ $opening = $options['opening'];
+ $ending = $options['ending'];
+
+ // Set the pagination iteration loop values.
+ $this->pagesStart = $this->page - (int)($displayed / 2);
+ if ($this->pagesStart < 1 + $opening) {
+ $this->pagesStart = 1 + $opening;
+ }
+ if ($this->pagesStart + $displayed - $opening > $this->pages) {
+ $this->pagesStop = $this->pages;
+ if ($this->pages < $displayed) {
+ $this->pagesStart = 1 + $opening;
+ } else {
+ $this->pagesStart = $this->pages - $displayed + 1 + $opening;
+ }
+ } else {
+ $this->pagesStop = (int)max(1, $this->pagesStart + $displayed - 1 - $ending);
+ }
+ }
+}
diff --git a/system/src/Grav/Framework/Pagination/AbstractPaginationPage.php b/system/src/Grav/Framework/Pagination/AbstractPaginationPage.php
new file mode 100644
index 0000000..04f536b
--- /dev/null
+++ b/system/src/Grav/Framework/Pagination/AbstractPaginationPage.php
@@ -0,0 +1,53 @@
+options['active'] ?? false;
+ }
+
+ public function isEnabled(): bool
+ {
+ return $this->options['enabled'] ?? false;
+ }
+
+ public function getOptions(): array
+ {
+ return $this->options ?? [];
+ }
+
+ public function getNumber(): ?int
+ {
+ return $this->options['number'] ?? null;
+ }
+
+ public function getLabel(): string
+ {
+ return $this->options['label'] ?? (string)$this->getNumber();
+ }
+
+ public function getUrl(): ?string
+ {
+ return $this->options['route'] ? (string)$this->options['route']->getUri() : null;
+ }
+
+ protected function setOptions(array $options): void
+ {
+ $this->options = $options;
+ }
+}
diff --git a/system/src/Grav/Framework/Pagination/Interfaces/PaginationInterface.php b/system/src/Grav/Framework/Pagination/Interfaces/PaginationInterface.php
new file mode 100644
index 0000000..0a14525
--- /dev/null
+++ b/system/src/Grav/Framework/Pagination/Interfaces/PaginationInterface.php
@@ -0,0 +1,43 @@
+initialize($route, $total, $pos, $limit, $options);
+ }
+}
diff --git a/system/src/Grav/Framework/Pagination/PaginationPage.php b/system/src/Grav/Framework/Pagination/PaginationPage.php
new file mode 100644
index 0000000..ca35a8d
--- /dev/null
+++ b/system/src/Grav/Framework/Pagination/PaginationPage.php
@@ -0,0 +1,18 @@
+setOptions($options);
+ }
+}
diff --git a/system/src/Grav/Framework/Psr7/AbstractUri.php b/system/src/Grav/Framework/Psr7/AbstractUri.php
index 5ddda9b..e0d46b6 100644
--- a/system/src/Grav/Framework/Psr7/AbstractUri.php
+++ b/system/src/Grav/Framework/Psr7/AbstractUri.php
@@ -1,8 +1,9 @@
user === $user && $this->password === $password) {
return $this;
diff --git a/system/src/Grav/Framework/Psr7/Request.php b/system/src/Grav/Framework/Psr7/Request.php
new file mode 100644
index 0000000..24ea3b6
--- /dev/null
+++ b/system/src/Grav/Framework/Psr7/Request.php
@@ -0,0 +1,34 @@
+message = new \Nyholm\Psr7\Request($method, $uri, $headers, $body, $version);
+ }
+}
diff --git a/system/src/Grav/Framework/Psr7/Response.php b/system/src/Grav/Framework/Psr7/Response.php
new file mode 100644
index 0000000..960621d
--- /dev/null
+++ b/system/src/Grav/Framework/Psr7/Response.php
@@ -0,0 +1,265 @@
+message = new \Nyholm\Psr7\Response($status, $headers, $body, $version, $reason);
+ }
+
+ /**
+ * Json.
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * This method prepares the response object to return an HTTP Json
+ * response to the client.
+ *
+ * @param mixed $data The data
+ * @param int $status The HTTP status code.
+ * @param int $options Json encoding options
+ * @param int $depth Json encoding max depth
+ * @return static
+ */
+ public function withJson($data, int $status = null, int $options = 0, int $depth = 512): ResponseInterface
+ {
+ $json = (string) json_encode($data, $options, $depth);
+
+ if (json_last_error() !== JSON_ERROR_NONE) {
+ throw new RuntimeException(json_last_error_msg(), json_last_error());
+ }
+
+ $response = $this->getResponse()
+ ->withHeader('Content-Type', 'application/json;charset=utf-8')
+ ->withBody(new Stream($json));
+
+ if ($status !== null) {
+ $response = $response->withStatus($status);
+ }
+
+ $new = clone $this;
+ $new->message = $response;
+
+ return $new;
+ }
+
+ /**
+ * Redirect.
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * This method prepares the response object to return an HTTP Redirect
+ * response to the client.
+ *
+ * @param string $url The redirect destination.
+ * @param int|null $status The redirect HTTP status code.
+ * @return static
+ */
+ public function withRedirect(string $url, $status = null): ResponseInterface
+ {
+ $response = $this->getResponse()->withHeader('Location', $url);
+
+ if ($status === null) {
+ $status = 302;
+ }
+
+ $new = clone $this;
+ $new->message = $response->withStatus($status);
+
+ return $new;
+ }
+
+ /**
+ * Is this response empty?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isEmpty(): bool
+ {
+ return \in_array($this->getResponse()->getStatusCode(), [204, 205, 304], true);
+ }
+
+
+ /**
+ * Is this response OK?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isOk(): bool
+ {
+ return $this->getResponse()->getStatusCode() === 200;
+ }
+
+ /**
+ * Is this response a redirect?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isRedirect(): bool
+ {
+ return \in_array($this->getResponse()->getStatusCode(), [301, 302, 303, 307, 308], true);
+ }
+
+ /**
+ * Is this response forbidden?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ * @api
+ */
+ public function isForbidden(): bool
+ {
+ return $this->getResponse()->getStatusCode() === 403;
+ }
+
+ /**
+ * Is this response not Found?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isNotFound(): bool
+ {
+ return $this->getResponse()->getStatusCode() === 404;
+ }
+
+ /**
+ * Is this response informational?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isInformational(): bool
+ {
+ $response = $this->getResponse();
+
+ return $response->getStatusCode() >= 100 && $response->getStatusCode() < 200;
+ }
+
+ /**
+ * Is this response successful?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isSuccessful(): bool
+ {
+ $response = $this->getResponse();
+
+ return $response->getStatusCode() >= 200 && $response->getStatusCode() < 300;
+ }
+
+ /**
+ * Is this response a redirection?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isRedirection(): bool
+ {
+ $response = $this->getResponse();
+
+ return $response->getStatusCode() >= 300 && $response->getStatusCode() < 400;
+ }
+
+ /**
+ * Is this response a client error?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isClientError(): bool
+ {
+ $response = $this->getResponse();
+
+ return $response->getStatusCode() >= 400 && $response->getStatusCode() < 500;
+ }
+
+ /**
+ * Is this response a server error?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isServerError(): bool
+ {
+ $response = $this->getResponse();
+
+ return $response->getStatusCode() >= 500 && $response->getStatusCode() < 600;
+ }
+
+ /**
+ * Convert response to string.
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return string
+ */
+ public function __toString(): string
+ {
+ $response = $this->getResponse();
+ $output = sprintf(
+ 'HTTP/%s %s %s%s',
+ $response->getProtocolVersion(),
+ $response->getStatusCode(),
+ $response->getReasonPhrase(),
+ self::EOL
+ );
+
+ foreach ($response->getHeaders() as $name => $values) {
+ $output .= sprintf('%s: %s', $name, $response->getHeaderLine($name)) . self::EOL;
+ }
+
+ $output .= self::EOL;
+ $output .= (string) $response->getBody();
+
+ return $output;
+ }
+}
diff --git a/system/src/Grav/Framework/Psr7/ServerRequest.php b/system/src/Grav/Framework/Psr7/ServerRequest.php
new file mode 100644
index 0000000..bb6b8b8
--- /dev/null
+++ b/system/src/Grav/Framework/Psr7/ServerRequest.php
@@ -0,0 +1,372 @@
+message = new \Nyholm\Psr7\ServerRequest($method, $uri, $headers, $body, $version, $serverParams);
+ }
+
+ /**
+ * Get serverRequest content character set, if known.
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return string|null
+ */
+ public function getContentCharset(): ?string
+ {
+ $mediaTypeParams = $this->getMediaTypeParams();
+
+ if (isset($mediaTypeParams['charset'])) {
+ return $mediaTypeParams['charset'];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get serverRequest content type.
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return string|null The serverRequest content type, if known
+ */
+ public function getContentType(): ?string
+ {
+ $result = $this->getRequest()->getHeader('Content-Type');
+
+ return $result ? $result[0] : null;
+ }
+
+ /**
+ * Get serverRequest content length, if known.
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return int|null
+ */
+ public function getContentLength(): ?int
+ {
+ $result = $this->getRequest()->getHeader('Content-Length');
+
+ return $result ? (int) $result[0] : null;
+ }
+
+ /**
+ * Fetch cookie value from cookies sent by the client to the server.
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @param string $key The attribute name.
+ * @param mixed $default Default value to return if the attribute does not exist.
+ *
+ * @return mixed
+ */
+ public function getCookieParam($key, $default = null)
+ {
+ $cookies = $this->getRequest()->getCookieParams();
+ $result = $default;
+
+ if (isset($cookies[$key])) {
+ $result = $cookies[$key];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get serverRequest media type, if known.
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return string|null The serverRequest media type, minus content-type params
+ */
+ public function getMediaType(): ?string
+ {
+ $contentType = $this->getContentType();
+
+ if ($contentType) {
+ $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
+ if ($contentTypeParts === false) {
+ return null;
+ }
+ return strtolower($contentTypeParts[0]);
+ }
+
+ return null;
+ }
+
+ /**
+ * Get serverRequest media type params, if known.
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return mixed[]
+ */
+ public function getMediaTypeParams(): array
+ {
+ $contentType = $this->getContentType();
+ $contentTypeParams = [];
+
+ if ($contentType) {
+ $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
+ if ($contentTypeParts !== false) {
+ $contentTypePartsLength = count($contentTypeParts);
+ for ($i = 1; $i < $contentTypePartsLength; $i++) {
+ $paramParts = explode('=', $contentTypeParts[$i]);
+ $contentTypeParams[strtolower($paramParts[0])] = $paramParts[1];
+ }
+ }
+ }
+
+ return $contentTypeParams;
+ }
+
+ /**
+ * Fetch serverRequest parameter value from body or query string (in that order).
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @param string $key The parameter key.
+ * @param string $default The default value.
+ *
+ * @return mixed The parameter value.
+ */
+ public function getParam($key, $default = null)
+ {
+ $postParams = $this->getParsedBody();
+ $getParams = $this->getQueryParams();
+ $result = $default;
+
+ if (is_array($postParams) && isset($postParams[$key])) {
+ $result = $postParams[$key];
+ } elseif (is_object($postParams) && property_exists($postParams, $key)) {
+ $result = $postParams->$key;
+ } elseif (isset($getParams[$key])) {
+ $result = $getParams[$key];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Fetch associative array of body and query string parameters.
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return mixed[]
+ */
+ public function getParams(): array
+ {
+ $params = $this->getQueryParams();
+ $postParams = $this->getParsedBody();
+
+ if ($postParams) {
+ $params = array_merge($params, (array)$postParams);
+ }
+
+ return $params;
+ }
+
+ /**
+ * Fetch parameter value from serverRequest body.
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @param string $key
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function getParsedBodyParam($key, $default = null)
+ {
+ $postParams = $this->getParsedBody();
+ $result = $default;
+
+ if (is_array($postParams) && isset($postParams[$key])) {
+ $result = $postParams[$key];
+ } elseif (is_object($postParams) && property_exists($postParams, $key)) {
+ $result = $postParams->{$key};
+ }
+
+ return $result;
+ }
+
+ /**
+ * Fetch parameter value from query string.
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @param string $key
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function getQueryParam($key, $default = null)
+ {
+ $getParams = $this->getQueryParams();
+ $result = $default;
+
+ if (isset($getParams[$key])) {
+ $result = $getParams[$key];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Retrieve a server parameter.
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @param string $key
+ * @param mixed $default
+ * @return mixed
+ */
+ public function getServerParam($key, $default = null)
+ {
+ $serverParams = $this->getRequest()->getServerParams();
+
+ return $serverParams[$key] ?? $default;
+ }
+
+ /**
+ * Does this serverRequest use a given method?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @param string $method HTTP method
+ * @return bool
+ */
+ public function isMethod($method): bool
+ {
+ return $this->getRequest()->getMethod() === $method;
+ }
+
+ /**
+ * Is this a DELETE serverRequest?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isDelete(): bool
+ {
+ return $this->isMethod('DELETE');
+ }
+
+ /**
+ * Is this a GET serverRequest?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isGet(): bool
+ {
+ return $this->isMethod('GET');
+ }
+
+ /**
+ * Is this a HEAD serverRequest?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isHead(): bool
+ {
+ return $this->isMethod('HEAD');
+ }
+
+ /**
+ * Is this a OPTIONS serverRequest?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isOptions(): bool
+ {
+ return $this->isMethod('OPTIONS');
+ }
+
+ /**
+ * Is this a PATCH serverRequest?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isPatch(): bool
+ {
+ return $this->isMethod('PATCH');
+ }
+
+ /**
+ * Is this a POST serverRequest?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isPost(): bool
+ {
+ return $this->isMethod('POST');
+ }
+
+ /**
+ * Is this a PUT serverRequest?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isPut(): bool
+ {
+ return $this->isMethod('PUT');
+ }
+
+ /**
+ * Is this an XHR serverRequest?
+ *
+ * Note: This method is not part of the PSR-7 standard.
+ *
+ * @return bool
+ */
+ public function isXhr(): bool
+ {
+ return $this->getRequest()->getHeaderLine('X-Requested-With') === 'XMLHttpRequest';
+ }
+}
diff --git a/system/src/Grav/Framework/Psr7/Stream.php b/system/src/Grav/Framework/Psr7/Stream.php
new file mode 100644
index 0000000..9431052
--- /dev/null
+++ b/system/src/Grav/Framework/Psr7/Stream.php
@@ -0,0 +1,25 @@
+stream = \Nyholm\Psr7\Stream::create($body);
+ }
+}
diff --git a/system/src/Grav/Framework/Psr7/Traits/MessageDecoratorTrait.php b/system/src/Grav/Framework/Psr7/Traits/MessageDecoratorTrait.php
new file mode 100644
index 0000000..28ed1f5
--- /dev/null
+++ b/system/src/Grav/Framework/Psr7/Traits/MessageDecoratorTrait.php
@@ -0,0 +1,140 @@
+
+ */
+trait MessageDecoratorTrait
+{
+ /** @var MessageInterface */
+ private $message;
+
+ /**
+ * Returns the decorated message.
+ *
+ * Since the underlying Message is immutable as well
+ * exposing it is not an issue, because it's state cannot be altered
+ *
+ * @return MessageInterface
+ */
+ public function getMessage(): MessageInterface
+ {
+ return $this->message;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getProtocolVersion(): string
+ {
+ return $this->message->getProtocolVersion();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withProtocolVersion($version): self
+ {
+ $new = clone $this;
+ $new->message = $this->message->withProtocolVersion($version);
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getHeaders(): array
+ {
+ return $this->message->getHeaders();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasHeader($header): bool
+ {
+ return $this->message->hasHeader($header);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getHeader($header): array
+ {
+ return $this->message->getHeader($header);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getHeaderLine($header): string
+ {
+ return $this->message->getHeaderLine($header);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getBody(): StreamInterface
+ {
+ return $this->message->getBody();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withHeader($header, $value): self
+ {
+ $new = clone $this;
+ $new->message = $this->message->withHeader($header, $value);
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withAddedHeader($header, $value): self
+ {
+ $new = clone $this;
+ $new->message = $this->message->withAddedHeader($header, $value);
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withoutHeader($header): self
+ {
+ $new = clone $this;
+ $new->message = $this->message->withoutHeader($header);
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withBody(StreamInterface $body): self
+ {
+ $new = clone $this;
+ $new->message = $this->message->withBody($body);
+
+ return $new;
+ }
+}
diff --git a/system/src/Grav/Framework/Psr7/Traits/RequestDecoratorTrait.php b/system/src/Grav/Framework/Psr7/Traits/RequestDecoratorTrait.php
new file mode 100644
index 0000000..ab1ffad
--- /dev/null
+++ b/system/src/Grav/Framework/Psr7/Traits/RequestDecoratorTrait.php
@@ -0,0 +1,112 @@
+
+ */
+trait RequestDecoratorTrait
+{
+ use MessageDecoratorTrait {
+ getMessage as private;
+ }
+
+ /**
+ * Returns the decorated request.
+ *
+ * Since the underlying Request is immutable as well
+ * exposing it is not an issue, because it's state cannot be altered
+ *
+ * @return RequestInterface
+ */
+ public function getRequest(): RequestInterface
+ {
+ /** @var RequestInterface $message */
+ $message = $this->getMessage();
+
+ return $message;
+ }
+
+ /**
+ * Exchanges the underlying request with another.
+ *
+ * @param RequestInterface $request
+ * @return self
+ */
+ public function withRequest(RequestInterface $request): self
+ {
+ $new = clone $this;
+ $new->message = $request;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getRequestTarget(): string
+ {
+ return $this->getRequest()->getRequestTarget();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withRequestTarget($requestTarget): self
+ {
+ $new = clone $this;
+ $new->message = $this->getRequest()->withRequestTarget($requestTarget);
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMethod(): string
+ {
+ return $this->getRequest()->getMethod();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withMethod($method): self
+ {
+ $new = clone $this;
+ $new->message = $this->getRequest()->withMethod($method);
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getUri(): UriInterface
+ {
+ return $this->getRequest()->getUri();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withUri(UriInterface $uri, $preserveHost = false): self
+ {
+ $new = clone $this;
+ $new->message = $this->getRequest()->withUri($uri, $preserveHost);
+
+ return $new;
+ }
+}
diff --git a/system/src/Grav/Framework/Psr7/Traits/ResponseDecoratorTrait.php b/system/src/Grav/Framework/Psr7/Traits/ResponseDecoratorTrait.php
new file mode 100644
index 0000000..32a1bfb
--- /dev/null
+++ b/system/src/Grav/Framework/Psr7/Traits/ResponseDecoratorTrait.php
@@ -0,0 +1,82 @@
+
+ */
+trait ResponseDecoratorTrait
+{
+ use MessageDecoratorTrait {
+ getMessage as private;
+ }
+
+ /**
+ * Returns the decorated response.
+ *
+ * Since the underlying Response is immutable as well
+ * exposing it is not an issue, because it's state cannot be altered
+ *
+ * @return ResponseInterface
+ */
+ public function getResponse(): ResponseInterface
+ {
+ /** @var ResponseInterface $message */
+ $message = $this->getMessage();
+
+ return $message;
+ }
+
+ /**
+ * Exchanges the underlying response with another.
+ *
+ * @param ResponseInterface $response
+ *
+ * @return self
+ */
+ public function withResponse(ResponseInterface $response): self
+ {
+ $new = clone $this;
+ $new->message = $response;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStatusCode(): int
+ {
+ return $this->getResponse()->getStatusCode();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withStatus($code, $reasonPhrase = ''): self
+ {
+ $new = clone $this;
+ $new->message = $this->getResponse()->withStatus($code, $reasonPhrase);
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getReasonPhrase(): string
+ {
+ return $this->getResponse()->getReasonPhrase();
+ }
+}
diff --git a/system/src/Grav/Framework/Psr7/Traits/ServerRequestDecoratorTrait.php b/system/src/Grav/Framework/Psr7/Traits/ServerRequestDecoratorTrait.php
new file mode 100644
index 0000000..f9297dd
--- /dev/null
+++ b/system/src/Grav/Framework/Psr7/Traits/ServerRequestDecoratorTrait.php
@@ -0,0 +1,168 @@
+getMessage();
+
+ return $message;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAttribute($name, $default = null)
+ {
+ return $this->getRequest()->getAttribute($name, $default);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAttributes()
+ {
+ return $this->getRequest()->getAttributes();
+ }
+
+
+ /**
+ * @inheritdoc
+ */
+ public function getCookieParams()
+ {
+ return $this->getRequest()->getCookieParams();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getParsedBody()
+ {
+ return $this->getRequest()->getParsedBody();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getQueryParams()
+ {
+ return $this->getRequest()->getQueryParams();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getServerParams()
+ {
+ return $this->getRequest()->getServerParams();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getUploadedFiles()
+ {
+ return $this->getRequest()->getUploadedFiles();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function withAttribute($name, $value)
+ {
+ $new = clone $this;
+ $new->message = $this->getRequest()->withAttribute($name, $value);
+
+ return $new;
+ }
+
+ public function withAttributes(array $attributes)
+ {
+ $new = clone $this;
+ foreach ($attributes as $attribute => $value) {
+ $new->message = $new->withAttribute($attribute, $value);
+ }
+
+ return $new;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function withoutAttribute($name)
+ {
+ $new = clone $this;
+ $new->message = $this->getRequest()->withoutAttribute($name);
+
+ return $new;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function withCookieParams(array $cookies)
+ {
+ $new = clone $this;
+ $new->message = $this->getRequest()->withCookieParams($cookies);
+
+ return $new;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function withParsedBody($data)
+ {
+ $new = clone $this;
+ $new->message = $this->getRequest()->withParsedBody($data);
+
+ return $new;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function withQueryParams(array $query)
+ {
+ $new = clone $this;
+ $new->message = $this->getRequest()->withQueryParams($query);
+
+ return $new;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function withUploadedFiles(array $uploadedFiles)
+ {
+ $new = clone $this;
+ $new->message = $this->getRequest()->withUploadedFiles($uploadedFiles);
+
+ return $new;
+ }
+}
diff --git a/system/src/Grav/Framework/Psr7/Traits/StreamDecoratorTrait.php b/system/src/Grav/Framework/Psr7/Traits/StreamDecoratorTrait.php
new file mode 100644
index 0000000..f61ea4b
--- /dev/null
+++ b/system/src/Grav/Framework/Psr7/Traits/StreamDecoratorTrait.php
@@ -0,0 +1,145 @@
+stream->__toString();
+ }
+
+ public function __destruct()
+ {
+ $this->stream->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close(): void
+ {
+ $this->stream->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function detach()
+ {
+ return $this->stream->detach();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSize(): ?int
+ {
+ return $this->stream->getSize();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tell(): int
+ {
+ return $this->stream->tell();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function eof(): bool
+ {
+ return $this->stream->eof();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isSeekable(): bool
+ {
+ return $this->stream->isSeekable();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = \SEEK_SET): void
+ {
+ $this->stream->seek($offset, $whence);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function rewind(): void
+ {
+ $this->stream->rewind();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isWritable(): bool
+ {
+ return $this->stream->isWritable();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($string): int
+ {
+ return $this->stream->write($string);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isReadable(): bool
+ {
+ return $this->stream->isReadable();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($length): string
+ {
+ return $this->stream->read($length);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getContents(): string
+ {
+ return $this->stream->getContents();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadata($key = null)
+ {
+ return $this->stream->getMetadata($key);
+ }
+}
diff --git a/system/src/Grav/Framework/Psr7/Traits/UploadedFileDecoratorTrait.php b/system/src/Grav/Framework/Psr7/Traits/UploadedFileDecoratorTrait.php
new file mode 100644
index 0000000..677c4d2
--- /dev/null
+++ b/system/src/Grav/Framework/Psr7/Traits/UploadedFileDecoratorTrait.php
@@ -0,0 +1,51 @@
+uploadedFile->getStream();
+ }
+
+ public function moveTo($targetPath): void
+ {
+ $this->uploadedFile->moveTo($targetPath);
+ }
+
+ public function getSize(): ?int
+ {
+ return $this->uploadedFile->getSize();
+ }
+
+ public function getError(): int
+ {
+ return $this->uploadedFile->getError();
+ }
+
+ public function getClientFilename(): ?string
+ {
+ return $this->uploadedFile->getClientFilename();
+ }
+
+ public function getClientMediaType(): ?string
+ {
+ return $this->uploadedFile->getClientMediaType();
+ }
+}
diff --git a/system/src/Grav/Framework/Psr7/Traits/UriDecorationTrait.php b/system/src/Grav/Framework/Psr7/Traits/UriDecorationTrait.php
new file mode 100644
index 0000000..9e7946a
--- /dev/null
+++ b/system/src/Grav/Framework/Psr7/Traits/UriDecorationTrait.php
@@ -0,0 +1,128 @@
+uri->__toString();
+ }
+
+ public function getScheme(): string
+ {
+ return $this->uri->getScheme();
+ }
+
+ public function getAuthority(): string
+ {
+ return $this->uri->getAuthority();
+ }
+
+ public function getUserInfo(): string
+ {
+ return $this->uri->getUserInfo();
+ }
+
+ public function getHost(): string
+ {
+ return $this->uri->getHost();
+ }
+
+ public function getPort(): ?int
+ {
+ return $this->uri->getPort();
+ }
+
+ public function getPath(): string
+ {
+ return $this->uri->getPath();
+ }
+
+ public function getQuery(): string
+ {
+ return $this->uri->getQuery();
+ }
+
+ public function getFragment(): string
+ {
+ return $this->uri->getFragment();
+ }
+
+ public function withScheme($scheme): UriInterface
+ {
+ /** @var UriInterface|UriDecorationTrait $new */
+ $new = clone $this;
+ $new->uri = $this->uri->withScheme($scheme);
+
+ return $new;
+ }
+
+ public function withUserInfo($user, $password = null): UriInterface
+ {
+ /** @var UriInterface|UriDecorationTrait $new */
+ $new = clone $this;
+ $new->uri = $this->uri->withUserInfo($user, $password);
+
+ return $new;
+ }
+
+ public function withHost($host): UriInterface
+ {
+ /** @var UriInterface|UriDecorationTrait $new */
+ $new = clone $this;
+ $new->uri = $this->uri->withHost($host);
+
+ return $new;
+ }
+
+ public function withPort($port): UriInterface
+ {
+ /** @var UriInterface|UriDecorationTrait $new */
+ $new = clone $this;
+ $new->uri = $this->uri->withPort($port);
+
+ return $new;
+ }
+
+ public function withPath($path): UriInterface
+ {
+ /** @var UriInterface|UriDecorationTrait $new */
+ $new = clone $this;
+ $new->uri = $this->uri->withPath($path);
+
+ return $new;
+ }
+
+ public function withQuery($query): UriInterface
+ {
+ /** @var UriInterface|UriDecorationTrait $new */
+ $new = clone $this;
+ $new->uri = $this->uri->withQuery($query);
+
+ return $new;
+ }
+
+ public function withFragment($fragment): UriInterface
+ {
+ /** @var UriInterface|UriDecorationTrait $new */
+ $new = clone $this;
+ $new->uri = $this->uri->withFragment($fragment);
+
+ return $new;
+ }
+}
diff --git a/system/src/Grav/Framework/Psr7/UploadedFile.php b/system/src/Grav/Framework/Psr7/UploadedFile.php
new file mode 100644
index 0000000..d6b062f
--- /dev/null
+++ b/system/src/Grav/Framework/Psr7/UploadedFile.php
@@ -0,0 +1,33 @@
+uploadedFile = new \Nyholm\Psr7\UploadedFile($streamOrFile, $size, $errorStatus, $clientFilename, $clientMediaType);
+ }
+}
diff --git a/system/src/Grav/Framework/Psr7/Uri.php b/system/src/Grav/Framework/Psr7/Uri.php
new file mode 100644
index 0000000..28614ca
--- /dev/null
+++ b/system/src/Grav/Framework/Psr7/Uri.php
@@ -0,0 +1,134 @@
+uri = new \Nyholm\Psr7\Uri($uri);
+ }
+
+ /**
+ * @return array
+ */
+ public function getQueryParams(): array
+ {
+ return UriFactory::parseQuery($this->getQuery());
+ }
+
+ /**
+ * @param array $params
+ * @return UriInterface
+ */
+ public function withQueryParams(array $params): UriInterface
+ {
+ $query = UriFactory::buildQuery($params);
+
+ return $this->withQuery($query);
+ }
+
+ /**
+ * Whether the URI has the default port of the current scheme.
+ *
+ * `$uri->getPort()` may return the standard port. This method can be used for some non-http/https Uri.
+ *
+ * @return bool
+ */
+ public function isDefaultPort(): bool
+ {
+ return $this->getPort() === null || GuzzleUri::isDefaultPort($this);
+ }
+
+ /**
+ * Whether the URI is absolute, i.e. it has a scheme.
+ *
+ * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
+ * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
+ * to another URI, the base URI. Relative references can be divided into several forms:
+ * - network-path references, e.g. '//example.com/path'
+ * - absolute-path references, e.g. '/path'
+ * - relative-path references, e.g. 'subpath'
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4
+ */
+ public function isAbsolute(): bool
+ {
+ return GuzzleUri::isAbsolute($this);
+ }
+
+ /**
+ * Whether the URI is a network-path reference.
+ *
+ * A relative reference that begins with two slash characters is termed an network-path reference.
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public function isNetworkPathReference(): bool
+ {
+ return GuzzleUri::isNetworkPathReference($this);
+ }
+
+ /**
+ * Whether the URI is a absolute-path reference.
+ *
+ * A relative reference that begins with a single slash character is termed an absolute-path reference.
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public function isAbsolutePathReference(): bool
+ {
+ return GuzzleUri::isAbsolutePathReference($this);
+ }
+
+ /**
+ * Whether the URI is a relative-path reference.
+ *
+ * A relative reference that does not begin with a slash character is termed a relative-path reference.
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public function isRelativePathReference(): bool
+ {
+ return GuzzleUri::isRelativePathReference($this);
+ }
+
+ /**
+ * Whether the URI is a same-document reference.
+ *
+ * A same-document reference refers to a URI that is, aside from its fragment
+ * component, identical to the base URI. When no base URI is given, only an empty
+ * URI reference (apart from its fragment) is considered a same-document reference.
+ *
+ * @param UriInterface|null $base An optional base URI to compare against
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.4
+ */
+ public function isSameDocumentReference(UriInterface $base = null): bool
+ {
+ return GuzzleUri::isSameDocumentReference($this, $base);
+ }
+}
diff --git a/system/src/Grav/Framework/RequestHandler/Exception/InvalidArgumentException.php b/system/src/Grav/Framework/RequestHandler/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..4cacd71
--- /dev/null
+++ b/system/src/Grav/Framework/RequestHandler/Exception/InvalidArgumentException.php
@@ -0,0 +1,47 @@
+invalidMiddleware = $invalidMiddleware;
+ }
+
+ /**
+ * Return the invalid middleware
+ *
+ * @return mixed|null
+ */
+ public function getInvalidMiddleware()
+ {
+ return $this->invalidMiddleware;
+ }
+}
diff --git a/system/src/Grav/Framework/RequestHandler/Exception/NotFoundException.php b/system/src/Grav/Framework/RequestHandler/Exception/NotFoundException.php
new file mode 100644
index 0000000..5e8d22a
--- /dev/null
+++ b/system/src/Grav/Framework/RequestHandler/Exception/NotFoundException.php
@@ -0,0 +1,39 @@
+getMethod()), ['PUT', 'PATCH', 'DELETE'])) {
+ parent::__construct($request, 'Method Not Allowed', 405, $previous);
+ } else {
+ parent::__construct($request, 'Not Found', 404, $previous);
+ }
+ }
+
+ public function getRequest(): ServerRequestInterface
+ {
+ return $this->request;
+ }
+}
diff --git a/system/src/Grav/Framework/RequestHandler/Exception/NotHandledException.php b/system/src/Grav/Framework/RequestHandler/Exception/NotHandledException.php
new file mode 100644
index 0000000..6696013
--- /dev/null
+++ b/system/src/Grav/Framework/RequestHandler/Exception/NotHandledException.php
@@ -0,0 +1,16 @@
+ 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Time-out',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested range not satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => 'I\'m a teapot',
+ 419 => 'Page Expired',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 425 => 'Unordered Collection',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 451 => 'Unavailable For Legal Reasons',
+
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Time-out',
+ 505 => 'HTTP Version not supported',
+ 506 => 'Variant Also Negotiates',
+ 507 => 'Insufficient Storage',
+ 508 => 'Loop Detected',
+ 511 => 'Network Authentication Required',
+ ];
+
+ /** @var ServerRequestInterface */
+ private $request;
+
+ /**
+ * @param ServerRequestInterface $request
+ * @param string $message
+ * @param int $code
+ * @param \Throwable|null $previous
+ */
+ public function __construct(ServerRequestInterface $request, string $message, int $code = 500, \Throwable $previous = null)
+ {
+ $this->request = $request;
+
+ parent::__construct($message, $code, $previous);
+ }
+
+ /**
+ * @return ServerRequestInterface
+ */
+ public function getRequest() : ServerRequestInterface
+ {
+ return $this->request;
+ }
+
+ public function getHttpCode() : int
+ {
+ $code = $this->getCode();
+
+ return isset(self::$phrases[$code]) ? $code : 500;
+ }
+
+ public function getHttpReason() : ?string
+ {
+ return self::$phrases[$this->getCode()] ?? self::$phrases[500];
+ }
+}
diff --git a/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php b/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php
new file mode 100644
index 0000000..ec1de6d
--- /dev/null
+++ b/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php
@@ -0,0 +1,44 @@
+handle($request);
+ } catch (\Throwable $exception) {
+ $response = [
+ 'error' => [
+ 'type' => \get_class($exception),
+ 'code' => $exception->getCode(),
+ 'message' => $exception->getMessage(),
+ 'file' => $exception->getFile(),
+ 'line' => $exception->getLine(),
+ 'trace' => explode("\n", $exception->getTraceAsString()),
+ ]
+ ];
+
+ /** @var string $json */
+ $json = json_encode($response);
+
+ return new Response($exception->getCode() ?: 500, ['Content-Type' => 'application/json'], $json);
+ }
+ }
+}
diff --git a/system/src/Grav/Framework/RequestHandler/RequestHandler.php b/system/src/Grav/Framework/RequestHandler/RequestHandler.php
new file mode 100644
index 0000000..c4d5387
--- /dev/null
+++ b/system/src/Grav/Framework/RequestHandler/RequestHandler.php
@@ -0,0 +1,66 @@
+middleware = $middleware;
+ $this->handler = $default;
+ $this->container = $container;
+ }
+
+ /**
+ * Add callable initializing Middleware that will be executed as soon as possible.
+ *
+ * @param string $name
+ * @param callable $callable
+ * @return $this
+ */
+ public function addCallable(string $name, callable $callable): self
+ {
+ $this->container[$name] = $callable;
+ array_unshift($this->middleware, $name);
+
+ return $this;
+ }
+
+ /**
+ * Add Middleware that will be executed as soon as possible.
+ *
+ * @param string $name
+ * @param MiddlewareInterface $middleware
+ * @return $this
+ */
+ public function addMiddleware(string $name, MiddlewareInterface $middleware): self
+ {
+ $this->container[$name] = $middleware;
+ array_unshift($this->middleware, $name);
+
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php b/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php
new file mode 100644
index 0000000..153d1a1
--- /dev/null
+++ b/system/src/Grav/Framework/RequestHandler/Traits/RequestHandlerTrait.php
@@ -0,0 +1,59 @@
+ */
+ protected $middleware;
+
+ /** @var callable */
+ private $handler;
+
+ /** @var ContainerInterface|null */
+ private $container;
+
+ /**
+ * {@inheritdoc}
+ * @throws InvalidArgumentException
+ */
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ $middleware = array_shift($this->middleware);
+
+ // Use default callable if there is no middleware.
+ if ($middleware === null) {
+ return \call_user_func($this->handler, $request);
+ }
+
+ if ($middleware instanceof MiddlewareInterface) {
+ return $middleware->process($request, clone $this);
+ }
+
+ if (null === $this->container || !$this->container->has($middleware)) {
+ throw new InvalidArgumentException(
+ sprintf('The middleware is not a valid %s and is not passed in the Container', MiddlewareInterface::class),
+ $middleware
+ );
+ }
+
+ array_unshift($this->middleware, $this->container->get($middleware));
+
+ return $this->handle($request);
+ }
+}
\ No newline at end of file
diff --git a/system/src/Grav/Framework/Route/Route.php b/system/src/Grav/Framework/Route/Route.php
index 63f6f7e..f287694 100644
--- a/system/src/Grav/Framework/Route/Route.php
+++ b/system/src/Grav/Framework/Route/Route.php
@@ -1,8 +1,9 @@
$this->getUriPath(),
+ 'path' => $this->getUriPath(true),
'query' => $this->getUriQuery(),
'grav' => [
'root' => $this->root,
'language' => $this->language,
'route' => $this->route,
+ 'extension' => $this->extension,
'grav_params' => $this->gravParams,
'query_params' => $this->queryParams,
],
@@ -91,6 +96,14 @@ class Route
return '/' . $this->route;
}
+ /**
+ * @return string
+ */
+ public function getExtension()
+ {
+ return $this->extension;
+ }
+
/**
* @param int $offset
* @param int|null $length
@@ -101,7 +114,7 @@ class Route
$parts = explode('/', $this->route);
if ($offset !== 0 || $length !== null) {
- $parts = array_slice($parts, $offset, $length);
+ $parts = \array_slice($parts, $offset, $length);
}
return $parts;
@@ -159,7 +172,7 @@ class Route
*/
public function getGravParam($param)
{
- return isset($this->gravParams[$param]) ? $this->gravParams[$param] : null;
+ return $this->gravParams[$param] ?? null;
}
/**
@@ -168,7 +181,53 @@ class Route
*/
public function getQueryParam($param)
{
- return isset($this->queryParams[$param]) ? $this->queryParams[$param] : null;
+ return $this->queryParams[$param] ?? null;
+ }
+
+ /**
+ * Allow the ability to set the route to something else
+ *
+ * @param string $route
+ * @return $this
+ */
+ public function withRoute($route)
+ {
+ $this->route = $route;
+ return $this;
+ }
+
+ /**
+ * Allow the ability to set the root to something else
+ *
+ * @param string $root
+ * @return $this
+ */
+ public function withRoot($root)
+ {
+ $this->root = $root;
+ return $this;
+ }
+
+ /**
+ * @param string $path
+ * @return Route
+ */
+ public function withAddedPath($path)
+ {
+ $this->route .= '/' . ltrim($path, '/');
+
+ return $this;
+ }
+
+ /**
+ * @param string $extension
+ * @return Route
+ */
+ public function withExtension($extension)
+ {
+ $this->extension = $extension;
+
+ return $this;
}
/**
@@ -200,11 +259,12 @@ class Route
}
/**
+ * @param bool $includeRoot
* @return string
*/
- public function __toString()
+ public function toString(bool $includeRoot = false)
{
- $url = $this->getUriPath();
+ $url = $this->getUriPath($includeRoot);
if ($this->queryParams) {
$url .= '?' . $this->getUriQuery();
@@ -213,6 +273,17 @@ class Route
return $url;
}
+ /**
+ * @return string
+ * @deprecated 1.6 Use ->toString(true) or ->getUri() instead.
+ */
+ public function __toString()
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() will change in the future to return route, not relative url: use ->toString(true) or ->getUri() instead.', E_USER_DEPRECATED);
+
+ return $this->toString(true);
+ }
+
/**
* @param string $type
* @param string $param
@@ -221,7 +292,7 @@ class Route
*/
protected function withParam($type, $param, $value)
{
- $oldValue = isset($this->{$type}[$param]) ? $this->{$type}[$param] : null;
+ $oldValue = $this->{$type}[$param] ?? null;
if ($oldValue === $value) {
return $this;
@@ -238,18 +309,19 @@ class Route
}
/**
+ * @param bool $includeRoot
* @return string
*/
- protected function getUriPath()
+ protected function getUriPath($includeRoot = false)
{
- $parts = [$this->root];
+ $parts = $includeRoot ? [$this->root] : [''];
if ($this->language !== '') {
$parts[] = $this->language;
}
if ($this->route !== '') {
- $parts[] = $this->route;
+ $parts[] = $this->extension ? $this->route . '.' . $this->extension : $this->route;
}
if ($this->gravParams) {
@@ -277,6 +349,7 @@ class Route
$this->root = $gravParts['root'];
$this->language = $gravParts['language'];
$this->route = $gravParts['route'];
+ $this->extension = $gravParts['extension'] ?? '';
$this->gravParams = $gravParts['params'];
$this->queryParams = $parts['query_params'];
@@ -284,7 +357,7 @@ class Route
$this->root = RouteFactory::getRoot();
$this->language = RouteFactory::getLanguage();
- $path = isset($parts['path']) ? $parts['path'] : '/';
+ $path = $parts['path'] ?? '/';
if (isset($parts['params'])) {
$this->route = trim(rawurldecode($path), '/');
$this->gravParams = $parts['params'];
diff --git a/system/src/Grav/Framework/Route/RouteFactory.php b/system/src/Grav/Framework/Route/RouteFactory.php
index 830d7c7..6b3414a 100644
--- a/system/src/Grav/Framework/Route/RouteFactory.php
+++ b/system/src/Grav/Framework/Route/RouteFactory.php
@@ -1,8 +1,9 @@
$val];
}
}
diff --git a/system/src/Grav/Framework/Session/Exceptions/SessionException.php b/system/src/Grav/Framework/Session/Exceptions/SessionException.php
new file mode 100644
index 0000000..4caab82
--- /dev/null
+++ b/system/src/Grav/Framework/Session/Exceptions/SessionException.php
@@ -0,0 +1,14 @@
+ 'nocache',
'use_trans_sid' => 0,
'use_cookies' => 1,
'lazy_write' => 1,
'use_strict_mode' => 1
- );
+ ];
$this->setOptions($options);
@@ -141,35 +141,30 @@ class Session implements SessionInterface
'cache_limiter' => true,
'cache_expire' => true,
'use_trans_sid' => true,
- 'trans_sid_tags' => true, // PHP 7.1
- 'trans_sid_hosts' => true, // PHP 7.1
- 'sid_length' => true, // PHP 7.1
- 'sid_bits_per_character' => true, // PHP 7.1
+ 'trans_sid_tags' => true,
+ 'trans_sid_hosts' => true,
+ 'sid_length' => true,
+ 'sid_bits_per_character' => true,
'upload_progress.enabled' => true,
'upload_progress.cleanup' => true,
'upload_progress.prefix' => true,
'upload_progress.name' => true,
'upload_progress.freq' => true,
'upload_progress.min-freq' => true,
- 'lazy_write' => true,
- 'url_rewriter.tags' => true, // Not used in PHP 7.1
- 'hash_function' => true, // Not used in PHP 7.1
- 'hash_bits_per_character' => true, // Not used in PHP 7.1
- 'entropy_file' => true, // Not used in PHP 7.1
- 'entropy_length' => true, // Not used in PHP 7.1
+ 'lazy_write' => true
];
foreach ($options as $key => $value) {
- if (is_array($value)) {
+ if (\is_array($value)) {
// Allow nested options.
foreach ($value as $key2 => $value2) {
$ckey = "{$key}.{$key2}";
if (isset($value2, $allowedOptions[$ckey])) {
- $this->ini_set("session.{$ckey}", $value2);
+ $this->setOption($ckey, $value2);
}
}
} elseif (isset($value, $allowedOptions[$key])) {
- $this->ini_set("session.{$key}", $value);
+ $this->setOption($key, $value);
}
}
}
@@ -179,6 +174,10 @@ class Session implements SessionInterface
*/
public function start($readonly = false)
{
+ if (\PHP_SAPI === 'cli') {
+ return $this;
+ }
+
// Protection against invalid session cookie names throwing exception: http://php.net/manual/en/function.session-id.php#116836
if (isset($_COOKIE[session_name()]) && !preg_match('/^[-,a-zA-Z0-9]{1,128}$/', $_COOKIE[session_name()])) {
unset($_COOKIE[session_name()]);
@@ -190,10 +189,17 @@ class Session implements SessionInterface
}
$success = @session_start($options);
+ $user = $success ? $this->__get('user') : null;
if (!$success) {
$last = error_get_last();
$error = $last ? $last['message'] : 'Unknown error';
- throw new \RuntimeException('Failed to start session: ' . $error, 500);
+
+ throw new SessionException('Failed to start session: ' . $error, 500);
+ }
+
+ if ($user && !$user->isValid()) {
+ $this->clear();
+ throw new SessionException('User Invalid', 500);
}
$params = session_get_cookie_params();
@@ -300,7 +306,7 @@ class Session implements SessionInterface
*/
public function __get($name)
{
- return isset($_SESSION[$name]) ? $_SESSION[$name] : null;
+ return $_SESSION[$name] ?? null;
}
/**
@@ -333,16 +339,17 @@ class Session implements SessionInterface
* @param string $key
* @param mixed $value
*/
- protected function ini_set($key, $value)
+ protected function setOption($key, $value)
{
- if (!is_string($value)) {
- if (is_bool($value)) {
+ if (!\is_string($value)) {
+ if (\is_bool($value)) {
$value = $value ? '1' : '0';
+ } else {
+ $value = (string)$value;
}
- $value = (string)$value;
}
$this->options[$key] = $value;
- ini_set($key, $value);
+ ini_set("session.{$key}", $value);
}
}
diff --git a/system/src/Grav/Framework/Session/SessionInterface.php b/system/src/Grav/Framework/Session/SessionInterface.php
index 74c5f2a..75df0e9 100644
--- a/system/src/Grav/Framework/Session/SessionInterface.php
+++ b/system/src/Grav/Framework/Session/SessionInterface.php
@@ -1,8 +1,9 @@
getQueryParams();
- return isset($queryParams[$key]) ? $queryParams[$key] : null;
+ return $queryParams[$key] ?? null;
}
/**
diff --git a/system/src/Grav/Framework/Uri/UriFactory.php b/system/src/Grav/Framework/Uri/UriFactory.php
index c6bd428..e5cb1d3 100644
--- a/system/src/Grav/Framework/Uri/UriFactory.php
+++ b/system/src/Grav/Framework/Uri/UriFactory.php
@@ -1,8 +1,9 @@
= 1 && $port <= 65535))) {
+ if (null === $port || (\is_int($port) && ($port >= 1 && $port <= 65535))) {
return $port;
}
@@ -103,7 +104,7 @@ class UriPartsFilter
*/
public static function filterPath($path)
{
- if (!is_string($path)) {
+ if (!\is_string($path)) {
throw new \InvalidArgumentException('Uri path must be a string');
}
@@ -113,7 +114,7 @@ class UriPartsFilter
return rawurlencode($match[0]);
},
$path
- );
+ ) ?? '';
}
/**
@@ -125,7 +126,7 @@ class UriPartsFilter
*/
public static function filterQueryOrFragment($query)
{
- if (!is_string($query)) {
+ if (!\is_string($query)) {
throw new \InvalidArgumentException('Uri query string and fragment must be a string');
}
@@ -135,6 +136,6 @@ class UriPartsFilter
return rawurlencode($match[0]);
},
$query
- );
+ ) ?? '';
}
}
diff --git a/system/src/Grav/Installer/Install.php b/system/src/Grav/Installer/Install.php
new file mode 100644
index 0000000..5345ce3
--- /dev/null
+++ b/system/src/Grav/Installer/Install.php
@@ -0,0 +1,287 @@
+ [
+ 'name' => 'PHP',
+ 'versions' => [
+ '7.3' => '7.3.1',
+ '7.2' => '7.2.0',
+ '7.1' => '7.1.3',
+ '' => '7.2.14'
+ ]
+ ],
+ 'grav' => [
+ 'name' => 'Grav',
+ 'versions' => [
+ '1.5' => '1.5.0',
+ '' => '1.5.7'
+ ]
+ ],
+ 'plugins' => [
+ 'admin' => [
+ 'name' => 'Admin',
+ 'optional' => true,
+ 'versions' => [
+ '1.8' => '1.8.0',
+ '' => '1.8.16'
+ ]
+ ],
+ 'email' => [
+ 'name' => 'Email',
+ 'optional' => true,
+ 'versions' => [
+ '2.7' => '2.7.0',
+ '' => '2.7.2'
+ ]
+ ],
+ 'form' => [
+ 'name' => 'Form',
+ 'optional' => true,
+ 'versions' => [
+ '2.16' => '2.16.0',
+ '' => '2.16.4'
+ ]
+ ],
+ 'login' => [
+ 'name' => 'Login',
+ 'optional' => true,
+ 'versions' => [
+ '2.8' => '2.8.0',
+ '' => '2.8.3'
+ ]
+ ],
+ ]
+ ];
+
+ private $ignores = [
+ 'backup',
+ 'cache',
+ 'images',
+ 'logs',
+ 'tmp',
+ 'user',
+ '.htaccess',
+ 'robots.txt'
+ ];
+
+ private $classMap = [
+ // 'Grav\\Installer\\Test' => __DIR__ . '/Test.php',
+ ];
+
+ private $zip;
+ private $location;
+
+ private static $instance;
+
+ public static function instance()
+ {
+ if (null === self::$instance) {
+ self::$instance = new static();
+ }
+
+ return self::$instance;
+ }
+
+ private function __construct()
+ {
+ }
+
+ public function setZip(string $zip)
+ {
+ $this->zip = $zip;
+
+ return $this;
+ }
+
+ public function __invoke(?string $zip)
+ {
+ $this->zip = $zip;
+
+ $failedRequirements = $this->checkRequirements();
+ if ($failedRequirements) {
+ $error = ['Following requirements have failed:'];
+
+ foreach ($failedRequirements as $name => $req) {
+ $error[] = "{$req['title']} >= v{$req['minimum']} required, you have v{$req['installed']}";
+ }
+
+ throw new \RuntimeException(implode("
\n", $error));
+ }
+
+ $this->prepare();
+ $this->install();
+ $this->finalize();
+ }
+
+ /**
+ * NOTE: This method can only be called after $grav['plugins']->init().
+ *
+ * @return array List of failed requirements. If the list is empty, installation can go on.
+ */
+ public function checkRequirements(): array
+ {
+ $results = [];
+
+ $this->checkVersion($results, 'php','php', $this->requires['php'], PHP_VERSION);
+ $this->checkVersion($results, 'grav', 'grav', $this->requires['grav'], GRAV_VERSION);
+ $this->checkPlugins($results, $this->requires['plugins']);
+
+ return $results;
+ }
+
+ /**
+ * @throws \RuntimeException
+ */
+ public function prepare(): void
+ {
+ // Locate the new Grav update and the target site from the filesystem.
+ $location = dirname(realpath(__DIR__), 4);
+ $target = dirname(realpath(GRAV_ROOT . '/index.php'));
+ if ($location === $target) {
+ // We cannot copy files into themselves, abort!
+ throw new \RuntimeException('Grav has already been installed here!', 400);
+ }
+
+ // Make sure that none of the Grav\Installer classes have been loaded, otherwise installation may fail!
+ foreach ($this->classMap as $class_name => $path) {
+ if (\class_exists($class_name, false)) {
+ throw new \RuntimeException(sprintf('Cannot update Grav, class %s has already been loaded!', $class_name), 500);
+ }
+ }
+
+ $grav = Grav::instance();
+
+ /** @var ClassLoader $loader */
+ $loader = $grav['loader'];
+
+ // Override Grav\Installer classes by using this version of Grav.
+ $loader->addClassMap($this->classMap);
+
+ $this->location = $location;
+ }
+
+ /**
+ * @throws \RuntimeException
+ */
+ public function install(): void
+ {
+ if (!$this->location) {
+ throw new \RuntimeException('Oops, installer was run without prepare()!', 500);
+ }
+
+ try {
+ Installer::install(
+ $this->zip,
+ GRAV_ROOT,
+ ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true, 'ignores' => $this->ignores],
+ $this->location,
+ !($this->zip && is_file($this->zip))
+ );
+ } catch (\Exception $e) {
+ Installer::setError($e->getMessage());
+ }
+
+ $errorCode = Installer::lastErrorCode();
+
+ $success = !(is_string($errorCode) || ($errorCode & (Installer::ZIP_OPEN_ERROR | Installer::ZIP_EXTRACT_ERROR)));
+
+ if (!$success) {
+ throw new \RuntimeException(Installer::lastErrorMsg());
+ }
+ }
+
+ /**
+ * @throws \RuntimeException
+ */
+ public function finalize(): void
+ {
+ Cache::clearCache();
+
+ clearstatcache();
+ if (function_exists('opcache_reset')) {
+ @opcache_reset();
+ }
+ }
+
+ protected function checkVersion(array &$results, $type, $name, array $check, $version): void
+ {
+ if (null === $version && !empty($check['optional'])) {
+ return;
+ }
+
+ $major = $minor = 0;
+ $versions = $check['versions'] ?? [];
+ foreach ($versions as $major => $minor) {
+ if (!$major || version_compare($version, $major, '<')) {
+ continue;
+ }
+
+ if (version_compare($version, $minor, '>=')) {
+ return;
+ }
+
+ break;
+ }
+
+ if (!$major) {
+ $minor = reset($versions);
+ }
+
+ $recommended = end($versions);
+
+ if (version_compare($recommended, $minor, '<=')) {
+ $recommended = null;
+ }
+
+ $results[$name] = [
+ 'type' => $type,
+ 'name' => $name,
+ 'title' => $check['name'] ?? $name,
+ 'installed' => $version,
+ 'minimum' => $minor,
+ 'recommended' => $recommended
+ ];
+ }
+
+ protected function checkPlugins(array &$results, array $plugins): void
+ {
+ if (!\class_exists('Plugins')) {
+ return;
+ }
+
+ foreach ($plugins as $name => $check) {
+ $plugin = Plugins::get($name);
+ if (!$plugin) {
+ $this->checkVersion($results, 'plugin', $name, $check, null);
+ continue;
+ }
+
+ $blueprint = $plugin->blueprints();
+ $version = (string)$blueprint->get('version');
+ $check['name'] = ($blueprint->get('name') ?? $check['name'] ?? $name) . ' Plugin';
+ $this->checkVersion($results, 'plugin', $name, $check, $version);
+ }
+ }
+}
\ No newline at end of file
diff --git a/user/themes/gilles_acezat/scss/theme/_mixins.scss b/user/config/backups.yaml
similarity index 100%
rename from user/themes/gilles_acezat/scss/theme/_mixins.scss
rename to user/config/backups.yaml
diff --git a/user/config/scheduler.yaml b/user/config/scheduler.yaml
new file mode 100644
index 0000000..e69de29
diff --git a/user/config/site.yaml b/user/config/site.yaml
index 7a41c70..68306f0 100644
--- a/user/config/site.yaml
+++ b/user/config/site.yaml
@@ -1,7 +1,7 @@
title: Burodesformes
default_lang: fr
author:
- name: 'Gilles Acezat'
+ name: 'Gilles Acézat'
email: burodesformes@yahoo.fr
taxonomies:
- category
@@ -16,4 +16,4 @@ summary:
blog:
route: /blog
header:
- status: 'Designer graphique'
+ status: 'Design graphique'
diff --git a/user/pages/01.projets/nom-du-projets/item.md b/user/pages/01.projets/nom-du-projets/item.md
new file mode 100644
index 0000000..9d3349f
--- /dev/null
+++ b/user/pages/01.projets/nom-du-projets/item.md
@@ -0,0 +1,12 @@
+---
+title: 'Nom du projets'
+media_order: logo-Alliance_gris.png
+mea: false
+taxonomy:
+ category:
+ - logo
+ tag:
+ - mea
+---
+
+cfecb dcbfh obd cobydez cydze bd
\ No newline at end of file
diff --git a/user/pages/01.projets/nom-du-projets/logo-Alliance_gris.png b/user/pages/01.projets/nom-du-projets/logo-Alliance_gris.png
new file mode 100644
index 0000000..3dbb3b6
Binary files /dev/null and b/user/pages/01.projets/nom-du-projets/logo-Alliance_gris.png differ
diff --git a/user/pages/01.projets/projet-n-1/item.md b/user/pages/01.projets/projet-n-1/item.md
index 2151607..b2ada5a 100644
--- a/user/pages/01.projets/projet-n-1/item.md
+++ b/user/pages/01.projets/projet-n-1/item.md
@@ -2,6 +2,7 @@
title: 'Projet N°1'
media_order: 'ouvrage Edith.jpg,affiche_grainesdelecteurs_2014.jpg'
mea: true
+meaimg: 'ouvrage Edith.jpg'
taxonomy:
category:
- Éditions
diff --git a/user/pages/01.projets/projet-n-2/item.md b/user/pages/01.projets/projet-n-2/item.md
index 113512c..badb19f 100644
--- a/user/pages/01.projets/projet-n-2/item.md
+++ b/user/pages/01.projets/projet-n-2/item.md
@@ -2,6 +2,7 @@
title: 'projet n°2'
media_order: 'brochure ESADHaR 2017.jpg'
mea: true
+meaimg: 'brochure ESADHaR 2017.jpg'
taxonomy:
category:
- Éditions
diff --git a/user/pages/01.projets/projet-n-3/item.md b/user/pages/01.projets/projet-n-3/item.md
index a5002d2..db101d3 100644
--- a/user/pages/01.projets/projet-n-3/item.md
+++ b/user/pages/01.projets/projet-n-3/item.md
@@ -2,6 +2,7 @@
title: 'projet n°3'
media_order: 'ouvrage Edith 2.jpg,brochure ESADHaR 2017.jpg,Cartevoeux2018_bdf.jpg,FLYER carnaval Colombelle.jpg,FLYER RDV.jpg'
mea: true
+meaimg: 'ouvrage Edith 2.jpg'
taxonomy:
category:
- logo
diff --git a/user/pages/02.contact/default.md b/user/pages/02.contact/default.md
index a9b6899..e2ac6b2 100644
--- a/user/pages/02.contact/default.md
+++ b/user/pages/02.contact/default.md
@@ -3,7 +3,7 @@ title: Contact
---
**GILLES ACÉZAT**
-3 ru d'Épernon
+3 rue d'Épernon
76600 Le Havre - France
+33(0)6 30 55 30 72
+33(0)2 31 73 35 96
diff --git a/user/themes/gilles_acezat/blueprints/item.yaml b/user/themes/gilles_acezat/blueprints/item.yaml
index c3597c8..0355e8b 100644
--- a/user/themes/gilles_acezat/blueprints/item.yaml
+++ b/user/themes/gilles_acezat/blueprints/item.yaml
@@ -16,7 +16,13 @@ form:
0: PLUGIN_ADMIN.NO
validate:
type: bool
-
+
+ header.meaimg:
+ type: filepicker
+ folder: '@self'
+ preview_images: true
+ label: Sélectionnez une image
+
blog:
type: tab
title: Blog Item
diff --git a/user/themes/gilles_acezat/css-compiled/theme.css b/user/themes/gilles_acezat/css-compiled/theme.css
index 3058458..a246395 100644
--- a/user/themes/gilles_acezat/css-compiled/theme.css
+++ b/user/themes/gilles_acezat/css-compiled/theme.css
@@ -1,20 +1,19 @@
-@charset "UTF-8";
* { box-sizing: border-box; }
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document ========================================================================== */
/** 1. Correct the line height in all browsers. 2. Prevent adjustments of font size after orientation changes in iOS. */
-html { margin: 0; padding: 0; font-size: 18px; line-height: 1.15; /* 1 */ -webkit-text-size-adjust: 100%; /* 2 */ }
+html { margin: 0; padding: 0; font-size: 18px; line-height: 1.15; /* 1 */ -webkit-text-size-adjust: 100%; /* 2 */ height: 100%; width: 100%; }
/* Sections ========================================================================== */
/** Remove the margin in all browsers. */
-body { margin: 0; padding: 0; }
+body { margin: 0; padding: 0; height: 100%; width: 100%; }
/** Render the `main` element consistently in IE. */
main { display: block; }
/** Correct the font size and margin on `h1` elements within `section` and `article` contexts in Chrome, Firefox, and Safari. */
-h1 { font-size: 2em; margin: 0; }
+h1 { font-size: 3em; margin: 0; }
h1, h2, h3, h4, h5, p, em, strong, ul, li, a { margin: 0; padding: 0; }
@@ -56,6 +55,8 @@ sup { top: -0.5em; }
/** Remove the border on images inside links in IE 10. */
img { border-style: none; width: 100%; height: 100%; vertical-align: bottom; }
+iframe { border: none; }
+
/* Forms ========================================================================== */
/** 1. Change the font styles in all browsers. 2. Remove the margin in Firefox and Safari. */
button, input, optgroup, select, textarea { font-family: inherit; /* 1 */ font-size: 100%; /* 1 */ line-height: 1.15; /* 1 */ margin: 0; /* 2 */ }
@@ -128,57 +129,39 @@ template { display: none; }
h1, h2 { font-family: "medium"; font-weight: normal; }
-h2 { font-size: 3rem; }
+h2 { font-size: 4.5vw; }
h3 { font-family: "medium"; font-size: 1rem; font-weight: normal; }
h5 { font-size: 0.8rem; font-family: "bold"; font-weight: normal; }
-p { font-family: "Regular"; font-weight: normal; font-size: 0.9rem; }
+p { font-family: "Regular"; font-weight: normal; font-size: 0.9rem; line-height: 1.3rem; }
time { font-family: "italic"; font-weight: normal; }
-header .contact h3 { text-transform: lowercase; color: white; }
+.blog h1 { font-size: 12vw; line-height: 5rem; -webkit-text-stroke: 2px black; color: transparent; }
-.scroll p { font-size: 0.8rem; font-family: "bold"; font-weight: normal; }
+.blog h2 { line-height: 8rem; }
-.cat-list .cat a { font-family: "light"; font-weight: normal; }
+.blog.down h1 { animation: reducH 1s 0.9s ease forwards; }
-body > .blog h1 { font-size: 11rem; -webkit-text-stroke: .1rem #111; color: transparent; line-height: 7rem; }
+.item h2 { font-size: 1rem; }
-body > .blog .card h5 { text-transform: uppercase; }
+.item .prev-next a .title { font-family: "medium"; font-size: 1rem; }
-body > .blog .card .card-category { font-family: "italic"; font-weight: normal; font-size: 0.8rem; }
+.category a { font-family: "Regular"; font-size: 1rem; }
-body > .blog .card .card-date { font-size: 0.8rem; }
+time { font-size: 1rem; }
-body > .blog .card .card-footer span { font-family: "medium"; font-weight: normal; font-size: 0.8rem; text-transform: capitalize; }
+.tags { font-family: "Regular"; font-size: 1rem; }
-body > .blog .card .card-footer span a { color: #999999; }
+#page-wrapper.blog .works-mea .category a, #page-wrapper.blog #items .category a { font-family: "Regular"; font-size: 0.8rem; }
-body > .item h1 { font-size: 3rem; }
+#page-wrapper.blog .works-mea time, #page-wrapper.blog #items time { font-size: 0.8rem; }
-body > .item h2 { font-size: 1.1rem; }
+#page-wrapper.blog .works-mea .tags, #page-wrapper.blog #items .tags { font-size: 0.8rem; }
-body > .item .content-item .content-title > a { font-family: "italic"; }
-
-body > .item .category { font-family: "italic"; font-weight: normal; font-size: 1.1rem; }
-
-body > .item time { font-size: 0.8rem; font-size: 1.1rem; }
-
-body > .item .tags { font-family: "medium"; font-weight: normal; font-size: 0.8rem; text-transform: capitalize; }
-
-body > .item .tags a { color: #999999; }
-
-body > .item .top p { font-family: "bold"; }
-
-.prev-next .title { font-family: "bold"; font-size: 0.8rem; }
-
-.prev-next .tags { font-family: "italic"; font-size: 0.8rem !important; }
-
-.prev-next .blog-date time { font-family: "italic"; font-size: 0.8rem !important; }
-
-.top p { font-size: 0.8rem; }
+.cat-list { font-family: "Regular"; }
.mCustomScrollbar { -ms-touch-action: pinch-zoom; touch-action: pinch-zoom; }
@@ -683,14 +666,6 @@ body > .item .top p { font-family: "bold"; }
@keyframes rotation { from { -webkit-transform: rotate(0deg); }
to { -webkit-transform: rotate(359deg); } }
-@keyframes translatep1 { 0% { transform: translateY(35px); }
- 100% { transform: translateY(5px);
- visibility: hidden; } }
-
-@keyframes translatep2 { 0% { transform: translateY(-20px); }
- 100% { transform: translateY(40px);
- visibility: hidden; } }
-
@keyframes translateright { 0% { right: -50px;
color: transparent;
display: none; }
@@ -709,169 +684,206 @@ body > .item .top p { font-family: "bold"; }
color: transparent;
display: none; } }
-@keyframes height { 0% { height: 50vh;
- min-height: 250px; }
- 100% { height: 0;
- min-height: 100px; } }
+@keyframes reducH { 0% { font-size: 12vw;
+ -webkit-text-stroke: 1px;
+ color: transparent;
+ line-height: 5rem; }
+ 70% { -webkit-text-stroke: 1px;
+ color: black; }
+ 100% { font-size: 3rem;
+ -webkit-text-stroke: 0px;
+ color: black;
+ line-height: 5rem; } }
-@keyframes margin { 0% { margin-top: 25vh;
- transform: translateY(-50%); }
- 100% { margin-top: 0px;
- transform: translateY(0%); } }
-
-@keyframes h1 { 0% { font-size: 11.2vw; }
- 100% { font-size: 3rem; } }
-
-@keyframes card { 0% { }
- 50% { transform: translateY(-40px); }
- 100% { transform: translateY(-50px);
+@keyframes translateY { 0% { transform: translateY(0px);
+ opacity: 1; }
+ 100% { transform: translateY(-20px);
opacity: 0; } }
-@keyframes textestroke { 0% { -webkit-text-stroke: 1px; }
- 100% { -webkit-text-stroke: 0px; } }
+@keyframes showcatlist { 0% { width: 0;
+ box-shadow: 0px 0px 0px 0px black;
+ opacity: 1; }
+ 100% { width: 100%;
+ box-shadow: 0px -2px 0px -2px black;
+ opacity: 1; } }
-body { padding: 0 2%; }
+@keyframes showborder { 0% { width: 0;
+ background: black; }
+ 100% { width: 100%;
+ background: black; } }
-header .contact { z-index: 9999; position: fixed; top: 20px; right: 2%; transform: scale(1); transition: transform 0.5s ease; cursor: pointer; }
+@keyframes ultranslate { 0% { opacity: 0;
+ transform: translateX(0px); }
+ 100% { opacity: 1;
+ transform: translateX(10px); } }
+
+@keyframes showcard { 0% { opacity: 0; }
+ 100% { opacity: 1; } }
+
+html, body { overflow: hidden; }
+
+.blog { height: 100%; }
+
+.blog.down { height: 5rem; }
+
+header .contact { width: 90px; height: auto; z-index: 999; position: fixed; top: 0px; right: 0px; transform: scale(1); transition: transform 0.5s ease; cursor: pointer; }
header .contact:hover { transition: transform 0.5s ease; transform: scale(0.9); }
-header .contact.open { transform: scale(15); transition: transform 0.5s ease; }
+header .contact svg { cursor: pointer; animation: rotation 6s infinite linear; }
+
+header .contact h3 { color: white; transform: rotate(35deg); position: absolute; top: 35px; left: 19px; }
+
+header .contact .txt { position: fixed; right: -200px; display: none; }
+
+header .contact.open { transform: scale(15); height: auto; transition: transform 0.5s ease; }
header .contact.open h3 { color: transparent; left: 500px; transition: visibility 0.5 ease left 1s ease; }
-header .contact.open img { animation: rotation 12s infinite linear; }
+header .contact.open svg { animation: rotation 12s infinite linear; }
header .contact.open + .txt { position: fixed; top: 190px; text-align: center; line-height: 1.5rem; z-index: 9999; animation: translateright 1s ease forwards; }
header .contact.close { transform: scale(1); transition: transform 0.5s 0.1s ease; }
-header .contact.close + .txt { position: fixed; top: 190px; text-align: center; line-height: 1.5rem; z-index: 9999; animation: translateleft 1s ease forwards; }
+header .contact.close + .txt { top: 190px; text-align: center; line-height: 1.5rem; z-index: 9999; animation: translateleft 1s ease forwards; }
-header .contact.close h3 { color: white; left: 20px; transition: color 0.5s 0.5s ease, left 0.5s ease; }
+header .contact.close h3 { transition: color 0.5s 0.5s ease, left 0.5s ease; }
header .contact.close:hover { transition: transform 0.5s ease !important; transform: scale(0.9) !important; }
-header .contact img { animation: rotation 6s infinite linear; }
-
-header .contact h3 { transform: rotate(35deg); position: absolute; top: 33px; left: 20px; }
-
-header .contact + .txt { position: fixed; right: -200px; }
-
-header h2 { margin-left: 10px; }
-
-.card { width: calc(100% / 3); padding: 0 10px; line-height: 0.8 !important; }
+.card { width: calc((100% / 3)); line-height: 0.8rem !important; }
.card .card-header { margin-top: 10px; display: -ms-inline-flexbox; display: inline-flex; }
.card .card-header > div { margin-right: 5px; }
-.card .card-header > div > a { vertical-align: baseline; }
+@media screen and (max-width: 960px) { .works-mea > div:last-child { display: none; }
+ .card { width: calc((100% / 2) - 5px); } }
-.card .card-header > div > time { vertical-align: baseline; }
+@media screen and (max-width: 600px) { .works-mea > div:nth-child(2) { display: none; }
+ .card { width: 100%; } }
-body { height: 100%; }
+body .blog header { height: 100vh; width: 100%; display: -ms-inline-flexbox; display: inline-flex; -ms-flex-direction: column; flex-direction: column; -ms-flex-line-pack: distribute; align-content: space-around; }
-body .blog header { z-index: 999; position: fixed; top: 0; left: 2%; right: 2%; height: 100vh; display: -ms-inline-flexbox; display: inline-flex; -ms-flex-direction: column; flex-direction: column; }
+body .blog header > div { position: fixed; }
-body .blog header > section { height: 50vh; min-height: 250px; top: 0; left: 2%; right: 2%; }
+body .blog header > div.head { width: 100%; z-index: 999; padding-top: 70px; }
-body .blog header > section.works-mea { display: -ms-inline-flexbox; display: inline-flex; -ms-flex-wrap: wrap; flex-wrap: wrap; -ms-flex-align: end; align-items: flex-end; height: 100%; margin-bottom: 20px; }
+body .blog header > div.head h2 { margin-left: 8px; position: absolute; }
-body .blog header > section.reduc { animation: reducfont 1s ease forwards; }
+body .blog header > div.head::after { display: block; content: " "; width: 0%; height: 1px; background: black; }
-body .blog header h1 { position: fixed; top: 80px; font-size: 11.2vw; line-height: 7vw !important; transition: top 1s ease; transition: font-size 1.2s ease, color 1.2s ease , top 1.2s ease; }
+body .blog header > div.works-mea { display: -ms-inline-flexbox; display: inline-flex; -ms-flex-item-align: end; align-self: flex-end; height: calc(100% - 20px); -ms-flex-align: end; align-items: flex-end; }
-body .blog header h2 { position: fixed; top: 220px; font-size: 3.7vw; opacity: 1; transform: translateX(0); transition: opacity 1s ease 1s, transform 1s ease 1s; }
+body .blog header > div.works-mea .card:nth-child(2) { margin: 0 10px; }
-body .blog header .scroll { width: 50px; height: 15px; margin: 0 auto 10px auto; overflow: hidden; position: fixed; bottom: 0; left: 50%; transform: translate(-50%); }
+body .blog header > div.works-mea .card:not(:nth-child(2)) { margin: 0; }
-body .blog header .scroll > p:nth-child(1) { text-align: center; position: absolute; top: -20px; animation: translatep1 2s infinite linear; }
+body .blog header > div.scroll { width: 100%; bottom: 0; text-align: center; }
-body .blog header .scroll > p:nth-child(2) { text-align: center; position: absolute; top: -20px; animation: translatep1 2s infinite linear; animation-delay: 1s; }
+body .blog.down header { height: 5rem; }
-body .blog header.hide > section:first-child { height: 100px; min-height: 100px; background: white; z-index: 999; }
+body .blog.down header .head { padding: 0; transition: all 1s 0.9s ease; }
-body .blog header.hide nav { margin-top: 0; transform: translate(0); transition: margin-top 1s ease, transform 1s ease; }
+body .blog.down header .head::after { animation: showborder 2.5s 1.5s ease forwards; }
-body .blog header.hide h1 { position: relative; font-size: 3rem; top: 0px; color: black; transition: font-size 1.2s ease 0.5s, color 1.2s ease 1s, top 1.2s ease 0.5s; animation: textestroke 0.5s ease 1.2s forwards; }
+body .blog.down header .head h2 { opacity: 0; margin-left: -100px; transition: all 1s ease; left: -50%; }
-body .blog header.hide h2 { opacity: 0; transform: translateX(-10px); top: 220px; transition: opacity 0.5s ease, transform 0.5s ease; }
+body .blog.down header .card:nth-child(1) { animation: translateY 1s ease forwards; }
-body .blog header.hide .card { animation: card 1s forwards ease; }
+body .blog.down header .card:nth-child(2) { animation: translateY 1.3s ease forwards; }
-body .blog header.hide .scroll { opacity: 0; transition: opacity 0.8s ease; }
+body .blog.down header .card:nth-child(3) { animation: translateY 1.6s ease forwards; }
-body > .item header { height: 130px; position: relative; }
+body .blog.down header .scroll { opacity: 0; transition: all 1s ease; }
-body > .item header nav { position: absolute; margin-top: 65px; /* poussé de la moitié de hauteur de viewport */ transform: translateY(-50%); /* tiré de la moitié de sa propre hauteur */ }
+body #page-wrapper.item header { border-bottom: 1px solid black; position: fixed; height: 112px; top: 0; left: 10px; right: 10px; background: white; z-index: 999; }
-body > .item header h1 { color: black; }
+body #page-wrapper.item header nav { margin-top: 25px; }
-body > .item header h2 { display: none; }
+body #page-wrapper.item header h1 { color: black; }
-body > .item header .scroll { display: none; }
+body #page-wrapper.item header h2, body #page-wrapper.item header .scroll { display: none; }
-#body-wrapper #items .works-mea { display: -ms-inline-flexbox; display: inline-flex; width: 100%; height: 50vh; -ms-flex-align: end; align-items: flex-end; }
+.blog #start .cat-list { width: 100%; z-index: 999; left: -100%; }
-#body-wrapper #items .works-mea .card .card-image img { width: 100%; height: auto; }
+.blog #start .cat-list::before { display: block; content: " "; width: 0%; height: 1px; background: black; }
-#body-wrapper #items .works-mea .card .card-header { margin-top: 5px; }
+.blog #start .cat-list::after { display: block; content: " "; width: 0%; height: 1px; background: black; }
-#body-wrapper #items .works-mea .card .card-footer { margin-top: 5px; }
+.blog #start .cat-list ul { opacity: 0; display: -ms-inline-flexbox; display: inline-flex; margin: 10px 0; }
-#body-wrapper #items .cat-list { width: 100%; height: auto; border-top: 1px solid black; border-bottom: 1px solid black; padding: 10px 0; margin: 20px 0; }
+.blog #start .cat-list ul .btn-group { display: -ms-flexbox; display: flex; margin-right: 10px; }
-#body-wrapper #items .cat-list ul { display: -ms-inline-flexbox; display: inline-flex; }
+.blog #start .cat-list ul .btn-group::after { width: 5px; content: " "; }
-#body-wrapper #items .cat-list ul li { margin-right: 10px; }
+.blog #start .cat-list ul .btn-group:hover::after { content: "\002022"; }
-#body-wrapper #items .full-works { display: -ms-inline-flexbox; display: inline-flex; -ms-flex-wrap: wrap; flex-wrap: wrap; }
+.blog #start .cat-list ul .btn-group input { display: none; }
+
+.blog #start #items .full-works .card { width: calc((100vw / 3) - 6.5px); }
+
+.blog .down .cat-list { left: 0; }
+
+.blog .down .cat-list::after { animation: showborder 2.5s 1.6s ease forwards; }
+
+.blog .down .cat-list ul { animation: ultranslate 1s 2.5s ease forwards; }
body #start { width: 100%; height: 100%; }
-body #page-wrapper.blog #start { position: relative; top: 200vh; left: 0; padding-top: 110px; opacity: 0; }
+body #page-wrapper.blog #start { height: calc(100% - 5rem); position: fixed; top: 100vh; left: 0; }
-body #page-wrapper.blog #start.visible { opacity: 1; }
+body #page-wrapper.down #start { top: 5rem; }
-#items .cat-list { padding: 0 5px; }
+body #page-wrapper.down #items .card { animation: showcard 1.5s 2.5s ease forwards; }
-#items .cat-list ul li { display: -ms-flexbox; display: flex; }
+#items .full-works { width: 100%; margin-top: 30px; }
-#items .cat-list ul li::after { width: 5px; content: " "; }
+#items .card { opacity: 0; margin-bottom: 10px; min-width: 200px; }
-#items .cat-list ul li:hover::after { content: "\002022"; }
+body #page-wrapper.item { margin: 0 10px; }
-#items .full-works { width: 100%; }
+body #page-wrapper.item .contact .txt { display: none; }
-#items .card { margin-bottom: 10px; min-width: 200px; }
+body #page-wrapper.item #start { margin-top: 112px; }
-body > .item #body-wrapper.blog-listing { border-top: 1px solid black; padding-top: 20px; }
+body #page-wrapper.item #body-wrapper.blog-listing .img-card { width: calc((100% / 3) - 6.5px); margin-bottom: 10px; }
-body > .item #body-wrapper.blog-listing .img-card { width: 49%; margin-bottom: 20px; }
+body #page-wrapper.item #body-wrapper.blog-listing .header-item { position: relative; margin-bottom: 20px; padding-top: 20px; }
-body > .item #body-wrapper.blog-listing .header-item { position: relative; }
+body #page-wrapper.item #body-wrapper.blog-listing .header-item .title { display: -ms-inline-flexbox; display: inline-flex; -ms-flex-align: baseline; align-items: baseline; }
-body > .item #body-wrapper.blog-listing .header-item .title { display: -ms-inline-flexbox; display: inline-flex; -ms-flex-align: baseline; align-items: baseline; }
+body #page-wrapper.item #body-wrapper.blog-listing .header-item .title h2 { margin-right: 5px; text-transform: uppercase; }
-body > .item #body-wrapper.blog-listing .header-item .title h2 { margin-right: 5px; }
+body #page-wrapper.item #body-wrapper.blog-listing .header-item .title time { margin-right: 5px; }
-body > .item #body-wrapper.blog-listing .header-item .title time { margin-right: 5px; }
+body #page-wrapper.item #body-wrapper.blog-listing .header-item .title span { margin-right: 5px; }
-body > .item #body-wrapper.blog-listing .header-item .title span { margin-right: 5px; }
+body #page-wrapper.item #body-wrapper.blog-listing .header-item .content { width: calc((100% / 3) - 15px); margin-top: 20px; }
-body > .item #body-wrapper.blog-listing .header-item .content { width: calc(100% / 3); margin: 20px 0; }
+body #page-wrapper.item #body-wrapper.blog-listing .header-item .prev-next { width: calc(100% / 3); height: 100%; position: absolute; top: calc( 50% + 10px); transform: translateY(-50%); right: 0px; }
-body > .item #body-wrapper.blog-listing .header-item .prev-next { width: calc(100% / 3); padding-left: 20px; position: absolute; top: 0; right: 0; }
+body #page-wrapper.item #body-wrapper.blog-listing .header-item .prev-next .btn { display: block; width: auto; height: 100%; }
-body > .item #body-wrapper.blog-listing .header-item .prev-next .btn-info { position: absolute; transform: rotate(-15deg); margin-left: 40px; margin-top: 20px; }
+body #page-wrapper.item #body-wrapper.blog-listing .header-item .prev-next .btn .btn-info { position: absolute; transform: rotate(-15deg); margin-left: 40px; margin-top: 20px; }
-body > .item #body-wrapper.blog-listing .header-item .prev-next img { float: right; width: 80%; }
+body #page-wrapper.item #body-wrapper.blog-listing .header-item .prev-next .btn img { float: right; width: 80%; }
-body > .item .top { text-align: center; margin: 20px auto 20px auto; width: 50px; }
+body #page-wrapper.item .top { text-align: center; margin: 20px auto 20px auto; width: 50px; }
-body > .item .top img { width: 20px; margin-bottom: 5px; }
+body #page-wrapper.item .top img { width: 20px; margin-bottom: 5px; }
-body > .item .top p { transform: rotate(-10deg); }
+body #page-wrapper.item .top p { transform: rotate(-10deg); }
-/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGhlbWUuY3NzIiwic291cmNlcyI6WyJ0aGVtZS5zY3NzIiwidGhlbWUvX3Jlc2V0LnNjc3MiLCJ0aGVtZS9fZm9udHMuc2NzcyIsInRoZW1lL192YXJpYWJsZXMuc2NzcyIsInRoZW1lL19taXhpbnMuc2NzcyIsInRoZW1lL190eXBvZ3JhcGh5LnNjc3MiLCJ0aGVtZS9fbUN1c3RvbVNjcm9sbGJhci5zY3NzIiwidGhlbWUvX2FuaW1hdGlvbi5zY3NzIiwidGhlbWUvYWxsLnNjc3MiLCJ0aGVtZS9faGVhZGVyLnNjc3MiLCJ0aGVtZS9faG9tZS5zY3NzIiwidGhlbWUvX2Jsb2cuc2NzcyIsInRoZW1lL2l0ZW0uc2NzcyIsInRoZW1lL19mb290ZXIuc2NzcyJdLCJzb3VyY2VzQ29udGVudCI6WyJAaW1wb3J0ICd0aGVtZS9yZXNldCc7XG5AaW1wb3J0ICd0aGVtZS9mb250cyc7XG5AaW1wb3J0ICd0aGVtZS92YXJpYWJsZXMnO1xuXG5AaW1wb3J0ICd0aGVtZS9taXhpbnMnO1xuQGltcG9ydCAndGhlbWUvdHlwb2dyYXBoeSc7XG5cbkBpbXBvcnQgJ3RoZW1lL21DdXN0b21TY3JvbGxiYXInO1xuQGltcG9ydCAndGhlbWUvYW5pbWF0aW9uJztcblxuQGltcG9ydCAndGhlbWUvYWxsJztcbkBpbXBvcnQgJ3RoZW1lL2hlYWRlcic7XG5AaW1wb3J0ICd0aGVtZS9ob21lJztcbkBpbXBvcnQgJ3RoZW1lL2Jsb2cnO1xuQGltcG9ydCAndGhlbWUvaXRlbSc7XG5AaW1wb3J0ICd0aGVtZS9mb290ZXInO1xuIiwiKntcbiAgLXdlYmtpdC1ib3gtc2l6aW5nIDogYm9yZGVyLWJveDtcbiAgICAgICAgICBib3gtc2l6aW5nIDogYm9yZGVyLWJveDtcbn1cbi8qISBub3JtYWxpemUuY3NzIHY4LjAuMSB8IE1JVCBMaWNlbnNlIHwgZ2l0aHViLmNvbS9uZWNvbGFzL25vcm1hbGl6ZS5jc3MgKi9cblxuLyogRG9jdW1lbnRcbiAgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09ICovXG5cbi8qKlxuICogMS4gQ29ycmVjdCB0aGUgbGluZSBoZWlnaHQgaW4gYWxsIGJyb3dzZXJzLlxuICogMi4gUHJldmVudCBhZGp1c3RtZW50cyBvZiBmb250IHNpemUgYWZ0ZXIgb3JpZW50YXRpb24gY2hhbmdlcyBpbiBpT1MuXG4gKi9cblxuaHRtbCB7XG4gIG1hcmdpbjogMDtcbiAgcGFkZGluZzogMDtcbiAgZm9udC1zaXplOiAxOHB4O1xuICBsaW5lLWhlaWdodDogMS4xNTsgLyogMSAqL1xuICAtd2Via2l0LXRleHQtc2l6ZS1hZGp1c3Q6IDEwMCU7IC8qIDIgKi9cbn1cblxuLyogU2VjdGlvbnNcbiAgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09ICovXG5cbi8qKlxuICogUmVtb3ZlIHRoZSBtYXJnaW4gaW4gYWxsIGJyb3dzZXJzLlxuICovXG5cbmJvZHkge1xuICBtYXJnaW46IDA7XG4gIHBhZGRpbmc6IDA7XG59XG5cbi8qKlxuICogUmVuZGVyIHRoZSBgbWFpbmAgZWxlbWVudCBjb25zaXN0ZW50bHkgaW4gSUUuXG4gKi9cblxubWFpbiB7XG4gIGRpc3BsYXk6IGJsb2NrO1xufVxuXG4vKipcbiAqIENvcnJlY3QgdGhlIGZvbnQgc2l6ZSBhbmQgbWFyZ2luIG9uIGBoMWAgZWxlbWVudHMgd2l0aGluIGBzZWN0aW9uYCBhbmRcbiAqIGBhcnRpY2xlYCBjb250ZXh0cyBpbiBDaHJvbWUsIEZpcmVmb3gsIGFuZCBTYWZhcmkuXG4gKi9cblxuaDEge1xuICBmb250LXNpemU6IDJlbTtcbiAgbWFyZ2luOjA7XG59XG5cbmgxLGgyLGgzLGg0LGg1LHAsZW0sc3Ryb25nLHVsLGxpLGF7XG4gIG1hcmdpbjowO1xuICBwYWRkaW5nOjA7XG59XG4vKiBHcm91cGluZyBjb250ZW50XG4gICA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSAqL1xuXG4vKipcbiAqIDEuIEFkZCB0aGUgY29ycmVjdCBib3ggc2l6aW5nIGluIEZpcmVmb3guXG4gKiAyLiBTaG93IHRoZSBvdmVyZmxvdyBpbiBFZGdlIGFuZCBJRS5cbiAqL1xuXG5ociB7XG4gIC13ZWJraXQtYm94LXNpemluZzogY29udGVudC1ib3g7XG4gICAgICAgICAgYm94LXNpemluZzogY29udGVudC1ib3g7IC8qIDEgKi9cbiAgaGVpZ2h0OiAwOyAvKiAxICovXG4gIG92ZXJmbG93OiB2aXNpYmxlOyAvKiAyICovXG59XG5cbi8qKlxuICogMS4gQ29ycmVjdCB0aGUgaW5oZXJpdGFuY2UgYW5kIHNjYWxpbmcgb2YgZm9udCBzaXplIGluIGFsbCBicm93c2Vycy5cbiAqIDIuIENvcnJlY3QgdGhlIG9kZCBgZW1gIGZvbnQgc2l6aW5nIGluIGFsbCBicm93c2Vycy5cbiAqL1xuXG5wcmUge1xuICBmb250LWZhbWlseTogbW9ub3NwYWNlLCBtb25vc3BhY2U7IC8qIDEgKi9cbiAgZm9udC1zaXplOiAxZW07IC8qIDIgKi9cbn1cblxuLyogVGV4dC1sZXZlbCBzZW1hbnRpY3NcbiAgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09ICovXG5cbi8qKlxuICogUmVtb3ZlIHRoZSBncmF5IGJhY2tncm91bmQgb24gYWN0aXZlIGxpbmtzIGluIElFIDEwLlxuICovXG51bHtcbiAgbGlzdC1zdHlsZTogbm9uZTtcbn1cbnVsLGxpe1xuICBtYXJnaW46IDA7XG4gIHBhZGRpbmc6IDA7XG59XG5cbmEge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDtcbiAgY29sb3I6IGJsYWNrO1xuICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG59XG5cbi8qKlxuICogMS4gUmVtb3ZlIHRoZSBib3R0b20gYm9yZGVyIGluIENocm9tZSA1Ny1cbiAqIDIuIEFkZCB0aGUgY29ycmVjdCB0ZXh0IGRlY29yYXRpb24gaW4gQ2hyb21lLCBFZGdlLCBJRSwgT3BlcmEsIGFuZCBTYWZhcmkuXG4gKi9cblxuYWJiclt0aXRsZV0ge1xuICBib3JkZXItYm90dG9tOiBub25lOyAvKiAxICovXG4gIHRleHQtZGVjb3JhdGlvbjogdW5kZXJsaW5lOyAvKiAyICovXG4gIC13ZWJraXQtdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmUgZG90dGVkO1xuICAgICAgICAgIHRleHQtZGVjb3JhdGlvbjogdW5kZXJsaW5lIGRvdHRlZDsgLyogMiAqL1xufVxuXG4vKipcbiAqIEFkZCB0aGUgY29ycmVjdCBmb250IHdlaWdodCBpbiBDaHJvbWUsIEVkZ2UsIGFuZCBTYWZhcmkuXG4gKi9cblxuYixcbnN0cm9uZyB7XG4gIGZvbnQtd2VpZ2h0OiBib2xkZXI7XG59XG5cbi8qKlxuICogMS4gQ29ycmVjdCB0aGUgaW5oZXJpdGFuY2UgYW5kIHNjYWxpbmcgb2YgZm9udCBzaXplIGluIGFsbCBicm93c2Vycy5cbiAqIDIuIENvcnJlY3QgdGhlIG9kZCBgZW1gIGZvbnQgc2l6aW5nIGluIGFsbCBicm93c2Vycy5cbiAqL1xuXG5jb2RlLFxua2JkLFxuc2FtcCB7XG4gIGZvbnQtZmFtaWx5OiBtb25vc3BhY2UsIG1vbm9zcGFjZTsgLyogMSAqL1xuICBmb250LXNpemU6IDFlbTsgLyogMiAqL1xufVxuXG4vKipcbiAqIEFkZCB0aGUgY29ycmVjdCBmb250IHNpemUgaW4gYWxsIGJyb3dzZXJzLlxuICovXG5cbnNtYWxsIHtcbiAgZm9udC1zaXplOiA4MCU7XG59XG5cbi8qKlxuICogUHJldmVudCBgc3ViYCBhbmQgYHN1cGAgZWxlbWVudHMgZnJvbSBhZmZlY3RpbmcgdGhlIGxpbmUgaGVpZ2h0IGluXG4gKiBhbGwgYnJvd3NlcnMuXG4gKi9cblxuc3ViLFxuc3VwIHtcbiAgZm9udC1zaXplOiA3NSU7XG4gIGxpbmUtaGVpZ2h0OiAwO1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIHZlcnRpY2FsLWFsaWduOiBiYXNlbGluZTtcbn1cblxuc3ViIHtcbiAgYm90dG9tOiAtMC4yNWVtO1xufVxuXG5zdXAge1xuICB0b3A6IC0wLjVlbTtcbn1cblxuLyogRW1iZWRkZWQgY29udGVudFxuICAgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0gKi9cblxuLyoqXG4gKiBSZW1vdmUgdGhlIGJvcmRlciBvbiBpbWFnZXMgaW5zaWRlIGxpbmtzIGluIElFIDEwLlxuICovXG5cbmltZyB7XG4gIGJvcmRlci1zdHlsZTogbm9uZTtcbiAgd2lkdGg6IDEwMCU7XG4gIGhlaWdodDogMTAwJTtcbiAgdmVydGljYWwtYWxpZ246IGJvdHRvbTtcbn1cblxuLyogRm9ybXNcbiAgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09ICovXG5cbi8qKlxuICogMS4gQ2hhbmdlIHRoZSBmb250IHN0eWxlcyBpbiBhbGwgYnJvd3NlcnMuXG4gKiAyLiBSZW1vdmUgdGhlIG1hcmdpbiBpbiBGaXJlZm94IGFuZCBTYWZhcmkuXG4gKi9cblxuYnV0dG9uLFxuaW5wdXQsXG5vcHRncm91cCxcbnNlbGVjdCxcbnRleHRhcmVhIHtcbiAgZm9udC1mYW1pbHk6IGluaGVyaXQ7IC8qIDEgKi9cbiAgZm9udC1zaXplOiAxMDAlOyAvKiAxICovXG4gIGxpbmUtaGVpZ2h0OiAxLjE1OyAvKiAxICovXG4gIG1hcmdpbjogMDsgLyogMiAqL1xufVxuXG4vKipcbiAqIFNob3cgdGhlIG92ZXJmbG93IGluIElFLlxuICogMS4gU2hvdyB0aGUgb3ZlcmZsb3cgaW4gRWRnZS5cbiAqL1xuXG5idXR0b24sXG5pbnB1dCB7IC8qIDEgKi9cbiAgb3ZlcmZsb3c6IHZpc2libGU7XG59XG5cbi8qKlxuICogUmVtb3ZlIHRoZSBpbmhlcml0YW5jZSBvZiB0ZXh0IHRyYW5zZm9ybSBpbiBFZGdlLCBGaXJlZm94LCBhbmQgSUUuXG4gKiAxLiBSZW1vdmUgdGhlIGluaGVyaXRhbmNlIG9mIHRleHQgdHJhbnNmb3JtIGluIEZpcmVmb3guXG4gKi9cblxuYnV0dG9uLFxuc2VsZWN0IHsgLyogMSAqL1xuICB0ZXh0LXRyYW5zZm9ybTogbm9uZTtcbn1cblxuLyoqXG4gKiBDb3JyZWN0IHRoZSBpbmFiaWxpdHkgdG8gc3R5bGUgY2xpY2thYmxlIHR5cGVzIGluIGlPUyBhbmQgU2FmYXJpLlxuICovXG5cbmJ1dHRvbixcblt0eXBlPVwiYnV0dG9uXCJdLFxuW3R5cGU9XCJyZXNldFwiXSxcblt0eXBlPVwic3VibWl0XCJdIHtcbiAgLXdlYmtpdC1hcHBlYXJhbmNlOiBidXR0b247XG59XG5cbi8qKlxuICogUmVtb3ZlIHRoZSBpbm5lciBib3JkZXIgYW5kIHBhZGRpbmcgaW4gRmlyZWZveC5cbiAqL1xuXG5idXR0b246Oi1tb3otZm9jdXMtaW5uZXIsXG5bdHlwZT1cImJ1dHRvblwiXTo6LW1vei1mb2N1cy1pbm5lcixcblt0eXBlPVwicmVzZXRcIl06Oi1tb3otZm9jdXMtaW5uZXIsXG5bdHlwZT1cInN1Ym1pdFwiXTo6LW1vei1mb2N1cy1pbm5lciB7XG4gIGJvcmRlci1zdHlsZTogbm9uZTtcbiAgcGFkZGluZzogMDtcbn1cblxuLyoqXG4gKiBSZXN0b3JlIHRoZSBmb2N1cyBzdHlsZXMgdW5zZXQgYnkgdGhlIHByZXZpb3VzIHJ1bGUuXG4gKi9cblxuYnV0dG9uOi1tb3otZm9jdXNyaW5nLFxuW3R5cGU9XCJidXR0b25cIl06LW1vei1mb2N1c3JpbmcsXG5bdHlwZT1cInJlc2V0XCJdOi1tb3otZm9jdXNyaW5nLFxuW3R5cGU9XCJzdWJtaXRcIl06LW1vei1mb2N1c3Jpbmcge1xuICBvdXRsaW5lOiAxcHggZG90dGVkIEJ1dHRvblRleHQ7XG59XG5cbi8qKlxuICogQ29ycmVjdCB0aGUgcGFkZGluZyBpbiBGaXJlZm94LlxuICovXG5cbmZpZWxkc2V0IHtcbiAgcGFkZGluZzogMC4zNWVtIDAuNzVlbSAwLjYyNWVtO1xufVxuXG4vKipcbiAqIDEuIENvcnJlY3QgdGhlIHRleHQgd3JhcHBpbmcgaW4gRWRnZSBhbmQgSUUuXG4gKiAyLiBDb3JyZWN0IHRoZSBjb2xvciBpbmhlcml0YW5jZSBmcm9tIGBmaWVsZHNldGAgZWxlbWVudHMgaW4gSUUuXG4gKiAzLiBSZW1vdmUgdGhlIHBhZGRpbmcgc28gZGV2ZWxvcGVycyBhcmUgbm90IGNhdWdodCBvdXQgd2hlbiB0aGV5IHplcm8gb3V0XG4gKiAgICBgZmllbGRzZXRgIGVsZW1lbnRzIGluIGFsbCBicm93c2Vycy5cbiAqL1xuXG5sZWdlbmQge1xuICAtd2Via2l0LWJveC1zaXppbmc6IGJvcmRlci1ib3g7XG4gICAgICAgICAgYm94LXNpemluZzogYm9yZGVyLWJveDsgLyogMSAqL1xuICBjb2xvcjogaW5oZXJpdDsgLyogMiAqL1xuICBkaXNwbGF5OiB0YWJsZTsgLyogMSAqL1xuICBtYXgtd2lkdGg6IDEwMCU7IC8qIDEgKi9cbiAgcGFkZGluZzogMDsgLyogMyAqL1xuICB3aGl0ZS1zcGFjZTogbm9ybWFsOyAvKiAxICovXG59XG5cbi8qKlxuICogQWRkIHRoZSBjb3JyZWN0IHZlcnRpY2FsIGFsaWdubWVudCBpbiBDaHJvbWUsIEZpcmVmb3gsIGFuZCBPcGVyYS5cbiAqL1xuXG5wcm9ncmVzcyB7XG4gIHZlcnRpY2FsLWFsaWduOiBiYXNlbGluZTtcbn1cblxuLyoqXG4gKiBSZW1vdmUgdGhlIGRlZmF1bHQgdmVydGljYWwgc2Nyb2xsYmFyIGluIElFIDEwKy5cbiAqL1xuXG50ZXh0YXJlYSB7XG4gIG92ZXJmbG93OiBhdXRvO1xufVxuXG4vKipcbiAqIDEuIEFkZCB0aGUgY29ycmVjdCBib3ggc2l6aW5nIGluIElFIDEwLlxuICogMi4gUmVtb3ZlIHRoZSBwYWRkaW5nIGluIElFIDEwLlxuICovXG5cblt0eXBlPVwiY2hlY2tib3hcIl0sXG5bdHlwZT1cInJhZGlvXCJdIHtcbiAgLXdlYmtpdC1ib3gtc2l6aW5nOiBib3JkZXItYm94O1xuICAgICAgICAgIGJveC1zaXppbmc6IGJvcmRlci1ib3g7IC8qIDEgKi9cbiAgcGFkZGluZzogMDsgLyogMiAqL1xufVxuXG4vKipcbiAqIENvcnJlY3QgdGhlIGN1cnNvciBzdHlsZSBvZiBpbmNyZW1lbnQgYW5kIGRlY3JlbWVudCBidXR0b25zIGluIENocm9tZS5cbiAqL1xuXG5bdHlwZT1cIm51bWJlclwiXTo6LXdlYmtpdC1pbm5lci1zcGluLWJ1dHRvbixcblt0eXBlPVwibnVtYmVyXCJdOjotd2Via2l0LW91dGVyLXNwaW4tYnV0dG9uIHtcbiAgaGVpZ2h0OiBhdXRvO1xufVxuXG4vKipcbiAqIDEuIENvcnJlY3QgdGhlIG9kZCBhcHBlYXJhbmNlIGluIENocm9tZSBhbmQgU2FmYXJpLlxuICogMi4gQ29ycmVjdCB0aGUgb3V0bGluZSBzdHlsZSBpbiBTYWZhcmkuXG4gKi9cblxuW3R5cGU9XCJzZWFyY2hcIl0ge1xuICAtd2Via2l0LWFwcGVhcmFuY2U6IHRleHRmaWVsZDsgLyogMSAqL1xuICBvdXRsaW5lLW9mZnNldDogLTJweDsgLyogMiAqL1xufVxuXG4vKipcbiAqIFJlbW92ZSB0aGUgaW5uZXIgcGFkZGluZyBpbiBDaHJvbWUgYW5kIFNhZmFyaSBvbiBtYWNPUy5cbiAqL1xuXG5bdHlwZT1cInNlYXJjaFwiXTo6LXdlYmtpdC1zZWFyY2gtZGVjb3JhdGlvbiB7XG4gIC13ZWJraXQtYXBwZWFyYW5jZTogbm9uZTtcbn1cblxuLyoqXG4gKiAxLiBDb3JyZWN0IHRoZSBpbmFiaWxpdHkgdG8gc3R5bGUgY2xpY2thYmxlIHR5cGVzIGluIGlPUyBhbmQgU2FmYXJpLlxuICogMi4gQ2hhbmdlIGZvbnQgcHJvcGVydGllcyB0byBgaW5oZXJpdGAgaW4gU2FmYXJpLlxuICovXG5cbjo6LXdlYmtpdC1maWxlLXVwbG9hZC1idXR0b24ge1xuICAtd2Via2l0LWFwcGVhcmFuY2U6IGJ1dHRvbjsgLyogMSAqL1xuICBmb250OiBpbmhlcml0OyAvKiAyICovXG59XG5cbi8qIEludGVyYWN0aXZlXG4gICA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSAqL1xuXG4vKlxuICogQWRkIHRoZSBjb3JyZWN0IGRpc3BsYXkgaW4gRWRnZSwgSUUgMTArLCBhbmQgRmlyZWZveC5cbiAqL1xuXG5kZXRhaWxzIHtcbiAgZGlzcGxheTogYmxvY2s7XG59XG5cbi8qXG4gKiBBZGQgdGhlIGNvcnJlY3QgZGlzcGxheSBpbiBhbGwgYnJvd3NlcnMuXG4gKi9cblxuc3VtbWFyeSB7XG4gIGRpc3BsYXk6IGxpc3QtaXRlbTtcbn1cblxuLyogTWlzY1xuICAgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0gKi9cblxuLyoqXG4gKiBBZGQgdGhlIGNvcnJlY3QgZGlzcGxheSBpbiBJRSAxMCsuXG4gKi9cblxudGVtcGxhdGUge1xuICBkaXNwbGF5OiBub25lO1xufVxuXG4vKipcbiAqIEFkZCB0aGUgY29ycmVjdCBkaXNwbGF5IGluIElFIDEwLlxuICovXG5cbltoaWRkZW5dIHtcbiAgZGlzcGxheTogbm9uZTtcbn1cbiIsIi8vIEZvbnQgRmFtaWx5XG4kZm9udC1mYW1pbHktZGVmYXVsdDogIFx0XHRcdFwiUmFsZXdheVwiLCBcIkhlbHZldGljYVwiLCBcIlRhaG9tYVwiLCBcIkdlbmV2YVwiLCBcIkFyaWFsXCIsIHNhbnMtc2VyaWY7XG4kZm9udC1mYW1pbHktaGVhZGVyOiAgXHRcdFx0XCJNb250c2VycmF0XCIsIFwiSGVsdmV0aWNhXCIsIFwiVGFob21hXCIsIFwiR2VuZXZhXCIsIFwiQXJpYWxcIiwgc2Fucy1zZXJpZjtcbiRmb250LWZhbWlseS1tb25vOiBcdFx0XHRcdFwiSW5jb25zb2xhdGFcIiwgbW9ub3NwYWNlO1xuJGZvbnQtZmFtaWx5LXNlcmlmOlx0XHRcdFx0XCJHZW9yZ2lhXCIsIFwiVGltZXNcIiwgXCJUaW1lcyBOZXcgUm9tYW5cIiwgc2VyaWY7XG4vLyBAZm9udC1mYWNlXG5cbiR0eXBvOiBcIlNlY3VlbGFcIjtcbiRjYXRlZ29yeTogXCJzYW5zLXNlcmlmXCI7XG4vLyBzZWN1ZWxhIHJlZ3VsYXJcblxuJGZpbGU6IFwiU2VjdWVsYS1SZWd1bGFyXCI7XG4kUmVndWxhcjogXCJSZWd1bGFyXCI7XG5AbWl4aW4gZm9udC1mYWNlKCRSZWd1bGFyLCAkZmlsZSwgJHR5cG8sICRjYXRlZ29yeTpcIlwiKSB7XG4gICAgJGZpbGVwYXRoOiBcIi4uL2ZvbnRzL1wiICsgJHR5cG8gKyBcIi9cIiArICRmaWxlO1xuICAgIEBmb250LWZhY2Uge1xuICAgICAgICBmb250LWZhbWlseTogXCIjeyRSZWd1bGFyfVwiO1xuICAgICAgICBzcmM6IHVybCgkZmlsZXBhdGggKyBcIi5lb3RcIik7XG4gICAgICAgIHNyYzogdXJsKCRmaWxlcGF0aCArIFwiLmVvdD8jaWVmaXhcIikgZm9ybWF0KCdlbWJlZGRlZC1vcGVudHlwZScpLFxuICAgICAgICAgICAgICB1cmwoJGZpbGVwYXRoICsgXCIud29mZlwiKSBmb3JtYXQoJ3dvZmYnKSxcbiAgICAgICAgICAgICAgdXJsKCRmaWxlcGF0aCArIFwiLnR0ZlwiKSAgZm9ybWF0KCd0cnVldHlwZScpLFxuICAgICAgICAgICAgICB1cmwoJGZpbGVwYXRoICsgXCIuc3ZnI1wiICsgJFJlZ3VsYXIgKyBcIlwiKSBmb3JtYXQoJ3N2ZycpO1xuICAgIH1cblxuICAgICUjeyRSZWd1bGFyfSB7XG4gICAgICAgIGZvbnQ6IHtcbiAgICAgICAgICAgIEBpZiAkY2F0ZWdvcnkgIT0gXCJcIiB7XG4gICAgICAgICAgICAgICAgZmFtaWx5OiBcIiN7JFJlZ3VsYXJ9XCIsICN7JGNhdGVnb3J5fTtcbiAgICAgICAgICAgICAgICB3ZWlnaHQ6IG5vcm1hbDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIEBlbHNlIHtcbiAgICAgICAgICAgICAgICBmYW1pbHk6IFwiI3skUmVndWxhcn1cIjtcbiAgICAgICAgICAgICAgICB3ZWlnaHQ6IG5vcm1hbDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbn1cblxuLy8gc2VjdWVsYSBtZWRpdW1cbiRmaWxlMjogXCJTZWN1ZWxhLU1lZGl1bVwiO1xuJG1lZGl1bTogXCJtZWRpdW1cIjtcbkBtaXhpbiBmb250LWZhY2UoJG1lZGl1bSwgJGZpbGUyLCAkdHlwbywgJGNhdGVnb3J5OlwiXCIpIHtcbiAgICAkZmlsZXBhdGg6IFwiLi4vZm9udHMvXCIgKyAkdHlwbyArIFwiL1wiICsgJGZpbGUyO1xuICAgIEBmb250LWZhY2Uge1xuICAgICAgICBmb250LWZhbWlseTogXCIjeyRtZWRpdW19XCI7XG4gICAgICAgIHNyYzogdXJsKCRmaWxlcGF0aCArIFwiLmVvdFwiKTtcbiAgICAgICAgc3JjOiB1cmwoJGZpbGVwYXRoICsgXCIuZW90PyNpZWZpeFwiKSBmb3JtYXQoJ2VtYmVkZGVkLW9wZW50eXBlJyksXG4gICAgICAgICAgICAgIHVybCgkZmlsZXBhdGggKyBcIi53b2ZmXCIpIGZvcm1hdCgnd29mZicpLFxuICAgICAgICAgICAgICB1cmwoJGZpbGVwYXRoICsgXCIudHRmXCIpICBmb3JtYXQoJ3RydWV0eXBlJyksXG4gICAgICAgICAgICAgIHVybCgkZmlsZXBhdGggKyBcIi5zdmcjXCIgKyAkbWVkaXVtICsgXCJcIikgZm9ybWF0KCdzdmcnKTtcbiAgICB9XG5cbiAgICAlI3skbWVkaXVtfSB7XG4gICAgICAgIGZvbnQ6IHtcbiAgICAgICAgICAgIEBpZiAkY2F0ZWdvcnkgIT0gXCJcIiB7XG4gICAgICAgICAgICAgICAgZmFtaWx5OiBcIiN7JG1lZGl1bX1cIiwgI3skY2F0ZWdvcnl9O1xuICAgICAgICAgICAgICAgIHdlaWdodDogbm9ybWFsO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgQGVsc2Uge1xuICAgICAgICAgICAgICAgIGZhbWlseTogXCIjeyRtZWRpdW19XCI7XG4gICAgICAgICAgICAgICAgd2VpZ2h0OiBub3JtYWw7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG59XG5cblxuLy8gc2VjdWVsYSBib2xkXG4kZmlsZTM6IFwiU2VjdWVsYS1Cb2xkXCI7XG4kYm9sZDogXCJib2xkXCI7XG5AbWl4aW4gZm9udC1mYWNlKCRib2xkLCAkZmlsZTMsICR0eXBvLCAkY2F0ZWdvcnk6XCJcIikge1xuICAgICRmaWxlcGF0aDogXCIuLi9mb250cy9cIiArICR0eXBvICsgXCIvXCIgKyAkZmlsZTM7XG4gICAgQGZvbnQtZmFjZSB7XG4gICAgICAgIGZvbnQtZmFtaWx5OiBcIiN7JGJvbGR9XCI7XG4gICAgICAgIHNyYzogdXJsKCRmaWxlcGF0aCArIFwiLmVvdFwiKTtcbiAgICAgICAgc3JjOiB1cmwoJGZpbGVwYXRoICsgXCIuZW90PyNpZWZpeFwiKSBmb3JtYXQoJ2VtYmVkZGVkLW9wZW50eXBlJyksXG4gICAgICAgICAgICAgIHVybCgkZmlsZXBhdGggKyBcIi53b2ZmXCIpIGZvcm1hdCgnd29mZicpLFxuICAgICAgICAgICAgICB1cmwoJGZpbGVwYXRoICsgXCIudHRmXCIpICBmb3JtYXQoJ3RydWV0eXBlJyksXG4gICAgICAgICAgICAgIHVybCgkZmlsZXBhdGggKyBcIi5zdmcjXCIgKyAkYm9sZCArIFwiXCIpIGZvcm1hdCgnc3ZnJyk7XG4gICAgfVxuXG4gICAgJSN7JGJvbGR9IHtcbiAgICAgICAgZm9udDoge1xuICAgICAgICAgICAgQGlmICRjYXRlZ29yeSAhPSBcIlwiIHtcbiAgICAgICAgICAgICAgICBmYW1pbHk6IFwiI3skYm9sZH1cIiwgI3skY2F0ZWdvcnl9O1xuICAgICAgICAgICAgICAgIHdlaWdodDogbm9ybWFsO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgQGVsc2Uge1xuICAgICAgICAgICAgICAgIGZhbWlseTogXCIjeyRib2xkfVwiO1xuICAgICAgICAgICAgICAgIHdlaWdodDogbm9ybWFsO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxufVxuXG5cbi8vIHNlY3VlbGEgaXRhbGljXG4kZmlsZTQ6IFwiU2VjdWVsYS1JdGFsaWNcIjtcbiRpdGFsaWM6IFwiaXRhbGljXCI7XG5AbWl4aW4gZm9udC1mYWNlKCRpdGFsaWMsICRmaWxlNCwgJHR5cG8sICRjYXRlZ29yeTpcIlwiKSB7XG4gICAgJGZpbGVwYXRoOiBcIi4uL2ZvbnRzL1wiICsgJHR5cG8gKyBcIi9cIiArICRmaWxlNDtcbiAgICBAZm9udC1mYWNlIHtcbiAgICAgICAgZm9udC1mYW1pbHk6IFwiI3skaXRhbGljfVwiO1xuICAgICAgICBzcmM6IHVybCgkZmlsZXBhdGggKyBcIi5lb3RcIik7XG4gICAgICAgIHNyYzogdXJsKCRmaWxlcGF0aCArIFwiLmVvdD8jaWVmaXhcIikgZm9ybWF0KCdlbWJlZGRlZC1vcGVudHlwZScpLFxuICAgICAgICAgICAgICB1cmwoJGZpbGVwYXRoICsgXCIud29mZlwiKSBmb3JtYXQoJ3dvZmYnKSxcbiAgICAgICAgICAgICAgdXJsKCRmaWxlcGF0aCArIFwiLnR0ZlwiKSAgZm9ybWF0KCd0cnVldHlwZScpLFxuICAgICAgICAgICAgICB1cmwoJGZpbGVwYXRoICsgXCIuc3ZnI1wiICsgJGl0YWxpYyArIFwiXCIpIGZvcm1hdCgnc3ZnJyk7XG4gICAgfVxuXG4gICAgJSN7JGl0YWxpY30ge1xuICAgICAgICBmb250OiB7XG4gICAgICAgICAgICBAaWYgJGNhdGVnb3J5ICE9IFwiXCIge1xuICAgICAgICAgICAgICAgIGZhbWlseTogXCIjeyRpdGFsaWN9XCIsICN7JGNhdGVnb3J5fTtcbiAgICAgICAgICAgICAgICB3ZWlnaHQ6IG5vcm1hbDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIEBlbHNlIHtcbiAgICAgICAgICAgICAgICBmYW1pbHk6IFwiI3skaXRhbGljfVwiO1xuICAgICAgICAgICAgICAgIHdlaWdodDogbm9ybWFsO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxufVxuXG5cbi8vIHNlY3VlbGEgbGlnaHRcbiRmaWxlNTogXCJTZWN1ZWxhLUxpZ2h0XCI7XG4kbGlnaHQ6IFwibGlnaHRcIjtcbkBtaXhpbiBmb250LWZhY2UoJGxpZ2h0LCAkZmlsZTUsICR0eXBvLCAkY2F0ZWdvcnk6XCJcIikge1xuICAgICRmaWxlcGF0aDogXCIuLi9mb250cy9cIiArICR0eXBvICsgXCIvXCIgKyAkZmlsZTU7XG4gICAgQGZvbnQtZmFjZSB7XG4gICAgICAgIGZvbnQtZmFtaWx5OiBcIiN7JGxpZ2h0fVwiO1xuICAgICAgICBzcmM6IHVybCgkZmlsZXBhdGggKyBcIi5lb3RcIik7XG4gICAgICAgIHNyYzogdXJsKCRmaWxlcGF0aCArIFwiLmVvdD8jaWVmaXhcIikgZm9ybWF0KCdlbWJlZGRlZC1vcGVudHlwZScpLFxuICAgICAgICAgICAgICB1cmwoJGZpbGVwYXRoICsgXCIud29mZlwiKSBmb3JtYXQoJ3dvZmYnKSxcbiAgICAgICAgICAgICAgdXJsKCRmaWxlcGF0aCArIFwiLnR0ZlwiKSAgZm9ybWF0KCd0cnVldHlwZScpLFxuICAgICAgICAgICAgICB1cmwoJGZpbGVwYXRoICsgXCIuc3ZnI1wiICsgJGxpZ2h0ICsgXCJcIikgZm9ybWF0KCdzdmcnKTtcbiAgICB9XG5cbiAgICAlI3skbGlnaHR9IHtcbiAgICAgICAgZm9udDoge1xuICAgICAgICAgICAgQGlmICRjYXRlZ29yeSAhPSBcIlwiIHtcbiAgICAgICAgICAgICAgICBmYW1pbHk6IFwiI3skbGlnaHR9XCIsICN7JGNhdGVnb3J5fTtcbiAgICAgICAgICAgICAgICB3ZWlnaHQ6IG5vcm1hbDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIEBlbHNlIHtcbiAgICAgICAgICAgICAgICBmYW1pbHk6IFwiI3skbGlnaHR9XCI7XG4gICAgICAgICAgICAgICAgd2VpZ2h0OiBub3JtYWw7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG59XG4iLCIvLyBmb250c1xuQGluY2x1ZGUgZm9udC1mYWNlKCRSZWd1bGFyLCAkZmlsZSwgJHR5cG8sICRjYXRlZ29yeSk7Ly8xXG5AaW5jbHVkZSBmb250LWZhY2UoJG1lZGl1bSwgJGZpbGUyLCAkdHlwbywgJGNhdGVnb3J5KTsvLzFcbkBpbmNsdWRlIGZvbnQtZmFjZSgkYm9sZCwgJGZpbGUzLCAkdHlwbywgJGNhdGVnb3J5KTsvLzFcbkBpbmNsdWRlIGZvbnQtZmFjZSgkaXRhbGljLCAkZmlsZTQsICR0eXBvLCAkY2F0ZWdvcnkpOy8vMVxuQGluY2x1ZGUgZm9udC1mYWNlKCRsaWdodCwgJGZpbGU1LCAkdHlwbywgJGNhdGVnb3J5KTsvLzFcblxuLy8gZm9udC1zaXplXG5cbiRmaDE6IDExcmVtO1xuJGZoMjozcmVtO1xuJGZoMi1pOiAxLjFyZW07XG4kZmgzOiAxcmVtO1xuJGZoNTowLjhyZW07XG4kZnA6IDAuOXJlbTtcbiRmczowLjhyZW07XG4kZnMtaTogMS4xcmVtO1xuLy8gaW50ZXJsaW5lXG5cbi8vaW50ZXJsZXR0cmVcbiRsZXR0ZXJJMTogMC4xcmVtO1xuXG4vLyBjb2xvclxuJGJsdWU6ICMwZjI2NWM7XG4kcmVkOiAjOGQyODE1ZmY7XG4kZ3JleTogIzk5OTk5OTtcbiIsIiIsImgxLCBoMntcbiAgZm9udC1mYW1pbHk6ICRtZWRpdW07XG4gIGZvbnQtd2VpZ2h0OiBub3JtYWw7XG59XG5cblxuaDJ7XG4gIGZvbnQtc2l6ZTogJGZoMjtcbn1cblxuaDN7XG4gIGZvbnQtZmFtaWx5OiAkbWVkaXVtO1xuICBmb250LXNpemU6ICRmaDM7XG4gIGZvbnQtd2VpZ2h0OiBub3JtYWw7XG59XG5cbmg1e1xuICBmb250LXNpemU6ICRmaDU7XG4gIGZvbnQtZmFtaWx5OiAkYm9sZDtcbiAgZm9udC13ZWlnaHQ6IG5vcm1hbDtcbn1cblxucHtcbiAgZm9udC1mYW1pbHk6ICRSZWd1bGFyO1xuICBmb250LXdlaWdodDogbm9ybWFsO1xuICBmb250LXNpemU6ICRmcDtcbn1cblxudGltZXtcbiAgZm9udC1mYW1pbHk6ICRpdGFsaWM7XG4gIGZvbnQtd2VpZ2h0OiBub3JtYWw7XG59XG5cbmhlYWRlcntcbiAgLmNvbnRhY3R7XG4gICAgaDN7XG4gICAgICB0ZXh0LXRyYW5zZm9ybTogbG93ZXJjYXNlO1xuICAgICAgY29sb3I6d2hpdGU7XG4gICAgfVxuICB9XG59XG5cbi5zY3JvbGx7XG4gIHB7XG4gICAgZm9udC1zaXplOiAkZnM7XG4gICAgZm9udC1mYW1pbHk6ICRib2xkO1xuICAgIGZvbnQtd2VpZ2h0OiBub3JtYWw7XG4gIH1cbn1cblxuLmNhdC1saXN0e1xuICAuY2F0e1xuICAgIGF7XG4gICAgICBmb250LWZhbWlseTogJGxpZ2h0O1xuICAgICAgZm9udC13ZWlnaHQ6IG5vcm1hbDtcbiAgICB9XG4gIH1cbn1cblxuYm9keXtcbiAgJj4uYmxvZ3tcbiAgICBoMXtcbiAgICAgIGZvbnQtc2l6ZTogJGZoMTtcbiAgICAgIC13ZWJraXQtdGV4dC1zdHJva2U6LjFyZW0gIzExMTtcbiAgICAgIGNvbG9yOiB0cmFuc3BhcmVudDtcbiAgICAgIGxpbmUtaGVpZ2h0OiA3cmVtO1xuICAgIH1cbiAgICAuY2FyZHtcbiAgICAgIGg1e1xuICAgICAgICB0ZXh0LXRyYW5zZm9ybTogdXBwZXJjYXNlO1xuICAgICAgfVxuICAgICAgLmNhcmQtY2F0ZWdvcnl7XG4gICAgICAgIGZvbnQtZmFtaWx5OiAkaXRhbGljO1xuICAgICAgICBmb250LXdlaWdodDogbm9ybWFsO1xuICAgICAgICBmb250LXNpemU6ICRmcztcbiAgICAgIH1cbiAgICAgIC5jYXJkLWRhdGV7XG4gICAgICAgIGZvbnQtc2l6ZTogJGZzO1xuICAgICAgfVxuICAgICAgLmNhcmQtZm9vdGVye1xuICAgICAgICBzcGFue1xuICAgICAgICAgIGZvbnQtZmFtaWx5OiAkbWVkaXVtO1xuICAgICAgICAgIGZvbnQtd2VpZ2h0OiBub3JtYWw7XG4gICAgICAgICAgZm9udC1zaXplOiAkZnM7XG4gICAgICAgICAgdGV4dC10cmFuc2Zvcm06IGNhcGl0YWxpemU7XG4gICAgICAgICAgYXtcbiAgICAgICAgICAgIGNvbG9yOiAkZ3JleTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cblxuYm9keXtcbiAgJiA+Lml0ZW17XG4gICAgaDF7XG4gICAgICBmb250LXNpemU6ICRmaDI7XG4gICAgfVxuICAgIGgye1xuICAgICAgZm9udC1zaXplOiAkZmgyLWk7XG4gICAgfVxuICAgIC5jb250ZW50LWl0ZW17XG4gICAgICAuY29udGVudC10aXRsZXtcbiAgICAgICAgJiA+IGF7XG4gICAgICAgICAgZm9udC1mYW1pbHk6ICRpdGFsaWM7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgLmNhdGVnb3J5e1xuICAgICAgICBmb250LWZhbWlseTogJGl0YWxpYztcbiAgICAgICAgZm9udC13ZWlnaHQ6IG5vcm1hbDtcbiAgICAgICAgZm9udC1zaXplOiAkZnMtaTtcbiAgICB9XG4gICAgdGltZXtcbiAgICAgIGZvbnQtc2l6ZTogJGZzO1xuICAgICAgZm9udC1zaXplOiAkZnMtaTtcbiAgICB9XG4gICAgLnRhZ3N7XG4gICAgICAgIGZvbnQtZmFtaWx5OiAkbWVkaXVtO1xuICAgICAgICBmb250LXdlaWdodDogbm9ybWFsO1xuICAgICAgICBmb250LXNpemU6IDAuOHJlbTtcbiAgICAgICAgdGV4dC10cmFuc2Zvcm06IGNhcGl0YWxpemU7XG4gICAgICAgIGF7XG4gICAgICAgICAgY29sb3I6ICRncmV5O1xuICAgICAgICB9XG4gICAgICB9XG4gICAgLnRvcHtcbiAgICAgIHB7XG4gICAgICAgIGZvbnQtZmFtaWx5OiAkYm9sZDtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cblxuLnByZXYtbmV4dHtcbiAgLnRpdGxle1xuICAgIGZvbnQtZmFtaWx5OiAkYm9sZDtcbiAgICBmb250LXNpemU6ICRmcztcbiAgfVxuICAudGFnc3tcbiAgICBmb250LWZhbWlseTogJGl0YWxpYztcbiAgICBmb250LXNpemU6ICRmcyFpbXBvcnRhbnQ7XG4gIH1cbiAgLmJsb2ctZGF0ZXtcbiAgICB0aW1le1xuICAgICAgZm9udC1mYW1pbHk6ICRpdGFsaWM7XG4gICAgICBmb250LXNpemU6ICRmcyFpbXBvcnRhbnQ7XG4gICAgfVxuICB9XG59XG5cbi50b3B7XG4gIHB7XG4gICAgZm9udC1zaXplOiAwLjhyZW07XG4gIH1cbn1cbiIsIi5tQ3VzdG9tU2Nyb2xsYmFyey1tcy10b3VjaC1hY3Rpb246cGluY2gtem9vbTt0b3VjaC1hY3Rpb246cGluY2gtem9vbX0ubUN1c3RvbVNjcm9sbGJhci5tQ1Nfbm9fc2Nyb2xsYmFyLC5tQ3VzdG9tU2Nyb2xsYmFyLm1DU190b3VjaF9hY3Rpb257LW1zLXRvdWNoLWFjdGlvbjphdXRvO3RvdWNoLWFjdGlvbjphdXRvfS5tQ3VzdG9tU2Nyb2xsQm94e3Bvc2l0aW9uOnJlbGF0aXZlO292ZXJmbG93OmhpZGRlbjtoZWlnaHQ6MTAwJTttYXgtd2lkdGg6MTAwJTtvdXRsaW5lOjA7ZGlyZWN0aW9uOmx0cn0ubUNTQl9jb250YWluZXJ7b3ZlcmZsb3c6aGlkZGVuO3dpZHRoOmF1dG87aGVpZ2h0OmF1dG99Lm1DU0JfaW5zaWRlPi5tQ1NCX2NvbnRhaW5lcnttYXJnaW4tcmlnaHQ6MzBweH0ubUNTQl9jb250YWluZXIubUNTX25vX3Njcm9sbGJhcl95Lm1DU195X2hpZGRlbnttYXJnaW4tcmlnaHQ6MH0ubUNTLWRpci1ydGw+Lm1DU0JfaW5zaWRlPi5tQ1NCX2NvbnRhaW5lcnttYXJnaW4tcmlnaHQ6MDttYXJnaW4tbGVmdDozMHB4fS5tQ1MtZGlyLXJ0bD4ubUNTQl9pbnNpZGU+Lm1DU0JfY29udGFpbmVyLm1DU19ub19zY3JvbGxiYXJfeS5tQ1NfeV9oaWRkZW57bWFyZ2luLWxlZnQ6MH0ubUNTQl9zY3JvbGxUb29sc3twb3NpdGlvbjphYnNvbHV0ZTt3aWR0aDoxNnB4O2hlaWdodDphdXRvO2xlZnQ6YXV0bzt0b3A6MDtyaWdodDowO2JvdHRvbTowO29wYWNpdHk6Ljc1O2ZpbHRlcjpcImFscGhhKG9wYWNpdHk9NzUpXCI7LW1zLWZpbHRlcjpcImFscGhhKG9wYWNpdHk9NzUpXCJ9Lm1DU0Jfb3V0c2lkZSsubUNTQl9zY3JvbGxUb29sc3tyaWdodDotMjZweH0ubUNTLWRpci1ydGw+Lm1DU0JfaW5zaWRlPi5tQ1NCX3Njcm9sbFRvb2xzLC5tQ1MtZGlyLXJ0bD4ubUNTQl9vdXRzaWRlKy5tQ1NCX3Njcm9sbFRvb2xze3JpZ2h0OmF1dG87bGVmdDowfS5tQ1MtZGlyLXJ0bD4ubUNTQl9vdXRzaWRlKy5tQ1NCX3Njcm9sbFRvb2xze2xlZnQ6LTI2cHh9Lm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcntwb3NpdGlvbjphYnNvbHV0ZTt0b3A6MDtsZWZ0OjA7Ym90dG9tOjA7cmlnaHQ6MDtoZWlnaHQ6YXV0b30ubUNTQl9zY3JvbGxUb29scyBhKy5tQ1NCX2RyYWdnZXJDb250YWluZXJ7bWFyZ2luOjIwcHggMH0ubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHt3aWR0aDoycHg7aGVpZ2h0OjEwMCU7bWFyZ2luOjAgYXV0bzstd2Via2l0LWJvcmRlci1yYWRpdXM6MTZweDstbW96LWJvcmRlci1yYWRpdXM6MTZweDtib3JkZXItcmFkaXVzOjE2cHh9Lm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcntjdXJzb3I6cG9pbnRlcjt3aWR0aDoxMDAlO2hlaWdodDozMHB4O3otaW5kZXg6MX0ubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye3Bvc2l0aW9uOnJlbGF0aXZlO3dpZHRoOjRweDtoZWlnaHQ6MTAwJTttYXJnaW46MCBhdXRvOy13ZWJraXQtYm9yZGVyLXJhZGl1czoxNnB4Oy1tb3otYm9yZGVyLXJhZGl1czoxNnB4O2JvcmRlci1yYWRpdXM6MTZweDt0ZXh0LWFsaWduOmNlbnRlcn0ubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnX2V4cGFuZGVkIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyQ29udGFpbmVyOmhvdmVyIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7d2lkdGg6MTJweH0ubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnX2V4cGFuZGVkKy5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyQ29udGFpbmVyOmhvdmVyIC5tQ1NCX2RyYWdnZXJSYWlse3dpZHRoOjhweH0ubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3duLC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwe2Rpc3BsYXk6YmxvY2s7cG9zaXRpb246YWJzb2x1dGU7aGVpZ2h0OjIwcHg7d2lkdGg6MTAwJTtvdmVyZmxvdzpoaWRkZW47bWFyZ2luOjAgYXV0bztjdXJzb3I6cG9pbnRlcn0ubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3due2JvdHRvbTowfS5tQ1NCX2hvcml6b250YWwubUNTQl9pbnNpZGU+Lm1DU0JfY29udGFpbmVye21hcmdpbi1yaWdodDowO21hcmdpbi1ib3R0b206MzBweH0ubUNTQl9ob3Jpem9udGFsLm1DU0Jfb3V0c2lkZT4ubUNTQl9jb250YWluZXJ7bWluLWhlaWdodDoxMDAlfS5tQ1NCX2hvcml6b250YWw+Lm1DU0JfY29udGFpbmVyLm1DU19ub19zY3JvbGxiYXJfeC5tQ1NfeF9oaWRkZW57bWFyZ2luLWJvdHRvbTowfS5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbHt3aWR0aDphdXRvO2hlaWdodDoxNnB4O3RvcDphdXRvO3JpZ2h0OjA7Ym90dG9tOjA7bGVmdDowfS5tQ3VzdG9tU2Nyb2xsQm94Ky5tQ1NCX3Njcm9sbFRvb2xzKy5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCwubUN1c3RvbVNjcm9sbEJveCsubUNTQl9zY3JvbGxUb29scy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWx7Ym90dG9tOi0yNnB4fS5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCBhKy5tQ1NCX2RyYWdnZXJDb250YWluZXJ7bWFyZ2luOjAgMjBweH0ubUNTQl9zY3JvbGxUb29scy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlclJhaWx7d2lkdGg6MTAwJTtoZWlnaHQ6MnB4O21hcmdpbjo3cHggMH0ubUNTQl9zY3JvbGxUb29scy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlcnt3aWR0aDozMHB4O2hlaWdodDoxMDAlO2xlZnQ6MH0ubUNTQl9zY3JvbGxUb29scy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2Jhcnt3aWR0aDoxMDAlO2hlaWdodDo0cHg7bWFyZ2luOjZweCBhdXRvfS5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZ19leHBhbmRlZCAubUNTQl9kcmFnZ2VyX2JhciwubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyQ29udGFpbmVyOmhvdmVyIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7aGVpZ2h0OjEycHg7bWFyZ2luOjJweCBhdXRvfS5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZ19leHBhbmRlZCsubUNTQl9kcmFnZ2VyUmFpbCwubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyQ29udGFpbmVyOmhvdmVyIC5tQ1NCX2RyYWdnZXJSYWlse2hlaWdodDo4cHg7bWFyZ2luOjRweCAwfS5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9idXR0b25MZWZ0LC5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9idXR0b25SaWdodHtkaXNwbGF5OmJsb2NrO3Bvc2l0aW9uOmFic29sdXRlO3dpZHRoOjIwcHg7aGVpZ2h0OjEwMCU7b3ZlcmZsb3c6aGlkZGVuO21hcmdpbjowIGF1dG87Y3Vyc29yOnBvaW50ZXJ9Lm1DU0Jfc2Nyb2xsVG9vbHMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2J1dHRvbkxlZnR7bGVmdDowfS5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9idXR0b25SaWdodHtyaWdodDowfS5tQ1NCX2NvbnRhaW5lcl93cmFwcGVye3Bvc2l0aW9uOmFic29sdXRlO2hlaWdodDphdXRvO3dpZHRoOmF1dG87b3ZlcmZsb3c6aGlkZGVuO3RvcDowO2xlZnQ6MDtyaWdodDowO2JvdHRvbTowO21hcmdpbi1yaWdodDozMHB4O21hcmdpbi1ib3R0b206MzBweH0ubUNTQl9jb250YWluZXJfd3JhcHBlcj4ubUNTQl9jb250YWluZXJ7cGFkZGluZy1yaWdodDozMHB4O3BhZGRpbmctYm90dG9tOjMwcHg7LXdlYmtpdC1ib3gtc2l6aW5nOmJvcmRlci1ib3g7LW1vei1ib3gtc2l6aW5nOmJvcmRlci1ib3g7Ym94LXNpemluZzpib3JkZXItYm94fS5tQ1NCX3ZlcnRpY2FsX2hvcml6b250YWw+Lm1DU0Jfc2Nyb2xsVG9vbHMubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbHtib3R0b206MjBweH0ubUNTQl92ZXJ0aWNhbF9ob3Jpem9udGFsPi5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbHtyaWdodDoyMHB4fS5tQ1NCX2NvbnRhaW5lcl93cmFwcGVyLm1DU19ub19zY3JvbGxiYXJfeC5tQ1NfeF9oaWRkZW4rLm1DU0Jfc2Nyb2xsVG9vbHMubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbHtib3R0b206MH0ubUNTLWRpci1ydGw+Lm1DdXN0b21TY3JvbGxCb3gubUNTQl92ZXJ0aWNhbF9ob3Jpem9udGFsLm1DU0JfaW5zaWRlPi5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCwubUNTQl9jb250YWluZXJfd3JhcHBlci5tQ1Nfbm9fc2Nyb2xsYmFyX3kubUNTX3lfaGlkZGVuKy5tQ1NCX3Njcm9sbFRvb2xzfi5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbHtyaWdodDowfS5tQ1MtZGlyLXJ0bD4ubUN1c3RvbVNjcm9sbEJveC5tQ1NCX3ZlcnRpY2FsX2hvcml6b250YWwubUNTQl9pbnNpZGU+Lm1DU0Jfc2Nyb2xsVG9vbHMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFse2xlZnQ6MjBweH0ubUNTLWRpci1ydGw+Lm1DdXN0b21TY3JvbGxCb3gubUNTQl92ZXJ0aWNhbF9ob3Jpem9udGFsLm1DU0JfaW5zaWRlPi5tQ1NCX2NvbnRhaW5lcl93cmFwcGVyLm1DU19ub19zY3JvbGxiYXJfeS5tQ1NfeV9oaWRkZW4rLm1DU0Jfc2Nyb2xsVG9vbHN+Lm1DU0Jfc2Nyb2xsVG9vbHMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFse2xlZnQ6MH0ubUNTLWRpci1ydGw+Lm1DU0JfaW5zaWRlPi5tQ1NCX2NvbnRhaW5lcl93cmFwcGVye21hcmdpbi1yaWdodDowO21hcmdpbi1sZWZ0OjMwcHh9Lm1DU0JfY29udGFpbmVyX3dyYXBwZXIubUNTX25vX3Njcm9sbGJhcl95Lm1DU195X2hpZGRlbj4ubUNTQl9jb250YWluZXJ7cGFkZGluZy1yaWdodDowfS5tQ1NCX2NvbnRhaW5lcl93cmFwcGVyLm1DU19ub19zY3JvbGxiYXJfeC5tQ1NfeF9oaWRkZW4+Lm1DU0JfY29udGFpbmVye3BhZGRpbmctYm90dG9tOjB9Lm1DdXN0b21TY3JvbGxCb3gubUNTQl92ZXJ0aWNhbF9ob3Jpem9udGFsLm1DU0JfaW5zaWRlPi5tQ1NCX2NvbnRhaW5lcl93cmFwcGVyLm1DU19ub19zY3JvbGxiYXJfeS5tQ1NfeV9oaWRkZW57bWFyZ2luLXJpZ2h0OjA7bWFyZ2luLWxlZnQ6MH0ubUN1c3RvbVNjcm9sbEJveC5tQ1NCX3ZlcnRpY2FsX2hvcml6b250YWwubUNTQl9pbnNpZGU+Lm1DU0JfY29udGFpbmVyX3dyYXBwZXIubUNTX25vX3Njcm9sbGJhcl94Lm1DU194X2hpZGRlbnttYXJnaW4tYm90dG9tOjB9Lm1DU0Jfc2Nyb2xsVG9vbHMsLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93biwubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0LC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0LC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwLC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7LXdlYmtpdC10cmFuc2l0aW9uOm9wYWNpdHkgLjJzIGVhc2UtaW4tb3V0LGJhY2tncm91bmQtY29sb3IgLjJzIGVhc2UtaW4tb3V0Oy1tb3otdHJhbnNpdGlvbjpvcGFjaXR5IC4ycyBlYXNlLWluLW91dCxiYWNrZ3JvdW5kLWNvbG9yIC4ycyBlYXNlLWluLW91dDstby10cmFuc2l0aW9uOm9wYWNpdHkgLjJzIGVhc2UtaW4tb3V0LGJhY2tncm91bmQtY29sb3IgLjJzIGVhc2UtaW4tb3V0O3RyYW5zaXRpb246b3BhY2l0eSAuMnMgZWFzZS1pbi1vdXQsYmFja2dyb3VuZC1jb2xvciAuMnMgZWFzZS1pbi1vdXR9Lm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlclJhaWwsLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyX2Jhcnstd2Via2l0LXRyYW5zaXRpb246d2lkdGggLjJzIGVhc2Utb3V0IC4ycyxoZWlnaHQgLjJzIGVhc2Utb3V0IC4ycyxtYXJnaW4tbGVmdCAuMnMgZWFzZS1vdXQgLjJzLG1hcmdpbi1yaWdodCAuMnMgZWFzZS1vdXQgLjJzLG1hcmdpbi10b3AgLjJzIGVhc2Utb3V0IC4ycyxtYXJnaW4tYm90dG9tIC4ycyBlYXNlLW91dCAuMnMsb3BhY2l0eSAuMnMgZWFzZS1pbi1vdXQsYmFja2dyb3VuZC1jb2xvciAuMnMgZWFzZS1pbi1vdXQ7LW1vei10cmFuc2l0aW9uOndpZHRoIC4ycyBlYXNlLW91dCAuMnMsaGVpZ2h0IC4ycyBlYXNlLW91dCAuMnMsbWFyZ2luLWxlZnQgLjJzIGVhc2Utb3V0IC4ycyxtYXJnaW4tcmlnaHQgLjJzIGVhc2Utb3V0IC4ycyxtYXJnaW4tdG9wIC4ycyBlYXNlLW91dCAuMnMsbWFyZ2luLWJvdHRvbSAuMnMgZWFzZS1vdXQgLjJzLG9wYWNpdHkgLjJzIGVhc2UtaW4tb3V0LGJhY2tncm91bmQtY29sb3IgLjJzIGVhc2UtaW4tb3V0Oy1vLXRyYW5zaXRpb246d2lkdGggLjJzIGVhc2Utb3V0IC4ycyxoZWlnaHQgLjJzIGVhc2Utb3V0IC4ycyxtYXJnaW4tbGVmdCAuMnMgZWFzZS1vdXQgLjJzLG1hcmdpbi1yaWdodCAuMnMgZWFzZS1vdXQgLjJzLG1hcmdpbi10b3AgLjJzIGVhc2Utb3V0IC4ycyxtYXJnaW4tYm90dG9tIC4ycyBlYXNlLW91dCAuMnMsb3BhY2l0eSAuMnMgZWFzZS1pbi1vdXQsYmFja2dyb3VuZC1jb2xvciAuMnMgZWFzZS1pbi1vdXQ7dHJhbnNpdGlvbjp3aWR0aCAuMnMgZWFzZS1vdXQgLjJzLGhlaWdodCAuMnMgZWFzZS1vdXQgLjJzLG1hcmdpbi1sZWZ0IC4ycyBlYXNlLW91dCAuMnMsbWFyZ2luLXJpZ2h0IC4ycyBlYXNlLW91dCAuMnMsbWFyZ2luLXRvcCAuMnMgZWFzZS1vdXQgLjJzLG1hcmdpbi1ib3R0b20gLjJzIGVhc2Utb3V0IC4ycyxvcGFjaXR5IC4ycyBlYXNlLWluLW91dCxiYWNrZ3JvdW5kLWNvbG9yIC4ycyBlYXNlLWluLW91dH0ubUNTLWF1dG9IaWRlPi5tQ3VzdG9tU2Nyb2xsQm94Pi5tQ1NCX3Njcm9sbFRvb2xzLC5tQ1MtYXV0b0hpZGU+Lm1DdXN0b21TY3JvbGxCb3h+Lm1DU0Jfc2Nyb2xsVG9vbHN7b3BhY2l0eTowO2ZpbHRlcjpcImFscGhhKG9wYWNpdHk9MClcIjstbXMtZmlsdGVyOlwiYWxwaGEob3BhY2l0eT0wKVwifS5tQ1MtYXV0b0hpZGU6aG92ZXI+Lm1DdXN0b21TY3JvbGxCb3g+Lm1DU0Jfc2Nyb2xsVG9vbHMsLm1DUy1hdXRvSGlkZTpob3Zlcj4ubUN1c3RvbVNjcm9sbEJveH4ubUNTQl9zY3JvbGxUb29scywubUN1c3RvbVNjcm9sbEJveDpob3Zlcj4ubUNTQl9zY3JvbGxUb29scywubUN1c3RvbVNjcm9sbEJveDpob3Zlcn4ubUNTQl9zY3JvbGxUb29scywubUN1c3RvbVNjcm9sbGJhcj4ubUN1c3RvbVNjcm9sbEJveD4ubUNTQl9zY3JvbGxUb29scy5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZywubUN1c3RvbVNjcm9sbGJhcj4ubUN1c3RvbVNjcm9sbEJveH4ubUNTQl9zY3JvbGxUb29scy5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ3tvcGFjaXR5OjE7ZmlsdGVyOlwiYWxwaGEob3BhY2l0eT0xMDApXCI7LW1zLWZpbHRlcjpcImFscGhhKG9wYWNpdHk9MTAwKVwifS5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlse2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjQpO2ZpbHRlcjpcImFscGhhKG9wYWNpdHk9NDApXCI7LW1zLWZpbHRlcjpcImFscGhhKG9wYWNpdHk9NDApXCJ9Lm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC43NSk7ZmlsdGVyOlwiYWxwaGEob3BhY2l0eT03NSlcIjstbXMtZmlsdGVyOlwiYWxwaGEob3BhY2l0eT03NSlcIn0ubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmhvdmVyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6I2ZmZjtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjg1KTtmaWx0ZXI6XCJhbHBoYShvcGFjaXR5PTg1KVwiOy1tcy1maWx0ZXI6XCJhbHBoYShvcGFjaXR5PTg1KVwifS5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZyAubUNTQl9kcmFnZ2VyX2JhciwubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmFjdGl2ZSAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC45KTtmaWx0ZXI6XCJhbHBoYShvcGFjaXR5PTkwKVwiOy1tcy1maWx0ZXI6XCJhbHBoYShvcGFjaXR5PTkwKVwifS5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd24sLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uTGVmdCwubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodCwubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcHtiYWNrZ3JvdW5kLWltYWdlOnVybChtQ1NCX2J1dHRvbnMucG5nKTtiYWNrZ3JvdW5kLXJlcGVhdDpuby1yZXBlYXQ7b3BhY2l0eTouNDtmaWx0ZXI6XCJhbHBoYShvcGFjaXR5PTQwKVwiOy1tcy1maWx0ZXI6XCJhbHBoYShvcGFjaXR5PTQwKVwifS5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwe2JhY2tncm91bmQtcG9zaXRpb246MCAwfS5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd257YmFja2dyb3VuZC1wb3NpdGlvbjowIC0yMHB4fS5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnR7YmFja2dyb3VuZC1wb3NpdGlvbjowIC00MHB4fS5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0e2JhY2tncm91bmQtcG9zaXRpb246MCAtNTZweH0ubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3duOmhvdmVyLC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnQ6aG92ZXIsLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHQ6aG92ZXIsLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXA6aG92ZXJ7b3BhY2l0eTouNzU7ZmlsdGVyOlwiYWxwaGEob3BhY2l0eT03NSlcIjstbXMtZmlsdGVyOlwiYWxwaGEob3BhY2l0eT03NSlcIn0ubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3duOmFjdGl2ZSwubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0OmFjdGl2ZSwubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodDphY3RpdmUsLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXA6YWN0aXZle29wYWNpdHk6Ljk7ZmlsdGVyOlwiYWxwaGEob3BhY2l0eT05MClcIjstbXMtZmlsdGVyOlwiYWxwaGEob3BhY2l0eT05MClcIn0ubUNTLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHtiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4xNSl9Lm1DUy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC43NSl9Lm1DUy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjpob3ZlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjg1KX0ubUNTLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWcgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjphY3RpdmUgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC45KX0ubUNTLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi04MHB4IDB9Lm1DUy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93bntiYWNrZ3JvdW5kLXBvc2l0aW9uOi04MHB4IC0yMHB4fS5tQ1MtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnR7YmFja2dyb3VuZC1wb3NpdGlvbjotODBweCAtNDBweH0ubUNTLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi04MHB4IC01NnB4fS5tQ1MtZGFyay0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1saWdodC0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7d2lkdGg6NHB4O2JhY2tncm91bmQtY29sb3I6I2ZmZjtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjEpOy13ZWJraXQtYm9yZGVyLXJhZGl1czoxcHg7LW1vei1ib3JkZXItcmFkaXVzOjFweDtib3JkZXItcmFkaXVzOjFweH0ubUNTLWRhcmstMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1saWdodC0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2Jhcnt3aWR0aDo0cHg7YmFja2dyb3VuZC1jb2xvcjojZmZmO2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNzUpOy13ZWJraXQtYm9yZGVyLXJhZGl1czoxcHg7LW1vei1ib3JkZXItcmFkaXVzOjFweDtib3JkZXItcmFkaXVzOjFweH0ubUNTLWRhcmstMi5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWRhcmstMi5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1saWdodC0yLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtbGlnaHQtMi5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlclJhaWx7d2lkdGg6MTAwJTtoZWlnaHQ6NHB4O21hcmdpbjo2cHggYXV0b30ubUNTLWxpZ2h0LTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmhvdmVyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6I2ZmZjtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjg1KX0ubUNTLWxpZ2h0LTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWcgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1saWdodC0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjphY3RpdmUgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojZmZmO2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuOSl9Lm1DUy1saWdodC0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXB7YmFja2dyb3VuZC1wb3NpdGlvbjotMzJweCAwfS5tQ1MtbGlnaHQtMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd257YmFja2dyb3VuZC1wb3NpdGlvbjotMzJweCAtMjBweH0ubUNTLWxpZ2h0LTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0e2JhY2tncm91bmQtcG9zaXRpb246LTQwcHggLTQwcHh9Lm1DUy1saWdodC0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHR7YmFja2dyb3VuZC1wb3NpdGlvbjotNDBweCAtNTZweH0ubUNTLWRhcmstMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlse2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjEpOy13ZWJraXQtYm9yZGVyLXJhZGl1czoxcHg7LW1vei1ib3JkZXItcmFkaXVzOjFweDtib3JkZXItcmFkaXVzOjFweH0ubUNTLWRhcmstMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuNzUpOy13ZWJraXQtYm9yZGVyLXJhZGl1czoxcHg7LW1vei1ib3JkZXItcmFkaXVzOjFweDtib3JkZXItcmFkaXVzOjFweH0ubUNTLWRhcmstMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6aG92ZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuODUpfS5tQ1MtZGFyay0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtZGFyay0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjphY3RpdmUgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuOSl9Lm1DUy1kYXJrLTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xMTJweCAwfS5tQ1MtZGFyay0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93bntiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xMTJweCAtMjBweH0ubUNTLWRhcmstMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnR7YmFja2dyb3VuZC1wb3NpdGlvbjotMTIwcHggLTQwcHh9Lm1DUy1kYXJrLTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xMjBweCAtNTZweH0ubUNTLWRhcmstdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLWxpZ2h0LXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7d2lkdGg6NHB4O2JhY2tncm91bmQtY29sb3I6I2ZmZjtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjEpOy13ZWJraXQtYm9yZGVyLXJhZGl1czoycHg7LW1vei1ib3JkZXItcmFkaXVzOjJweDtib3JkZXItcmFkaXVzOjJweH0ubUNTLWRhcmstdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtbGlnaHQtdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye3dpZHRoOjZweDtiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC43NSk7LXdlYmtpdC1ib3JkZXItcmFkaXVzOjJweDstbW96LWJvcmRlci1yYWRpdXM6MnB4O2JvcmRlci1yYWRpdXM6MnB4fS5tQ1MtZGFyay10aGljay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1saWdodC10aGljay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlclJhaWx7d2lkdGg6MTAwJTtoZWlnaHQ6NHB4O21hcmdpbjo2cHggMH0ubUNTLWRhcmstdGhpY2subUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1saWdodC10aGljay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2Jhcnt3aWR0aDoxMDAlO2hlaWdodDo2cHg7bWFyZ2luOjVweCBhdXRvfS5tQ1MtbGlnaHQtdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmhvdmVyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6I2ZmZjtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjg1KX0ubUNTLWxpZ2h0LXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtbGlnaHQtdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmFjdGl2ZSAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC45KX0ubUNTLWxpZ2h0LXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXB7YmFja2dyb3VuZC1wb3NpdGlvbjotMTZweCAwfS5tQ1MtbGlnaHQtdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3due2JhY2tncm91bmQtcG9zaXRpb246LTE2cHggLTIwcHh9Lm1DUy1saWdodC10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnR7YmFja2dyb3VuZC1wb3NpdGlvbjotMjBweCAtNDBweH0ubUNTLWxpZ2h0LXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHR7YmFja2dyb3VuZC1wb3NpdGlvbjotMjBweCAtNTZweH0ubUNTLWRhcmstdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHtiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4xKTstd2Via2l0LWJvcmRlci1yYWRpdXM6MnB4Oy1tb3otYm9yZGVyLXJhZGl1czoycHg7Ym9yZGVyLXJhZGl1czoycHh9Lm1DUy1kYXJrLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC43NSk7LXdlYmtpdC1ib3JkZXItcmFkaXVzOjJweDstbW96LWJvcmRlci1yYWRpdXM6MnB4O2JvcmRlci1yYWRpdXM6MnB4fS5tQ1MtZGFyay10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6aG92ZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuODUpfS5tQ1MtZGFyay10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZyAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWRhcmstdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmFjdGl2ZSAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC45KX0ubUNTLWRhcmstdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi05NnB4IDB9Lm1DUy1kYXJrLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93bntiYWNrZ3JvdW5kLXBvc2l0aW9uOi05NnB4IC0yMHB4fS5tQ1MtZGFyay10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnR7YmFja2dyb3VuZC1wb3NpdGlvbjotMTAwcHggLTQwcHh9Lm1DUy1kYXJrLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHR7YmFja2dyb3VuZC1wb3NpdGlvbjotMTAwcHggLTU2cHh9Lm1DUy1saWdodC10aGluLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7YmFja2dyb3VuZC1jb2xvcjojZmZmO2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuMSl9Lm1DUy1kYXJrLXRoaW4ubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtbGlnaHQtdGhpbi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7d2lkdGg6MnB4fS5tQ1MtZGFyay10aGluLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLWxpZ2h0LXRoaW4ubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlse3dpZHRoOjEwMCV9Lm1DUy1kYXJrLXRoaW4ubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1saWdodC10aGluLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye3dpZHRoOjEwMCU7aGVpZ2h0OjJweDttYXJnaW46N3B4IGF1dG99Lm1DUy1kYXJrLXRoaW4ubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHtiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4xNSl9Lm1DUy1kYXJrLXRoaW4ubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjc1KX0ubUNTLWRhcmstdGhpbi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6aG92ZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuODUpfS5tQ1MtZGFyay10aGluLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtZGFyay10aGluLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjphY3RpdmUgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuOSl9Lm1DUy1kYXJrLXRoaW4ubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi04MHB4IDB9Lm1DUy1kYXJrLXRoaW4ubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3due2JhY2tncm91bmQtcG9zaXRpb246LTgwcHggLTIwcHh9Lm1DUy1kYXJrLXRoaW4ubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0e2JhY2tncm91bmQtcG9zaXRpb246LTgwcHggLTQwcHh9Lm1DUy1kYXJrLXRoaW4ubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi04MHB4IC01NnB4fS5tQ1Mtcm91bmRlZC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlse2JhY2tncm91bmQtY29sb3I6I2ZmZjtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjE1KX0ubUNTLXJvdW5kZWQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIsLm1DUy1yb3VuZGVkLWRvdHMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIsLm1DUy1yb3VuZGVkLWRvdHMubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLC5tQ1Mtcm91bmRlZC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJ7aGVpZ2h0OjE0cHh9Lm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1Mtcm91bmRlZC1kb3RzLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1Mtcm91bmRlZC1kb3RzLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLXJvdW5kZWQubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye3dpZHRoOjE0cHg7bWFyZ2luOjAgMXB4fS5tQ1Mtcm91bmRlZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyLC5tQ1Mtcm91bmRlZC1kb3RzLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIsLm1DUy1yb3VuZGVkLWRvdHMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIsLm1DUy1yb3VuZGVkLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2Vye3dpZHRoOjE0cHh9Lm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1yb3VuZGVkLWRvdHMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLXJvdW5kZWQtZG90cy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLXJvdW5kZWQubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7aGVpZ2h0OjE0cHg7bWFyZ2luOjFweCAwfS5tQ1Mtcm91bmRlZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZ19leHBhbmRlZCAubUNTQl9kcmFnZ2VyX2JhciwubUNTLXJvdW5kZWQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyQ29udGFpbmVyOmhvdmVyIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1yb3VuZGVkLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZ19leHBhbmRlZCAubUNTQl9kcmFnZ2VyX2JhciwubUNTLXJvdW5kZWQubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcjpob3ZlciAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye3dpZHRoOjE2cHg7aGVpZ2h0OjE2cHg7bWFyZ2luOi0xcHggMH0ubUNTLXJvdW5kZWQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWdfZXhwYW5kZWQrLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcjpob3ZlciAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLXJvdW5kZWQubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnX2V4cGFuZGVkKy5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1Mtcm91bmRlZC5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyQ29udGFpbmVyOmhvdmVyIC5tQ1NCX2RyYWdnZXJSYWlse3dpZHRoOjRweH0ubUNTLXJvdW5kZWQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZ19leHBhbmRlZCAubUNTQl9kcmFnZ2VyX2JhciwubUNTLXJvdW5kZWQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXJDb250YWluZXI6aG92ZXIgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLXJvdW5kZWQubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWdfZXhwYW5kZWQgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1yb3VuZGVkLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcjpob3ZlciAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye2hlaWdodDoxNnB4O3dpZHRoOjE2cHg7bWFyZ2luOjAgLTFweH0ubUNTLXJvdW5kZWQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZ19leHBhbmRlZCsubUNTQl9kcmFnZ2VyUmFpbCwubUNTLXJvdW5kZWQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXJDb250YWluZXI6aG92ZXIgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1yb3VuZGVkLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnX2V4cGFuZGVkKy5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1Mtcm91bmRlZC5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXJDb250YWluZXI6aG92ZXIgLm1DU0JfZHJhZ2dlclJhaWx7aGVpZ2h0OjRweDttYXJnaW46NnB4IDB9Lm1DUy1yb3VuZGVkLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXB7YmFja2dyb3VuZC1wb3NpdGlvbjowIC03MnB4fS5tQ1Mtcm91bmRlZC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd257YmFja2dyb3VuZC1wb3NpdGlvbjowIC05MnB4fS5tQ1Mtcm91bmRlZC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnR7YmFja2dyb3VuZC1wb3NpdGlvbjowIC0xMTJweH0ubUNTLXJvdW5kZWQubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodHtiYWNrZ3JvdW5kLXBvc2l0aW9uOjAgLTEyOHB4fS5tQ1Mtcm91bmRlZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLXJvdW5kZWQtZG90cy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC43NSl9Lm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHtiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4xNSl9Lm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmhvdmVyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1Mtcm91bmRlZC1kb3RzLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmhvdmVyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjg1KX0ubUNTLXJvdW5kZWQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZyAubUNTQl9kcmFnZ2VyX2JhciwubUNTLXJvdW5kZWQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6YWN0aXZlIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1Mtcm91bmRlZC1kb3RzLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWcgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1yb3VuZGVkLWRvdHMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6YWN0aXZlIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjkpfS5tQ1Mtcm91bmRlZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXB7YmFja2dyb3VuZC1wb3NpdGlvbjotODBweCAtNzJweH0ubUNTLXJvdW5kZWQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd257YmFja2dyb3VuZC1wb3NpdGlvbjotODBweCAtOTJweH0ubUNTLXJvdW5kZWQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnR7YmFja2dyb3VuZC1wb3NpdGlvbjotODBweCAtMTEycHh9Lm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi04MHB4IC0xMjhweH0ubUNTLXJvdW5kZWQtZG90cy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1yb3VuZGVkLWRvdHMubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbCAubUNTQl9kcmFnZ2VyUmFpbHt3aWR0aDo0cHh9Lm1DUy1yb3VuZGVkLWRvdHMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1Mtcm91bmRlZC1kb3RzLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1Mtcm91bmRlZC1kb3RzLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1yb3VuZGVkLWRvdHMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlse2JhY2tncm91bmQtY29sb3I6dHJhbnNwYXJlbnQ7YmFja2dyb3VuZC1wb3NpdGlvbjpjZW50ZXJ9Lm1DUy1yb3VuZGVkLWRvdHMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1Mtcm91bmRlZC1kb3RzLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7YmFja2dyb3VuZC1pbWFnZTp1cmwoZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFBZ0FBQUFJQ0FZQUFBREVENzZMQUFBQU5FbEVRVlFZVjJOa0lBQVlpVmJ3Ly85L1k2RGlNMUFOSm95TWpHZEJiTGdKUUFYL2tVMERLZ0RMa2FRQXZ4VzRIRXZRRndDUmN4SUpLMVh6bkFBQUFBQkpSVTVFcmtKZ2dnPT0pO2JhY2tncm91bmQtcmVwZWF0OnJlcGVhdC15O29wYWNpdHk6LjM7ZmlsdGVyOlwiYWxwaGEob3BhY2l0eT0zMClcIjstbXMtZmlsdGVyOlwiYWxwaGEob3BhY2l0eT0zMClcIn0ubUNTLXJvdW5kZWQtZG90cy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLXJvdW5kZWQtZG90cy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlclJhaWx7aGVpZ2h0OjRweDttYXJnaW46NnB4IDA7YmFja2dyb3VuZC1yZXBlYXQ6cmVwZWF0LXh9Lm1DUy1yb3VuZGVkLWRvdHMubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xNnB4IC03MnB4fS5tQ1Mtcm91bmRlZC1kb3RzLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93bntiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xNnB4IC05MnB4fS5tQ1Mtcm91bmRlZC1kb3RzLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uTGVmdHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0yMHB4IC0xMTJweH0ubUNTLXJvdW5kZWQtZG90cy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0e2JhY2tncm91bmQtcG9zaXRpb246LTIwcHggLTEyOHB4fS5tQ1Mtcm91bmRlZC1kb3RzLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHtiYWNrZ3JvdW5kLWltYWdlOnVybChkYXRhOmltYWdlL3BuZztiYXNlNjQsaVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQUFnQUFBQUlDQVlBQUFERUQ3NkxBQUFBTEVsRVFWUVlWMk5rSUFBWVNWRmdERlI4QnFyQkJFaWZCYkdSVGZpUFpocFlqaVFGQkszQTZsNkN2Z0FBRTlrR0NkMW12Z0VBQUFBQVNVVk9SSzVDWUlJPSl9Lm1DUy1yb3VuZGVkLWRvdHMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwe2JhY2tncm91bmQtcG9zaXRpb246LTk2cHggLTcycHh9Lm1DUy1yb3VuZGVkLWRvdHMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd257YmFja2dyb3VuZC1wb3NpdGlvbjotOTZweCAtOTJweH0ubUNTLXJvdW5kZWQtZG90cy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uTGVmdHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xMDBweCAtMTEycHh9Lm1DUy1yb3VuZGVkLWRvdHMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0e2JhY2tncm91bmQtcG9zaXRpb246LTEwMHB4IC0xMjhweH0ubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy0zZC10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy0zZC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1yZXBlYXQ6cmVwZWF0LXk7YmFja2dyb3VuZC1pbWFnZTotbW96LWxpbmVhci1ncmFkaWVudChsZWZ0LHJnYmEoMjU1LDI1NSwyNTUsLjUpIDAscmdiYSgyNTUsMjU1LDI1NSwwKSAxMDAlKTtiYWNrZ3JvdW5kLWltYWdlOi13ZWJraXQtZ3JhZGllbnQobGluZWFyLGxlZnQgdG9wLHJpZ2h0IHRvcCxjb2xvci1zdG9wKDAscmdiYSgyNTUsMjU1LDI1NSwuNSkpLGNvbG9yLXN0b3AoMTAwJSxyZ2JhKDI1NSwyNTUsMjU1LDApKSk7YmFja2dyb3VuZC1pbWFnZTotd2Via2l0LWxpbmVhci1ncmFkaWVudChsZWZ0LHJnYmEoMjU1LDI1NSwyNTUsLjUpIDAscmdiYSgyNTUsMjU1LDI1NSwwKSAxMDAlKTtiYWNrZ3JvdW5kLWltYWdlOi1vLWxpbmVhci1ncmFkaWVudChsZWZ0LHJnYmEoMjU1LDI1NSwyNTUsLjUpIDAscmdiYSgyNTUsMjU1LDI1NSwwKSAxMDAlKTtiYWNrZ3JvdW5kLWltYWdlOi1tcy1saW5lYXItZ3JhZGllbnQobGVmdCxyZ2JhKDI1NSwyNTUsMjU1LC41KSAwLHJnYmEoMjU1LDI1NSwyNTUsMCkgMTAwJSk7YmFja2dyb3VuZC1pbWFnZTpsaW5lYXItZ3JhZGllbnQodG8gcmlnaHQscmdiYSgyNTUsMjU1LDI1NSwuNSkgMCxyZ2JhKDI1NSwyNTUsMjU1LDApIDEwMCUpfS5tQ1MtM2QtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLXRoaWNrLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy0zZC10aGljay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtcmVwZWF0OnJlcGVhdC14O2JhY2tncm91bmQtaW1hZ2U6LW1vei1saW5lYXItZ3JhZGllbnQodG9wLHJnYmEoMjU1LDI1NSwyNTUsLjUpIDAscmdiYSgyNTUsMjU1LDI1NSwwKSAxMDAlKTtiYWNrZ3JvdW5kLWltYWdlOi13ZWJraXQtZ3JhZGllbnQobGluZWFyLGxlZnQgdG9wLGxlZnQgYm90dG9tLGNvbG9yLXN0b3AoMCxyZ2JhKDI1NSwyNTUsMjU1LC41KSksY29sb3Itc3RvcCgxMDAlLHJnYmEoMjU1LDI1NSwyNTUsMCkpKTtiYWNrZ3JvdW5kLWltYWdlOi13ZWJraXQtbGluZWFyLWdyYWRpZW50KHRvcCxyZ2JhKDI1NSwyNTUsMjU1LC41KSAwLHJnYmEoMjU1LDI1NSwyNTUsMCkgMTAwJSk7YmFja2dyb3VuZC1pbWFnZTotby1saW5lYXItZ3JhZGllbnQodG9wLHJnYmEoMjU1LDI1NSwyNTUsLjUpIDAscmdiYSgyNTUsMjU1LDI1NSwwKSAxMDAlKTtiYWNrZ3JvdW5kLWltYWdlOi1tcy1saW5lYXItZ3JhZGllbnQodG9wLHJnYmEoMjU1LDI1NSwyNTUsLjUpIDAscmdiYSgyNTUsMjU1LDI1NSwwKSAxMDAlKTtiYWNrZ3JvdW5kLWltYWdlOmxpbmVhci1ncmFkaWVudCh0byBib3R0b20scmdiYSgyNTUsMjU1LDI1NSwuNSkgMCxyZ2JhKDI1NSwyNTUsMjU1LDApIDEwMCUpfS5tQ1MtM2QtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsIC5tQ1NCX2RyYWdnZXIsLm1DUy0zZC5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsIC5tQ1NCX2RyYWdnZXJ7aGVpZ2h0OjcwcHh9Lm1DUy0zZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyLC5tQ1MtM2QubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJ7d2lkdGg6NzBweH0ubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29scywubUNTLTNkLm1DU0Jfc2Nyb2xsVG9vbHN7b3BhY2l0eToxO2ZpbHRlcjpcImFscGhhKG9wYWNpdHk9MzApXCI7LW1zLWZpbHRlcjpcImFscGhhKG9wYWNpdHk9MzApXCJ9Lm1DUy0zZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLTNkLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7LXdlYmtpdC1ib3JkZXItcmFkaXVzOjE2cHg7LW1vei1ib3JkZXItcmFkaXVzOjE2cHg7Ym9yZGVyLXJhZGl1czoxNnB4fS5tQ1MtM2QtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtM2QubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHt3aWR0aDo4cHg7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuMik7Ym94LXNoYWRvdzppbnNldCAxcHggMCAxcHggcmdiYSgwLDAsMCwuNSksaW5zZXQgLTFweCAwIDFweCByZ2JhKDI1NSwyNTUsMjU1LC4yKX0ubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZyAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmFjdGl2ZSAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmhvdmVyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWcgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy0zZC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6YWN0aXZlIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmhvdmVyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzU1NX0ubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye3dpZHRoOjhweH0ubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtM2QubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlse3dpZHRoOjEwMCU7aGVpZ2h0OjhweDttYXJnaW46NHB4IDA7Ym94LXNoYWRvdzppbnNldCAwIDFweCAxcHggcmdiYSgwLDAsMCwuNSksaW5zZXQgMCAtMXB4IDFweCByZ2JhKDI1NSwyNTUsMjU1LC4yKX0ubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy0zZC5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2Jhcnt3aWR0aDoxMDAlO2hlaWdodDo4cHg7bWFyZ2luOjRweCBhdXRvfS5tQ1MtM2QubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0zMnB4IC03MnB4fS5tQ1MtM2QubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3due2JhY2tncm91bmQtcG9zaXRpb246LTMycHggLTkycHh9Lm1DUy0zZC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnR7YmFja2dyb3VuZC1wb3NpdGlvbjotNDBweCAtMTEycHh9Lm1DUy0zZC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0e2JhY2tncm91bmQtcG9zaXRpb246LTQwcHggLTEyOHB4fS5tQ1MtM2QtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlse2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjEpO2JveC1zaGFkb3c6aW5zZXQgMXB4IDAgMXB4IHJnYmEoMCwwLDAsLjEpfS5tQ1MtM2QtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlclJhaWx7Ym94LXNoYWRvdzppbnNldCAwIDFweCAxcHggcmdiYSgwLDAsMCwuMSl9Lm1DUy0zZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXB7YmFja2dyb3VuZC1wb3NpdGlvbjotMTEycHggLTcycHh9Lm1DUy0zZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93bntiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xMTJweCAtOTJweH0ubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0e2JhY2tncm91bmQtcG9zaXRpb246LTEyMHB4IC0xMTJweH0ubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xMjBweCAtMTI4cHh9Lm1DUy0zZC10aGljay1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMsLm1DUy0zZC10aGljay5tQ1NCX3Njcm9sbFRvb2xze29wYWNpdHk6MTtmaWx0ZXI6XCJhbHBoYShvcGFjaXR5PTMwKVwiOy1tcy1maWx0ZXI6XCJhbHBoYShvcGFjaXR5PTMwKVwifS5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzLC5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJDb250YWluZXIsLm1DUy0zZC10aGljay5tQ1NCX3Njcm9sbFRvb2xzLC5tQ1MtM2QtdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyQ29udGFpbmVyey13ZWJraXQtYm9yZGVyLXJhZGl1czo3cHg7LW1vei1ib3JkZXItcmFkaXVzOjdweDtib3JkZXItcmFkaXVzOjdweH0ubUNTQl9pbnNpZGUrLm1DUy0zZC10aGljay1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwsLm1DU0JfaW5zaWRlKy5tQ1MtM2QtdGhpY2subUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbHtyaWdodDoxcHh9Lm1DUy0zZC10aGljay1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwsLm1DUy0zZC10aGljay5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2Fse2JveC1zaGFkb3c6aW5zZXQgMXB4IDAgMXB4IHJnYmEoMCwwLDAsLjEpLGluc2V0IDAgMCAxNHB4IHJnYmEoMCwwLDAsLjUpfS5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwsLm1DUy0zZC10aGljay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWx7Ym90dG9tOjFweDtib3gtc2hhZG93Omluc2V0IDAgMXB4IDFweCByZ2JhKDAsMCwwLC4xKSxpbnNldCAwIDAgMTRweCByZ2JhKDAsMCwwLC41KX0ubUNTLTNkLXRoaWNrLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QtdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyey13ZWJraXQtYm9yZGVyLXJhZGl1czo1cHg7LW1vei1ib3JkZXItcmFkaXVzOjVweDtib3JkZXItcmFkaXVzOjVweDtib3gtc2hhZG93Omluc2V0IDFweCAwIDAgcmdiYSgyNTUsMjU1LDI1NSwuNCk7d2lkdGg6MTJweDttYXJnaW46MnB4O3Bvc2l0aW9uOmFic29sdXRlO2hlaWdodDphdXRvO3RvcDowO2JvdHRvbTowO2xlZnQ6MDtyaWdodDowfS5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye2JveC1zaGFkb3c6aW5zZXQgMCAxcHggMCByZ2JhKDI1NSwyNTUsMjU1LC40KTtoZWlnaHQ6MTJweDt3aWR0aDphdXRvfS5tQ1MtM2QtdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QtdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWcgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy0zZC10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6YWN0aXZlIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QtdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmhvdmVyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzU1NX0ubUNTLTNkLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4wNSk7Ym94LXNoYWRvdzppbnNldCAxcHggMXB4IDE2cHggcmdiYSgwLDAsMCwuMSl9Lm1DUy0zZC10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlse2JhY2tncm91bmQtY29sb3I6dHJhbnNwYXJlbnR9Lm1DUy0zZC10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwe2JhY2tncm91bmQtcG9zaXRpb246LTMycHggLTcycHh9Lm1DUy0zZC10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd257YmFja2dyb3VuZC1wb3NpdGlvbjotMzJweCAtOTJweH0ubUNTLTNkLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uTGVmdHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi00MHB4IC0xMTJweH0ubUNTLTNkLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHR7YmFja2dyb3VuZC1wb3NpdGlvbjotNDBweCAtMTI4cHh9Lm1DUy0zZC10aGljay1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHN7Ym94LXNoYWRvdzppbnNldCAwIDAgMTRweCByZ2JhKDAsMCwwLC4yKX0ubUNTLTNkLXRoaWNrLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFse2JveC1zaGFkb3c6aW5zZXQgMCAxcHggMXB4IHJnYmEoMCwwLDAsLjEpLGluc2V0IDAgMCAxNHB4IHJnYmEoMCwwLDAsLjIpfS5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7Ym94LXNoYWRvdzppbnNldCAxcHggMCAwIHJnYmEoMjU1LDI1NSwyNTUsLjQpLGluc2V0IC0xcHggMCAwIHJnYmEoMCwwLDAsLjIpfS5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2Jhcntib3gtc2hhZG93Omluc2V0IDAgMXB4IDAgcmdiYSgyNTUsMjU1LDI1NSwuNCksaW5zZXQgMCAtMXB4IDAgcmdiYSgwLDAsMCwuMil9Lm1DUy0zZC10aGljay1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLXRoaWNrLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWcgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy0zZC10aGljay1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjphY3RpdmUgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy0zZC10aGljay1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjpob3ZlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiM3Nzd9Lm1DUy0zZC10aGljay1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcntiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4wNSk7Ym94LXNoYWRvdzppbnNldCAxcHggMXB4IDE2cHggcmdiYSgwLDAsMCwuMSl9Lm1DUy0zZC10aGljay1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1taW5pbWFsLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLW1pbmltYWwubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHtiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50fS5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwe2JhY2tncm91bmQtcG9zaXRpb246LTExMnB4IC03MnB4fS5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd257YmFja2dyb3VuZC1wb3NpdGlvbjotMTEycHggLTkycHh9Lm1DUy0zZC10aGljay1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uTGVmdHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xMjBweCAtMTEycHh9Lm1DUy0zZC10aGljay1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHR7YmFja2dyb3VuZC1wb3NpdGlvbjotMTIwcHggLTEyOHB4fS5tQ1NCX291dHNpZGUrLm1DUy1taW5pbWFsLWRhcmsubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbCwubUNTQl9vdXRzaWRlKy5tQ1MtbWluaW1hbC5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2Fse3JpZ2h0OjA7bWFyZ2luOjEycHggMH0ubUN1c3RvbVNjcm9sbEJveC5tQ1MtbWluaW1hbCsubUNTQl9zY3JvbGxUb29scysubUNTQl9zY3JvbGxUb29scy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwsLm1DdXN0b21TY3JvbGxCb3gubUNTLW1pbmltYWwrLm1DU0Jfc2Nyb2xsVG9vbHMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLC5tQ3VzdG9tU2Nyb2xsQm94Lm1DUy1taW5pbWFsLWRhcmsrLm1DU0Jfc2Nyb2xsVG9vbHMrLm1DU0Jfc2Nyb2xsVG9vbHMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLC5tQ3VzdG9tU2Nyb2xsQm94Lm1DUy1taW5pbWFsLWRhcmsrLm1DU0Jfc2Nyb2xsVG9vbHMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFse2JvdHRvbTowO21hcmdpbjowIDEycHh9Lm1DUy1kaXItcnRsPi5tQ1NCX291dHNpZGUrLm1DUy1taW5pbWFsLWRhcmsubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbCwubUNTLWRpci1ydGw+Lm1DU0Jfb3V0c2lkZSsubUNTLW1pbmltYWwubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbHtsZWZ0OjA7cmlnaHQ6YXV0b30ubUNTLW1pbmltYWwtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsIC5tQ1NCX2RyYWdnZXIsLm1DUy1taW5pbWFsLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwgLm1DU0JfZHJhZ2dlcntoZWlnaHQ6NTBweH0ubUNTLW1pbmltYWwtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciwubUNTLW1pbmltYWwubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJ7d2lkdGg6NTBweH0ubUNTLW1pbmltYWwubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6I2ZmZjtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjIpO2ZpbHRlcjpcImFscGhhKG9wYWNpdHk9MjApXCI7LW1zLWZpbHRlcjpcImFscGhhKG9wYWNpdHk9MjApXCJ9Lm1DUy1taW5pbWFsLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtbWluaW1hbC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6YWN0aXZlIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6I2ZmZjtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjUpO2ZpbHRlcjpcImFscGhhKG9wYWNpdHk9NTApXCI7LW1zLWZpbHRlcjpcImFscGhhKG9wYWNpdHk9NTApXCJ9Lm1DUy1taW5pbWFsLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjIpO2ZpbHRlcjpcImFscGhhKG9wYWNpdHk9MjApXCI7LW1zLWZpbHRlcjpcImFscGhhKG9wYWNpdHk9MjApXCJ9Lm1DUy1taW5pbWFsLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWcgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1taW5pbWFsLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmFjdGl2ZSAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC41KTtmaWx0ZXI6XCJhbHBoYShvcGFjaXR5PTUwKVwiOy1tcy1maWx0ZXI6XCJhbHBoYShvcGFjaXR5PTUwKVwifS5tQ1MtZGFyay0zLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1saWdodC0zLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7d2lkdGg6NnB4O2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjIpfS5tQ1MtZGFyay0zLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWxpZ2h0LTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye3dpZHRoOjZweH0ubUNTLWRhcmstMy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWRhcmstMy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1saWdodC0zLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtbGlnaHQtMy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlclJhaWx7d2lkdGg6MTAwJTtoZWlnaHQ6NnB4O21hcmdpbjo1cHggMH0ubUNTLWRhcmstMy5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWdfZXhwYW5kZWQrLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1kYXJrLTMubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcjpob3ZlciAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLWxpZ2h0LTMubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnX2V4cGFuZGVkKy5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtbGlnaHQtMy5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyQ29udGFpbmVyOmhvdmVyIC5tQ1NCX2RyYWdnZXJSYWlse3dpZHRoOjEycHh9Lm1DUy1kYXJrLTMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWdfZXhwYW5kZWQrLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1kYXJrLTMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyQ29udGFpbmVyOmhvdmVyIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtbGlnaHQtMy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZ19leHBhbmRlZCsubUNTQl9kcmFnZ2VyUmFpbCwubUNTLWxpZ2h0LTMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyQ29udGFpbmVyOmhvdmVyIC5tQ1NCX2RyYWdnZXJSYWlse2hlaWdodDoxMnB4O21hcmdpbjoycHggMH0ubUNTLWxpZ2h0LTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0zMnB4IC03MnB4fS5tQ1MtbGlnaHQtMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd257YmFja2dyb3VuZC1wb3NpdGlvbjotMzJweCAtOTJweH0ubUNTLWxpZ2h0LTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0e2JhY2tncm91bmQtcG9zaXRpb246LTQwcHggLTExMnB4fS5tQ1MtbGlnaHQtMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0e2JhY2tncm91bmQtcG9zaXRpb246LTQwcHggLTEyOHB4fS5tQ1MtZGFyay0zLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC43NSl9Lm1DUy1kYXJrLTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmhvdmVyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjg1KX0ubUNTLWRhcmstMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZyAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWRhcmstMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6YWN0aXZlIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjkpfS5tQ1MtZGFyay0zLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuMSl9Lm1DUy1kYXJrLTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xMTJweCAtNzJweH0ubUNTLWRhcmstMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd257YmFja2dyb3VuZC1wb3NpdGlvbjotMTEycHggLTkycHh9Lm1DUy1kYXJrLTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0e2JhY2tncm91bmQtcG9zaXRpb246LTEyMHB4IC0xMTJweH0ubUNTLWRhcmstMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0e2JhY2tncm91bmQtcG9zaXRpb246LTEyMHB4IC0xMjhweH0ubUNTLWluc2V0LTItZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtaW5zZXQtMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtaW5zZXQtMy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1pbnNldC0zLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1pbnNldC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1pbnNldC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlse3dpZHRoOjEycHg7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuMil9Lm1DUy1pbnNldC0yLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtaW5zZXQtMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1pbnNldC0zLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtaW5zZXQtMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1pbnNldC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWluc2V0Lm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2Jhcnt3aWR0aDo2cHg7bWFyZ2luOjNweCA1cHg7cG9zaXRpb246YWJzb2x1dGU7aGVpZ2h0OmF1dG87dG9wOjA7Ym90dG9tOjA7bGVmdDowO3JpZ2h0OjB9Lm1DUy1pbnNldC0yLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1pbnNldC0yLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtaW5zZXQtMy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtaW5zZXQtMy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWluc2V0LWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1pbnNldC5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhcntoZWlnaHQ6NnB4O21hcmdpbjo1cHggM3B4O3Bvc2l0aW9uOmFic29sdXRlO3dpZHRoOmF1dG87dG9wOjA7Ym90dG9tOjA7bGVmdDowO3JpZ2h0OjB9Lm1DUy1pbnNldC0yLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtaW5zZXQtMi5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1pbnNldC0zLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtaW5zZXQtMy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1pbnNldC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLWluc2V0Lm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyUmFpbHt3aWR0aDoxMDAlO2hlaWdodDoxMnB4O21hcmdpbjoycHggMH0ubUNTLWluc2V0LTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcCwubUNTLWluc2V0LTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcCwubUNTLWluc2V0Lm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXB7YmFja2dyb3VuZC1wb3NpdGlvbjotMzJweCAtNzJweH0ubUNTLWluc2V0LTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3duLC5tQ1MtaW5zZXQtMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd24sLm1DUy1pbnNldC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd257YmFja2dyb3VuZC1wb3NpdGlvbjotMzJweCAtOTJweH0ubUNTLWluc2V0LTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0LC5tQ1MtaW5zZXQtMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnQsLm1DUy1pbnNldC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnR7YmFja2dyb3VuZC1wb3NpdGlvbjotNDBweCAtMTEycHh9Lm1DUy1pbnNldC0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHQsLm1DUy1pbnNldC0zLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHQsLm1DUy1pbnNldC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0e2JhY2tncm91bmQtcG9zaXRpb246LTQwcHggLTEyOHB4fS5tQ1MtaW5zZXQtMi1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWluc2V0LTMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1pbnNldC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC43NSl9Lm1DUy1pbnNldC0yLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmhvdmVyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtaW5zZXQtMy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjpob3ZlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWluc2V0LWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmhvdmVyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjg1KX0ubUNTLWluc2V0LTItZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZyAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWluc2V0LTItZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6YWN0aXZlIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtaW5zZXQtMy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtaW5zZXQtMy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjphY3RpdmUgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1pbnNldC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtaW5zZXQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6YWN0aXZlIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjkpfS5tQ1MtaW5zZXQtMi1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1pbnNldC0zLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLWluc2V0LWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHtiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4xKX0ubUNTLWluc2V0LTItZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwLC5tQ1MtaW5zZXQtMy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXAsLm1DUy1pbnNldC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXB7YmFja2dyb3VuZC1wb3NpdGlvbjotMTEycHggLTcycHh9Lm1DUy1pbnNldC0yLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3duLC5tQ1MtaW5zZXQtMy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93biwubUNTLWluc2V0LWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3due2JhY2tncm91bmQtcG9zaXRpb246LTExMnB4IC05MnB4fS5tQ1MtaW5zZXQtMi1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uTGVmdCwubUNTLWluc2V0LTMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnQsLm1DUy1pbnNldC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uTGVmdHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xMjBweCAtMTEycHh9Lm1DUy1pbnNldC0yLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodCwubUNTLWluc2V0LTMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0LC5tQ1MtaW5zZXQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0e2JhY2tncm91bmQtcG9zaXRpb246LTEyMHB4IC0xMjhweH0ubUNTLWluc2V0LTItZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtaW5zZXQtMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlse2JhY2tncm91bmQtY29sb3I6dHJhbnNwYXJlbnQ7Ym9yZGVyLXdpZHRoOjFweDtib3JkZXItc3R5bGU6c29saWQ7Ym9yZGVyLWNvbG9yOiNmZmY7Ym9yZGVyLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjIpOy13ZWJraXQtYm94LXNpemluZzpib3JkZXItYm94Oy1tb3otYm94LXNpemluZzpib3JkZXItYm94O2JveC1zaXppbmc6Ym9yZGVyLWJveH0ubUNTLWluc2V0LTItZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlse2JvcmRlci1jb2xvcjojMDAwO2JvcmRlci1jb2xvcjpyZ2JhKDAsMCwwLC4yKX0ubUNTLWluc2V0LTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHtiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC42KX0ubUNTLWluc2V0LTMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlse2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjYpfS5tQ1MtaW5zZXQtMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuNzUpfS5tQ1MtaW5zZXQtMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6aG92ZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuODUpfS5tQ1MtaW5zZXQtMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZyAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWluc2V0LTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmFjdGl2ZSAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC45KX0ubUNTLWluc2V0LTMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojZmZmO2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNzUpfS5tQ1MtaW5zZXQtMy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjpob3ZlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC44NSl9Lm1DUy1pbnNldC0zLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWcgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1pbnNldC0zLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmFjdGl2ZSAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC45KX0iLCJAa2V5ZnJhbWVzIHJvdGF0aW9uIHtcbiAgZnJvbSB7XG4gICAgICAtd2Via2l0LXRyYW5zZm9ybTogcm90YXRlKDBkZWcpO1xuICB9XG4gIHRvIHtcbiAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoMzU5ZGVnKTtcbiAgfVxufVxuXG5cbkBrZXlmcmFtZXMgdHJhbnNsYXRlcDF7XG4gIDAle1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgzNXB4KTtcbiAgfVxuXG4gIDEwMCV7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDVweCk7XG4gICAgdmlzaWJpbGl0eTogaGlkZGVuO1xuICB9XG59XG5cbkBrZXlmcmFtZXMgdHJhbnNsYXRlcDJ7XG4gIDAle1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgtMjBweCk7XG4gIH1cblxuICAxMDAle1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSg0MHB4KTtcbiAgICB2aXNpYmlsaXR5OiBoaWRkZW47XG4gIH1cbn1cblxuQGtleWZyYW1lcyB0cmFuc2xhdGVyaWdodHtcbiAgMCV7XG4gICAgcmlnaHQ6IC01MHB4O1xuICAgIGNvbG9yOiB0cmFuc3BhcmVudDtcbiAgICBkaXNwbGF5OiBub25lO1xuICB9XG5cbiAgMzAle1xuICAgIHJpZ2h0OiAxNTBweDtcbiAgICBjb2xvcjogdHJhbnNwYXJlbnQ7XG4gICAgZGlzcGxheTogYmxvY2s7XG4gIH1cblxuICAxMDAle1xuICAgIHJpZ2h0OiAyMDBweDtcbiAgICBjb2xvcjogd2hpdGU7XG4gIH1cbn1cblxuQGtleWZyYW1lcyB0cmFuc2xhdGVsZWZ0e1xuICAwJXtcbiAgICByaWdodDogMjAwcHg7XG4gICAgY29sb3I6IHdoaXRlO1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICB9XG4gIDMwJXtcbiAgICByaWdodDogMTUwcHg7XG4gICAgY29sb3I6IHRyYW5zcGFyZW50O1xuICB9XG5cbiAgMTAwJXtcbiAgICByaWdodDogLTIwMHB4O1xuICAgIGNvbG9yOiB0cmFuc3BhcmVudDtcbiAgICBkaXNwbGF5OiBub25lO1xuICB9XG59XG5cblxuQGtleWZyYW1lcyBoZWlnaHR7XG4gIDAle1xuICAgIGhlaWdodDogNTB2aDtcbiAgICBtaW4taGVpZ2h0OiAyNTBweDtcbiAgfVxuXG4gIDEwMCV7XG4gICAgaGVpZ2h0OiAwO1xuICAgIG1pbi1oZWlnaHQ6IDEwMHB4O1xuICB9XG59XG5cbkBrZXlmcmFtZXMgbWFyZ2lue1xuICAwJXtcbiAgICBtYXJnaW4tdG9wOiAyNXZoO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgtNTAlKTtcbiAgfVxuICAxMDAle1xuICAgIG1hcmdpbi10b3A6IDBweDtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoMCUpO1xuICB9XG59XG5cbkBrZXlmcmFtZXMgaDF7XG4gIDAle1xuICAgIGZvbnQtc2l6ZTogMTEuMnZ3O1xuICB9XG4gIDEwMCV7XG4gICAgZm9udC1zaXplOiAzcmVtO1xuICB9XG59XG5cblxuQGtleWZyYW1lcyBjYXJke1xuICAwJXt9XG5cbiAgNTAle1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgtNDBweCk7XG4gIH1cblxuICAxMDAle1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgtNTBweCk7XG4gICAgb3BhY2l0eTogMDtcbiAgfVxufVxuXG5Aa2V5ZnJhbWVzIHRleHRlc3Ryb2tle1xuICAwJXtcbiAgICAtd2Via2l0LXRleHQtc3Ryb2tlIDogMXB4O1xuICB9XG5cbiAgMTAwJXtcbiAgICAtd2Via2l0LXRleHQtc3Ryb2tlIDogMHB4O1xuICB9XG59XG4iLCJib2R5e1xuICBwYWRkaW5nOiAwIDIlO1xufVxuXG5oZWFkZXJ7XG4gIC5jb250YWN0e1xuICAgIHotaW5kZXg6IDk5OTk7XG4gICAgcG9zaXRpb246IGZpeGVkO1xuICAgIHRvcDogMjBweDtcbiAgICByaWdodDogMiU7XG4gICAgdHJhbnNmb3JtOiBzY2FsZSgxKTtcbiAgICB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gMC41cyBlYXNlO1xuICAgIGN1cnNvcjogcG9pbnRlcjtcbiAgICAmOmhvdmVye1xuICAgICAgdHJhbnNpdGlvbjogdHJhbnNmb3JtIDAuNXMgZWFzZTtcbiAgICAgIHRyYW5zZm9ybTogc2NhbGUoMC45KTtcbiAgICB9XG4gICAgJi5vcGVue1xuICAgICAgdHJhbnNmb3JtOiBzY2FsZSgxNSk7XG4gICAgICB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gMC41cyBlYXNlO1xuICAgICAgaDN7XG4gICAgICAgIGNvbG9yOiB0cmFuc3BhcmVudDtcbiAgICAgICAgbGVmdDogNTAwcHg7XG4gICAgICAgIHRyYW5zaXRpb246IHZpc2liaWxpdHkgMC41IGVhc2UgbGVmdCAxcyBlYXNlO1xuICAgICAgfVxuICAgICAgaW1ne1xuICAgICAgICBhbmltYXRpb246IHJvdGF0aW9uIDEycyBpbmZpbml0ZSBsaW5lYXI7XG4gICAgICB9XG4gICAgICAmICsgLnR4dHtcbiAgICAgICAgICBwb3NpdGlvbjogZml4ZWQ7XG4gICAgICAgICAgdG9wOiAxOTBweDtcbiAgICAgICAgICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gICAgICAgICAgbGluZS1oZWlnaHQ6IDEuNXJlbTtcbiAgICAgICAgICB6LWluZGV4OiA5OTk5O1xuICAgICAgICAgIGFuaW1hdGlvbjogdHJhbnNsYXRlcmlnaHQgMXMgZWFzZSBmb3J3YXJkcztcbiAgICAgIH1cbiAgICB9XG4gICAgJi5jbG9zZXtcbiAgICAgIHRyYW5zZm9ybTogc2NhbGUoMSk7XG4gICAgICB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gMC41cyAwLjFzIGVhc2U7XG4gICAgICAmICsgLnR4dHtcbiAgICAgICAgICBwb3NpdGlvbjogZml4ZWQ7XG4gICAgICAgICAgdG9wOiAxOTBweDtcbiAgICAgICAgICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gICAgICAgICAgbGluZS1oZWlnaHQ6IDEuNXJlbTtcbiAgICAgICAgICB6LWluZGV4OiA5OTk5O1xuICAgICAgICAgIGFuaW1hdGlvbjogdHJhbnNsYXRlbGVmdCAxcyBlYXNlIGZvcndhcmRzO1xuICAgICAgfVxuICAgICAgaDN7XG4gICAgICAgIGNvbG9yOiB3aGl0ZTtcbiAgICAgICAgbGVmdDogMjBweDtcbiAgICAgICAgdHJhbnNpdGlvbjogY29sb3IgMC41cyAwLjVzIGVhc2UsIGxlZnQgMC41cyBlYXNlO1xuICAgICAgfVxuICAgICAgJjpob3ZlcntcbiAgICAgICAgdHJhbnNpdGlvbjogdHJhbnNmb3JtIDAuNXMgZWFzZSFpbXBvcnRhbnQ7XG4gICAgICAgIHRyYW5zZm9ybTogc2NhbGUoMC45KSFpbXBvcnRhbnQ7XG4gICAgICB9XG4gICAgfVxuICAgIGltZ3tcbiAgICAgIGFuaW1hdGlvbjogcm90YXRpb24gNnMgaW5maW5pdGUgbGluZWFyO1xuICAgIH1cbiAgICBoM3tcbiAgICAgIHRyYW5zZm9ybTogcm90YXRlKDM1ZGVnKTtcbiAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICAgIHRvcDogMzNweDtcbiAgICAgIGxlZnQ6IDIwcHg7XG4gICAgfVxuICAgICYgKyAudHh0e1xuICAgICAgICBwb3NpdGlvbjogZml4ZWQ7XG4gICAgICAgIHJpZ2h0OiAtMjAwcHg7XG4gICAgICAgIHB7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIGgye1xuICAgIG1hcmdpbi1sZWZ0OiAxMHB4O1xuICB9XG59XG5cbi5jYXJke1xuICB3aWR0aDogY2FsYygxMDAlIC8gMyk7XG4gIHBhZGRpbmc6IDAgMTBweDtcbiAgbGluZS1oZWlnaHQ6IDAuOCFpbXBvcnRhbnQ7XG4gIC5jYXJkLWhlYWRlcntcbiAgICBtYXJnaW4tdG9wOiAxMHB4O1xuICAgIGRpc3BsYXk6IGlubGluZS1mbGV4O1xuICAgICYgPiBkaXZ7XG4gICAgICBtYXJnaW4tcmlnaHQ6IDVweDtcbiAgICAgICYgPiBhe1xuICAgICAgICB2ZXJ0aWNhbC1hbGlnbjogIGJhc2VsaW5lO1xuICAgICAgfVxuICAgICAgJiA+IHRpbWV7XG4gICAgICAgIHZlcnRpY2FsLWFsaWduOiAgYmFzZWxpbmU7XG4gICAgICB9XG4gICAgfVxuICB9XG59XG4iLCIvLyBob21lXG5ib2R5e1xuICBoZWlnaHQ6IDEwMCU7XG4gICAuYmxvZ3tcbiAgICBoZWFkZXJ7XG4gICAgICB6LWluZGV4OiA5OTk7XG4gICAgICBwb3NpdGlvbjogZml4ZWQ7XG4gICAgICB0b3A6IDA7XG4gICAgICBsZWZ0OiAyJTtcbiAgICAgIHJpZ2h0OiAyJTtcbiAgICAgIGhlaWdodDogMTAwdmg7XG4gICAgICBkaXNwbGF5OiAtd2Via2l0LWlubGluZS1ib3g7XG4gICAgICBkaXNwbGF5OiAtbXMtaW5saW5lLWZsZXhib3g7XG4gICAgICBkaXNwbGF5OiBpbmxpbmUtZmxleDtcbiAgICAgIC13ZWJraXQtYm94LW9yaWVudDogdmVydGljYWw7XG4gICAgICAtd2Via2l0LWJveC1kaXJlY3Rpb246IG5vcm1hbDtcbiAgICAgICAgICAtbXMtZmxleC1kaXJlY3Rpb246IGNvbHVtbjtcbiAgICAgICAgICAgICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjtcbiAgICAgICYgPiBzZWN0aW9ue1xuICAgICAgICBoZWlnaHQ6IDUwdmg7XG4gICAgICAgIG1pbi1oZWlnaHQ6IDI1MHB4O1xuICAgICAgICB0b3A6IDA7XG4gICAgICAgIGxlZnQ6IDIlO1xuICAgICAgICByaWdodDogMiU7XG4gICAgICAgICYud29ya3MtbWVhe1xuICAgICAgICAgIGRpc3BsYXk6IC13ZWJraXQtaW5saW5lLWJveDtcbiAgICAgICAgICBkaXNwbGF5OiAtbXMtaW5saW5lLWZsZXhib3g7XG4gICAgICAgICAgZGlzcGxheTogaW5saW5lLWZsZXg7XG4gICAgICAgICAgLW1zLWZsZXgtd3JhcDogd3JhcDtcbiAgICAgICAgICAgICAgZmxleC13cmFwOiB3cmFwO1xuICAgICAgICAgIC13ZWJraXQtYm94LWFsaWduOiBlbmQ7XG4gICAgICAgICAgICAgIC1tcy1mbGV4LWFsaWduOiBlbmQ7XG4gICAgICAgICAgICAgICAgICBhbGlnbi1pdGVtczogZmxleC1lbmQ7XG4gICAgICAgICAgaGVpZ2h0OiAxMDAlO1xuICAgICAgICAgIG1hcmdpbi1ib3R0b206IDIwcHg7XG4gICAgICAgIH1cbiAgICAgICAgJi5yZWR1Y3tcbiAgICAgICAgICAtd2Via2l0LWFuaW1hdGlvbjogcmVkdWNmb250IDFzIGVhc2UgZm9yd2FyZHM7XG4gICAgICAgICAgICAgICAgICBhbmltYXRpb246IHJlZHVjZm9udCAxcyBlYXNlIGZvcndhcmRzO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBoMXtcbiAgICAgICAgcG9zaXRpb246IGZpeGVkO1xuICAgICAgICB0b3A6IDgwcHg7XG4gICAgICAgIGZvbnQtc2l6ZTogMTEuMnZ3O1xuICAgICAgICBsaW5lLWhlaWdodDogN3Z3IWltcG9ydGFudDtcbiAgICAgICAgdHJhbnNpdGlvbjogdG9wIDFzIGVhc2U7XG4gICAgICAgIHRyYW5zaXRpb246IGZvbnQtc2l6ZSAxLjJzIGVhc2UsIGNvbG9yIDEuMnMgZWFzZSAsIHRvcCAxLjJzIGVhc2U7XG4gICAgICB9XG4gICAgICBoMntcbiAgICAgICAgcG9zaXRpb246IGZpeGVkO1xuICAgICAgICB0b3A6MjIwcHg7XG4gICAgICAgIGZvbnQtc2l6ZTogMy43dnc7XG4gICAgICAgIG9wYWNpdHk6IDE7XG4gICAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWCgwKTtcbiAgICAgICAgdHJhbnNpdGlvbjogb3BhY2l0eSAxcyBlYXNlIDFzLCB0cmFuc2Zvcm0gMXMgZWFzZSAxcztcbiAgICAgIH1cbiAgICAgIG5hdntcbiAgICAgICAgLy8gbWFyZ2luLXRvcDogMjV2aDsgLyogcG91c3PDqSBkZSBsYSBtb2l0acOpIGRlIGhhdXRldXIgZGUgdmlld3BvcnQgKi9cbiAgICAgICAgLy8gLXdlYmtpdC10cmFuc2Zvcm06IHRyYW5zbGF0ZVkoLTUwJSk7XG4gICAgICAgIC8vICAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKC01MCUpOyAvKiB0aXLDqSBkZSBsYSBtb2l0acOpIGRlIHNhIHByb3ByZSBoYXV0ZXVyICovXG4gICAgICB9XG4gICAgICAuc2Nyb2xse1xuICAgICAgICB3aWR0aDogNTBweDtcbiAgICAgICAgaGVpZ2h0OiAxNXB4O1xuICAgICAgICBtYXJnaW46IDAgYXV0byAxMHB4IGF1dG87XG4gICAgICAgIG92ZXJmbG93OiBoaWRkZW47XG4gICAgICAgIHBvc2l0aW9uOiBmaXhlZDtcbiAgICAgICAgYm90dG9tOiAwO1xuICAgICAgICBsZWZ0OiA1MCU7XG4gICAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTUwJSk7XG4gICAgICAgICAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoLTUwJSk7XG4gICAgICAgICYgPiBwOm50aC1jaGlsZCgxKXtcbiAgICAgICAgICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gICAgICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICAgICAgIHRvcDogLTIwcHg7XG4gICAgICAgICAgLXdlYmtpdC1hbmltYXRpb246IHRyYW5zbGF0ZXAxIDJzIGluZmluaXRlIGxpbmVhcjtcbiAgICAgICAgICAgICAgICAgIGFuaW1hdGlvbjogdHJhbnNsYXRlcDEgMnMgaW5maW5pdGUgbGluZWFyO1xuICAgICAgICB9XG4gICAgICAgICYgPiBwOm50aC1jaGlsZCgyKXtcbiAgICAgICAgICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gICAgICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICAgICAgIHRvcDogLTIwcHg7XG4gICAgICAgICAgLXdlYmtpdC1hbmltYXRpb246IHRyYW5zbGF0ZXAxIDJzIGluZmluaXRlIGxpbmVhcjtcbiAgICAgICAgICAgICAgICAgIGFuaW1hdGlvbjogdHJhbnNsYXRlcDEgMnMgaW5maW5pdGUgbGluZWFyO1xuICAgICAgICAgIC13ZWJraXQtYW5pbWF0aW9uLWRlbGF5OiAxcztcbiAgICAgICAgICAgICAgICAgIGFuaW1hdGlvbi1kZWxheTogMXM7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgICYuaGlkZXtcbiAgICAgICAgJiA+IHNlY3Rpb246Zmlyc3QtY2hpbGR7XG4gICAgICAgICAgaGVpZ2h0OiAxMDBweDtcbiAgICAgICAgICBtaW4taGVpZ2h0OiAxMDBweDtcbiAgICAgICAgICBiYWNrZ3JvdW5kOiB3aGl0ZTtcbiAgICAgICAgICB6LWluZGV4OiA5OTk7XG4gICAgICAgIH1cbiAgICAgICAgbmF2e1xuICAgICAgICAgIG1hcmdpbi10b3A6IDA7XG4gICAgICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHRyYW5zbGF0ZSgwKTtcbiAgICAgICAgICAgICAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKDApO1xuICAgICAgICAgIC13ZWJraXQtdHJhbnNpdGlvbjogbWFyZ2luLXRvcCAxcyBlYXNlLCAtd2Via2l0LXRyYW5zZm9ybSAxcyBlYXNlO1xuICAgICAgICAgIHRyYW5zaXRpb246IG1hcmdpbi10b3AgMXMgZWFzZSwgLXdlYmtpdC10cmFuc2Zvcm0gMXMgZWFzZTtcbiAgICAgICAgICB0cmFuc2l0aW9uOiBtYXJnaW4tdG9wIDFzIGVhc2UsIHRyYW5zZm9ybSAxcyBlYXNlO1xuICAgICAgICAgIHRyYW5zaXRpb246IG1hcmdpbi10b3AgMXMgZWFzZSwgdHJhbnNmb3JtIDFzIGVhc2UsIC13ZWJraXQtdHJhbnNmb3JtIDFzIGVhc2U7XG4gICAgICAgIH1cbiAgICAgICAgaDF7XG4gICAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgICAgICAgIGZvbnQtc2l6ZTogM3JlbTtcbiAgICAgICAgICB0b3A6IDBweDtcbiAgICAgICAgICBjb2xvcjogYmxhY2s7XG4gICAgICAgICAgdHJhbnNpdGlvbjogZm9udC1zaXplIDEuMnMgZWFzZSAwLjVzLCBjb2xvciAxLjJzIGVhc2UgMXMsIHRvcCAxLjJzIGVhc2UgMC41cztcbiAgICAgICAgICBhbmltYXRpb246IHRleHRlc3Ryb2tlIDAuNXMgZWFzZSAxLjJzIGZvcndhcmRzO1xuICAgICAgICB9XG4gICAgICAgIGgye1xuICAgICAgICAgIG9wYWNpdHk6IDA7XG4gICAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVYKC0xMHB4KTtcbiAgICAgICAgICB0b3A6MjIwcHg7XG4gICAgICAgICAgLXdlYmtpdC10cmFuc2l0aW9uOiBvcGFjaXR5IDAuNXMgZWFzZSwgdHJhbnNmb3JtIDAuNXMgZWFzZTtcbiAgICAgICAgICB0cmFuc2l0aW9uOiBvcGFjaXR5IDAuNXMgZWFzZSwgdHJhbnNmb3JtIDAuNXMgZWFzZTtcbiAgICAgICAgfVxuICAgICAgICAuY2FyZHtcbiAgICAgICAgICAtd2Via2l0LWFuaW1hdGlvbjogY2FyZCAxcyBmb3J3YXJkcyBlYXNlO1xuICAgICAgICAgICAgICAgICAgYW5pbWF0aW9uOiBjYXJkIDFzIGZvcndhcmRzIGVhc2U7XG4gICAgICAgIH1cbiAgICAgICAgLnNjcm9sbHtcbiAgICAgICAgICBvcGFjaXR5OiAwO1xuICAgICAgICAgIC13ZWJraXQtdHJhbnNpdGlvbjogb3BhY2l0eSAwLjhzIGVhc2U7XG4gICAgICAgICAgdHJhbnNpdGlvbjogb3BhY2l0eSAwLjhzIGVhc2U7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cbi8vIGh0bWx7XG4vLyBcdFx0XHRvdmVyZmxvdzogaGlkZGVuO1xuLy8gXHRcdH1cbi8vIFx0XHRodG1sLCBib2R5IHtcbi8vIFx0XHRcdHBhZGRpbmc6IDA7XG4vLyBcdFx0XHRtYXJnaW46IDA7XG4vL1xuLy8gXHRcdH1cbi8vIFx0XHRib2R5IHtcbi8vIFx0XHRcdHBvc2l0aW9uOiBhYnNvbHV0ZTtcbi8vIFx0XHRcdGxlZnQ6IDA7XG4vLyBcdFx0XHR0b3A6IDA7XG4vLyBcdFx0XHRyaWdodDogLTMwcHg7XG4vLyBcdFx0XHRib3R0b206IDA7XG4vLyBcdFx0XHRwYWRkaW5nLXJpZ2h0OiAxNXB4O1xuLy8gXHRcdFx0b3ZlcmZsb3cteTogc2Nyb2xsO1xuLy8gXHRcdH1cblxuLy8gcGFnZVxuYm9keXtcbiAgJiA+IC5pdGVte1xuICAgIGhlYWRlcntcbiAgICAgIGhlaWdodDogMTMwcHg7XG4gICAgICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gICAgICBuYXZ7XG4gICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICAgICAgbWFyZ2luLXRvcDogNjVweDsgLyogcG91c3PDqSBkZSBsYSBtb2l0acOpIGRlIGhhdXRldXIgZGUgdmlld3BvcnQgKi9cbiAgICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHRyYW5zbGF0ZVkoLTUwJSk7XG4gICAgICAgICAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKC01MCUpOyAvKiB0aXLDqSBkZSBsYSBtb2l0acOpIGRlIHNhIHByb3ByZSBoYXV0ZXVyICovXG4gICAgICB9XG4gICAgICBoMXtcbiAgICAgICAgY29sb3I6IGJsYWNrO1xuICAgICAgfVxuICAgICAgaDJ7XG4gICAgICAgIGRpc3BsYXk6IG5vbmU7XG4gICAgICB9XG4gICAgICAuc2Nyb2xse1xuICAgICAgICBkaXNwbGF5OiBub25lO1xuICAgICAgfVxuICAgIH1cbiAgfVxufVxuIiwiI2JvZHktd3JhcHBlcntcbiAgI2l0ZW1ze1xuICAgICAgLndvcmtzLW1lYXtcbiAgICAgICAgZGlzcGxheTogaW5saW5lLWZsZXg7XG4gICAgICAgIHdpZHRoOiAxMDAlO1xuICAgICAgICBoZWlnaHQ6IDUwdmg7XG4gICAgICAgIGFsaWduLWl0ZW1zOiBmbGV4LWVuZDtcbiAgICAgICAgLmNhcmR7XG4gICAgICAgICAgLmNhcmQtaW1hZ2V7XG4gICAgICAgICAgICBpbWd7XG4gICAgICAgICAgICAgIHdpZHRoOiAxMDAlO1xuICAgICAgICAgICAgICBoZWlnaHQ6IGF1dG87XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIC5jYXJkLWhlYWRlcntcbiAgICAgICAgICAgIG1hcmdpbi10b3A6IDVweDtcbiAgICAgICAgICB9XG4gICAgICAgICAgLmNhcmQtZm9vdGVye1xuICAgICAgICAgICAgbWFyZ2luLXRvcDogNXB4O1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgLmNhdC1saXN0e1xuICAgICAgICB3aWR0aDogMTAwJTtcbiAgICAgICAgaGVpZ2h0OiBhdXRvO1xuICAgICAgICBib3JkZXItdG9wOiAxcHggc29saWQgYmxhY2s7XG4gICAgICAgIGJvcmRlci1ib3R0b206IDFweCBzb2xpZCBibGFjaztcbiAgICAgICAgcGFkZGluZzogMTBweCAwO1xuICAgICAgICBtYXJnaW46ICAyMHB4IDA7XG4gICAgICAgIHVse1xuICAgICAgICAgIGRpc3BsYXk6IGlubGluZS1mbGV4O1xuICAgICAgICAgIGxpe1xuICAgICAgICAgICAgbWFyZ2luLXJpZ2h0OiAxMHB4O1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgLmZ1bGwtd29ya3N7XG4gICAgICAgIGRpc3BsYXk6IGlubGluZS1mbGV4O1xuICAgICAgICBmbGV4LXdyYXA6IHdyYXA7XG4gICAgICB9XG4gIH1cbn1cbiIsImJvZHl7XG4gICNzdGFydHtcbiAgICB3aWR0aDogMTAwJTtcbiAgICBoZWlnaHQ6IDEwMCU7XG4gIH1cbiAgI3BhZ2Utd3JhcHBlci5ibG9ne1xuICAgICNzdGFydHtcbiAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgICAgIHRvcDogMjAwdmg7XG4gICAgICBsZWZ0OiAwO1xuICAgICAgcGFkZGluZy10b3A6IDExMHB4O1xuICAgICAgb3BhY2l0eTogMDtcbiAgICAgICYudmlzaWJsZXtcbiAgICAgICAgb3BhY2l0eTogMTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cblxuI2l0ZW1ze1xuICAuY2F0LWxpc3R7XG4gICAgcGFkZGluZzogMCA1cHg7XG4gICAgdWx7XG4gICAgICBsaXtcbiAgICAgICAgZGlzcGxheTogZmxleDtcbiAgICAgICAgJjo6YWZ0ZXJ7XG4gICAgICAgICAgd2lkdGg6IDVweDtcbiAgICAgICAgICBjb250ZW50OiBcIiBcIjtcbiAgICAgICAgfVxuICAgICAgICAmOmhvdmVye1xuICAgICAgICAgICY6OmFmdGVye1xuICAgICAgICAgICAgY29udGVudDogXCJcXDAwMjAyMlwiO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuICAuZnVsbC13b3Jrc3tcbiAgICB3aWR0aDogMTAwJTtcbiAgfVxuICAuY2FyZHtcbiAgICBtYXJnaW4tYm90dG9tOiAxMHB4O1xuICAgIG1pbi13aWR0aDogMjAwcHg7XG4gIH1cbiAgJi5maXhlZHtcbiAgICAvLyAuY2F0LWxpc3R7XG4gICAgLy8gICBwb3NpdGlvbjogZml4ZWQ7XG4gICAgLy8gICB0b3A6IDgwcHg7XG4gICAgLy8gICB6LWluZGV4OiA5OTk7XG4gICAgLy8gICBiYWNrZ3JvdW5kOiB3aGl0ZTtcbiAgICAvLyB9XG4gICAgLy8gLmZ1bGwtd29ya3N7XG4gICAgLy8gICBtYXJnaW4tdG9wOiA2MHB4O1xuICAgIC8vIH1cbiAgfVxufVxuIiwiYm9keXtcbiAgJiA+Lml0ZW17XG4gICAgI2JvZHktd3JhcHBlci5ibG9nLWxpc3Rpbmd7XG4gICAgICBib3JkZXItdG9wOiAxcHggc29saWQgYmxhY2s7XG4gICAgICBwYWRkaW5nLXRvcDogMjBweDtcbiAgICAgIC5pbWctY2FyZHtcbiAgICAgICAgd2lkdGg6IDQ5JTtcbiAgICAgICAgbWFyZ2luLWJvdHRvbTogMjBweDtcbiAgICAgIH1cbiAgICAgIC5oZWFkZXItaXRlbXtcbiAgICAgICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgICAgICAudGl0bGV7XG4gICAgICAgICAgZGlzcGxheTogaW5saW5lLWZsZXg7XG4gICAgICAgICAgYWxpZ24taXRlbXM6IGJhc2VsaW5lO1xuICAgICAgICAgIGgye1xuICAgICAgICAgICAgbWFyZ2luLXJpZ2h0OiA1cHg7XG4gICAgICAgICAgfVxuICAgICAgICAgIHRpbWV7XG4gICAgICAgICAgICBtYXJnaW4tcmlnaHQ6IDVweDtcbiAgICAgICAgICB9XG4gICAgICAgICAgc3BhbntcbiAgICAgICAgICAgIG1hcmdpbi1yaWdodDogNXB4O1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICAuY29udGVudHtcbiAgICAgICAgICB3aWR0aDogY2FsYygxMDAlIC8gMyk7XG4gICAgICAgICAgbWFyZ2luOiAyMHB4ICAwO1xuICAgICAgICB9XG4gICAgICAgIC5wcmV2LW5leHR7XG4gICAgICAgICAgd2lkdGg6IGNhbGMoMTAwJSAvIDMpO1xuICAgICAgICAgIHBhZGRpbmctbGVmdDogMjBweDtcbiAgICAgICAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgICAgICAgdG9wOiAwO1xuICAgICAgICAgIHJpZ2h0OiAwO1xuICAgICAgICAgIC5idG4taW5mb3tcbiAgICAgICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICAgICAgICAgIHRyYW5zZm9ybTogcm90YXRlKC0xNWRlZyk7XG4gICAgICAgICAgICBtYXJnaW4tbGVmdDogNDBweDtcbiAgICAgICAgICAgIG1hcmdpbi10b3A6IDIwcHg7XG4gICAgICAgICAgfVxuICAgICAgICAgIGltZ3tcbiAgICAgICAgICAgIGZsb2F0OiByaWdodDtcbiAgICAgICAgICAgIHdpZHRoOiA4MCU7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIC50b3B7XG4gICAgICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gICAgICBtYXJnaW46IDIwcHggYXV0byAyMHB4IGF1dG87XG4gICAgICB3aWR0aDogNTBweDtcbiAgICAgIGltZ3tcbiAgICAgICAgd2lkdGg6IDIwcHg7XG4gICAgICAgIG1hcmdpbi1ib3R0b206NXB4O1xuICAgICAgfVxuICAgICAgcHtcbiAgICAgICAgdHJhbnNmb3JtOiByb3RhdGUoLTEwZGVnKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cbiIsIiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FDQUEsQUFBQSxDQUFDLENBQUEsRUFDQyxrQkFBa0IsRUFBRyxVQUFVLEVBQ3ZCLFVBQVUsRUFBRyxVQUFVLEdBQ2hDOztBQUNELDRFQUE0RTtBQUU1RSx5RkFDZ0Y7QUFFaEYsd0hBR0c7QUFFSCxBQUFBLElBQUksQ0FBQyxFQUNILE1BQU0sRUFBRSxDQUFDLEVBQ1QsT0FBTyxFQUFFLENBQUMsRUFDVixTQUFTLEVBQUUsSUFBSSxFQUNmLFdBQVcsRUFBRSxJQUFJLEVBQUUsT0FBTyxDQUMxQix3QkFBd0IsRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUN4Qzs7QUFFRCx5RkFDZ0Y7QUFFaEYseUNBRUc7QUFFSCxBQUFB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QUFpQixDQUFDLGFBQWEsQ0FBQyxpQkFBaUIsQ0FBQSxFQUFDLEtBQUssRUFBQyxHQUFHLEVBQUMsTUFBTSxFQUFDLE9BQU8sRUFBQyxRQUFRLEVBQUMsUUFBUSxFQUFDLE1BQU0sRUFBQyxJQUFJLEVBQUMsR0FBRyxFQUFDLENBQUMsRUFBQyxNQUFNLEVBQUMsQ0FBQyxFQUFDLElBQUksRUFBQyxDQUFDLEVBQUMsS0FBSyxFQUFDLENBQUMsR0FBRTs7QUFBRCxBQUFBLGlCQUFpQixBQUFBLDRCQUE0QixDQUFDLGFBQWEsQ0FBQyxpQkFBaUIsRUFBQyxZQUFZLEFBQUEsNEJBQTRCLENBQUMsYUFBYSxDQUFDLGlCQUFpQixFQUFDLGlCQUFpQixBQUFBLDRCQUE0QixDQUFDLGFBQWEsQ0FBQyxpQkFBaUIsRUFBQyxZQUFZLEFBQUEsNEJBQTRCLENBQUMsYUFBYSxDQUFDLGlCQUFpQixFQUFDLGVBQWUsQUFBQSw0QkFBNEIsQ0FBQyxhQUFhLENBQUMsaUJBQWlCLEVBQUMsVUFBVSxBQUFBLDRCQUE0QixDQUFDLGFBQWEsQ0FBQyxpQkFBaUIsQ0FBQSxFQUFDLE1BQU0sRUFBQyxHQUFHLEVBQUMsTUFBTSxFQUFDLE9BQU8sRUFBQyxRQUFRLEVBQUMsUUFBUSxFQUFDLEtBQUssRUFBQyxJQUFJLEVBQUMsR0FBRyxFQUFDLENBQUMsRUFBQyxNQUFNLEVBQUMsQ0FBQyxFQUFDLElBQUksRUFBQyxDQUFDLEVBQUMsS0FBSyxFQUFDLENBQUMsR0FBRTs7QUFBRCxBQUFBLGlCQUFpQixBQUFBLDRCQUE0QixDQUFDLGlCQUFpQixFQUFDLFlBQVksQUFBQSw0QkFBNEIsQ0FBQyxpQkFBaUIsRUFBQyxpQkFBaUIsQUFBQSw0QkFBNEIsQ0FBQyxpQkFBaUIsRUFBQyxZQUFZLEFBQUEsNEJBQTRCLENBQUMsaUJBQWlCLEVBQUMsZUFBZSxBQUFBLDRCQUE0QixDQUFDLGlCQUFpQixFQUFDLFVBQVUsQUFBQSw0QkFBNEIsQ0FBQyxpQkFBaUIsQ0FBQSxFQUFDLEtBQUssRUFBQyxJQUFJLEVBQUMsTUFBTSxFQUFDLElBQUks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 */
+@media screen and (max-width: 960px) { body #page-wrapper.item #body-wrapper.blog-listing .header-item .content { width: calc((100% / 2) - 5px); }
+ body #page-wrapper.item #body-wrapper.blog-listing .header-item .prev-next { width: calc((100% / 2) - 5px); }
+ body #page-wrapper.item #body-wrapper.blog-listing .img-card { width: calc((100% / 2) - 5px); } }
+
+@media screen and (max-width: 600px) { body #page-wrapper.item #body-wrapper.blog-listing .header-item .content { width: calc((100%) - 15px); }
+ body #page-wrapper.item #body-wrapper.blog-listing .header-item .prev-next { width: calc((100%) - 15px); position: relative; transform: translateY(0%); }
+ body #page-wrapper.item #body-wrapper.blog-listing .header-item .prev-next .btn .btn-info { display: none; }
+ body #page-wrapper.item #body-wrapper.blog-listing .header-item .prev-next .btn img { float: none; width: 100%; }
+ body #page-wrapper.item #body-wrapper.blog-listing .img-card { width: 100%; } }
+
+/*# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGhlbWUuY3NzIiwic291cmNlcyI6WyJ0aGVtZS5zY3NzIiwiY29uZmlndXJhdGlvbnMvX3Jlc2V0LnNjc3MiLCJjb25maWd1cmF0aW9ucy9fZm9udHMuc2NzcyIsImNvbmZpZ3VyYXRpb25zL192YXJpYWJsZXMuc2NzcyIsImNvbmZpZ3VyYXRpb25zL19taXhpbnMuc2NzcyIsInRoZW1lL190eXBvZ3JhcGh5LnNjc3MiLCJ0aGVtZS9fbUN1c3RvbVNjcm9sbGJhci5zY3NzIiwiY29uZmlndXJhdGlvbnMvX2FuaW1hdGlvbi5zY3NzIiwidGhlbWUvYWxsLnNjc3MiLCJ0aGVtZS9faGVhZGVyLnNjc3MiLCJ0aGVtZS9faG9tZS5zY3NzIiwidGhlbWUvX2Jsb2cuc2NzcyIsInRoZW1lL2l0ZW0uc2NzcyJdLCJzb3VyY2VzQ29udGVudCI6WyJAaW1wb3J0ICdjb25maWd1cmF0aW9ucy9yZXNldCc7XG5AaW1wb3J0ICdjb25maWd1cmF0aW9ucy9mb250cyc7XG5AaW1wb3J0ICdjb25maWd1cmF0aW9ucy92YXJpYWJsZXMnO1xuXG5AaW1wb3J0ICdjb25maWd1cmF0aW9ucy9taXhpbnMnO1xuQGltcG9ydCAndGhlbWUvdHlwb2dyYXBoeSc7XG5cbkBpbXBvcnQgJ3RoZW1lL21DdXN0b21TY3JvbGxiYXInO1xuQGltcG9ydCAnY29uZmlndXJhdGlvbnMvYW5pbWF0aW9uJztcblxuQGltcG9ydCAndGhlbWUvYWxsJztcbkBpbXBvcnQgJ3RoZW1lL2hlYWRlcic7XG5AaW1wb3J0ICd0aGVtZS9ob21lJztcbkBpbXBvcnQgJ3RoZW1lL2Jsb2cnO1xuQGltcG9ydCAndGhlbWUvaXRlbSc7XG4vLyBAaW1wb3J0ICd0aGVtZS9mb290ZXInO1xuIiwiKntcbiAgLXdlYmtpdC1ib3gtc2l6aW5nIDogYm9yZGVyLWJveDtcbiAgICAgICAgICBib3gtc2l6aW5nIDogYm9yZGVyLWJveDtcbn1cbi8qISBub3JtYWxpemUuY3NzIHY4LjAuMSB8IE1JVCBMaWNlbnNlIHwgZ2l0aHViLmNvbS9uZWNvbGFzL25vcm1hbGl6ZS5jc3MgKi9cblxuLyogRG9jdW1lbnRcbiAgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09ICovXG5cbi8qKlxuICogMS4gQ29ycmVjdCB0aGUgbGluZSBoZWlnaHQgaW4gYWxsIGJyb3dzZXJzLlxuICogMi4gUHJldmVudCBhZGp1c3RtZW50cyBvZiBmb250IHNpemUgYWZ0ZXIgb3JpZW50YXRpb24gY2hhbmdlcyBpbiBpT1MuXG4gKi9cblxuaHRtbCB7XG4gIG1hcmdpbjogMDtcbiAgcGFkZGluZzogMDtcbiAgZm9udC1zaXplOiAxOHB4O1xuICBsaW5lLWhlaWdodDogMS4xNTsgLyogMSAqL1xuICAtd2Via2l0LXRleHQtc2l6ZS1hZGp1c3Q6IDEwMCU7IC8qIDIgKi9cbiAgaGVpZ2h0OiAxMDAlO1xuICB3aWR0aDogMTAwJTtcbn1cblxuLyogU2VjdGlvbnNcbiAgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09ICovXG5cbi8qKlxuICogUmVtb3ZlIHRoZSBtYXJnaW4gaW4gYWxsIGJyb3dzZXJzLlxuICovXG5cbmJvZHkge1xuICBtYXJnaW46IDA7XG4gIHBhZGRpbmc6IDA7XG4gIGhlaWdodDogMTAwJTtcbiAgd2lkdGg6IDEwMCU7XG59XG5cbi8qKlxuICogUmVuZGVyIHRoZSBgbWFpbmAgZWxlbWVudCBjb25zaXN0ZW50bHkgaW4gSUUuXG4gKi9cblxubWFpbiB7XG4gIGRpc3BsYXk6IGJsb2NrO1xufVxuXG4vKipcbiAqIENvcnJlY3QgdGhlIGZvbnQgc2l6ZSBhbmQgbWFyZ2luIG9uIGBoMWAgZWxlbWVudHMgd2l0aGluIGBzZWN0aW9uYCBhbmRcbiAqIGBhcnRpY2xlYCBjb250ZXh0cyBpbiBDaHJvbWUsIEZpcmVmb3gsIGFuZCBTYWZhcmkuXG4gKi9cblxuaDEge1xuICBmb250LXNpemU6IDNlbTtcbiAgbWFyZ2luOjA7XG59XG5cbmgxLGgyLGgzLGg0LGg1LHAsZW0sc3Ryb25nLHVsLGxpLGF7XG4gIG1hcmdpbjowO1xuICBwYWRkaW5nOjA7XG59XG4vKiBHcm91cGluZyBjb250ZW50XG4gICA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSAqL1xuXG4vKipcbiAqIDEuIEFkZCB0aGUgY29ycmVjdCBib3ggc2l6aW5nIGluIEZpcmVmb3guXG4gKiAyLiBTaG93IHRoZSBvdmVyZmxvdyBpbiBFZGdlIGFuZCBJRS5cbiAqL1xuXG5ociB7XG4gIC13ZWJraXQtYm94LXNpemluZzogY29udGVudC1ib3g7XG4gICAgICAgICAgYm94LXNpemluZzogY29udGVudC1ib3g7IC8qIDEgKi9cbiAgaGVpZ2h0OiAwOyAvKiAxICovXG4gIG92ZXJmbG93OiB2aXNpYmxlOyAvKiAyICovXG59XG5cbi8qKlxuICogMS4gQ29ycmVjdCB0aGUgaW5oZXJpdGFuY2UgYW5kIHNjYWxpbmcgb2YgZm9udCBzaXplIGluIGFsbCBicm93c2Vycy5cbiAqIDIuIENvcnJlY3QgdGhlIG9kZCBgZW1gIGZvbnQgc2l6aW5nIGluIGFsbCBicm93c2Vycy5cbiAqL1xuXG5wcmUge1xuICBmb250LWZhbWlseTogbW9ub3NwYWNlLCBtb25vc3BhY2U7IC8qIDEgKi9cbiAgZm9udC1zaXplOiAxZW07IC8qIDIgKi9cbn1cblxuLyogVGV4dC1sZXZlbCBzZW1hbnRpY3NcbiAgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09ICovXG5cbi8qKlxuICogUmVtb3ZlIHRoZSBncmF5IGJhY2tncm91bmQgb24gYWN0aXZlIGxpbmtzIGluIElFIDEwLlxuICovXG51bHtcbiAgbGlzdC1zdHlsZTogbm9uZTtcbn1cbnVsLGxpe1xuICBtYXJnaW46IDA7XG4gIHBhZGRpbmc6IDA7XG59XG5cbmEge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDtcbiAgY29sb3I6IGJsYWNrO1xuICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG59XG5cbi8qKlxuICogMS4gUmVtb3ZlIHRoZSBib3R0b20gYm9yZGVyIGluIENocm9tZSA1Ny1cbiAqIDIuIEFkZCB0aGUgY29ycmVjdCB0ZXh0IGRlY29yYXRpb24gaW4gQ2hyb21lLCBFZGdlLCBJRSwgT3BlcmEsIGFuZCBTYWZhcmkuXG4gKi9cblxuYWJiclt0aXRsZV0ge1xuICBib3JkZXItYm90dG9tOiBub25lOyAvKiAxICovXG4gIHRleHQtZGVjb3JhdGlvbjogdW5kZXJsaW5lOyAvKiAyICovXG4gIC13ZWJraXQtdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmUgZG90dGVkO1xuICAgICAgICAgIHRleHQtZGVjb3JhdGlvbjogdW5kZXJsaW5lIGRvdHRlZDsgLyogMiAqL1xufVxuXG4vKipcbiAqIEFkZCB0aGUgY29ycmVjdCBmb250IHdlaWdodCBpbiBDaHJvbWUsIEVkZ2UsIGFuZCBTYWZhcmkuXG4gKi9cblxuYixcbnN0cm9uZyB7XG4gIGZvbnQtd2VpZ2h0OiBib2xkZXI7XG59XG5cbi8qKlxuICogMS4gQ29ycmVjdCB0aGUgaW5oZXJpdGFuY2UgYW5kIHNjYWxpbmcgb2YgZm9udCBzaXplIGluIGFsbCBicm93c2Vycy5cbiAqIDIuIENvcnJlY3QgdGhlIG9kZCBgZW1gIGZvbnQgc2l6aW5nIGluIGFsbCBicm93c2Vycy5cbiAqL1xuXG5jb2RlLFxua2JkLFxuc2FtcCB7XG4gIGZvbnQtZmFtaWx5OiBtb25vc3BhY2UsIG1vbm9zcGFjZTsgLyogMSAqL1xuICBmb250LXNpemU6IDFlbTsgLyogMiAqL1xufVxuXG4vKipcbiAqIEFkZCB0aGUgY29ycmVjdCBmb250IHNpemUgaW4gYWxsIGJyb3dzZXJzLlxuICovXG5cbnNtYWxsIHtcbiAgZm9udC1zaXplOiA4MCU7XG59XG5cbi8qKlxuICogUHJldmVudCBgc3ViYCBhbmQgYHN1cGAgZWxlbWVudHMgZnJvbSBhZmZlY3RpbmcgdGhlIGxpbmUgaGVpZ2h0IGluXG4gKiBhbGwgYnJvd3NlcnMuXG4gKi9cblxuc3ViLFxuc3VwIHtcbiAgZm9udC1zaXplOiA3NSU7XG4gIGxpbmUtaGVpZ2h0OiAwO1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIHZlcnRpY2FsLWFsaWduOiBiYXNlbGluZTtcbn1cblxuc3ViIHtcbiAgYm90dG9tOiAtMC4yNWVtO1xufVxuXG5zdXAge1xuICB0b3A6IC0wLjVlbTtcbn1cblxuLyogRW1iZWRkZWQgY29udGVudFxuICAgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0gKi9cblxuLyoqXG4gKiBSZW1vdmUgdGhlIGJvcmRlciBvbiBpbWFnZXMgaW5zaWRlIGxpbmtzIGluIElFIDEwLlxuICovXG5cbmltZyB7XG4gIGJvcmRlci1zdHlsZTogbm9uZTtcbiAgd2lkdGg6IDEwMCU7XG4gIGhlaWdodDogMTAwJTtcbiAgdmVydGljYWwtYWxpZ246IGJvdHRvbTtcbn1cblxuaWZyYW1le1xuICBib3JkZXI6IG5vbmU7XG59XG4vKiBGb3Jtc1xuICAgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0gKi9cblxuLyoqXG4gKiAxLiBDaGFuZ2UgdGhlIGZvbnQgc3R5bGVzIGluIGFsbCBicm93c2Vycy5cbiAqIDIuIFJlbW92ZSB0aGUgbWFyZ2luIGluIEZpcmVmb3ggYW5kIFNhZmFyaS5cbiAqL1xuXG5idXR0b24sXG5pbnB1dCxcbm9wdGdyb3VwLFxuc2VsZWN0LFxudGV4dGFyZWEge1xuICBmb250LWZhbWlseTogaW5oZXJpdDsgLyogMSAqL1xuICBmb250LXNpemU6IDEwMCU7IC8qIDEgKi9cbiAgbGluZS1oZWlnaHQ6IDEuMTU7IC8qIDEgKi9cbiAgbWFyZ2luOiAwOyAvKiAyICovXG59XG5cbi8qKlxuICogU2hvdyB0aGUgb3ZlcmZsb3cgaW4gSUUuXG4gKiAxLiBTaG93IHRoZSBvdmVyZmxvdyBpbiBFZGdlLlxuICovXG5cbmJ1dHRvbixcbmlucHV0IHsgLyogMSAqL1xuICBvdmVyZmxvdzogdmlzaWJsZTtcbn1cblxuLyoqXG4gKiBSZW1vdmUgdGhlIGluaGVyaXRhbmNlIG9mIHRleHQgdHJhbnNmb3JtIGluIEVkZ2UsIEZpcmVmb3gsIGFuZCBJRS5cbiAqIDEuIFJlbW92ZSB0aGUgaW5oZXJpdGFuY2Ugb2YgdGV4dCB0cmFuc2Zvcm0gaW4gRmlyZWZveC5cbiAqL1xuXG5idXR0b24sXG5zZWxlY3QgeyAvKiAxICovXG4gIHRleHQtdHJhbnNmb3JtOiBub25lO1xufVxuXG4vKipcbiAqIENvcnJlY3QgdGhlIGluYWJpbGl0eSB0byBzdHlsZSBjbGlja2FibGUgdHlwZXMgaW4gaU9TIGFuZCBTYWZhcmkuXG4gKi9cblxuYnV0dG9uLFxuW3R5cGU9XCJidXR0b25cIl0sXG5bdHlwZT1cInJlc2V0XCJdLFxuW3R5cGU9XCJzdWJtaXRcIl0ge1xuICAtd2Via2l0LWFwcGVhcmFuY2U6IGJ1dHRvbjtcbn1cblxuLyoqXG4gKiBSZW1vdmUgdGhlIGlubmVyIGJvcmRlciBhbmQgcGFkZGluZyBpbiBGaXJlZm94LlxuICovXG5cbmJ1dHRvbjo6LW1vei1mb2N1cy1pbm5lcixcblt0eXBlPVwiYnV0dG9uXCJdOjotbW96LWZvY3VzLWlubmVyLFxuW3R5cGU9XCJyZXNldFwiXTo6LW1vei1mb2N1cy1pbm5lcixcblt0eXBlPVwic3VibWl0XCJdOjotbW96LWZvY3VzLWlubmVyIHtcbiAgYm9yZGVyLXN0eWxlOiBub25lO1xuICBwYWRkaW5nOiAwO1xufVxuXG4vKipcbiAqIFJlc3RvcmUgdGhlIGZvY3VzIHN0eWxlcyB1bnNldCBieSB0aGUgcHJldmlvdXMgcnVsZS5cbiAqL1xuXG5idXR0b246LW1vei1mb2N1c3JpbmcsXG5bdHlwZT1cImJ1dHRvblwiXTotbW96LWZvY3VzcmluZyxcblt0eXBlPVwicmVzZXRcIl06LW1vei1mb2N1c3JpbmcsXG5bdHlwZT1cInN1Ym1pdFwiXTotbW96LWZvY3VzcmluZyB7XG4gIG91dGxpbmU6IDFweCBkb3R0ZWQgQnV0dG9uVGV4dDtcbn1cblxuLyoqXG4gKiBDb3JyZWN0IHRoZSBwYWRkaW5nIGluIEZpcmVmb3guXG4gKi9cblxuZmllbGRzZXQge1xuICBwYWRkaW5nOiAwLjM1ZW0gMC43NWVtIDAuNjI1ZW07XG59XG5cbi8qKlxuICogMS4gQ29ycmVjdCB0aGUgdGV4dCB3cmFwcGluZyBpbiBFZGdlIGFuZCBJRS5cbiAqIDIuIENvcnJlY3QgdGhlIGNvbG9yIGluaGVyaXRhbmNlIGZyb20gYGZpZWxkc2V0YCBlbGVtZW50cyBpbiBJRS5cbiAqIDMuIFJlbW92ZSB0aGUgcGFkZGluZyBzbyBkZXZlbG9wZXJzIGFyZSBub3QgY2F1Z2h0IG91dCB3aGVuIHRoZXkgemVybyBvdXRcbiAqICAgIGBmaWVsZHNldGAgZWxlbWVudHMgaW4gYWxsIGJyb3dzZXJzLlxuICovXG5cbmxlZ2VuZCB7XG4gIC13ZWJraXQtYm94LXNpemluZzogYm9yZGVyLWJveDtcbiAgICAgICAgICBib3gtc2l6aW5nOiBib3JkZXItYm94OyAvKiAxICovXG4gIGNvbG9yOiBpbmhlcml0OyAvKiAyICovXG4gIGRpc3BsYXk6IHRhYmxlOyAvKiAxICovXG4gIG1heC13aWR0aDogMTAwJTsgLyogMSAqL1xuICBwYWRkaW5nOiAwOyAvKiAzICovXG4gIHdoaXRlLXNwYWNlOiBub3JtYWw7IC8qIDEgKi9cbn1cblxuLyoqXG4gKiBBZGQgdGhlIGNvcnJlY3QgdmVydGljYWwgYWxpZ25tZW50IGluIENocm9tZSwgRmlyZWZveCwgYW5kIE9wZXJhLlxuICovXG5cbnByb2dyZXNzIHtcbiAgdmVydGljYWwtYWxpZ246IGJhc2VsaW5lO1xufVxuXG4vKipcbiAqIFJlbW92ZSB0aGUgZGVmYXVsdCB2ZXJ0aWNhbCBzY3JvbGxiYXIgaW4gSUUgMTArLlxuICovXG5cbnRleHRhcmVhIHtcbiAgb3ZlcmZsb3c6IGF1dG87XG59XG5cbi8qKlxuICogMS4gQWRkIHRoZSBjb3JyZWN0IGJveCBzaXppbmcgaW4gSUUgMTAuXG4gKiAyLiBSZW1vdmUgdGhlIHBhZGRpbmcgaW4gSUUgMTAuXG4gKi9cblxuW3R5cGU9XCJjaGVja2JveFwiXSxcblt0eXBlPVwicmFkaW9cIl0ge1xuICAtd2Via2l0LWJveC1zaXppbmc6IGJvcmRlci1ib3g7XG4gICAgICAgICAgYm94LXNpemluZzogYm9yZGVyLWJveDsgLyogMSAqL1xuICBwYWRkaW5nOiAwOyAvKiAyICovXG59XG5cbi8qKlxuICogQ29ycmVjdCB0aGUgY3Vyc29yIHN0eWxlIG9mIGluY3JlbWVudCBhbmQgZGVjcmVtZW50IGJ1dHRvbnMgaW4gQ2hyb21lLlxuICovXG5cblt0eXBlPVwibnVtYmVyXCJdOjotd2Via2l0LWlubmVyLXNwaW4tYnV0dG9uLFxuW3R5cGU9XCJudW1iZXJcIl06Oi13ZWJraXQtb3V0ZXItc3Bpbi1idXR0b24ge1xuICBoZWlnaHQ6IGF1dG87XG59XG5cbi8qKlxuICogMS4gQ29ycmVjdCB0aGUgb2RkIGFwcGVhcmFuY2UgaW4gQ2hyb21lIGFuZCBTYWZhcmkuXG4gKiAyLiBDb3JyZWN0IHRoZSBvdXRsaW5lIHN0eWxlIGluIFNhZmFyaS5cbiAqL1xuXG5bdHlwZT1cInNlYXJjaFwiXSB7XG4gIC13ZWJraXQtYXBwZWFyYW5jZTogdGV4dGZpZWxkOyAvKiAxICovXG4gIG91dGxpbmUtb2Zmc2V0OiAtMnB4OyAvKiAyICovXG59XG5cbi8qKlxuICogUmVtb3ZlIHRoZSBpbm5lciBwYWRkaW5nIGluIENocm9tZSBhbmQgU2FmYXJpIG9uIG1hY09TLlxuICovXG5cblt0eXBlPVwic2VhcmNoXCJdOjotd2Via2l0LXNlYXJjaC1kZWNvcmF0aW9uIHtcbiAgLXdlYmtpdC1hcHBlYXJhbmNlOiBub25lO1xufVxuXG4vKipcbiAqIDEuIENvcnJlY3QgdGhlIGluYWJpbGl0eSB0byBzdHlsZSBjbGlja2FibGUgdHlwZXMgaW4gaU9TIGFuZCBTYWZhcmkuXG4gKiAyLiBDaGFuZ2UgZm9udCBwcm9wZXJ0aWVzIHRvIGBpbmhlcml0YCBpbiBTYWZhcmkuXG4gKi9cblxuOjotd2Via2l0LWZpbGUtdXBsb2FkLWJ1dHRvbiB7XG4gIC13ZWJraXQtYXBwZWFyYW5jZTogYnV0dG9uOyAvKiAxICovXG4gIGZvbnQ6IGluaGVyaXQ7IC8qIDIgKi9cbn1cblxuLyogSW50ZXJhY3RpdmVcbiAgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09ICovXG5cbi8qXG4gKiBBZGQgdGhlIGNvcnJlY3QgZGlzcGxheSBpbiBFZGdlLCBJRSAxMCssIGFuZCBGaXJlZm94LlxuICovXG5cbmRldGFpbHMge1xuICBkaXNwbGF5OiBibG9jaztcbn1cblxuLypcbiAqIEFkZCB0aGUgY29ycmVjdCBkaXNwbGF5IGluIGFsbCBicm93c2Vycy5cbiAqL1xuXG5zdW1tYXJ5IHtcbiAgZGlzcGxheTogbGlzdC1pdGVtO1xufVxuXG4vKiBNaXNjXG4gICA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSAqL1xuXG4vKipcbiAqIEFkZCB0aGUgY29ycmVjdCBkaXNwbGF5IGluIElFIDEwKy5cbiAqL1xuXG50ZW1wbGF0ZSB7XG4gIGRpc3BsYXk6IG5vbmU7XG59XG5cbi8qKlxuICogQWRkIHRoZSBjb3JyZWN0IGRpc3BsYXkgaW4gSUUgMTAuXG4gKi9cblxuW2hpZGRlbl0ge1xuICBkaXNwbGF5OiBub25lO1xufVxuIiwiLy8gRm9udCBGYW1pbHlcbiRmb250LWZhbWlseS1kZWZhdWx0OiAgXHRcdFx0XCJSYWxld2F5XCIsIFwiSGVsdmV0aWNhXCIsIFwiVGFob21hXCIsIFwiR2VuZXZhXCIsIFwiQXJpYWxcIiwgc2Fucy1zZXJpZjtcbiRmb250LWZhbWlseS1oZWFkZXI6ICBcdFx0XHRcIk1vbnRzZXJyYXRcIiwgXCJIZWx2ZXRpY2FcIiwgXCJUYWhvbWFcIiwgXCJHZW5ldmFcIiwgXCJBcmlhbFwiLCBzYW5zLXNlcmlmO1xuJGZvbnQtZmFtaWx5LW1vbm86IFx0XHRcdFx0XCJJbmNvbnNvbGF0YVwiLCBtb25vc3BhY2U7XG4kZm9udC1mYW1pbHktc2VyaWY6XHRcdFx0XHRcIkdlb3JnaWFcIiwgXCJUaW1lc1wiLCBcIlRpbWVzIE5ldyBSb21hblwiLCBzZXJpZjtcbi8vIEBmb250LWZhY2VcblxuJHR5cG86IFwiU2VjdWVsYVwiO1xuJGNhdGVnb3J5OiBcInNhbnMtc2VyaWZcIjtcbi8vIHNlY3VlbGEgcmVndWxhclxuXG4kZmlsZTogXCJTZWN1ZWxhLVJlZ3VsYXJcIjtcbiRSZWd1bGFyOiBcIlJlZ3VsYXJcIjtcbkBtaXhpbiBmb250LWZhY2UoJFJlZ3VsYXIsICRmaWxlLCAkdHlwbywgJGNhdGVnb3J5OlwiXCIpIHtcbiAgICAkZmlsZXBhdGg6IFwiLi4vZm9udHMvXCIgKyAkdHlwbyArIFwiL1wiICsgJGZpbGU7XG4gICAgQGZvbnQtZmFjZSB7XG4gICAgICAgIGZvbnQtZmFtaWx5OiBcIiN7JFJlZ3VsYXJ9XCI7XG4gICAgICAgIHNyYzogdXJsKCRmaWxlcGF0aCArIFwiLmVvdFwiKTtcbiAgICAgICAgc3JjOiB1cmwoJGZpbGVwYXRoICsgXCIuZW90PyNpZWZpeFwiKSBmb3JtYXQoJ2VtYmVkZGVkLW9wZW50eXBlJyksXG4gICAgICAgICAgICAgIHVybCgkZmlsZXBhdGggKyBcIi53b2ZmXCIpIGZvcm1hdCgnd29mZicpLFxuICAgICAgICAgICAgICB1cmwoJGZpbGVwYXRoICsgXCIudHRmXCIpICBmb3JtYXQoJ3RydWV0eXBlJyksXG4gICAgICAgICAgICAgIHVybCgkZmlsZXBhdGggKyBcIi5zdmcjXCIgKyAkUmVndWxhciArIFwiXCIpIGZvcm1hdCgnc3ZnJyk7XG4gICAgfVxuXG4gICAgJSN7JFJlZ3VsYXJ9IHtcbiAgICAgICAgZm9udDoge1xuICAgICAgICAgICAgQGlmICRjYXRlZ29yeSAhPSBcIlwiIHtcbiAgICAgICAgICAgICAgICBmYW1pbHk6IFwiI3skUmVndWxhcn1cIiwgI3skY2F0ZWdvcnl9O1xuICAgICAgICAgICAgICAgIHdlaWdodDogbm9ybWFsO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgQGVsc2Uge1xuICAgICAgICAgICAgICAgIGZhbWlseTogXCIjeyRSZWd1bGFyfVwiO1xuICAgICAgICAgICAgICAgIHdlaWdodDogbm9ybWFsO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxufVxuXG4vLyBzZWN1ZWxhIG1lZGl1bVxuJGZpbGUyOiBcIlNlY3VlbGEtTWVkaXVtXCI7XG4kbWVkaXVtOiBcIm1lZGl1bVwiO1xuQG1peGluIGZvbnQtZmFjZSgkbWVkaXVtLCAkZmlsZTIsICR0eXBvLCAkY2F0ZWdvcnk6XCJcIikge1xuICAgICRmaWxlcGF0aDogXCIuLi9mb250cy9cIiArICR0eXBvICsgXCIvXCIgKyAkZmlsZTI7XG4gICAgQGZvbnQtZmFjZSB7XG4gICAgICAgIGZvbnQtZmFtaWx5OiBcIiN7JG1lZGl1bX1cIjtcbiAgICAgICAgc3JjOiB1cmwoJGZpbGVwYXRoICsgXCIuZW90XCIpO1xuICAgICAgICBzcmM6IHVybCgkZmlsZXBhdGggKyBcIi5lb3Q/I2llZml4XCIpIGZvcm1hdCgnZW1iZWRkZWQtb3BlbnR5cGUnKSxcbiAgICAgICAgICAgICAgdXJsKCRmaWxlcGF0aCArIFwiLndvZmZcIikgZm9ybWF0KCd3b2ZmJyksXG4gICAgICAgICAgICAgIHVybCgkZmlsZXBhdGggKyBcIi50dGZcIikgIGZvcm1hdCgndHJ1ZXR5cGUnKSxcbiAgICAgICAgICAgICAgdXJsKCRmaWxlcGF0aCArIFwiLnN2ZyNcIiArICRtZWRpdW0gKyBcIlwiKSBmb3JtYXQoJ3N2ZycpO1xuICAgIH1cblxuICAgICUjeyRtZWRpdW19IHtcbiAgICAgICAgZm9udDoge1xuICAgICAgICAgICAgQGlmICRjYXRlZ29yeSAhPSBcIlwiIHtcbiAgICAgICAgICAgICAgICBmYW1pbHk6IFwiI3skbWVkaXVtfVwiLCAjeyRjYXRlZ29yeX07XG4gICAgICAgICAgICAgICAgd2VpZ2h0OiBub3JtYWw7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBAZWxzZSB7XG4gICAgICAgICAgICAgICAgZmFtaWx5OiBcIiN7JG1lZGl1bX1cIjtcbiAgICAgICAgICAgICAgICB3ZWlnaHQ6IG5vcm1hbDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbn1cblxuXG4vLyBzZWN1ZWxhIGJvbGRcbiRmaWxlMzogXCJTZWN1ZWxhLUJvbGRcIjtcbiRib2xkOiBcImJvbGRcIjtcbkBtaXhpbiBmb250LWZhY2UoJGJvbGQsICRmaWxlMywgJHR5cG8sICRjYXRlZ29yeTpcIlwiKSB7XG4gICAgJGZpbGVwYXRoOiBcIi4uL2ZvbnRzL1wiICsgJHR5cG8gKyBcIi9cIiArICRmaWxlMztcbiAgICBAZm9udC1mYWNlIHtcbiAgICAgICAgZm9udC1mYW1pbHk6IFwiI3skYm9sZH1cIjtcbiAgICAgICAgc3JjOiB1cmwoJGZpbGVwYXRoICsgXCIuZW90XCIpO1xuICAgICAgICBzcmM6IHVybCgkZmlsZXBhdGggKyBcIi5lb3Q/I2llZml4XCIpIGZvcm1hdCgnZW1iZWRkZWQtb3BlbnR5cGUnKSxcbiAgICAgICAgICAgICAgdXJsKCRmaWxlcGF0aCArIFwiLndvZmZcIikgZm9ybWF0KCd3b2ZmJyksXG4gICAgICAgICAgICAgIHVybCgkZmlsZXBhdGggKyBcIi50dGZcIikgIGZvcm1hdCgndHJ1ZXR5cGUnKSxcbiAgICAgICAgICAgICAgdXJsKCRmaWxlcGF0aCArIFwiLnN2ZyNcIiArICRib2xkICsgXCJcIikgZm9ybWF0KCdzdmcnKTtcbiAgICB9XG5cbiAgICAlI3skYm9sZH0ge1xuICAgICAgICBmb250OiB7XG4gICAgICAgICAgICBAaWYgJGNhdGVnb3J5ICE9IFwiXCIge1xuICAgICAgICAgICAgICAgIGZhbWlseTogXCIjeyRib2xkfVwiLCAjeyRjYXRlZ29yeX07XG4gICAgICAgICAgICAgICAgd2VpZ2h0OiBub3JtYWw7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBAZWxzZSB7XG4gICAgICAgICAgICAgICAgZmFtaWx5OiBcIiN7JGJvbGR9XCI7XG4gICAgICAgICAgICAgICAgd2VpZ2h0OiBub3JtYWw7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG59XG5cblxuLy8gc2VjdWVsYSBpdGFsaWNcbiRmaWxlNDogXCJTZWN1ZWxhLUl0YWxpY1wiO1xuJGl0YWxpYzogXCJpdGFsaWNcIjtcbkBtaXhpbiBmb250LWZhY2UoJGl0YWxpYywgJGZpbGU0LCAkdHlwbywgJGNhdGVnb3J5OlwiXCIpIHtcbiAgICAkZmlsZXBhdGg6IFwiLi4vZm9udHMvXCIgKyAkdHlwbyArIFwiL1wiICsgJGZpbGU0O1xuICAgIEBmb250LWZhY2Uge1xuICAgICAgICBmb250LWZhbWlseTogXCIjeyRpdGFsaWN9XCI7XG4gICAgICAgIHNyYzogdXJsKCRmaWxlcGF0aCArIFwiLmVvdFwiKTtcbiAgICAgICAgc3JjOiB1cmwoJGZpbGVwYXRoICsgXCIuZW90PyNpZWZpeFwiKSBmb3JtYXQoJ2VtYmVkZGVkLW9wZW50eXBlJyksXG4gICAgICAgICAgICAgIHVybCgkZmlsZXBhdGggKyBcIi53b2ZmXCIpIGZvcm1hdCgnd29mZicpLFxuICAgICAgICAgICAgICB1cmwoJGZpbGVwYXRoICsgXCIudHRmXCIpICBmb3JtYXQoJ3RydWV0eXBlJyksXG4gICAgICAgICAgICAgIHVybCgkZmlsZXBhdGggKyBcIi5zdmcjXCIgKyAkaXRhbGljICsgXCJcIikgZm9ybWF0KCdzdmcnKTtcbiAgICB9XG5cbiAgICAlI3skaXRhbGljfSB7XG4gICAgICAgIGZvbnQ6IHtcbiAgICAgICAgICAgIEBpZiAkY2F0ZWdvcnkgIT0gXCJcIiB7XG4gICAgICAgICAgICAgICAgZmFtaWx5OiBcIiN7JGl0YWxpY31cIiwgI3skY2F0ZWdvcnl9O1xuICAgICAgICAgICAgICAgIHdlaWdodDogbm9ybWFsO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgQGVsc2Uge1xuICAgICAgICAgICAgICAgIGZhbWlseTogXCIjeyRpdGFsaWN9XCI7XG4gICAgICAgICAgICAgICAgd2VpZ2h0OiBub3JtYWw7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG59XG5cblxuLy8gc2VjdWVsYSBsaWdodFxuJGZpbGU1OiBcIlNlY3VlbGEtTGlnaHRcIjtcbiRsaWdodDogXCJsaWdodFwiO1xuQG1peGluIGZvbnQtZmFjZSgkbGlnaHQsICRmaWxlNSwgJHR5cG8sICRjYXRlZ29yeTpcIlwiKSB7XG4gICAgJGZpbGVwYXRoOiBcIi4uL2ZvbnRzL1wiICsgJHR5cG8gKyBcIi9cIiArICRmaWxlNTtcbiAgICBAZm9udC1mYWNlIHtcbiAgICAgICAgZm9udC1mYW1pbHk6IFwiI3skbGlnaHR9XCI7XG4gICAgICAgIHNyYzogdXJsKCRmaWxlcGF0aCArIFwiLmVvdFwiKTtcbiAgICAgICAgc3JjOiB1cmwoJGZpbGVwYXRoICsgXCIuZW90PyNpZWZpeFwiKSBmb3JtYXQoJ2VtYmVkZGVkLW9wZW50eXBlJyksXG4gICAgICAgICAgICAgIHVybCgkZmlsZXBhdGggKyBcIi53b2ZmXCIpIGZvcm1hdCgnd29mZicpLFxuICAgICAgICAgICAgICB1cmwoJGZpbGVwYXRoICsgXCIudHRmXCIpICBmb3JtYXQoJ3RydWV0eXBlJyksXG4gICAgICAgICAgICAgIHVybCgkZmlsZXBhdGggKyBcIi5zdmcjXCIgKyAkbGlnaHQgKyBcIlwiKSBmb3JtYXQoJ3N2ZycpO1xuICAgIH1cblxuICAgICUjeyRsaWdodH0ge1xuICAgICAgICBmb250OiB7XG4gICAgICAgICAgICBAaWYgJGNhdGVnb3J5ICE9IFwiXCIge1xuICAgICAgICAgICAgICAgIGZhbWlseTogXCIjeyRsaWdodH1cIiwgI3skY2F0ZWdvcnl9O1xuICAgICAgICAgICAgICAgIHdlaWdodDogbm9ybWFsO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgQGVsc2Uge1xuICAgICAgICAgICAgICAgIGZhbWlseTogXCIjeyRsaWdodH1cIjtcbiAgICAgICAgICAgICAgICB3ZWlnaHQ6IG5vcm1hbDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbn1cbiIsIi8vIGZvbnRzXG5AaW5jbHVkZSBmb250LWZhY2UoJFJlZ3VsYXIsICRmaWxlLCAkdHlwbywgJGNhdGVnb3J5KTsvLzFcbkBpbmNsdWRlIGZvbnQtZmFjZSgkbWVkaXVtLCAkZmlsZTIsICR0eXBvLCAkY2F0ZWdvcnkpOy8vMVxuQGluY2x1ZGUgZm9udC1mYWNlKCRib2xkLCAkZmlsZTMsICR0eXBvLCAkY2F0ZWdvcnkpOy8vMVxuQGluY2x1ZGUgZm9udC1mYWNlKCRpdGFsaWMsICRmaWxlNCwgJHR5cG8sICRjYXRlZ29yeSk7Ly8xXG5AaW5jbHVkZSBmb250LWZhY2UoJGxpZ2h0LCAkZmlsZTUsICR0eXBvLCAkY2F0ZWdvcnkpOy8vMVxuXG4vLyBmb250LXNpemVcbiRmaDE6IDEydnc7XG4kZmgyOiA0LjV2dztcbiRmaDItaTogMXJlbTtcbiRmaDM6IDFyZW07XG4kZmg1OiAwLjhyZW07XG4kZnA6IDAuOXJlbTtcbiRmczogMC44cmVtO1xuJGZzLWk6IDFyZW07XG4kbWluLWgxOiAzcmVtO1xuXG4vLyBpbnRlcmxpbmVcblxuLy9pbnRlcmxldHRyZVxuJGxldHRlckkxOiAwLjFyZW07XG5cbi8vIGNvbG9yXG4kYmx1ZTogIzBmMjY1YztcbiRyZWQ6ICM4ZDI4MTVmZjtcbiRncmV5OiAjOTk5OTk5O1xuIiwiIiwiaDEsIGgye1xuICBmb250LWZhbWlseTogJG1lZGl1bTtcbiAgZm9udC13ZWlnaHQ6IG5vcm1hbDtcbn1cblxuaDJ7XG4gIGZvbnQtc2l6ZTogJGZoMjtcbn1cblxuaDN7XG4gIGZvbnQtZmFtaWx5OiAkbWVkaXVtO1xuICBmb250LXNpemU6ICRmaDM7XG4gIGZvbnQtd2VpZ2h0OiBub3JtYWw7XG59XG5cbmg1e1xuICBmb250LXNpemU6ICRmaDU7XG4gIGZvbnQtZmFtaWx5OiAkYm9sZDtcbiAgZm9udC13ZWlnaHQ6IG5vcm1hbDtcbn1cblxucHtcbiAgZm9udC1mYW1pbHk6ICRSZWd1bGFyO1xuICBmb250LXdlaWdodDogbm9ybWFsO1xuICBmb250LXNpemU6ICRmcDtcbiAgbGluZS1oZWlnaHQ6IDEuM3JlbTtcbn1cblxudGltZXtcbiAgZm9udC1mYW1pbHk6ICRpdGFsaWM7XG4gIGZvbnQtd2VpZ2h0OiBub3JtYWw7XG59XG5cbi5ibG9ne1xuICBoMXtcbiAgICBmb250LXNpemU6ICRmaDE7XG4gICAgbGluZS1oZWlnaHQ6IDVyZW07XG4gICAgLXdlYmtpdC10ZXh0LXN0cm9rZTogMnB4IGJsYWNrO1xuICAgIGNvbG9yOiB0cmFuc3BhcmVudDtcbiAgfVxuICBoMntcbiAgICBsaW5lLWhlaWdodDogOHJlbTtcbiAgfVxuICAmLmRvd257XG4gICAgaDF7XG4gICAgICBhbmltYXRpb246IHJlZHVjSCAxcyAwLjlzIGVhc2UgZm9yd2FyZHM7XG4gICAgfVxuICB9XG59XG5cbi5pdGVte1xuICBoMntcbiAgICBmb250LXNpemU6ICRmaDItaTtcbiAgfVxuICAucHJldi1uZXh0e1xuICAgIGF7XG4gICAgICAudGl0bGV7XG4gICAgICAgIGZvbnQtZmFtaWx5OiAkbWVkaXVtO1xuICAgICAgICBmb250LXNpemU6ICRmaDItaTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cblxuLmNhdGVnb3J5e1xuICBhe1xuICAgIGZvbnQtZmFtaWx5OiAkUmVndWxhcjtcbiAgICBmb250LXNpemU6ICRmcy1pO1xuICB9XG59XG5cbnRpbWV7XG4gIGZvbnQtc2l6ZTogJGZzLWk7XG59XG5cbi50YWdze1xuICBmb250LWZhbWlseTogJFJlZ3VsYXI7XG4gIGZvbnQtc2l6ZTogJGZzLWk7XG59XG5cbiNwYWdlLXdyYXBwZXIuYmxvZ3tcbiAgLndvcmtzLW1lYSwgI2l0ZW1ze1xuICAgIC5jYXRlZ29yeXtcbiAgICAgIGF7XG4gICAgICAgIGZvbnQtZmFtaWx5OiAkUmVndWxhcjtcbiAgICAgICAgZm9udC1zaXplOiAkZnM7XG4gICAgICB9XG4gICAgfVxuICAgIHRpbWV7XG4gICAgICBmb250LXNpemU6ICRmcztcbiAgICB9XG4gICAgLnRhZ3N7XG4gICAgICBmb250LXNpemU6ICRmcztcbiAgICB9XG4gIH1cbn1cblxuLmNhdC1saXN0e1xuICBmb250LWZhbWlseTogJFJlZ3VsYXI7XG59XG4iLCIubUN1c3RvbVNjcm9sbGJhcnstbXMtdG91Y2gtYWN0aW9uOnBpbmNoLXpvb207dG91Y2gtYWN0aW9uOnBpbmNoLXpvb219Lm1DdXN0b21TY3JvbGxiYXIubUNTX25vX3Njcm9sbGJhciwubUN1c3RvbVNjcm9sbGJhci5tQ1NfdG91Y2hfYWN0aW9uey1tcy10b3VjaC1hY3Rpb246YXV0bzt0b3VjaC1hY3Rpb246YXV0b30ubUN1c3RvbVNjcm9sbEJveHtwb3NpdGlvbjpyZWxhdGl2ZTtvdmVyZmxvdzpoaWRkZW47aGVpZ2h0OjEwMCU7bWF4LXdpZHRoOjEwMCU7b3V0bGluZTowO2RpcmVjdGlvbjpsdHJ9Lm1DU0JfY29udGFpbmVye292ZXJmbG93OmhpZGRlbjt3aWR0aDphdXRvO2hlaWdodDphdXRvfS5tQ1NCX2luc2lkZT4ubUNTQl9jb250YWluZXJ7bWFyZ2luLXJpZ2h0OjMwcHh9Lm1DU0JfY29udGFpbmVyLm1DU19ub19zY3JvbGxiYXJfeS5tQ1NfeV9oaWRkZW57bWFyZ2luLXJpZ2h0OjB9Lm1DUy1kaXItcnRsPi5tQ1NCX2luc2lkZT4ubUNTQl9jb250YWluZXJ7bWFyZ2luLXJpZ2h0OjA7bWFyZ2luLWxlZnQ6MzBweH0ubUNTLWRpci1ydGw+Lm1DU0JfaW5zaWRlPi5tQ1NCX2NvbnRhaW5lci5tQ1Nfbm9fc2Nyb2xsYmFyX3kubUNTX3lfaGlkZGVue21hcmdpbi1sZWZ0OjB9Lm1DU0Jfc2Nyb2xsVG9vbHN7cG9zaXRpb246YWJzb2x1dGU7d2lkdGg6MTZweDtoZWlnaHQ6YXV0bztsZWZ0OmF1dG87dG9wOjA7cmlnaHQ6MDtib3R0b206MDtvcGFjaXR5Oi43NTtmaWx0ZXI6XCJhbHBoYShvcGFjaXR5PTc1KVwiOy1tcy1maWx0ZXI6XCJhbHBoYShvcGFjaXR5PTc1KVwifS5tQ1NCX291dHNpZGUrLm1DU0Jfc2Nyb2xsVG9vbHN7cmlnaHQ6LTI2cHh9Lm1DUy1kaXItcnRsPi5tQ1NCX2luc2lkZT4ubUNTQl9zY3JvbGxUb29scywubUNTLWRpci1ydGw+Lm1DU0Jfb3V0c2lkZSsubUNTQl9zY3JvbGxUb29sc3tyaWdodDphdXRvO2xlZnQ6MH0ubUNTLWRpci1ydGw+Lm1DU0Jfb3V0c2lkZSsubUNTQl9zY3JvbGxUb29sc3tsZWZ0Oi0yNnB4fS5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJDb250YWluZXJ7cG9zaXRpb246YWJzb2x1dGU7dG9wOjA7bGVmdDowO2JvdHRvbTowO3JpZ2h0OjA7aGVpZ2h0OmF1dG99Lm1DU0Jfc2Nyb2xsVG9vbHMgYSsubUNTQl9kcmFnZ2VyQ29udGFpbmVye21hcmdpbjoyMHB4IDB9Lm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7d2lkdGg6MnB4O2hlaWdodDoxMDAlO21hcmdpbjowIGF1dG87LXdlYmtpdC1ib3JkZXItcmFkaXVzOjE2cHg7LW1vei1ib3JkZXItcmFkaXVzOjE2cHg7Ym9yZGVyLXJhZGl1czoxNnB4fS5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJ7Y3Vyc29yOnBvaW50ZXI7d2lkdGg6MTAwJTtoZWlnaHQ6MzBweDt6LWluZGV4OjF9Lm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2Jhcntwb3NpdGlvbjpyZWxhdGl2ZTt3aWR0aDo0cHg7aGVpZ2h0OjEwMCU7bWFyZ2luOjAgYXV0bzstd2Via2l0LWJvcmRlci1yYWRpdXM6MTZweDstbW96LWJvcmRlci1yYWRpdXM6MTZweDtib3JkZXItcmFkaXVzOjE2cHg7dGV4dC1hbGlnbjpjZW50ZXJ9Lm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZ19leHBhbmRlZCAubUNTQl9kcmFnZ2VyX2JhciwubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcjpob3ZlciAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye3dpZHRoOjEycHh9Lm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZ19leHBhbmRlZCsubUNTQl9kcmFnZ2VyUmFpbCwubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcjpob3ZlciAubUNTQl9kcmFnZ2VyUmFpbHt3aWR0aDo4cHh9Lm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93biwubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcHtkaXNwbGF5OmJsb2NrO3Bvc2l0aW9uOmFic29sdXRlO2hlaWdodDoyMHB4O3dpZHRoOjEwMCU7b3ZlcmZsb3c6aGlkZGVuO21hcmdpbjowIGF1dG87Y3Vyc29yOnBvaW50ZXJ9Lm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93bntib3R0b206MH0ubUNTQl9ob3Jpem9udGFsLm1DU0JfaW5zaWRlPi5tQ1NCX2NvbnRhaW5lcnttYXJnaW4tcmlnaHQ6MDttYXJnaW4tYm90dG9tOjMwcHh9Lm1DU0JfaG9yaXpvbnRhbC5tQ1NCX291dHNpZGU+Lm1DU0JfY29udGFpbmVye21pbi1oZWlnaHQ6MTAwJX0ubUNTQl9ob3Jpem9udGFsPi5tQ1NCX2NvbnRhaW5lci5tQ1Nfbm9fc2Nyb2xsYmFyX3gubUNTX3hfaGlkZGVue21hcmdpbi1ib3R0b206MH0ubUNTQl9zY3JvbGxUb29scy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWx7d2lkdGg6YXV0bztoZWlnaHQ6MTZweDt0b3A6YXV0bztyaWdodDowO2JvdHRvbTowO2xlZnQ6MH0ubUN1c3RvbVNjcm9sbEJveCsubUNTQl9zY3JvbGxUb29scysubUNTQl9zY3JvbGxUb29scy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwsLm1DdXN0b21TY3JvbGxCb3grLm1DU0Jfc2Nyb2xsVG9vbHMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFse2JvdHRvbTotMjZweH0ubUNTQl9zY3JvbGxUb29scy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgYSsubUNTQl9kcmFnZ2VyQ29udGFpbmVye21hcmdpbjowIDIwcHh9Lm1DU0Jfc2Nyb2xsVG9vbHMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlse3dpZHRoOjEwMCU7aGVpZ2h0OjJweDttYXJnaW46N3B4IDB9Lm1DU0Jfc2Nyb2xsVG9vbHMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJ7d2lkdGg6MzBweDtoZWlnaHQ6MTAwJTtsZWZ0OjB9Lm1DU0Jfc2Nyb2xsVG9vbHMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7d2lkdGg6MTAwJTtoZWlnaHQ6NHB4O21hcmdpbjo2cHggYXV0b30ubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWdfZXhwYW5kZWQgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcjpob3ZlciAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye2hlaWdodDoxMnB4O21hcmdpbjoycHggYXV0b30ubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWdfZXhwYW5kZWQrLm1DU0JfZHJhZ2dlclJhaWwsLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcjpob3ZlciAubUNTQl9kcmFnZ2VyUmFpbHtoZWlnaHQ6OHB4O21hcmdpbjo0cHggMH0ubUNTQl9zY3JvbGxUb29scy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfYnV0dG9uTGVmdCwubUNTQl9zY3JvbGxUb29scy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfYnV0dG9uUmlnaHR7ZGlzcGxheTpibG9jaztwb3NpdGlvbjphYnNvbHV0ZTt3aWR0aDoyMHB4O2hlaWdodDoxMDAlO292ZXJmbG93OmhpZGRlbjttYXJnaW46MCBhdXRvO2N1cnNvcjpwb2ludGVyfS5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9idXR0b25MZWZ0e2xlZnQ6MH0ubUNTQl9zY3JvbGxUb29scy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfYnV0dG9uUmlnaHR7cmlnaHQ6MH0ubUNTQl9jb250YWluZXJfd3JhcHBlcntwb3NpdGlvbjphYnNvbHV0ZTtoZWlnaHQ6YXV0bzt3aWR0aDphdXRvO292ZXJmbG93OmhpZGRlbjt0b3A6MDtsZWZ0OjA7cmlnaHQ6MDtib3R0b206MDttYXJnaW4tcmlnaHQ6MzBweDttYXJnaW4tYm90dG9tOjMwcHh9Lm1DU0JfY29udGFpbmVyX3dyYXBwZXI+Lm1DU0JfY29udGFpbmVye3BhZGRpbmctcmlnaHQ6MzBweDtwYWRkaW5nLWJvdHRvbTozMHB4Oy13ZWJraXQtYm94LXNpemluZzpib3JkZXItYm94Oy1tb3otYm94LXNpemluZzpib3JkZXItYm94O2JveC1zaXppbmc6Ym9yZGVyLWJveH0ubUNTQl92ZXJ0aWNhbF9ob3Jpem9udGFsPi5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWx7Ym90dG9tOjIwcHh9Lm1DU0JfdmVydGljYWxfaG9yaXpvbnRhbD4ubUNTQl9zY3JvbGxUb29scy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWx7cmlnaHQ6MjBweH0ubUNTQl9jb250YWluZXJfd3JhcHBlci5tQ1Nfbm9fc2Nyb2xsYmFyX3gubUNTX3hfaGlkZGVuKy5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWx7Ym90dG9tOjB9Lm1DUy1kaXItcnRsPi5tQ3VzdG9tU2Nyb2xsQm94Lm1DU0JfdmVydGljYWxfaG9yaXpvbnRhbC5tQ1NCX2luc2lkZT4ubUNTQl9zY3JvbGxUb29scy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwsLm1DU0JfY29udGFpbmVyX3dyYXBwZXIubUNTX25vX3Njcm9sbGJhcl95Lm1DU195X2hpZGRlbisubUNTQl9zY3JvbGxUb29sc34ubUNTQl9zY3JvbGxUb29scy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWx7cmlnaHQ6MH0ubUNTLWRpci1ydGw+Lm1DdXN0b21TY3JvbGxCb3gubUNTQl92ZXJ0aWNhbF9ob3Jpem9udGFsLm1DU0JfaW5zaWRlPi5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbHtsZWZ0OjIwcHh9Lm1DUy1kaXItcnRsPi5tQ3VzdG9tU2Nyb2xsQm94Lm1DU0JfdmVydGljYWxfaG9yaXpvbnRhbC5tQ1NCX2luc2lkZT4ubUNTQl9jb250YWluZXJfd3JhcHBlci5tQ1Nfbm9fc2Nyb2xsYmFyX3kubUNTX3lfaGlkZGVuKy5tQ1NCX3Njcm9sbFRvb2xzfi5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbHtsZWZ0OjB9Lm1DUy1kaXItcnRsPi5tQ1NCX2luc2lkZT4ubUNTQl9jb250YWluZXJfd3JhcHBlcnttYXJnaW4tcmlnaHQ6MDttYXJnaW4tbGVmdDozMHB4fS5tQ1NCX2NvbnRhaW5lcl93cmFwcGVyLm1DU19ub19zY3JvbGxiYXJfeS5tQ1NfeV9oaWRkZW4+Lm1DU0JfY29udGFpbmVye3BhZGRpbmctcmlnaHQ6MH0ubUNTQl9jb250YWluZXJfd3JhcHBlci5tQ1Nfbm9fc2Nyb2xsYmFyX3gubUNTX3hfaGlkZGVuPi5tQ1NCX2NvbnRhaW5lcntwYWRkaW5nLWJvdHRvbTowfS5tQ3VzdG9tU2Nyb2xsQm94Lm1DU0JfdmVydGljYWxfaG9yaXpvbnRhbC5tQ1NCX2luc2lkZT4ubUNTQl9jb250YWluZXJfd3JhcHBlci5tQ1Nfbm9fc2Nyb2xsYmFyX3kubUNTX3lfaGlkZGVue21hcmdpbi1yaWdodDowO21hcmdpbi1sZWZ0OjB9Lm1DdXN0b21TY3JvbGxCb3gubUNTQl92ZXJ0aWNhbF9ob3Jpem9udGFsLm1DU0JfaW5zaWRlPi5tQ1NCX2NvbnRhaW5lcl93cmFwcGVyLm1DU19ub19zY3JvbGxiYXJfeC5tQ1NfeF9oaWRkZW57bWFyZ2luLWJvdHRvbTowfS5tQ1NCX3Njcm9sbFRvb2xzLC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd24sLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uTGVmdCwubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodCwubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcCwubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyey13ZWJraXQtdHJhbnNpdGlvbjpvcGFjaXR5IC4ycyBlYXNlLWluLW91dCxiYWNrZ3JvdW5kLWNvbG9yIC4ycyBlYXNlLWluLW91dDstbW96LXRyYW5zaXRpb246b3BhY2l0eSAuMnMgZWFzZS1pbi1vdXQsYmFja2dyb3VuZC1jb2xvciAuMnMgZWFzZS1pbi1vdXQ7LW8tdHJhbnNpdGlvbjpvcGFjaXR5IC4ycyBlYXNlLWluLW91dCxiYWNrZ3JvdW5kLWNvbG9yIC4ycyBlYXNlLWluLW91dDt0cmFuc2l0aW9uOm9wYWNpdHkgLjJzIGVhc2UtaW4tb3V0LGJhY2tncm91bmQtY29sb3IgLjJzIGVhc2UtaW4tb3V0fS5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyUmFpbCwubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlcl9iYXJ7LXdlYmtpdC10cmFuc2l0aW9uOndpZHRoIC4ycyBlYXNlLW91dCAuMnMsaGVpZ2h0IC4ycyBlYXNlLW91dCAuMnMsbWFyZ2luLWxlZnQgLjJzIGVhc2Utb3V0IC4ycyxtYXJnaW4tcmlnaHQgLjJzIGVhc2Utb3V0IC4ycyxtYXJnaW4tdG9wIC4ycyBlYXNlLW91dCAuMnMsbWFyZ2luLWJvdHRvbSAuMnMgZWFzZS1vdXQgLjJzLG9wYWNpdHkgLjJzIGVhc2UtaW4tb3V0LGJhY2tncm91bmQtY29sb3IgLjJzIGVhc2UtaW4tb3V0Oy1tb3otdHJhbnNpdGlvbjp3aWR0aCAuMnMgZWFzZS1vdXQgLjJzLGhlaWdodCAuMnMgZWFzZS1vdXQgLjJzLG1hcmdpbi1sZWZ0IC4ycyBlYXNlLW91dCAuMnMsbWFyZ2luLXJpZ2h0IC4ycyBlYXNlLW91dCAuMnMsbWFyZ2luLXRvcCAuMnMgZWFzZS1vdXQgLjJzLG1hcmdpbi1ib3R0b20gLjJzIGVhc2Utb3V0IC4ycyxvcGFjaXR5IC4ycyBlYXNlLWluLW91dCxiYWNrZ3JvdW5kLWNvbG9yIC4ycyBlYXNlLWluLW91dDstby10cmFuc2l0aW9uOndpZHRoIC4ycyBlYXNlLW91dCAuMnMsaGVpZ2h0IC4ycyBlYXNlLW91dCAuMnMsbWFyZ2luLWxlZnQgLjJzIGVhc2Utb3V0IC4ycyxtYXJnaW4tcmlnaHQgLjJzIGVhc2Utb3V0IC4ycyxtYXJnaW4tdG9wIC4ycyBlYXNlLW91dCAuMnMsbWFyZ2luLWJvdHRvbSAuMnMgZWFzZS1vdXQgLjJzLG9wYWNpdHkgLjJzIGVhc2UtaW4tb3V0LGJhY2tncm91bmQtY29sb3IgLjJzIGVhc2UtaW4tb3V0O3RyYW5zaXRpb246d2lkdGggLjJzIGVhc2Utb3V0IC4ycyxoZWlnaHQgLjJzIGVhc2Utb3V0IC4ycyxtYXJnaW4tbGVmdCAuMnMgZWFzZS1vdXQgLjJzLG1hcmdpbi1yaWdodCAuMnMgZWFzZS1vdXQgLjJzLG1hcmdpbi10b3AgLjJzIGVhc2Utb3V0IC4ycyxtYXJnaW4tYm90dG9tIC4ycyBlYXNlLW91dCAuMnMsb3BhY2l0eSAuMnMgZWFzZS1pbi1vdXQsYmFja2dyb3VuZC1jb2xvciAuMnMgZWFzZS1pbi1vdXR9Lm1DUy1hdXRvSGlkZT4ubUN1c3RvbVNjcm9sbEJveD4ubUNTQl9zY3JvbGxUb29scywubUNTLWF1dG9IaWRlPi5tQ3VzdG9tU2Nyb2xsQm94fi5tQ1NCX3Njcm9sbFRvb2xze29wYWNpdHk6MDtmaWx0ZXI6XCJhbHBoYShvcGFjaXR5PTApXCI7LW1zLWZpbHRlcjpcImFscGhhKG9wYWNpdHk9MClcIn0ubUNTLWF1dG9IaWRlOmhvdmVyPi5tQ3VzdG9tU2Nyb2xsQm94Pi5tQ1NCX3Njcm9sbFRvb2xzLC5tQ1MtYXV0b0hpZGU6aG92ZXI+Lm1DdXN0b21TY3JvbGxCb3h+Lm1DU0Jfc2Nyb2xsVG9vbHMsLm1DdXN0b21TY3JvbGxCb3g6aG92ZXI+Lm1DU0Jfc2Nyb2xsVG9vbHMsLm1DdXN0b21TY3JvbGxCb3g6aG92ZXJ+Lm1DU0Jfc2Nyb2xsVG9vbHMsLm1DdXN0b21TY3JvbGxiYXI+Lm1DdXN0b21TY3JvbGxCb3g+Lm1DU0Jfc2Nyb2xsVG9vbHMubUNTQl9zY3JvbGxUb29sc19vbkRyYWcsLm1DdXN0b21TY3JvbGxiYXI+Lm1DdXN0b21TY3JvbGxCb3h+Lm1DU0Jfc2Nyb2xsVG9vbHMubUNTQl9zY3JvbGxUb29sc19vbkRyYWd7b3BhY2l0eToxO2ZpbHRlcjpcImFscGhhKG9wYWNpdHk9MTAwKVwiOy1tcy1maWx0ZXI6XCJhbHBoYShvcGFjaXR5PTEwMClcIn0ubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHtiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC40KTtmaWx0ZXI6XCJhbHBoYShvcGFjaXR5PTQwKVwiOy1tcy1maWx0ZXI6XCJhbHBoYShvcGFjaXR5PTQwKVwifS5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojZmZmO2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNzUpO2ZpbHRlcjpcImFscGhhKG9wYWNpdHk9NzUpXCI7LW1zLWZpbHRlcjpcImFscGhhKG9wYWNpdHk9NzUpXCJ9Lm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjpob3ZlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC44NSk7ZmlsdGVyOlwiYWxwaGEob3BhY2l0eT04NSlcIjstbXMtZmlsdGVyOlwiYWxwaGEob3BhY2l0eT04NSlcIn0ubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWcgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjphY3RpdmUgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojZmZmO2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuOSk7ZmlsdGVyOlwiYWxwaGEob3BhY2l0eT05MClcIjstbXMtZmlsdGVyOlwiYWxwaGEob3BhY2l0eT05MClcIn0ubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3duLC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnQsLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHQsLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXB7YmFja2dyb3VuZC1pbWFnZTp1cmwobUNTQl9idXR0b25zLnBuZyk7YmFja2dyb3VuZC1yZXBlYXQ6bm8tcmVwZWF0O29wYWNpdHk6LjQ7ZmlsdGVyOlwiYWxwaGEob3BhY2l0eT00MClcIjstbXMtZmlsdGVyOlwiYWxwaGEob3BhY2l0eT00MClcIn0ubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcHtiYWNrZ3JvdW5kLXBvc2l0aW9uOjAgMH0ubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3due2JhY2tncm91bmQtcG9zaXRpb246MCAtMjBweH0ubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0e2JhY2tncm91bmQtcG9zaXRpb246MCAtNDBweH0ubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodHtiYWNrZ3JvdW5kLXBvc2l0aW9uOjAgLTU2cHh9Lm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93bjpob3ZlciwubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0OmhvdmVyLC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0OmhvdmVyLC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwOmhvdmVye29wYWNpdHk6Ljc1O2ZpbHRlcjpcImFscGhhKG9wYWNpdHk9NzUpXCI7LW1zLWZpbHRlcjpcImFscGhhKG9wYWNpdHk9NzUpXCJ9Lm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93bjphY3RpdmUsLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uTGVmdDphY3RpdmUsLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHQ6YWN0aXZlLC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwOmFjdGl2ZXtvcGFjaXR5Oi45O2ZpbHRlcjpcImFscGhhKG9wYWNpdHk9OTApXCI7LW1zLWZpbHRlcjpcImFscGhhKG9wYWNpdHk9OTApXCJ9Lm1DUy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuMTUpfS5tQ1MtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuNzUpfS5tQ1MtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6aG92ZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC44NSl9Lm1DUy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6YWN0aXZlIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuOSl9Lm1DUy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXB7YmFja2dyb3VuZC1wb3NpdGlvbjotODBweCAwfS5tQ1MtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd257YmFja2dyb3VuZC1wb3NpdGlvbjotODBweCAtMjBweH0ubUNTLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0e2JhY2tncm91bmQtcG9zaXRpb246LTgwcHggLTQwcHh9Lm1DUy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHR7YmFja2dyb3VuZC1wb3NpdGlvbjotODBweCAtNTZweH0ubUNTLWRhcmstMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtbGlnaHQtMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlse3dpZHRoOjRweDtiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4xKTstd2Via2l0LWJvcmRlci1yYWRpdXM6MXB4Oy1tb3otYm9yZGVyLXJhZGl1czoxcHg7Ym9yZGVyLXJhZGl1czoxcHh9Lm1DUy1kYXJrLTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtbGlnaHQtMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7d2lkdGg6NHB4O2JhY2tncm91bmQtY29sb3I6I2ZmZjtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjc1KTstd2Via2l0LWJvcmRlci1yYWRpdXM6MXB4Oy1tb3otYm9yZGVyLXJhZGl1czoxcHg7Ym9yZGVyLXJhZGl1czoxcHh9Lm1DUy1kYXJrLTIubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1kYXJrLTIubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtbGlnaHQtMi5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWxpZ2h0LTIubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlse3dpZHRoOjEwMCU7aGVpZ2h0OjRweDttYXJnaW46NnB4IGF1dG99Lm1DUy1saWdodC0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjpob3ZlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC44NSl9Lm1DUy1saWdodC0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtbGlnaHQtMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6YWN0aXZlIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6I2ZmZjtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjkpfS5tQ1MtbGlnaHQtMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwe2JhY2tncm91bmQtcG9zaXRpb246LTMycHggMH0ubUNTLWxpZ2h0LTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3due2JhY2tncm91bmQtcG9zaXRpb246LTMycHggLTIwcHh9Lm1DUy1saWdodC0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uTGVmdHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi00MHB4IC00MHB4fS5tQ1MtbGlnaHQtMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0e2JhY2tncm91bmQtcG9zaXRpb246LTQwcHggLTU2cHh9Lm1DUy1kYXJrLTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHtiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4xKTstd2Via2l0LWJvcmRlci1yYWRpdXM6MXB4Oy1tb3otYm9yZGVyLXJhZGl1czoxcHg7Ym9yZGVyLXJhZGl1czoxcHh9Lm1DUy1kYXJrLTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjc1KTstd2Via2l0LWJvcmRlci1yYWRpdXM6MXB4Oy1tb3otYm9yZGVyLXJhZGl1czoxcHg7Ym9yZGVyLXJhZGl1czoxcHh9Lm1DUy1kYXJrLTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmhvdmVyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjg1KX0ubUNTLWRhcmstMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZyAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWRhcmstMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6YWN0aXZlIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjkpfS5tQ1MtZGFyay0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXB7YmFja2dyb3VuZC1wb3NpdGlvbjotMTEycHggMH0ubUNTLWRhcmstMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd257YmFja2dyb3VuZC1wb3NpdGlvbjotMTEycHggLTIwcHh9Lm1DUy1kYXJrLTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0e2JhY2tncm91bmQtcG9zaXRpb246LTEyMHB4IC00MHB4fS5tQ1MtZGFyay0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHR7YmFja2dyb3VuZC1wb3NpdGlvbjotMTIwcHggLTU2cHh9Lm1DUy1kYXJrLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1saWdodC10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlse3dpZHRoOjRweDtiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4xKTstd2Via2l0LWJvcmRlci1yYWRpdXM6MnB4Oy1tb3otYm9yZGVyLXJhZGl1czoycHg7Ym9yZGVyLXJhZGl1czoycHh9Lm1DUy1kYXJrLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWxpZ2h0LXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2Jhcnt3aWR0aDo2cHg7YmFja2dyb3VuZC1jb2xvcjojZmZmO2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNzUpOy13ZWJraXQtYm9yZGVyLXJhZGl1czoycHg7LW1vei1ib3JkZXItcmFkaXVzOjJweDtib3JkZXItcmFkaXVzOjJweH0ubUNTLWRhcmstdGhpY2subUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtbGlnaHQtdGhpY2subUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlse3dpZHRoOjEwMCU7aGVpZ2h0OjRweDttYXJnaW46NnB4IDB9Lm1DUy1kYXJrLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtbGlnaHQtdGhpY2subUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7d2lkdGg6MTAwJTtoZWlnaHQ6NnB4O21hcmdpbjo1cHggYXV0b30ubUNTLWxpZ2h0LXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjpob3ZlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC44NSl9Lm1DUy1saWdodC10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZyAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWxpZ2h0LXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjphY3RpdmUgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojZmZmO2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuOSl9Lm1DUy1saWdodC10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwe2JhY2tncm91bmQtcG9zaXRpb246LTE2cHggMH0ubUNTLWxpZ2h0LXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93bntiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xNnB4IC0yMHB4fS5tQ1MtbGlnaHQtdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0e2JhY2tncm91bmQtcG9zaXRpb246LTIwcHggLTQwcHh9Lm1DUy1saWdodC10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0e2JhY2tncm91bmQtcG9zaXRpb246LTIwcHggLTU2cHh9Lm1DUy1kYXJrLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuMSk7LXdlYmtpdC1ib3JkZXItcmFkaXVzOjJweDstbW96LWJvcmRlci1yYWRpdXM6MnB4O2JvcmRlci1yYWRpdXM6MnB4fS5tQ1MtZGFyay10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuNzUpOy13ZWJraXQtYm9yZGVyLXJhZGl1czoycHg7LW1vei1ib3JkZXItcmFkaXVzOjJweDtib3JkZXItcmFkaXVzOjJweH0ubUNTLWRhcmstdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmhvdmVyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjg1KX0ubUNTLWRhcmstdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWcgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1kYXJrLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjphY3RpdmUgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuOSl9Lm1DUy1kYXJrLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXB7YmFja2dyb3VuZC1wb3NpdGlvbjotOTZweCAwfS5tQ1MtZGFyay10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd257YmFja2dyb3VuZC1wb3NpdGlvbjotOTZweCAtMjBweH0ubUNTLWRhcmstdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0e2JhY2tncm91bmQtcG9zaXRpb246LTEwMHB4IC00MHB4fS5tQ1MtZGFyay10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0e2JhY2tncm91bmQtcG9zaXRpb246LTEwMHB4IC01NnB4fS5tQ1MtbGlnaHQtdGhpbi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlse2JhY2tncm91bmQtY29sb3I6I2ZmZjtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjEpfS5tQ1MtZGFyay10aGluLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWxpZ2h0LXRoaW4ubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye3dpZHRoOjJweH0ubUNTLWRhcmstdGhpbi5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1saWdodC10aGluLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyUmFpbHt3aWR0aDoxMDAlfS5tQ1MtZGFyay10aGluLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtbGlnaHQtdGhpbi5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2Jhcnt3aWR0aDoxMDAlO2hlaWdodDoycHg7bWFyZ2luOjdweCBhdXRvfS5tQ1MtZGFyay10aGluLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuMTUpfS5tQ1MtZGFyay10aGluLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC43NSl9Lm1DUy1kYXJrLXRoaW4ubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmhvdmVyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjg1KX0ubUNTLWRhcmstdGhpbi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZyAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWRhcmstdGhpbi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6YWN0aXZlIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjkpfS5tQ1MtZGFyay10aGluLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXB7YmFja2dyb3VuZC1wb3NpdGlvbjotODBweCAwfS5tQ1MtZGFyay10aGluLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93bntiYWNrZ3JvdW5kLXBvc2l0aW9uOi04MHB4IC0yMHB4fS5tQ1MtZGFyay10aGluLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uTGVmdHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi04MHB4IC00MHB4fS5tQ1MtZGFyay10aGluLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHR7YmFja2dyb3VuZC1wb3NpdGlvbjotODBweCAtNTZweH0ubUNTLXJvdW5kZWQubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHtiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4xNSl9Lm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLC5tQ1Mtcm91bmRlZC1kb3RzLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLC5tQ1Mtcm91bmRlZC1kb3RzLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciwubUNTLXJvdW5kZWQubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2Vye2hlaWdodDoxNHB4fS5tQ1Mtcm91bmRlZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLXJvdW5kZWQtZG90cy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLXJvdW5kZWQtZG90cy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1yb3VuZGVkLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2Jhcnt3aWR0aDoxNHB4O21hcmdpbjowIDFweH0ubUNTLXJvdW5kZWQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciwubUNTLXJvdW5kZWQtZG90cy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyLC5tQ1Mtcm91bmRlZC1kb3RzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyLC5tQ1Mtcm91bmRlZC5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlcnt3aWR0aDoxNHB4fS5tQ1Mtcm91bmRlZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1Mtcm91bmRlZC1kb3RzLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1yb3VuZGVkLWRvdHMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1yb3VuZGVkLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye2hlaWdodDoxNHB4O21hcmdpbjoxcHggMH0ubUNTLXJvdW5kZWQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWdfZXhwYW5kZWQgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcjpob3ZlciAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1Mtcm91bmRlZC5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWdfZXhwYW5kZWQgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1yb3VuZGVkLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXJDb250YWluZXI6aG92ZXIgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2Jhcnt3aWR0aDoxNnB4O2hlaWdodDoxNnB4O21hcmdpbjotMXB4IDB9Lm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnX2V4cGFuZGVkKy5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1Mtcm91bmRlZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXJDb250YWluZXI6aG92ZXIgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1yb3VuZGVkLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZ19leHBhbmRlZCsubUNTQl9kcmFnZ2VyUmFpbCwubUNTLXJvdW5kZWQubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcjpob3ZlciAubUNTQl9kcmFnZ2VyUmFpbHt3aWR0aDo0cHh9Lm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWdfZXhwYW5kZWQgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyQ29udGFpbmVyOmhvdmVyIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1yb3VuZGVkLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnX2V4cGFuZGVkIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1Mtcm91bmRlZC5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXJDb250YWluZXI6aG92ZXIgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhcntoZWlnaHQ6MTZweDt3aWR0aDoxNnB4O21hcmdpbjowIC0xcHh9Lm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWdfZXhwYW5kZWQrLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyQ29udGFpbmVyOmhvdmVyIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1Mtcm91bmRlZC5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZ19leHBhbmRlZCsubUNTQl9kcmFnZ2VyUmFpbCwubUNTLXJvdW5kZWQubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyQ29udGFpbmVyOmhvdmVyIC5tQ1NCX2RyYWdnZXJSYWlse2hlaWdodDo0cHg7bWFyZ2luOjZweCAwfS5tQ1Mtcm91bmRlZC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwe2JhY2tncm91bmQtcG9zaXRpb246MCAtNzJweH0ubUNTLXJvdW5kZWQubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3due2JhY2tncm91bmQtcG9zaXRpb246MCAtOTJweH0ubUNTLXJvdW5kZWQubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0e2JhY2tncm91bmQtcG9zaXRpb246MCAtMTEycHh9Lm1DUy1yb3VuZGVkLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHR7YmFja2dyb3VuZC1wb3NpdGlvbjowIC0xMjhweH0ubUNTLXJvdW5kZWQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1yb3VuZGVkLWRvdHMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuNzUpfS5tQ1Mtcm91bmRlZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuMTUpfS5tQ1Mtcm91bmRlZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjpob3ZlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLXJvdW5kZWQtZG90cy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjpob3ZlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC44NSl9Lm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWcgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmFjdGl2ZSAubUNTQl9kcmFnZ2VyX2JhciwubUNTLXJvdW5kZWQtZG90cy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1Mtcm91bmRlZC1kb3RzLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmFjdGl2ZSAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC45KX0ubUNTLXJvdW5kZWQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwe2JhY2tncm91bmQtcG9zaXRpb246LTgwcHggLTcycHh9Lm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3due2JhY2tncm91bmQtcG9zaXRpb246LTgwcHggLTkycHh9Lm1DUy1yb3VuZGVkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0e2JhY2tncm91bmQtcG9zaXRpb246LTgwcHggLTExMnB4fS5tQ1Mtcm91bmRlZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHR7YmFja2dyb3VuZC1wb3NpdGlvbjotODBweCAtMTI4cHh9Lm1DUy1yb3VuZGVkLWRvdHMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1Mtcm91bmRlZC1kb3RzLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwgLm1DU0JfZHJhZ2dlclJhaWx7d2lkdGg6NHB4fS5tQ1Mtcm91bmRlZC1kb3RzLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLXJvdW5kZWQtZG90cy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLXJvdW5kZWQtZG90cy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1Mtcm91bmRlZC1kb3RzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyUmFpbHtiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50O2JhY2tncm91bmQtcG9zaXRpb246Y2VudGVyfS5tQ1Mtcm91bmRlZC1kb3RzLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLXJvdW5kZWQtZG90cy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlse2JhY2tncm91bmQtaW1hZ2U6dXJsKGRhdGE6aW1hZ2UvcG5nO2Jhc2U2NCxpVkJPUncwS0dnb0FBQUFOU1VoRVVnQUFBQWdBQUFBSUNBWUFBQURFRDc2TEFBQUFORWxFUVZRWVYyTmtJQUFZaVZidy8vOS9ZNkRpTTFBTkpveU1qR2RCYkxnSlFBWC9rVTBES2dETGthUUF2eFc0SEV2UUZ3Q1JjeElKSzFYem5BQUFBQUJKUlU1RXJrSmdnZz09KTtiYWNrZ3JvdW5kLXJlcGVhdDpyZXBlYXQteTtvcGFjaXR5Oi4zO2ZpbHRlcjpcImFscGhhKG9wYWNpdHk9MzApXCI7LW1zLWZpbHRlcjpcImFscGhhKG9wYWNpdHk9MzApXCJ9Lm1DUy1yb3VuZGVkLWRvdHMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1yb3VuZGVkLWRvdHMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlse2hlaWdodDo0cHg7bWFyZ2luOjZweCAwO2JhY2tncm91bmQtcmVwZWF0OnJlcGVhdC14fS5tQ1Mtcm91bmRlZC1kb3RzLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXB7YmFja2dyb3VuZC1wb3NpdGlvbjotMTZweCAtNzJweH0ubUNTLXJvdW5kZWQtZG90cy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd257YmFja2dyb3VuZC1wb3NpdGlvbjotMTZweCAtOTJweH0ubUNTLXJvdW5kZWQtZG90cy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnR7YmFja2dyb3VuZC1wb3NpdGlvbjotMjBweCAtMTEycHh9Lm1DUy1yb3VuZGVkLWRvdHMubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0yMHB4IC0xMjhweH0ubUNTLXJvdW5kZWQtZG90cy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7YmFja2dyb3VuZC1pbWFnZTp1cmwoZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFBZ0FBQUFJQ0FZQUFBREVENzZMQUFBQUxFbEVRVlFZVjJOa0lBQVlTVkZnREZSOEJxckJCRWlmQmJHUlRmaVBaaHBZamlRRkJLM0E2bDZDdmdBQUU5a0dDZDFtdmdFQUFBQUFTVVZPUks1Q1lJST0pfS5tQ1Mtcm91bmRlZC1kb3RzLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi05NnB4IC03MnB4fS5tQ1Mtcm91bmRlZC1kb3RzLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3due2JhY2tncm91bmQtcG9zaXRpb246LTk2cHggLTkycHh9Lm1DUy1yb3VuZGVkLWRvdHMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnR7YmFja2dyb3VuZC1wb3NpdGlvbjotMTAwcHggLTExMnB4fS5tQ1Mtcm91bmRlZC1kb3RzLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xMDBweCAtMTI4cHh9Lm1DUy0zZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLXRoaWNrLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QtdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtcmVwZWF0OnJlcGVhdC15O2JhY2tncm91bmQtaW1hZ2U6LW1vei1saW5lYXItZ3JhZGllbnQobGVmdCxyZ2JhKDI1NSwyNTUsMjU1LC41KSAwLHJnYmEoMjU1LDI1NSwyNTUsMCkgMTAwJSk7YmFja2dyb3VuZC1pbWFnZTotd2Via2l0LWdyYWRpZW50KGxpbmVhcixsZWZ0IHRvcCxyaWdodCB0b3AsY29sb3Itc3RvcCgwLHJnYmEoMjU1LDI1NSwyNTUsLjUpKSxjb2xvci1zdG9wKDEwMCUscmdiYSgyNTUsMjU1LDI1NSwwKSkpO2JhY2tncm91bmQtaW1hZ2U6LXdlYmtpdC1saW5lYXItZ3JhZGllbnQobGVmdCxyZ2JhKDI1NSwyNTUsMjU1LC41KSAwLHJnYmEoMjU1LDI1NSwyNTUsMCkgMTAwJSk7YmFja2dyb3VuZC1pbWFnZTotby1saW5lYXItZ3JhZGllbnQobGVmdCxyZ2JhKDI1NSwyNTUsMjU1LC41KSAwLHJnYmEoMjU1LDI1NSwyNTUsMCkgMTAwJSk7YmFja2dyb3VuZC1pbWFnZTotbXMtbGluZWFyLWdyYWRpZW50KGxlZnQscmdiYSgyNTUsMjU1LDI1NSwuNSkgMCxyZ2JhKDI1NSwyNTUsMjU1LDApIDEwMCUpO2JhY2tncm91bmQtaW1hZ2U6bGluZWFyLWdyYWRpZW50KHRvIHJpZ2h0LHJnYmEoMjU1LDI1NSwyNTUsLjUpIDAscmdiYSgyNTUsMjU1LDI1NSwwKSAxMDAlKX0ubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy0zZC10aGljay1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QtdGhpY2subUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy0zZC5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLXJlcGVhdDpyZXBlYXQteDtiYWNrZ3JvdW5kLWltYWdlOi1tb3otbGluZWFyLWdyYWRpZW50KHRvcCxyZ2JhKDI1NSwyNTUsMjU1LC41KSAwLHJnYmEoMjU1LDI1NSwyNTUsMCkgMTAwJSk7YmFja2dyb3VuZC1pbWFnZTotd2Via2l0LWdyYWRpZW50KGxpbmVhcixsZWZ0IHRvcCxsZWZ0IGJvdHRvbSxjb2xvci1zdG9wKDAscmdiYSgyNTUsMjU1LDI1NSwuNSkpLGNvbG9yLXN0b3AoMTAwJSxyZ2JhKDI1NSwyNTUsMjU1LDApKSk7YmFja2dyb3VuZC1pbWFnZTotd2Via2l0LWxpbmVhci1ncmFkaWVudCh0b3AscmdiYSgyNTUsMjU1LDI1NSwuNSkgMCxyZ2JhKDI1NSwyNTUsMjU1LDApIDEwMCUpO2JhY2tncm91bmQtaW1hZ2U6LW8tbGluZWFyLWdyYWRpZW50KHRvcCxyZ2JhKDI1NSwyNTUsMjU1LC41KSAwLHJnYmEoMjU1LDI1NSwyNTUsMCkgMTAwJSk7YmFja2dyb3VuZC1pbWFnZTotbXMtbGluZWFyLWdyYWRpZW50KHRvcCxyZ2JhKDI1NSwyNTUsMjU1LC41KSAwLHJnYmEoMjU1LDI1NSwyNTUsMCkgMTAwJSk7YmFja2dyb3VuZC1pbWFnZTpsaW5lYXItZ3JhZGllbnQodG8gYm90dG9tLHJnYmEoMjU1LDI1NSwyNTUsLjUpIDAscmdiYSgyNTUsMjU1LDI1NSwwKSAxMDAlKX0ubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbCAubUNTQl9kcmFnZ2VyLC5tQ1MtM2QubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbCAubUNTQl9kcmFnZ2Vye2hlaWdodDo3MHB4fS5tQ1MtM2QtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciwubUNTLTNkLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2Vye3dpZHRoOjcwcHh9Lm1DUy0zZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMsLm1DUy0zZC5tQ1NCX3Njcm9sbFRvb2xze29wYWNpdHk6MTtmaWx0ZXI6XCJhbHBoYShvcGFjaXR5PTMwKVwiOy1tcy1maWx0ZXI6XCJhbHBoYShvcGFjaXR5PTMwKVwifS5tQ1MtM2QtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy0zZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy0zZC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy0zZC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlsey13ZWJraXQtYm9yZGVyLXJhZGl1czoxNnB4Oy1tb3otYm9yZGVyLXJhZGl1czoxNnB4O2JvcmRlci1yYWRpdXM6MTZweH0ubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLTNkLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7d2lkdGg6OHB4O2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjIpO2JveC1zaGFkb3c6aW5zZXQgMXB4IDAgMXB4IHJnYmEoMCwwLDAsLjUpLGluc2V0IC0xcHggMCAxcHggcmdiYSgyNTUsMjU1LDI1NSwuMil9Lm1DUy0zZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWcgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy0zZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjphY3RpdmUgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy0zZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjpob3ZlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmFjdGl2ZSAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjpob3ZlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiM1NTV9Lm1DUy0zZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2Jhcnt3aWR0aDo4cHh9Lm1DUy0zZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLTNkLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyUmFpbHt3aWR0aDoxMDAlO2hlaWdodDo4cHg7bWFyZ2luOjRweCAwO2JveC1zaGFkb3c6aW5zZXQgMCAxcHggMXB4IHJnYmEoMCwwLDAsLjUpLGluc2V0IDAgLTFweCAxcHggcmdiYSgyNTUsMjU1LDI1NSwuMil9Lm1DUy0zZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7d2lkdGg6MTAwJTtoZWlnaHQ6OHB4O21hcmdpbjo0cHggYXV0b30ubUNTLTNkLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXB7YmFja2dyb3VuZC1wb3NpdGlvbjotMzJweCAtNzJweH0ubUNTLTNkLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93bntiYWNrZ3JvdW5kLXBvc2l0aW9uOi0zMnB4IC05MnB4fS5tQ1MtM2QubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0e2JhY2tncm91bmQtcG9zaXRpb246LTQwcHggLTExMnB4fS5tQ1MtM2QubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi00MHB4IC0xMjhweH0ubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHtiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4xKTtib3gtc2hhZG93Omluc2V0IDFweCAwIDFweCByZ2JhKDAsMCwwLC4xKX0ubUNTLTNkLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlse2JveC1zaGFkb3c6aW5zZXQgMCAxcHggMXB4IHJnYmEoMCwwLDAsLjEpfS5tQ1MtM2QtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwe2JhY2tncm91bmQtcG9zaXRpb246LTExMnB4IC03MnB4fS5tQ1MtM2QtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd257YmFja2dyb3VuZC1wb3NpdGlvbjotMTEycHggLTkycHh9Lm1DUy0zZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uTGVmdHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xMjBweCAtMTEycHh9Lm1DUy0zZC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHR7YmFja2dyb3VuZC1wb3NpdGlvbjotMTIwcHggLTEyOHB4fS5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzLC5tQ1MtM2QtdGhpY2subUNTQl9zY3JvbGxUb29sc3tvcGFjaXR5OjE7ZmlsdGVyOlwiYWxwaGEob3BhY2l0eT0zMClcIjstbXMtZmlsdGVyOlwiYWxwaGEob3BhY2l0eT0zMClcIn0ubUNTLTNkLXRoaWNrLWRhcmsubUNTQl9zY3JvbGxUb29scywubUNTLTNkLXRoaWNrLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyQ29udGFpbmVyLC5tQ1MtM2QtdGhpY2subUNTQl9zY3JvbGxUb29scywubUNTLTNkLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcnstd2Via2l0LWJvcmRlci1yYWRpdXM6N3B4Oy1tb3otYm9yZGVyLXJhZGl1czo3cHg7Ym9yZGVyLXJhZGl1czo3cHh9Lm1DU0JfaW5zaWRlKy5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsLC5tQ1NCX2luc2lkZSsubUNTLTNkLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWx7cmlnaHQ6MXB4fS5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsLC5tQ1MtM2QtdGhpY2subUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbHtib3gtc2hhZG93Omluc2V0IDFweCAwIDFweCByZ2JhKDAsMCwwLC4xKSxpbnNldCAwIDAgMTRweCByZ2JhKDAsMCwwLC41KX0ubUNTLTNkLXRoaWNrLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLC5tQ1MtM2QtdGhpY2subUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFse2JvdHRvbToxcHg7Ym94LXNoYWRvdzppbnNldCAwIDFweCAxcHggcmdiYSgwLDAsMCwuMSksaW5zZXQgMCAwIDE0cHggcmdiYSgwLDAsMCwuNSl9Lm1DUy0zZC10aGljay1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2Jhcnstd2Via2l0LWJvcmRlci1yYWRpdXM6NXB4Oy1tb3otYm9yZGVyLXJhZGl1czo1cHg7Ym9yZGVyLXJhZGl1czo1cHg7Ym94LXNoYWRvdzppbnNldCAxcHggMCAwIHJnYmEoMjU1LDI1NSwyNTUsLjQpO3dpZHRoOjEycHg7bWFyZ2luOjJweDtwb3NpdGlvbjphYnNvbHV0ZTtoZWlnaHQ6YXV0bzt0b3A6MDtib3R0b206MDtsZWZ0OjA7cmlnaHQ6MH0ubUNTLTNkLXRoaWNrLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy0zZC10aGljay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2Jhcntib3gtc2hhZG93Omluc2V0IDAgMXB4IDAgcmdiYSgyNTUsMjU1LDI1NSwuNCk7aGVpZ2h0OjEycHg7d2lkdGg6YXV0b30ubUNTLTNkLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QtdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmFjdGl2ZSAubUNTQl9kcmFnZ2VyX2JhciwubUNTLTNkLXRoaWNrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjpob3ZlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiM1NTV9Lm1DUy0zZC10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJDb250YWluZXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuMDUpO2JveC1zaGFkb3c6aW5zZXQgMXB4IDFweCAxNnB4IHJnYmEoMCwwLDAsLjEpfS5tQ1MtM2QtdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHtiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50fS5tQ1MtM2QtdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0zMnB4IC03MnB4fS5tQ1MtM2QtdGhpY2subUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3due2JhY2tncm91bmQtcG9zaXRpb246LTMycHggLTkycHh9Lm1DUy0zZC10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnR7YmFja2dyb3VuZC1wb3NpdGlvbjotNDBweCAtMTEycHh9Lm1DUy0zZC10aGljay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0e2JhY2tncm91bmQtcG9zaXRpb246LTQwcHggLTEyOHB4fS5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xze2JveC1zaGFkb3c6aW5zZXQgMCAwIDE0cHggcmdiYSgwLDAsMCwuMil9Lm1DUy0zZC10aGljay1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbHtib3gtc2hhZG93Omluc2V0IDAgMXB4IDFweCByZ2JhKDAsMCwwLC4xKSxpbnNldCAwIDAgMTRweCByZ2JhKDAsMCwwLC4yKX0ubUNTLTNkLXRoaWNrLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye2JveC1zaGFkb3c6aW5zZXQgMXB4IDAgMCByZ2JhKDI1NSwyNTUsMjU1LC40KSxpbnNldCAtMXB4IDAgMCByZ2JhKDAsMCwwLC4yKX0ubUNTLTNkLXRoaWNrLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7Ym94LXNoYWRvdzppbnNldCAwIDFweCAwIHJnYmEoMjU1LDI1NSwyNTUsLjQpLGluc2V0IDAgLTFweCAwIHJnYmEoMCwwLDAsLjIpfS5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy0zZC10aGljay1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6YWN0aXZlIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6aG92ZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojNzc3fS5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJDb250YWluZXJ7YmFja2dyb3VuZC1jb2xvcjojZmZmO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuMDUpO2JveC1zaGFkb3c6aW5zZXQgMXB4IDFweCAxNnB4IHJnYmEoMCwwLDAsLjEpfS5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtbWluaW1hbC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1taW5pbWFsLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudH0ubUNTLTNkLXRoaWNrLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xMTJweCAtNzJweH0ubUNTLTNkLXRoaWNrLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3due2JhY2tncm91bmQtcG9zaXRpb246LTExMnB4IC05MnB4fS5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnR7YmFja2dyb3VuZC1wb3NpdGlvbjotMTIwcHggLTExMnB4fS5tQ1MtM2QtdGhpY2stZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0e2JhY2tncm91bmQtcG9zaXRpb246LTEyMHB4IC0xMjhweH0ubUNTQl9vdXRzaWRlKy5tQ1MtbWluaW1hbC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwsLm1DU0Jfb3V0c2lkZSsubUNTLW1pbmltYWwubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbHtyaWdodDowO21hcmdpbjoxMnB4IDB9Lm1DdXN0b21TY3JvbGxCb3gubUNTLW1pbmltYWwrLm1DU0Jfc2Nyb2xsVG9vbHMrLm1DU0Jfc2Nyb2xsVG9vbHMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLC5tQ3VzdG9tU2Nyb2xsQm94Lm1DUy1taW5pbWFsKy5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCwubUN1c3RvbVNjcm9sbEJveC5tQ1MtbWluaW1hbC1kYXJrKy5tQ1NCX3Njcm9sbFRvb2xzKy5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCwubUN1c3RvbVNjcm9sbEJveC5tQ1MtbWluaW1hbC1kYXJrKy5tQ1NCX3Njcm9sbFRvb2xzLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbHtib3R0b206MDttYXJnaW46MCAxMnB4fS5tQ1MtZGlyLXJ0bD4ubUNTQl9vdXRzaWRlKy5tQ1MtbWluaW1hbC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwsLm1DUy1kaXItcnRsPi5tQ1NCX291dHNpZGUrLm1DUy1taW5pbWFsLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWx7bGVmdDowO3JpZ2h0OmF1dG99Lm1DUy1taW5pbWFsLWRhcmsubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbCAubUNTQl9kcmFnZ2VyLC5tQ1MtbWluaW1hbC5tQ1NCX3Njcm9sbFRvb2xzX3ZlcnRpY2FsIC5tQ1NCX2RyYWdnZXJ7aGVpZ2h0OjUwcHh9Lm1DUy1taW5pbWFsLWRhcmsubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIsLm1DUy1taW5pbWFsLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2Vye3dpZHRoOjUwcHh9Lm1DUy1taW5pbWFsLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4yKTtmaWx0ZXI6XCJhbHBoYShvcGFjaXR5PTIwKVwiOy1tcy1maWx0ZXI6XCJhbHBoYShvcGFjaXR5PTIwKVwifS5tQ1MtbWluaW1hbC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZyAubUNTQl9kcmFnZ2VyX2JhciwubUNTLW1pbmltYWwubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmFjdGl2ZSAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiNmZmY7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC41KTtmaWx0ZXI6XCJhbHBoYShvcGFjaXR5PTUwKVwiOy1tcy1maWx0ZXI6XCJhbHBoYShvcGFjaXR5PTUwKVwifS5tQ1MtbWluaW1hbC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4yKTtmaWx0ZXI6XCJhbHBoYShvcGFjaXR5PTIwKVwiOy1tcy1maWx0ZXI6XCJhbHBoYShvcGFjaXR5PTIwKVwifS5tQ1MtbWluaW1hbC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtbWluaW1hbC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjphY3RpdmUgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuNSk7ZmlsdGVyOlwiYWxwaGEob3BhY2l0eT01MClcIjstbXMtZmlsdGVyOlwiYWxwaGEob3BhY2l0eT01MClcIn0ubUNTLWRhcmstMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtbGlnaHQtMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlse3dpZHRoOjZweDtiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC4yKX0ubUNTLWRhcmstMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1saWdodC0zLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2Jhcnt3aWR0aDo2cHh9Lm1DUy1kYXJrLTMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1kYXJrLTMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtbGlnaHQtMy5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWxpZ2h0LTMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlse3dpZHRoOjEwMCU7aGVpZ2h0OjZweDttYXJnaW46NXB4IDB9Lm1DUy1kYXJrLTMubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnX2V4cGFuZGVkKy5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtZGFyay0zLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXJDb250YWluZXI6aG92ZXIgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1saWdodC0zLm1DU0Jfc2Nyb2xsVG9vbHNfdmVydGljYWwubUNTQl9zY3JvbGxUb29sc19vbkRyYWdfZXhwYW5kIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZ19leHBhbmRlZCsubUNTQl9kcmFnZ2VyUmFpbCwubUNTLWxpZ2h0LTMubUNTQl9zY3JvbGxUb29sc192ZXJ0aWNhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcjpob3ZlciAubUNTQl9kcmFnZ2VyUmFpbHt3aWR0aDoxMnB4fS5tQ1MtZGFyay0zLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnX2V4cGFuZGVkKy5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtZGFyay0zLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcjpob3ZlciAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLWxpZ2h0LTMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsLm1DU0Jfc2Nyb2xsVG9vbHNfb25EcmFnX2V4cGFuZCAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWdfZXhwYW5kZWQrLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1saWdodC0zLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbC5tQ1NCX3Njcm9sbFRvb2xzX29uRHJhZ19leHBhbmQgLm1DU0JfZHJhZ2dlckNvbnRhaW5lcjpob3ZlciAubUNTQl9kcmFnZ2VyUmFpbHtoZWlnaHQ6MTJweDttYXJnaW46MnB4IDB9Lm1DUy1saWdodC0zLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXB7YmFja2dyb3VuZC1wb3NpdGlvbjotMzJweCAtNzJweH0ubUNTLWxpZ2h0LTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3due2JhY2tncm91bmQtcG9zaXRpb246LTMycHggLTkycHh9Lm1DUy1saWdodC0zLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uTGVmdHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi00MHB4IC0xMTJweH0ubUNTLWxpZ2h0LTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi00MHB4IC0xMjhweH0ubUNTLWRhcmstMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuNzUpfS5tQ1MtZGFyay0zLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjpob3ZlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC44NSl9Lm1DUy1kYXJrLTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWcgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1kYXJrLTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmFjdGl2ZSAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC45KX0ubUNTLWRhcmstMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlse2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjEpfS5tQ1MtZGFyay0zLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXB7YmFja2dyb3VuZC1wb3NpdGlvbjotMTEycHggLTcycHh9Lm1DUy1kYXJrLTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3due2JhY2tncm91bmQtcG9zaXRpb246LTExMnB4IC05MnB4fS5tQ1MtZGFyay0zLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uTGVmdHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xMjBweCAtMTEycHh9Lm1DUy1kYXJrLTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xMjBweCAtMTI4cHh9Lm1DUy1pbnNldC0yLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLWluc2V0LTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLWluc2V0LTMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtaW5zZXQtMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtaW5zZXQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtaW5zZXQubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHt3aWR0aDoxMnB4O2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjIpfS5tQ1MtaW5zZXQtMi1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWluc2V0LTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtaW5zZXQtMy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWluc2V0LTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtaW5zZXQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1pbnNldC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7d2lkdGg6NnB4O21hcmdpbjozcHggNXB4O3Bvc2l0aW9uOmFic29sdXRlO2hlaWdodDphdXRvO3RvcDowO2JvdHRvbTowO2xlZnQ6MDtyaWdodDowfS5tQ1MtaW5zZXQtMi1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtaW5zZXQtMi5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWluc2V0LTMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWluc2V0LTMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1pbnNldC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtaW5zZXQubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7aGVpZ2h0OjZweDttYXJnaW46NXB4IDNweDtwb3NpdGlvbjphYnNvbHV0ZTt3aWR0aDphdXRvO3RvcDowO2JvdHRvbTowO2xlZnQ6MDtyaWdodDowfS5tQ1MtaW5zZXQtMi1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLWluc2V0LTIubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtaW5zZXQtMy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHNfaG9yaXpvbnRhbCAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLWluc2V0LTMubUNTQl9zY3JvbGxUb29sc19ob3Jpem9udGFsIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtaW5zZXQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1pbnNldC5tQ1NCX3Njcm9sbFRvb2xzX2hvcml6b250YWwgLm1DU0JfZHJhZ2dlclJhaWx7d2lkdGg6MTAwJTtoZWlnaHQ6MTJweDttYXJnaW46MnB4IDB9Lm1DUy1pbnNldC0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXAsLm1DUy1pbnNldC0zLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uVXAsLm1DUy1pbnNldC5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwe2JhY2tncm91bmQtcG9zaXRpb246LTMycHggLTcycHh9Lm1DUy1pbnNldC0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93biwubUNTLWluc2V0LTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3duLC5tQ1MtaW5zZXQubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25Eb3due2JhY2tncm91bmQtcG9zaXRpb246LTMycHggLTkycHh9Lm1DUy1pbnNldC0yLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uTGVmdCwubUNTLWluc2V0LTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0LC5tQ1MtaW5zZXQubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0e2JhY2tncm91bmQtcG9zaXRpb246LTQwcHggLTExMnB4fS5tQ1MtaW5zZXQtMi5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0LC5tQ1MtaW5zZXQtMy5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblJpZ2h0LC5tQ1MtaW5zZXQubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi00MHB4IC0xMjhweH0ubUNTLWluc2V0LTItZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1pbnNldC0zLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtaW5zZXQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuNzUpfS5tQ1MtaW5zZXQtMi1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjpob3ZlciAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWluc2V0LTMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6aG92ZXIgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1pbnNldC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjpob3ZlciAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC44NSl9Lm1DUy1pbnNldC0yLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWcgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1pbnNldC0yLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmFjdGl2ZSAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWluc2V0LTMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZyAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWluc2V0LTMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6YWN0aXZlIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtaW5zZXQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXIubUNTQl9kcmFnZ2VyX29uRHJhZyAubUNTQl9kcmFnZ2VyX2JhciwubUNTLWluc2V0LWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmFjdGl2ZSAubUNTQl9kcmFnZ2VyX2JhcntiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC45KX0ubUNTLWluc2V0LTItZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXJSYWlsLC5tQ1MtaW5zZXQtMy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWwsLm1DUy1pbnNldC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuMSl9Lm1DUy1pbnNldC0yLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25VcCwubUNTLWluc2V0LTMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwLC5tQ1MtaW5zZXQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvblVwe2JhY2tncm91bmQtcG9zaXRpb246LTExMnB4IC03MnB4fS5tQ1MtaW5zZXQtMi1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93biwubUNTLWluc2V0LTMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkRvd24sLm1DUy1pbnNldC1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uRG93bntiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xMTJweCAtOTJweH0ubUNTLWluc2V0LTItZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnQsLm1DUy1pbnNldC0zLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25MZWZ0LC5tQ1MtaW5zZXQtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2J1dHRvbkxlZnR7YmFja2dyb3VuZC1wb3NpdGlvbjotMTIwcHggLTExMnB4fS5tQ1MtaW5zZXQtMi1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfYnV0dG9uUmlnaHQsLm1DUy1pbnNldC0zLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodCwubUNTLWluc2V0LWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9idXR0b25SaWdodHtiYWNrZ3JvdW5kLXBvc2l0aW9uOi0xMjBweCAtMTI4cHh9Lm1DUy1pbnNldC0yLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbCwubUNTLWluc2V0LTIubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHtiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50O2JvcmRlci13aWR0aDoxcHg7Ym9yZGVyLXN0eWxlOnNvbGlkO2JvcmRlci1jb2xvcjojZmZmO2JvcmRlci1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LC4yKTstd2Via2l0LWJveC1zaXppbmc6Ym9yZGVyLWJveDstbW96LWJveC1zaXppbmc6Ym9yZGVyLWJveDtib3gtc2l6aW5nOmJvcmRlci1ib3h9Lm1DUy1pbnNldC0yLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHtib3JkZXItY29sb3I6IzAwMDtib3JkZXItY29sb3I6cmdiYSgwLDAsMCwuMil9Lm1DUy1pbnNldC0zLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlclJhaWx7YmFja2dyb3VuZC1jb2xvcjojZmZmO2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuNil9Lm1DUy1pbnNldC0zLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyUmFpbHtiYWNrZ3JvdW5kLWNvbG9yOiMwMDA7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDAsMCwwLC42KX0ubUNTLWluc2V0LTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjc1KX0ubUNTLWluc2V0LTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyOmhvdmVyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6IzAwMDtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMCwwLDAsLjg1KX0ubUNTLWluc2V0LTMubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyLm1DU0JfZHJhZ2dlcl9vbkRyYWcgLm1DU0JfZHJhZ2dlcl9iYXIsLm1DUy1pbnNldC0zLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjphY3RpdmUgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojMDAwO2JhY2tncm91bmQtY29sb3I6cmdiYSgwLDAsMCwuOSl9Lm1DUy1pbnNldC0zLWRhcmsubUNTQl9zY3JvbGxUb29scyAubUNTQl9kcmFnZ2VyIC5tQ1NCX2RyYWdnZXJfYmFye2JhY2tncm91bmQtY29sb3I6I2ZmZjtiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjU1LDI1NSwyNTUsLjc1KX0ubUNTLWluc2V0LTMtZGFyay5tQ1NCX3Njcm9sbFRvb2xzIC5tQ1NCX2RyYWdnZXI6aG92ZXIgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojZmZmO2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuODUpfS5tQ1MtaW5zZXQtMy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlci5tQ1NCX2RyYWdnZXJfb25EcmFnIC5tQ1NCX2RyYWdnZXJfYmFyLC5tQ1MtaW5zZXQtMy1kYXJrLm1DU0Jfc2Nyb2xsVG9vbHMgLm1DU0JfZHJhZ2dlcjphY3RpdmUgLm1DU0JfZHJhZ2dlcl9iYXJ7YmFja2dyb3VuZC1jb2xvcjojZmZmO2JhY2tncm91bmQtY29sb3I6cmdiYSgyNTUsMjU1LDI1NSwuOSl9IiwiQGtleWZyYW1lcyByb3RhdGlvbiB7XG4gIGZyb20ge1xuICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHJvdGF0ZSgwZGVnKTtcbiAgfVxuICB0byB7XG4gICAgICAtd2Via2l0LXRyYW5zZm9ybTogcm90YXRlKDM1OWRlZyk7XG4gIH1cbn1cblxuQGtleWZyYW1lcyB0cmFuc2xhdGVyaWdodHtcbiAgMCV7XG4gICAgcmlnaHQ6IC01MHB4O1xuICAgIGNvbG9yOiB0cmFuc3BhcmVudDtcbiAgICBkaXNwbGF5OiBub25lO1xuICB9XG5cbiAgMzAle1xuICAgIHJpZ2h0OiAxNTBweDtcbiAgICBjb2xvcjogdHJhbnNwYXJlbnQ7XG4gICAgZGlzcGxheTogYmxvY2s7XG4gIH1cblxuICAxMDAle1xuICAgIHJpZ2h0OiAyMDBweDtcbiAgICBjb2xvcjogd2hpdGU7XG4gIH1cbn1cblxuQGtleWZyYW1lcyB0cmFuc2xhdGVsZWZ0e1xuICAwJXtcbiAgICByaWdodDogMjAwcHg7XG4gICAgY29sb3I6IHdoaXRlO1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICB9XG4gIDMwJXtcbiAgICByaWdodDogMTUwcHg7XG4gICAgY29sb3I6IHRyYW5zcGFyZW50O1xuICB9XG5cbiAgMTAwJXtcbiAgICByaWdodDogLTIwMHB4O1xuICAgIGNvbG9yOiB0cmFuc3BhcmVudDtcbiAgICBkaXNwbGF5OiBub25lO1xuICB9XG59XG5cbkBrZXlmcmFtZXMgcmVkdWNIe1xuICAwJXtcbiAgICBmb250LXNpemU6ICRmaDE7XG4gICAgLXdlYmtpdC10ZXh0LXN0cm9rZSA6IDFweDtcbiAgICBjb2xvcjogdHJhbnNwYXJlbnQ7XG4gICAgbGluZS1oZWlnaHQ6IDVyZW07XG4gIH1cblxuICA3MCV7XG4gICAgLXdlYmtpdC10ZXh0LXN0cm9rZSA6IDFweDtcbiAgICBjb2xvcjogYmxhY2s7XG4gICAgLy8gbGluZS1oZWlnaHQ6IDYuNXJlbTtcblxuICB9XG5cbiAgMTAwJXtcbiAgICBmb250LXNpemU6ICRtaW4taDE7XG4gICAgLXdlYmtpdC10ZXh0LXN0cm9rZSA6IDBweDtcbiAgICBjb2xvcjogYmxhY2s7XG4gICAgbGluZS1oZWlnaHQ6IDVyZW07XG4gIH1cbn1cblxuQGtleWZyYW1lcyB0cmFuc2xhdGVZe1xuICAwJXtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoMHB4KTtcbiAgICBvcGFjaXR5OiAxO1xuICB9XG5cbiAgMTAwJXtcbiAgICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoLTIwcHgpO1xuICAgIG9wYWNpdHk6IDA7XG4gIH1cbn1cblxuQGtleWZyYW1lcyBzaG93Y2F0bGlzdCB7XG4gIDAle1xuICAgIHdpZHRoOiAwO1xuICAgIGJveC1zaGFkb3c6IDBweCAwcHggMHB4IDBweCBibGFjaztcbiAgICBvcGFjaXR5OiAxO1xuICB9XG5cbiAgMTAwJXtcbiAgICB3aWR0aDogMTAwJTtcbiAgICBib3gtc2hhZG93OiAwcHggLTJweCAwcHggLTJweCBibGFjaztcbiAgICBvcGFjaXR5OiAxO1xuICB9XG59XG5cbkBrZXlmcmFtZXMgc2hvd2JvcmRlciB7XG4gIDAle1xuICAgIHdpZHRoOiAwO1xuICAgIGJhY2tncm91bmQ6IGJsYWNrO1xuICB9XG4gIDEwMCV7XG4gICAgd2lkdGg6IDEwMCU7XG4gICAgYmFja2dyb3VuZDogYmxhY2s7XG4gIH1cbn1cblxuQGtleWZyYW1lcyB1bHRyYW5zbGF0ZSB7XG4gIDAle1xuICAgIG9wYWNpdHk6IDA7XG4gICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVYKDBweCk7XG4gIH1cbiAgMTAwJXtcbiAgICBvcGFjaXR5OiAxO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWCgxMHB4KTtcbiAgfVxufVxuXG5cbkBrZXlmcmFtZXMgc2hvd2NhcmQge1xuICAwJXtcbiAgICBvcGFjaXR5OiAwO1xuXG4gIH1cbiAgMTAwJXtcbiAgICBvcGFjaXR5OiAxO1xuXG4gIH1cbn1cbiIsImh0bWwsIGJvZHl7XG4gIG92ZXJmbG93OiBoaWRkZW47XG59XG5cbi5ibG9ne1xuICBoZWlnaHQ6IDEwMCU7XG4gICYuZG93bntcbiAgICBoZWlnaHQ6IDVyZW07XG4gIH1cbn1cblxuaGVhZGVye1xuICAuY29udGFjdHtcbiAgICB3aWR0aDogOTBweDtcbiAgICBoZWlnaHQ6IGF1dG87XG4gICAgei1pbmRleDogOTk5O1xuICAgIHBvc2l0aW9uOiBmaXhlZDtcbiAgICB0b3A6IDBweDtcbiAgICByaWdodDogMHB4O1xuICAgIC13ZWJraXQtdHJhbnNmb3JtOiBzY2FsZSgxKTtcbiAgICAgICAgICAgIHRyYW5zZm9ybTogc2NhbGUoMSk7XG4gICAgLXdlYmtpdC10cmFuc2l0aW9uOiAtd2Via2l0LXRyYW5zZm9ybSAwLjVzIGVhc2U7XG4gICAgdHJhbnNpdGlvbjogLXdlYmtpdC10cmFuc2Zvcm0gMC41cyBlYXNlO1xuICAgIHRyYW5zaXRpb246IHRyYW5zZm9ybSAwLjVzIGVhc2U7XG4gICAgdHJhbnNpdGlvbjogdHJhbnNmb3JtIDAuNXMgZWFzZSwgLXdlYmtpdC10cmFuc2Zvcm0gMC41cyBlYXNlO1xuICAgIGN1cnNvcjogcG9pbnRlcjtcbiAgICAmOmhvdmVye1xuICAgICAgLXdlYmtpdC10cmFuc2l0aW9uOiAtd2Via2l0LXRyYW5zZm9ybSAwLjVzIGVhc2U7XG4gICAgICB0cmFuc2l0aW9uOiAtd2Via2l0LXRyYW5zZm9ybSAwLjVzIGVhc2U7XG4gICAgICB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gMC41cyBlYXNlO1xuICAgICAgdHJhbnNpdGlvbjogdHJhbnNmb3JtIDAuNXMgZWFzZSwgLXdlYmtpdC10cmFuc2Zvcm0gMC41cyBlYXNlO1xuICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlKDAuOSk7XG4gICAgICAgICAgICAgIHRyYW5zZm9ybTogc2NhbGUoMC45KTtcbiAgICB9XG4gICAgc3Zne1xuICAgICAgY3Vyc29yOiBwb2ludGVyO1xuICAgICAgLXdlYmtpdC1hbmltYXRpb246IHJvdGF0aW9uIDZzIGluZmluaXRlIGxpbmVhcjtcbiAgICAgICAgICAgICAgYW5pbWF0aW9uOiByb3RhdGlvbiA2cyBpbmZpbml0ZSBsaW5lYXI7XG4gICAgfVxuICAgIGgze1xuICAgICAgY29sb3I6IHdoaXRlO1xuICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHJvdGF0ZSgzNWRlZyk7XG4gICAgICAgICAgICAgIHRyYW5zZm9ybTogcm90YXRlKDM1ZGVnKTtcbiAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICAgIHRvcDogMzVweDtcbiAgICAgIGxlZnQ6IDE5cHg7XG4gICAgfVxuICAgIC50eHR7XG4gICAgICAgIHBvc2l0aW9uOiBmaXhlZDtcbiAgICAgICAgcmlnaHQ6IC0yMDBweDtcbiAgICAgICAgZGlzcGxheTogbm9uZTtcbiAgICB9XG4gICAgJi5vcGVue1xuICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlKDE1KTtcbiAgICAgICAgICAgICAgdHJhbnNmb3JtOiBzY2FsZSgxNSk7XG4gICAgICBoZWlnaHQ6IGF1dG87XG4gICAgICAtd2Via2l0LXRyYW5zaXRpb246IC13ZWJraXQtdHJhbnNmb3JtIDAuNXMgZWFzZTtcbiAgICAgIHRyYW5zaXRpb246IC13ZWJraXQtdHJhbnNmb3JtIDAuNXMgZWFzZTtcbiAgICAgIHRyYW5zaXRpb246IHRyYW5zZm9ybSAwLjVzIGVhc2U7XG4gICAgICB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gMC41cyBlYXNlLCAtd2Via2l0LXRyYW5zZm9ybSAwLjVzIGVhc2U7XG4gICAgICBoM3tcbiAgICAgICAgY29sb3I6IHRyYW5zcGFyZW50O1xuICAgICAgICBsZWZ0OiA1MDBweDtcbiAgICAgICAgLXdlYmtpdC10cmFuc2l0aW9uOiB2aXNpYmlsaXR5IDAuNSBlYXNlIGxlZnQgMXMgZWFzZTtcbiAgICAgICAgdHJhbnNpdGlvbjogdmlzaWJpbGl0eSAwLjUgZWFzZSBsZWZ0IDFzIGVhc2U7XG4gICAgICB9XG4gICAgICBzdmd7XG4gICAgICAgIC13ZWJraXQtYW5pbWF0aW9uOiByb3RhdGlvbiAxMnMgaW5maW5pdGUgbGluZWFyO1xuICAgICAgICAgICAgICAgIGFuaW1hdGlvbjogcm90YXRpb24gMTJzIGluZmluaXRlIGxpbmVhcjtcbiAgICAgIH1cbiAgICAgICYgKyAudHh0e1xuICAgICAgICAgIHBvc2l0aW9uOiBmaXhlZDtcbiAgICAgICAgICB0b3A6IDE5MHB4O1xuICAgICAgICAgIHRleHQtYWxpZ246IGNlbnRlcjtcbiAgICAgICAgICBsaW5lLWhlaWdodDogMS41cmVtO1xuICAgICAgICAgIHotaW5kZXg6IDk5OTk7XG4gICAgICAgICAgLXdlYmtpdC1hbmltYXRpb246IHRyYW5zbGF0ZXJpZ2h0IDFzIGVhc2UgZm9yd2FyZHM7XG4gICAgICAgICAgICAgICAgICBhbmltYXRpb246IHRyYW5zbGF0ZXJpZ2h0IDFzIGVhc2UgZm9yd2FyZHM7XG4gICAgICB9XG4gICAgfVxuICAgICYuY2xvc2V7XG4gICAgICAtd2Via2l0LXRyYW5zZm9ybTogc2NhbGUoMSk7XG4gICAgICAgICAgICAgIHRyYW5zZm9ybTogc2NhbGUoMSk7XG4gICAgICAtd2Via2l0LXRyYW5zaXRpb246IC13ZWJraXQtdHJhbnNmb3JtIDAuNXMgMC4xcyBlYXNlO1xuICAgICAgdHJhbnNpdGlvbjogLXdlYmtpdC10cmFuc2Zvcm0gMC41cyAwLjFzIGVhc2U7XG4gICAgICB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gMC41cyAwLjFzIGVhc2U7XG4gICAgICB0cmFuc2l0aW9uOiB0cmFuc2Zvcm0gMC41cyAwLjFzIGVhc2UsIC13ZWJraXQtdHJhbnNmb3JtIDAuNXMgMC4xcyBlYXNlO1xuICAgICAgJiArIC50eHR7XG4gICAgICAgICAgdG9wOiAxOTBweDtcbiAgICAgICAgICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gICAgICAgICAgbGluZS1oZWlnaHQ6IDEuNXJlbTtcbiAgICAgICAgICB6LWluZGV4OiA5OTk5O1xuICAgICAgICAgIC13ZWJraXQtYW5pbWF0aW9uOiB0cmFuc2xhdGVsZWZ0IDFzIGVhc2UgZm9yd2FyZHM7XG4gICAgICAgICAgICAgICAgICBhbmltYXRpb246IHRyYW5zbGF0ZWxlZnQgMXMgZWFzZSBmb3J3YXJkcztcbiAgICAgIH1cbiAgICAgIGgze1xuXG5cbiAgICAgICAgLXdlYmtpdC10cmFuc2l0aW9uOiBjb2xvciAwLjVzIDAuNXMgZWFzZSwgbGVmdCAwLjVzIGVhc2U7XG4gICAgICAgIHRyYW5zaXRpb246IGNvbG9yIDAuNXMgMC41cyBlYXNlLCBsZWZ0IDAuNXMgZWFzZTtcbiAgICAgIH1cbiAgICAgICY6aG92ZXJ7XG4gICAgICAgIC13ZWJraXQtdHJhbnNpdGlvbjogLXdlYmtpdC10cmFuc2Zvcm0gMC41cyBlYXNlIWltcG9ydGFudDtcbiAgICAgICAgdHJhbnNpdGlvbjogLXdlYmtpdC10cmFuc2Zvcm0gMC41cyBlYXNlIWltcG9ydGFudDtcbiAgICAgICAgdHJhbnNpdGlvbjogdHJhbnNmb3JtIDAuNXMgZWFzZSFpbXBvcnRhbnQ7XG4gICAgICAgIHRyYW5zaXRpb246IHRyYW5zZm9ybSAwLjVzIGVhc2UsIC13ZWJraXQtdHJhbnNmb3JtIDAuNXMgZWFzZSFpbXBvcnRhbnQ7XG4gICAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiBzY2FsZSgwLjkpIWltcG9ydGFudDtcbiAgICAgICAgICAgICAgICB0cmFuc2Zvcm06IHNjYWxlKDAuOSkhaW1wb3J0YW50O1xuICAgICAgfVxuICAgIH1cbiAgfVxufVxuXG4uY2FyZHtcbiAgd2lkdGg6IGNhbGMoKDEwMCUgLyAzKSk7XG4gIGxpbmUtaGVpZ2h0OiAwLjhyZW0haW1wb3J0YW50O1xuICAuY2FyZC1oZWFkZXJ7XG4gICAgbWFyZ2luLXRvcDogMTBweDtcbiAgICBkaXNwbGF5OiAtd2Via2l0LWlubGluZS1ib3g7XG4gICAgZGlzcGxheTogLW1zLWlubGluZS1mbGV4Ym94O1xuICAgIGRpc3BsYXk6IGlubGluZS1mbGV4O1xuICAgICYgPiBkaXZ7XG4gICAgICBtYXJnaW4tcmlnaHQ6IDVweDtcbiAgICB9XG4gIH1cbn1cblxuXG5AbWVkaWEgc2NyZWVuIGFuZCAobWF4LXdpZHRoOiA5NjBweCkge1xuICAud29ya3MtbWVhe1xuICAgICYgPiBkaXY6bGFzdC1jaGlsZHtcbiAgICAgIGRpc3BsYXk6IG5vbmU7XG4gICAgfVxuICB9XG4gIC5jYXJke1xuICAgIHdpZHRoOiBjYWxjKCgxMDAlIC8gMikgLSA1cHggKTtcbiAgfVxuXG59XG5cbkBtZWRpYSBzY3JlZW4gYW5kIChtYXgtd2lkdGg6IDYwMHB4KSB7XG4gIC53b3Jrcy1tZWF7XG4gICAgJiA+IGRpdjpudGgtY2hpbGQoMil7XG4gICAgICBkaXNwbGF5OiBub25lO1xuICAgIH1cbiAgfVxuICAuY2FyZHtcbiAgICB3aWR0aDogMTAwJTtcbiAgfVxufVxuIiwiLy8gaG9tZVxuYm9keSB7XG4gICAgLmJsb2cge1xuICAgICAgICBoZWFkZXIge1xuICAgICAgICAgICAgaGVpZ2h0OjEwMHZoO1xuICAgICAgICAgICAgd2lkdGg6IDEwMCU7XG4gICAgICAgICAgICBkaXNwbGF5OiBpbmxpbmUtZmxleDtcbiAgICAgICAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47XG4gICAgICAgICAgICBhbGlnbi1jb250ZW50OiBzcGFjZS1hcm91bmQ7XG4gICAgICAgICAgICAmID4gZGl2e1xuICAgICAgICAgICAgICBwb3NpdGlvbjogZml4ZWQ7XG4gICAgICAgICAgICAgICYuaGVhZHtcbiAgICAgICAgICAgICAgICB3aWR0aDogMTAwJTtcbiAgICAgICAgICAgICAgICB6LWluZGV4OiA5OTk7XG4gICAgICAgICAgICAgICAgcGFkZGluZy10b3A6IDcwcHg7XG4gICAgICAgICAgICAgICAgaDIge1xuICAgICAgICAgICAgICAgICAgbWFyZ2luLWxlZnQ6IDhweDtcbiAgICAgICAgICAgICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgJjo6YWZ0ZXIge1xuICAgICAgICAgICAgICAgICAgZGlzcGxheTogYmxvY2s7XG4gICAgICAgICAgICAgICAgICBjb250ZW50OiBcIiBcIjtcbiAgICAgICAgICAgICAgICAgIHdpZHRoOiAwJTtcbiAgICAgICAgICAgICAgICAgIGhlaWdodDogMXB4O1xuICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZDogYmxhY2s7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgJi53b3Jrcy1tZWEge1xuICAgICAgICAgICAgICAgIGRpc3BsYXk6IC13ZWJraXQtaW5saW5lLWJveDtcbiAgICAgICAgICAgICAgICBkaXNwbGF5OiAtbXMtaW5saW5lLWZsZXhib3g7XG4gICAgICAgICAgICAgICAgZGlzcGxheTogaW5saW5lLWZsZXg7XG4gICAgICAgICAgICAgICAgYWxpZ24tc2VsZjogZmxleC1lbmQ7XG4gICAgICAgICAgICAgICAgaGVpZ2h0OiBjYWxjKDEwMCUgLSAyMHB4KTtcbiAgICAgICAgICAgICAgICBhbGlnbi1pdGVtczogZmxleC1lbmQ7XG4gICAgICAgICAgICAgICAgLmNhcmR7XG4gICAgICAgICAgICAgICAgICAmOm50aC1jaGlsZCgyKXtcbiAgICAgICAgICAgICAgICAgICAgbWFyZ2luOjAgMTBweDtcbiAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICY6bm90KDpudGgtY2hpbGQoMikpe1xuICAgICAgICAgICAgICAgICAgICBtYXJnaW46IDA7XG4gICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICYuc2Nyb2xse1xuICAgICAgICAgICAgICAgIHdpZHRoOiAxMDAlO1xuICAgICAgICAgICAgICAgIGJvdHRvbTogMDtcbiAgICAgICAgICAgICAgICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICAmLmRvd257XG4gICAgICAgICAgaGVhZGVye1xuICAgICAgICAgICAgaGVpZ2h0OiA1cmVtO1xuICAgICAgICAgICAgLmhlYWR7XG4gICAgICAgICAgICAgIHBhZGRpbmc6MDtcbiAgICAgICAgICAgICAgdHJhbnNpdGlvbjogYWxsIDFzIDAuOXMgZWFzZTtcbiAgICAgICAgICAgICAgJjo6YWZ0ZXJ7XG4gICAgICAgICAgICAgICAgYW5pbWF0aW9uOiBzaG93Ym9yZGVyIDIuNXMgMS41cyBlYXNlIGZvcndhcmRzO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGgye1xuICAgICAgICAgICAgICAgIG9wYWNpdHk6IDA7XG4gICAgICAgICAgICAgICAgbWFyZ2luLWxlZnQ6IC0xMDBweDtcbiAgICAgICAgICAgICAgICB0cmFuc2l0aW9uOiBhbGwgMXMgZWFzZTtcbiAgICAgICAgICAgICAgICBsZWZ0OiAtNTAlO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgLmNhcmR7XG4gICAgICAgICAgICAmOm50aC1jaGlsZCgxKXtcbiAgICAgICAgICAgICAgYW5pbWF0aW9uOiB0cmFuc2xhdGVZIDFzIGVhc2UgZm9yd2FyZHM7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAmOm50aC1jaGlsZCgyKXtcbiAgICAgICAgICAgICAgYW5pbWF0aW9uOiB0cmFuc2xhdGVZIDEuM3MgZWFzZSBmb3J3YXJkcztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgICY6bnRoLWNoaWxkKDMpe1xuICAgICAgICAgICAgICBhbmltYXRpb246IHRyYW5zbGF0ZVkgMS42cyBlYXNlIGZvcndhcmRzO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgICAuc2Nyb2xse1xuICAgICAgICAgICAgb3BhY2l0eTogMDtcbiAgICAgICAgICAgIHRyYW5zaXRpb246IGFsbCAxcyBlYXNlO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbn1cblxuXG4vLyBwYWdlXG5ib2R5IHtcbiAgICAjcGFnZS13cmFwcGVyLml0ZW0ge1xuICAgICAgICBoZWFkZXIge1xuICAgICAgICAgICAgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkIGJsYWNrO1xuICAgICAgICAgICAgcG9zaXRpb246IGZpeGVkO1xuICAgICAgICAgICAgaGVpZ2h0OiAxMTJweDtcbiAgICAgICAgICAgIHRvcDogMDtcbiAgICAgICAgICAgIGxlZnQ6IDEwcHg7XG4gICAgICAgICAgICByaWdodDogMTBweDtcbiAgICAgICAgICAgIGJhY2tncm91bmQ6IHdoaXRlO1xuICAgICAgICAgICAgei1pbmRleDogOTk5O1xuICAgICAgICAgICAgbmF2IHtcbiAgICAgICAgICAgICAgbWFyZ2luLXRvcDogMjVweDtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaDEge1xuICAgICAgICAgICAgICAgIGNvbG9yOiBibGFjaztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaDIsIC5zY3JvbGwge1xuICAgICAgICAgICAgICAgIGRpc3BsYXk6IG5vbmU7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG59XG4iLCIuYmxvZ3tcbiAgI3N0YXJ0e1xuICAgIC5jYXQtbGlzdCB7XG4gICAgICAgIHdpZHRoOiAxMDAlO1xuICAgICAgICB6LWluZGV4OiA5OTk7XG4gICAgICAgIGxlZnQ6IC0xMDAlO1xuICAgICAgICAmOjpiZWZvcmV7XG4gICAgICAgICAgZGlzcGxheTogYmxvY2s7XG4gICAgICAgICAgY29udGVudDogXCIgXCI7XG4gICAgICAgICAgd2lkdGg6IDAlO1xuICAgICAgICAgIGhlaWdodDogMXB4O1xuICAgICAgICAgIGJhY2tncm91bmQ6IGJsYWNrO1xuICAgICAgICB9XG4gICAgICAgICY6OmFmdGVyIHtcbiAgICAgICAgICBkaXNwbGF5OiBibG9jaztcbiAgICAgICAgICBjb250ZW50OiBcIiBcIjtcbiAgICAgICAgICB3aWR0aDogMCU7XG4gICAgICAgICAgaGVpZ2h0OiAxcHg7XG4gICAgICAgICAgYmFja2dyb3VuZDogYmxhY2s7XG4gICAgICAgIH1cbiAgICAgICAgdWwge1xuICAgICAgICAgIG9wYWNpdHk6IDA7XG4gICAgICAgICAgZGlzcGxheTogaW5saW5lLWZsZXg7XG4gICAgICAgICAgbWFyZ2luOiAxMHB4IDA7XG4gICAgICAgICAgLmJ0bi1ncm91cHtcbiAgICAgICAgICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgICAgICAgICBtYXJnaW4tcmlnaHQ6IDEwcHg7XG4gICAgICAgICAgICAmOjphZnRlcntcbiAgICAgICAgICAgICAgd2lkdGg6IDVweDtcbiAgICAgICAgICAgICAgY29udGVudDogXCIgXCI7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAmOmhvdmVye1xuICAgICAgICAgICAgICAmOjphZnRlcntcbiAgICAgICAgICAgICAgICBjb250ZW50OiBcIlxcMDAyMDIyXCI7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlucHV0e1xuICAgICAgICAgICAgICBkaXNwbGF5OiBub25lO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgICNpdGVtc3tcbiAgICAgIC5mdWxsLXdvcmtze1xuICAgICAgICAuY2FyZHtcbiAgICAgICAgICB3aWR0aDogY2FsYygoMTAwdncgLyAzKSAtIDYuNXB4KTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuICAuZG93bntcbiAgICAuY2F0LWxpc3R7XG4gICAgICBsZWZ0OiAwO1xuICAgICAgJjo6YWZ0ZXJ7XG4gICAgICAgIGFuaW1hdGlvbjogc2hvd2JvcmRlciAyLjVzIDEuNnMgZWFzZSBmb3J3YXJkcztcbiAgICAgIH1cbiAgICAgIHVse1xuICAgICAgICBhbmltYXRpb246IHVsdHJhbnNsYXRlIDFzIDIuNXMgZWFzZSBmb3J3YXJkcztcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cbiIsImJvZHl7XG4gICNzdGFydHtcbiAgICB3aWR0aDogMTAwJTtcbiAgICBoZWlnaHQ6IDEwMCU7XG4gIH1cbiAgI3BhZ2Utd3JhcHBlci5ibG9ne1xuICAgICNzdGFydHtcbiAgICAgIGhlaWdodDogY2FsYygxMDAlIC0gNXJlbSk7XG4gICAgICBwb3NpdGlvbjogZml4ZWQ7XG4gICAgICB0b3A6IDEwMHZoO1xuICAgICAgbGVmdDogMDtcbiAgICB9XG4gIH1cbiAgI3BhZ2Utd3JhcHBlci5kb3due1xuICAgICNzdGFydHtcbiAgICAgIHRvcDogNXJlbTtcbiAgICB9XG4gICAgI2l0ZW1ze1xuICAgICAgLmNhcmR7XG4gICAgICAgIGFuaW1hdGlvbjogc2hvd2NhcmQgMS41cyAyLjVzIGVhc2UgZm9yd2FyZHM7XG4gICAgICB9XG4gICAgfVxuICB9XG59XG5cbiNpdGVtc3tcbiAgLmZ1bGwtd29ya3N7XG4gICAgd2lkdGg6IDEwMCU7XG4gICAgbWFyZ2luLXRvcDogMzBweDtcbiAgfVxuICAuY2FyZHtcbiAgICBvcGFjaXR5OiAwO1xuICAgIG1hcmdpbi1ib3R0b206IDEwcHg7XG4gICAgbWluLXdpZHRoOiAyMDBweDtcbiAgfVxufVxuIiwiYm9keXtcbiAgI3BhZ2Utd3JhcHBlci5pdGVte1xuICAgIG1hcmdpbjogMCAxMHB4O1xuICAuY29udGFjdHtcbiAgICAudHh0e2Rpc3BsYXk6IG5vbmV9XG4gIH1cbiAgICAjc3RhcnR7XG4gICAgICBtYXJnaW4tdG9wOiAxMTJweDtcbiAgICB9XG4gICAgI2JvZHktd3JhcHBlci5ibG9nLWxpc3Rpbmd7XG4gICAgICAuaW1nLWNhcmR7XG4gICAgICAgIHdpZHRoOiBjYWxjKCgxMDAlIC8gMykgLSA2LjVweCk7XG4gICAgICAgIG1hcmdpbi1ib3R0b206IDEwcHg7XG4gICAgICB9XG4gICAgICAuaGVhZGVyLWl0ZW17XG4gICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgICAgICAgbWFyZ2luLWJvdHRvbTogMjBweDtcbiAgICAgICAgcGFkZGluZy10b3A6IDIwcHg7XG4gICAgICAgIC50aXRsZXtcbiAgICAgICAgICBkaXNwbGF5OiBpbmxpbmUtZmxleDtcbiAgICAgICAgICBhbGlnbi1pdGVtczogYmFzZWxpbmU7XG4gICAgICAgICAgaDJ7XG4gICAgICAgICAgICBtYXJnaW4tcmlnaHQ6IDVweDtcbiAgICAgICAgICAgIHRleHQtdHJhbnNmb3JtOiB1cHBlcmNhc2U7XG4gICAgICAgICAgfVxuICAgICAgICAgIHRpbWV7XG4gICAgICAgICAgICBtYXJnaW4tcmlnaHQ6IDVweDtcbiAgICAgICAgICB9XG4gICAgICAgICAgc3BhbntcbiAgICAgICAgICAgIG1hcmdpbi1yaWdodDogNXB4O1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICAuY29udGVudHtcbiAgICAgICAgICB3aWR0aDogY2FsYygoMTAwJSAvIDMpIC0gMTVweCk7XG4gICAgICAgICAgbWFyZ2luLXRvcDogMjBweDtcbiAgICAgICAgfVxuICAgICAgICAucHJldi1uZXh0e1xuICAgICAgICAgIHdpZHRoOiBjYWxjKDEwMCUgLyAzKTtcbiAgICAgICAgICBoZWlnaHQ6IDEwMCU7XG4gICAgICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICAgICAgIHRvcDogY2FsYyggNTAlICsgMTBweCApO1xuICAgICAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgtNTAlKTtcbiAgICAgICAgICByaWdodDogMHB4O1xuICAgICAgICAgIC5idG57XG4gICAgICAgICAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgICAgICAgICB3aWR0aDogYXV0bztcbiAgICAgICAgICAgICAgaGVpZ2h0OiAxMDAlO1xuICAgICAgICAgIC5idG4taW5mb3tcbiAgICAgICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICAgICAgICAgIHRyYW5zZm9ybTogcm90YXRlKC0xNWRlZyk7XG4gICAgICAgICAgICBtYXJnaW4tbGVmdDogNDBweDtcbiAgICAgICAgICAgIG1hcmdpbi10b3A6IDIwcHg7XG4gICAgICAgICAgfVxuICAgICAgICAgIGltZ3tcbiAgICAgICAgICAgIGZsb2F0OiByaWdodDtcbiAgICAgICAgICAgIHdpZHRoOiA4MCU7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgLnRvcHtcbiAgICAgIHRleHQtYWxpZ246IGNlbnRlcjtcbiAgICAgIG1hcmdpbjogMjBweCBhdXRvIDIwcHggYXV0bztcbiAgICAgIHdpZHRoOiA1MHB4O1xuICAgICAgaW1ne1xuICAgICAgICB3aWR0aDogMjBweDtcbiAgICAgICAgbWFyZ2luLWJvdHRvbTo1cHg7XG4gICAgICB9XG4gICAgICBwe1xuICAgICAgICB0cmFuc2Zvcm06IHJvdGF0ZSgtMTBkZWcpO1xuICAgICAgfVxuICAgIH1cbiAgfVxufVxuXG5AbWVkaWEgc2NyZWVuIGFuZCAobWF4LXdpZHRoOiA5NjBweCkge1xuICBib2R5e1xuICAgICNwYWdlLXdyYXBwZXIuaXRlbXtcbiAgICAgICNib2R5LXdyYXBwZXIuYmxvZy1saXN0aW5ne1xuICAgICAgICAuaGVhZGVyLWl0ZW17XG4gICAgICAgICAgLmNvbnRlbnR7XG4gICAgICAgICAgICB3aWR0aDogY2FsYygoMTAwJSAvIDIpIC0gNXB4KTtcbiAgICAgICAgICB9XG4gICAgICAgICAgLnByZXYtbmV4dHtcbiAgICAgICAgICAgIHdpZHRoOiBjYWxjKCgxMDAlIC8gMikgLSA1cHgpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICAuaW1nLWNhcmR7XG4gICAgICAgICAgd2lkdGg6IGNhbGMoKDEwMCUgLyAyKSAtIDVweCk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cblxuQG1lZGlhIHNjcmVlbiBhbmQgKG1heC13aWR0aDogNjAwcHgpIHtcbiAgYm9keXtcbiAgICAjcGFnZS13cmFwcGVyLml0ZW17XG4gICAgICAjYm9keS13cmFwcGVyLmJsb2ctbGlzdGluZ3tcbiAgICAgICAgLmhlYWRlci1pdGVte1xuICAgICAgICAgIC5jb250ZW50e1xuICAgICAgICAgICAgd2lkdGg6IGNhbGMoKDEwMCUpIC0gMTVweCk7XG4gICAgICAgICAgfVxuICAgICAgICAgIC5wcmV2LW5leHR7XG4gICAgICAgICAgICB3aWR0aDogY2FsYygoMTAwJSkgLSAxNXB4KTtcbiAgICAgICAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgICAgICAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgwJSk7XG4gICAgICAgICAgICAuYnRue1xuICAgICAgICAgICAgICAuYnRuLWluZm97XG4gICAgICAgICAgICAgICAgICBkaXNwbGF5OiBub25lO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGltZ3tcbiAgICAgICAgICAgICAgICBmbG9hdDogbm9uZTtcbiAgICAgICAgICAgICAgICB3aWR0aDogMTAwJTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICAuaW1nLWNhcmR7XG4gICAgICAgICAgd2lkdGg6IDEwMCU7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUNBQSxBQUFBLENBQUMsQ0FBQSxFQUNDLGtCQUFrQixFQUFHLFVBQVUsRUFDdkIsVUFBVSxFQUFHLFVBQVUsR0FDaEM7O0FBQ0QsNEVBQTRFO0FBRTVFLHlGQUNnRjtBQUVoRix3SEFHRztBQUVILEFBQUEsSUFBSSxDQUFDLEVBQ0gsTUFBTSxFQUFFLENBQUMsRUFDVCxPQUFPLEVBQUUsQ0FBQyxFQUNWLFNBQVMsRUFBRSxJQUFJLEVBQ2YsV0FBVyxFQUFFLElBQUksRUFBRSxPQUFPLENBQzFCLHdCQUF3QixFQUFFLElBQUksRUFBRSxPQUFPLENBQ3ZDLE1BQU0sRUFBRSxJQUFJLEVBQ1osS0FBSyxFQUFFLElBQUksR0FDWjs7QUFFRCx5RkFDZ0Y7QUFFaEYseUNBRUc7QUFFSCxBQUFBLElBQUksQ0FBQyxFQUNILE1BQU0sRUFBRSxDQUFDLEVBQ1QsT0FBTyxFQUFFLENBQUMsRUFDVixNQUFNLEVBQUUsSUFBSSxFQUNaLEtBQUssRUFBRSxJQUFJLEdBQ1o7O0FBRUQsb0RBRUc7QUFFSCxBQUFBLElBQUksQ0FBQyxFQUNILE9BQU8sRUFBRSxLQUFLLEdBQ2Y7O0FBRUQsZ0lBR0c7QUFFSCxBQUFBLEVBQUUsQ0FBQyxFQUNELFNBQVMsRUFBRSxHQUFHLEVBQ2QsTUFBTSxFQUFDLENBQUMsR0FDVDs7QUFF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 */
diff --git a/user/themes/gilles_acezat/css-compiled/theme.min.css b/user/themes/gilles_acezat/css-compiled/theme.min.css
index 34c0b72..fa21615 100644
--- a/user/themes/gilles_acezat/css-compiled/theme.min.css
+++ b/user/themes/gilles_acezat/css-compiled/theme.min.css
@@ -1 +1 @@
-@charset 'UTF-8';*{box-sizing:border-box}/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{font-size:18px;line-height:1.15;margin:0;padding:0;-webkit-text-size-adjust:100%}body{margin:0;padding:0}main{display:block}h1{font-size:2em;margin:0}a,em,h1,h2,h3,h4,h5,li,p,strong,ul{margin:0;padding:0}hr{overflow:visible;box-sizing:content-box;height:0}pre{font-family:monospace,monospace;font-size:1em}ul{list-style:none}li,ul{margin:0;padding:0}a{text-decoration:none;color:#000;background-color:transparent}abbr[title]{text-decoration:underline;text-decoration:underline dotted;border-bottom:none;-webkit-text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{width:100%;height:100%;vertical-align:bottom;border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{display:table;box-sizing:border-box;max-width:100%;padding:0;white-space:normal;color:inherit}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}@font-face{font-family:Regular;src:url(../fonts/Secuela/Secuela-Regular.eot);src:url(../fonts/Secuela/Secuela-Regular.eot?#iefix) format('embedded-opentype'),url(../fonts/Secuela/Secuela-Regular.woff) format('woff'),url(../fonts/Secuela/Secuela-Regular.ttf) format('truetype'),url(../fonts/Secuela/Secuela-Regular.svg#Regular) format('svg')}@font-face{font-family:medium;src:url(../fonts/Secuela/Secuela-Medium.eot);src:url(../fonts/Secuela/Secuela-Medium.eot?#iefix) format('embedded-opentype'),url(../fonts/Secuela/Secuela-Medium.woff) format('woff'),url(../fonts/Secuela/Secuela-Medium.ttf) format('truetype'),url(../fonts/Secuela/Secuela-Medium.svg#medium) format('svg')}@font-face{font-family:bold;src:url(../fonts/Secuela/Secuela-Bold.eot);src:url(../fonts/Secuela/Secuela-Bold.eot?#iefix) format('embedded-opentype'),url(../fonts/Secuela/Secuela-Bold.woff) format('woff'),url(../fonts/Secuela/Secuela-Bold.ttf) format('truetype'),url(../fonts/Secuela/Secuela-Bold.svg#bold) format('svg')}@font-face{font-family:italic;src:url(../fonts/Secuela/Secuela-Italic.eot);src:url(../fonts/Secuela/Secuela-Italic.eot?#iefix) format('embedded-opentype'),url(../fonts/Secuela/Secuela-Italic.woff) format('woff'),url(../fonts/Secuela/Secuela-Italic.ttf) format('truetype'),url(../fonts/Secuela/Secuela-Italic.svg#italic) format('svg')}@font-face{font-family:light;src:url(../fonts/Secuela/Secuela-Light.eot);src:url(../fonts/Secuela/Secuela-Light.eot?#iefix) format('embedded-opentype'),url(../fonts/Secuela/Secuela-Light.woff) format('woff'),url(../fonts/Secuela/Secuela-Light.ttf) format('truetype'),url(../fonts/Secuela/Secuela-Light.svg#light) format('svg')}h1,h2{font-family:medium;font-weight:400}h2{font-size:3rem}h3{font-family:medium;font-size:1rem;font-weight:400}h5{font-family:bold;font-size:.8rem;font-weight:400}p{font-family:Regular;font-size:.9rem;font-weight:400}time{font-family:italic;font-weight:400}header .contact h3{text-transform:lowercase;color:#fff}.scroll p{font-family:bold;font-size:.8rem;font-weight:400}.cat-list .cat a{font-family:light;font-weight:400}body>.blog h1{font-size:11rem;line-height:7rem;color:transparent;-webkit-text-stroke:.1rem #111}body>.blog .card h5{text-transform:uppercase}body>.blog .card .card-category{font-family:italic;font-size:.8rem;font-weight:400}body>.blog .card .card-date{font-size:.8rem}body>.blog .card .card-footer span{font-family:medium;font-size:.8rem;font-weight:400;text-transform:capitalize}body>.blog .card .card-footer span a{color:#999}body>.item h1{font-size:3rem}body>.item h2{font-size:1.1rem}body>.item .content-item .content-title>a{font-family:italic}body>.item .category{font-family:italic;font-size:1.1rem;font-weight:400}body>.item time{font-size:.8rem;font-size:1.1rem}body>.item .tags{font-family:medium;font-size:.8rem;font-weight:400;text-transform:capitalize}body>.item .tags a{color:#999}body>.item .top p{font-family:bold}.prev-next .title{font-family:bold;font-size:.8rem}.prev-next .tags{font-family:italic;font-size:.8rem!important}.prev-next .blog-date time{font-family:italic;font-size:.8rem!important}.top p{font-size:.8rem}.mCustomScrollbar{-ms-touch-action:pinch-zoom;touch-action:pinch-zoom}.mCustomScrollbar.mCS_no_scrollbar,.mCustomScrollbar.mCS_touch_action{-ms-touch-action:auto;touch-action:auto}.mCustomScrollBox{position:relative;overflow:hidden;max-width:100%;height:100%;outline:0;direction:ltr}.mCSB_container{overflow:hidden;width:auto;height:auto}.mCSB_inside>.mCSB_container{margin-right:30px}.mCSB_container.mCS_no_scrollbar_y.mCS_y_hidden{margin-right:0}.mCS-dir-rtl>.mCSB_inside>.mCSB_container{margin-right:0;margin-left:30px}.mCS-dir-rtl>.mCSB_inside>.mCSB_container.mCS_no_scrollbar_y.mCS_y_hidden{margin-left:0}.mCSB_scrollTools{position:absolute;top:0;right:0;bottom:0;left:auto;width:16px;height:auto;opacity:.75;filter:'alpha(opacity=75)';-ms-filter:'alpha(opacity=75)'}.mCSB_outside+.mCSB_scrollTools{right:-26px}.mCS-dir-rtl>.mCSB_inside>.mCSB_scrollTools,.mCS-dir-rtl>.mCSB_outside+.mCSB_scrollTools{right:auto;left:0}.mCS-dir-rtl>.mCSB_outside+.mCSB_scrollTools{left:-26px}.mCSB_scrollTools .mCSB_draggerContainer{position:absolute;top:0;right:0;bottom:0;left:0;height:auto}.mCSB_scrollTools a+.mCSB_draggerContainer{margin:20px 0}.mCSB_scrollTools .mCSB_draggerRail{width:2px;height:100%;margin:0 auto;border-radius:16px}.mCSB_scrollTools .mCSB_dragger{z-index:1;width:100%;height:30px;cursor:pointer}.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{position:relative;width:4px;height:100%;margin:0 auto;text-align:center;border-radius:16px}.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar,.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{width:12px}.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{width:8px}.mCSB_scrollTools .mCSB_buttonDown,.mCSB_scrollTools .mCSB_buttonUp{position:absolute;display:block;overflow:hidden;width:100%;height:20px;margin:0 auto;cursor:pointer}.mCSB_scrollTools .mCSB_buttonDown{bottom:0}.mCSB_horizontal.mCSB_inside>.mCSB_container{margin-right:0;margin-bottom:30px}.mCSB_horizontal.mCSB_outside>.mCSB_container{min-height:100%}.mCSB_horizontal>.mCSB_container.mCS_no_scrollbar_x.mCS_x_hidden{margin-bottom:0}.mCSB_scrollTools.mCSB_scrollTools_horizontal{top:auto;right:0;bottom:0;left:0;width:auto;height:16px}.mCustomScrollBox+.mCSB_scrollTools+.mCSB_scrollTools.mCSB_scrollTools_horizontal,.mCustomScrollBox+.mCSB_scrollTools.mCSB_scrollTools_horizontal{bottom:-26px}.mCSB_scrollTools.mCSB_scrollTools_horizontal a+.mCSB_draggerContainer{margin:0 20px}.mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_draggerRail{width:100%;height:2px;margin:7px 0}.mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_dragger{left:0;width:30px;height:100%}.mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{width:100%;height:4px;margin:6px auto}.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar,.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{height:12px;margin:2px auto}.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{height:8px;margin:4px 0}.mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonLeft,.mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonRight{position:absolute;display:block;overflow:hidden;width:20px;height:100%;margin:0 auto;cursor:pointer}.mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonLeft{left:0}.mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonRight{right:0}.mCSB_container_wrapper{position:absolute;top:0;right:0;bottom:0;left:0;overflow:hidden;width:auto;height:auto;margin-right:30px;margin-bottom:30px}.mCSB_container_wrapper>.mCSB_container{box-sizing:border-box;padding-right:30px;padding-bottom:30px}.mCSB_vertical_horizontal>.mCSB_scrollTools.mCSB_scrollTools_vertical{bottom:20px}.mCSB_vertical_horizontal>.mCSB_scrollTools.mCSB_scrollTools_horizontal{right:20px}.mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden+.mCSB_scrollTools.mCSB_scrollTools_vertical{bottom:0}.mCS-dir-rtl>.mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside>.mCSB_scrollTools.mCSB_scrollTools_horizontal,.mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden+.mCSB_scrollTools~.mCSB_scrollTools.mCSB_scrollTools_horizontal{right:0}.mCS-dir-rtl>.mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside>.mCSB_scrollTools.mCSB_scrollTools_horizontal{left:20px}.mCS-dir-rtl>.mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside>.mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden+.mCSB_scrollTools~.mCSB_scrollTools.mCSB_scrollTools_horizontal{left:0}.mCS-dir-rtl>.mCSB_inside>.mCSB_container_wrapper{margin-right:0;margin-left:30px}.mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden>.mCSB_container{padding-right:0}.mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden>.mCSB_container{padding-bottom:0}.mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside>.mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden{margin-right:0;margin-left:0}.mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside>.mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden{margin-bottom:0}.mCSB_scrollTools,.mCSB_scrollTools .mCSB_buttonDown,.mCSB_scrollTools .mCSB_buttonLeft,.mCSB_scrollTools .mCSB_buttonRight,.mCSB_scrollTools .mCSB_buttonUp,.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{transition:opacity .2s ease-in-out,background-color .2s ease-in-out}.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerRail,.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger_bar,.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerRail,.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger_bar{transition:width .2s ease-out .2s,height .2s ease-out .2s,margin-left .2s ease-out .2s,margin-right .2s ease-out .2s,margin-top .2s ease-out .2s,margin-bottom .2s ease-out .2s,opacity .2s ease-in-out,background-color .2s ease-in-out}.mCS-autoHide>.mCustomScrollBox>.mCSB_scrollTools,.mCS-autoHide>.mCustomScrollBox~.mCSB_scrollTools{opacity:0;filter:'alpha(opacity=0)';-ms-filter:'alpha(opacity=0)'}.mCS-autoHide:hover>.mCustomScrollBox>.mCSB_scrollTools,.mCS-autoHide:hover>.mCustomScrollBox~.mCSB_scrollTools,.mCustomScrollBox:hover>.mCSB_scrollTools,.mCustomScrollBox:hover~.mCSB_scrollTools,.mCustomScrollbar>.mCustomScrollBox>.mCSB_scrollTools.mCSB_scrollTools_onDrag,.mCustomScrollbar>.mCustomScrollBox~.mCSB_scrollTools.mCSB_scrollTools_onDrag{opacity:1;filter:'alpha(opacity=100)';-ms-filter:'alpha(opacity=100)'}.mCSB_scrollTools .mCSB_draggerRail{background-color:#000;background-color:rgba(0,0,0,.4);filter:'alpha(opacity=40)';-ms-filter:'alpha(opacity=40)'}.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.75);filter:'alpha(opacity=75)';-ms-filter:'alpha(opacity=75)'}.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.85);filter:'alpha(opacity=85)';-ms-filter:'alpha(opacity=85)'}.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.9);filter:'alpha(opacity=90)';-ms-filter:'alpha(opacity=90)'}.mCSB_scrollTools .mCSB_buttonDown,.mCSB_scrollTools .mCSB_buttonLeft,.mCSB_scrollTools .mCSB_buttonRight,.mCSB_scrollTools .mCSB_buttonUp{opacity:.4;background-image:url(mCSB_buttons.png);background-repeat:no-repeat;filter:'alpha(opacity=40)';-ms-filter:'alpha(opacity=40)'}.mCSB_scrollTools .mCSB_buttonUp{background-position:0 0}.mCSB_scrollTools .mCSB_buttonDown{background-position:0 -20px}.mCSB_scrollTools .mCSB_buttonLeft{background-position:0 -40px}.mCSB_scrollTools .mCSB_buttonRight{background-position:0 -56px}.mCSB_scrollTools .mCSB_buttonDown:hover,.mCSB_scrollTools .mCSB_buttonLeft:hover,.mCSB_scrollTools .mCSB_buttonRight:hover,.mCSB_scrollTools .mCSB_buttonUp:hover{opacity:.75;filter:'alpha(opacity=75)';-ms-filter:'alpha(opacity=75)'}.mCSB_scrollTools .mCSB_buttonDown:active,.mCSB_scrollTools .mCSB_buttonLeft:active,.mCSB_scrollTools .mCSB_buttonRight:active,.mCSB_scrollTools .mCSB_buttonUp:active{opacity:.9;filter:'alpha(opacity=90)';-ms-filter:'alpha(opacity=90)'}.mCS-dark.mCSB_scrollTools .mCSB_draggerRail{background-color:#000;background-color:rgba(0,0,0,.15)}.mCS-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.75)}.mCS-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:rgba(0,0,0,.85)}.mCS-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:rgba(0,0,0,.9)}.mCS-dark.mCSB_scrollTools .mCSB_buttonUp{background-position:-80px 0}.mCS-dark.mCSB_scrollTools .mCSB_buttonDown{background-position:-80px -20px}.mCS-dark.mCSB_scrollTools .mCSB_buttonLeft{background-position:-80px -40px}.mCS-dark.mCSB_scrollTools .mCSB_buttonRight{background-position:-80px -56px}.mCS-dark-2.mCSB_scrollTools .mCSB_draggerRail,.mCS-light-2.mCSB_scrollTools .mCSB_draggerRail{width:4px;border-radius:1px;background-color:#fff;background-color:rgba(255,255,255,.1)}.mCS-dark-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-light-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{width:4px;border-radius:1px;background-color:#fff;background-color:rgba(255,255,255,.75)}.mCS-dark-2.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-dark-2.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-light-2.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-light-2.mCSB_scrollTools_horizontal .mCSB_draggerRail{width:100%;height:4px;margin:6px auto}.mCS-light-2.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.85)}.mCS-light-2.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-light-2.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.9)}.mCS-light-2.mCSB_scrollTools .mCSB_buttonUp{background-position:-32px 0}.mCS-light-2.mCSB_scrollTools .mCSB_buttonDown{background-position:-32px -20px}.mCS-light-2.mCSB_scrollTools .mCSB_buttonLeft{background-position:-40px -40px}.mCS-light-2.mCSB_scrollTools .mCSB_buttonRight{background-position:-40px -56px}.mCS-dark-2.mCSB_scrollTools .mCSB_draggerRail{border-radius:1px;background-color:#000;background-color:rgba(0,0,0,.1)}.mCS-dark-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{border-radius:1px;background-color:#000;background-color:rgba(0,0,0,.75)}.mCS-dark-2.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.85)}.mCS-dark-2.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-dark-2.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.9)}.mCS-dark-2.mCSB_scrollTools .mCSB_buttonUp{background-position:-112px 0}.mCS-dark-2.mCSB_scrollTools .mCSB_buttonDown{background-position:-112px -20px}.mCS-dark-2.mCSB_scrollTools .mCSB_buttonLeft{background-position:-120px -40px}.mCS-dark-2.mCSB_scrollTools .mCSB_buttonRight{background-position:-120px -56px}.mCS-dark-thick.mCSB_scrollTools .mCSB_draggerRail,.mCS-light-thick.mCSB_scrollTools .mCSB_draggerRail{width:4px;border-radius:2px;background-color:#fff;background-color:rgba(255,255,255,.1)}.mCS-dark-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-light-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{width:6px;border-radius:2px;background-color:#fff;background-color:rgba(255,255,255,.75)}.mCS-dark-thick.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-light-thick.mCSB_scrollTools_horizontal .mCSB_draggerRail{width:100%;height:4px;margin:6px 0}.mCS-dark-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-light-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{width:100%;height:6px;margin:5px auto}.mCS-light-thick.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.85)}.mCS-light-thick.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-light-thick.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.9)}.mCS-light-thick.mCSB_scrollTools .mCSB_buttonUp{background-position:-16px 0}.mCS-light-thick.mCSB_scrollTools .mCSB_buttonDown{background-position:-16px -20px}.mCS-light-thick.mCSB_scrollTools .mCSB_buttonLeft{background-position:-20px -40px}.mCS-light-thick.mCSB_scrollTools .mCSB_buttonRight{background-position:-20px -56px}.mCS-dark-thick.mCSB_scrollTools .mCSB_draggerRail{border-radius:2px;background-color:#000;background-color:rgba(0,0,0,.1)}.mCS-dark-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{border-radius:2px;background-color:#000;background-color:rgba(0,0,0,.75)}.mCS-dark-thick.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.85)}.mCS-dark-thick.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-dark-thick.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.9)}.mCS-dark-thick.mCSB_scrollTools .mCSB_buttonUp{background-position:-96px 0}.mCS-dark-thick.mCSB_scrollTools .mCSB_buttonDown{background-position:-96px -20px}.mCS-dark-thick.mCSB_scrollTools .mCSB_buttonLeft{background-position:-100px -40px}.mCS-dark-thick.mCSB_scrollTools .mCSB_buttonRight{background-position:-100px -56px}.mCS-light-thin.mCSB_scrollTools .mCSB_draggerRail{background-color:#fff;background-color:rgba(255,255,255,.1)}.mCS-dark-thin.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-light-thin.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{width:2px}.mCS-dark-thin.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-light-thin.mCSB_scrollTools_horizontal .mCSB_draggerRail{width:100%}.mCS-dark-thin.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-light-thin.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{width:100%;height:2px;margin:7px auto}.mCS-dark-thin.mCSB_scrollTools .mCSB_draggerRail{background-color:#000;background-color:rgba(0,0,0,.15)}.mCS-dark-thin.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.75)}.mCS-dark-thin.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.85)}.mCS-dark-thin.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-dark-thin.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.9)}.mCS-dark-thin.mCSB_scrollTools .mCSB_buttonUp{background-position:-80px 0}.mCS-dark-thin.mCSB_scrollTools .mCSB_buttonDown{background-position:-80px -20px}.mCS-dark-thin.mCSB_scrollTools .mCSB_buttonLeft{background-position:-80px -40px}.mCS-dark-thin.mCSB_scrollTools .mCSB_buttonRight{background-position:-80px -56px}.mCS-rounded.mCSB_scrollTools .mCSB_draggerRail{background-color:#fff;background-color:rgba(255,255,255,.15)}.mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger,.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger,.mCS-rounded-dots.mCSB_scrollTools .mCSB_dragger,.mCS-rounded.mCSB_scrollTools .mCSB_dragger{height:14px}.mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded-dots.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{width:14px;margin:0 1px}.mCS-rounded-dark.mCSB_scrollTools_horizontal .mCSB_dragger,.mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_dragger,.mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_dragger,.mCS-rounded.mCSB_scrollTools_horizontal .mCSB_dragger{width:14px}.mCS-rounded-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{height:14px;margin:1px 0}.mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar,.mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar,.mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{width:16px;height:16px;margin:-1px 0}.mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail,.mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{width:4px}.mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar,.mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar,.mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{width:16px;height:16px;margin:0 -1px}.mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail,.mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{height:4px;margin:6px 0}.mCS-rounded.mCSB_scrollTools .mCSB_buttonUp{background-position:0 -72px}.mCS-rounded.mCSB_scrollTools .mCSB_buttonDown{background-position:0 -92px}.mCS-rounded.mCSB_scrollTools .mCSB_buttonLeft{background-position:0 -112px}.mCS-rounded.mCSB_scrollTools .mCSB_buttonRight{background-position:0 -128px}.mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.75)}.mCS-rounded-dark.mCSB_scrollTools .mCSB_draggerRail{background-color:#000;background-color:rgba(0,0,0,.15)}.mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar,.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.85)}.mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.9)}.mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonUp{background-position:-80px -72px}.mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonDown{background-position:-80px -92px}.mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonLeft{background-position:-80px -112px}.mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonRight{background-position:-80px -128px}.mCS-rounded-dots-dark.mCSB_scrollTools_vertical .mCSB_draggerRail,.mCS-rounded-dots.mCSB_scrollTools_vertical .mCSB_draggerRail{width:4px}.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-rounded-dots.mCSB_scrollTools .mCSB_draggerRail,.mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_draggerRail{background-color:transparent;background-position:center}.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-rounded-dots.mCSB_scrollTools .mCSB_draggerRail{opacity:.3;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAANElEQVQYV2NkIAAYiVbw//9/Y6DiM1ANJoyMjGdBbLgJQAX/kU0DKgDLkaQAvxW4HEvQFwCRcxIJK1XznAAAAABJRU5ErkJggg==);background-repeat:repeat-y;filter:'alpha(opacity=30)';-ms-filter:'alpha(opacity=30)'}.mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_draggerRail{height:4px;margin:6px 0;background-repeat:repeat-x}.mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonUp{background-position:-16px -72px}.mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonDown{background-position:-16px -92px}.mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonLeft{background-position:-20px -112px}.mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonRight{background-position:-20px -128px}.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_draggerRail{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAALElEQVQYV2NkIAAYSVFgDFR8BqrBBEifBbGRTfiPZhpYjiQFBK3A6l6CvgAAE9kGCd1mvgEAAAAASUVORK5CYII=)}.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonUp{background-position:-96px -72px}.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonDown{background-position:-96px -92px}.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonLeft{background-position:-100px -112px}.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonRight{background-position:-100px -128px}.mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-image:linear-gradient(to right,rgba(255,255,255,.5) 0,rgba(255,255,255,0) 100%);background-repeat:repeat-y}.mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{background-image:linear-gradient(to bottom,rgba(255,255,255,.5) 0,rgba(255,255,255,0) 100%);background-repeat:repeat-x}.mCS-3d-dark.mCSB_scrollTools_vertical .mCSB_dragger,.mCS-3d.mCSB_scrollTools_vertical .mCSB_dragger{height:70px}.mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_dragger,.mCS-3d.mCSB_scrollTools_horizontal .mCSB_dragger{width:70px}.mCS-3d-dark.mCSB_scrollTools,.mCS-3d.mCSB_scrollTools{opacity:1;filter:'alpha(opacity=30)';-ms-filter:'alpha(opacity=30)'}.mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools .mCSB_draggerRail{border-radius:16px}.mCS-3d-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-3d.mCSB_scrollTools .mCSB_draggerRail{width:8px;background-color:#000;background-color:rgba(0,0,0,.2);box-shadow:inset 1px 0 1px rgba(0,0,0,.5),inset -1px 0 1px rgba(255,255,255,.2)}.mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-3d-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,.mCS-3d-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#555}.mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{width:8px}.mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-3d.mCSB_scrollTools_horizontal .mCSB_draggerRail{width:100%;height:8px;margin:4px 0;box-shadow:inset 0 1px 1px rgba(0,0,0,.5),inset 0 -1px 1px rgba(255,255,255,.2)}.mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{width:100%;height:8px;margin:4px auto}.mCS-3d.mCSB_scrollTools .mCSB_buttonUp{background-position:-32px -72px}.mCS-3d.mCSB_scrollTools .mCSB_buttonDown{background-position:-32px -92px}.mCS-3d.mCSB_scrollTools .mCSB_buttonLeft{background-position:-40px -112px}.mCS-3d.mCSB_scrollTools .mCSB_buttonRight{background-position:-40px -128px}.mCS-3d-dark.mCSB_scrollTools .mCSB_draggerRail{background-color:#000;background-color:rgba(0,0,0,.1);box-shadow:inset 1px 0 1px rgba(0,0,0,.1)}.mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{box-shadow:inset 0 1px 1px rgba(0,0,0,.1)}.mCS-3d-dark.mCSB_scrollTools .mCSB_buttonUp{background-position:-112px -72px}.mCS-3d-dark.mCSB_scrollTools .mCSB_buttonDown{background-position:-112px -92px}.mCS-3d-dark.mCSB_scrollTools .mCSB_buttonLeft{background-position:-120px -112px}.mCS-3d-dark.mCSB_scrollTools .mCSB_buttonRight{background-position:-120px -128px}.mCS-3d-thick-dark.mCSB_scrollTools,.mCS-3d-thick.mCSB_scrollTools{opacity:1;filter:'alpha(opacity=30)';-ms-filter:'alpha(opacity=30)'}.mCS-3d-thick-dark.mCSB_scrollTools,.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_draggerContainer,.mCS-3d-thick.mCSB_scrollTools,.mCS-3d-thick.mCSB_scrollTools .mCSB_draggerContainer{border-radius:7px}.mCSB_inside+.mCS-3d-thick-dark.mCSB_scrollTools_vertical,.mCSB_inside+.mCS-3d-thick.mCSB_scrollTools_vertical{right:1px}.mCS-3d-thick-dark.mCSB_scrollTools_vertical,.mCS-3d-thick.mCSB_scrollTools_vertical{box-shadow:inset 1px 0 1px rgba(0,0,0,.1),inset 0 0 14px rgba(0,0,0,.5)}.mCS-3d-thick-dark.mCSB_scrollTools_horizontal,.mCS-3d-thick.mCSB_scrollTools_horizontal{bottom:1px;box-shadow:inset 0 1px 1px rgba(0,0,0,.1),inset 0 0 14px rgba(0,0,0,.5)}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{position:absolute;top:0;right:0;bottom:0;left:0;width:12px;height:auto;margin:2px;border-radius:5px;box-shadow:inset 1px 0 0 rgba(255,255,255,.4)}.mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{width:auto;height:12px;box-shadow:inset 0 1px 0 rgba(255,255,255,.4)}.mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-thick.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-3d-thick.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,.mCS-3d-thick.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#555}.mCS-3d-thick.mCSB_scrollTools .mCSB_draggerContainer{background-color:#000;background-color:rgba(0,0,0,.05);box-shadow:inset 1px 1px 16px rgba(0,0,0,.1)}.mCS-3d-thick.mCSB_scrollTools .mCSB_draggerRail{background-color:transparent}.mCS-3d-thick.mCSB_scrollTools .mCSB_buttonUp{background-position:-32px -72px}.mCS-3d-thick.mCSB_scrollTools .mCSB_buttonDown{background-position:-32px -92px}.mCS-3d-thick.mCSB_scrollTools .mCSB_buttonLeft{background-position:-40px -112px}.mCS-3d-thick.mCSB_scrollTools .mCSB_buttonRight{background-position:-40px -128px}.mCS-3d-thick-dark.mCSB_scrollTools{box-shadow:inset 0 0 14px rgba(0,0,0,.2)}.mCS-3d-thick-dark.mCSB_scrollTools_horizontal{box-shadow:inset 0 1px 1px rgba(0,0,0,.1),inset 0 0 14px rgba(0,0,0,.2)}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{box-shadow:inset 1px 0 0 rgba(255,255,255,.4),inset -1px 0 0 rgba(0,0,0,.2)}.mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{box-shadow:inset 0 1px 0 rgba(255,255,255,.4),inset 0 -1px 0 rgba(0,0,0,.2)}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#777}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_draggerContainer{background-color:#fff;background-color:rgba(0,0,0,.05);box-shadow:inset 1px 1px 16px rgba(0,0,0,.1)}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-minimal-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-minimal.mCSB_scrollTools .mCSB_draggerRail{background-color:transparent}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonUp{background-position:-112px -72px}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonDown{background-position:-112px -92px}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonLeft{background-position:-120px -112px}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonRight{background-position:-120px -128px}.mCSB_outside+.mCS-minimal-dark.mCSB_scrollTools_vertical,.mCSB_outside+.mCS-minimal.mCSB_scrollTools_vertical{right:0;margin:12px 0}.mCustomScrollBox.mCS-minimal+.mCSB_scrollTools+.mCSB_scrollTools.mCSB_scrollTools_horizontal,.mCustomScrollBox.mCS-minimal+.mCSB_scrollTools.mCSB_scrollTools_horizontal,.mCustomScrollBox.mCS-minimal-dark+.mCSB_scrollTools+.mCSB_scrollTools.mCSB_scrollTools_horizontal,.mCustomScrollBox.mCS-minimal-dark+.mCSB_scrollTools.mCSB_scrollTools_horizontal{bottom:0;margin:0 12px}.mCS-dir-rtl>.mCSB_outside+.mCS-minimal-dark.mCSB_scrollTools_vertical,.mCS-dir-rtl>.mCSB_outside+.mCS-minimal.mCSB_scrollTools_vertical{right:auto;left:0}.mCS-minimal-dark.mCSB_scrollTools_vertical .mCSB_dragger,.mCS-minimal.mCSB_scrollTools_vertical .mCSB_dragger{height:50px}.mCS-minimal-dark.mCSB_scrollTools_horizontal .mCSB_dragger,.mCS-minimal.mCSB_scrollTools_horizontal .mCSB_dragger{width:50px}.mCS-minimal.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.2);filter:'alpha(opacity=20)';-ms-filter:'alpha(opacity=20)'}.mCS-minimal.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-minimal.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.5);filter:'alpha(opacity=50)';-ms-filter:'alpha(opacity=50)'}.mCS-minimal-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.2);filter:'alpha(opacity=20)';-ms-filter:'alpha(opacity=20)'}.mCS-minimal-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-minimal-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.5);filter:'alpha(opacity=50)';-ms-filter:'alpha(opacity=50)'}.mCS-dark-3.mCSB_scrollTools .mCSB_draggerRail,.mCS-light-3.mCSB_scrollTools .mCSB_draggerRail{width:6px;background-color:#000;background-color:rgba(0,0,0,.2)}.mCS-dark-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-light-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{width:6px}.mCS-dark-3.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-dark-3.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-light-3.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-light-3.mCSB_scrollTools_horizontal .mCSB_draggerRail{width:100%;height:6px;margin:5px 0}.mCS-dark-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCS-dark-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail,.mCS-light-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCS-light-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{width:12px}.mCS-dark-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCS-dark-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail,.mCS-light-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCS-light-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{height:12px;margin:2px 0}.mCS-light-3.mCSB_scrollTools .mCSB_buttonUp{background-position:-32px -72px}.mCS-light-3.mCSB_scrollTools .mCSB_buttonDown{background-position:-32px -92px}.mCS-light-3.mCSB_scrollTools .mCSB_buttonLeft{background-position:-40px -112px}.mCS-light-3.mCSB_scrollTools .mCSB_buttonRight{background-position:-40px -128px}.mCS-dark-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.75)}.mCS-dark-3.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.85)}.mCS-dark-3.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-dark-3.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.9)}.mCS-dark-3.mCSB_scrollTools .mCSB_draggerRail{background-color:#000;background-color:rgba(0,0,0,.1)}.mCS-dark-3.mCSB_scrollTools .mCSB_buttonUp{background-position:-112px -72px}.mCS-dark-3.mCSB_scrollTools .mCSB_buttonDown{background-position:-112px -92px}.mCS-dark-3.mCSB_scrollTools .mCSB_buttonLeft{background-position:-120px -112px}.mCS-dark-3.mCSB_scrollTools .mCSB_buttonRight{background-position:-120px -128px}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-inset-2.mCSB_scrollTools .mCSB_draggerRail,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-inset-3.mCSB_scrollTools .mCSB_draggerRail,.mCS-inset-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-inset.mCSB_scrollTools .mCSB_draggerRail{width:12px;background-color:#000;background-color:rgba(0,0,0,.2)}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-inset.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{position:absolute;top:0;right:0;bottom:0;left:0;width:6px;height:auto;margin:3px 5px}.mCS-inset-2-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-2.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-3-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-3.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-inset.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{position:absolute;top:0;right:0;bottom:0;left:0;width:auto;height:6px;margin:5px 3px}.mCS-inset-2-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-inset-2.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-inset-3-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-inset-3.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-inset-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-inset.mCSB_scrollTools_horizontal .mCSB_draggerRail{width:100%;height:12px;margin:2px 0}.mCS-inset-2.mCSB_scrollTools .mCSB_buttonUp,.mCS-inset-3.mCSB_scrollTools .mCSB_buttonUp,.mCS-inset.mCSB_scrollTools .mCSB_buttonUp{background-position:-32px -72px}.mCS-inset-2.mCSB_scrollTools .mCSB_buttonDown,.mCS-inset-3.mCSB_scrollTools .mCSB_buttonDown,.mCS-inset.mCSB_scrollTools .mCSB_buttonDown{background-position:-32px -92px}.mCS-inset-2.mCSB_scrollTools .mCSB_buttonLeft,.mCS-inset-3.mCSB_scrollTools .mCSB_buttonLeft,.mCS-inset.mCSB_scrollTools .mCSB_buttonLeft{background-position:-40px -112px}.mCS-inset-2.mCSB_scrollTools .mCSB_buttonRight,.mCS-inset-3.mCSB_scrollTools .mCSB_buttonRight,.mCS-inset.mCSB_scrollTools .mCSB_buttonRight{background-position:-40px -128px}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.75)}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar,.mCS-inset-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.85)}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,.mCS-inset-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-inset-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.9)}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-inset-dark.mCSB_scrollTools .mCSB_draggerRail{background-color:#000;background-color:rgba(0,0,0,.1)}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonUp,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonUp,.mCS-inset-dark.mCSB_scrollTools .mCSB_buttonUp{background-position:-112px -72px}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonDown,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonDown,.mCS-inset-dark.mCSB_scrollTools .mCSB_buttonDown{background-position:-112px -92px}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonLeft,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonLeft,.mCS-inset-dark.mCSB_scrollTools .mCSB_buttonLeft{background-position:-120px -112px}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonRight,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonRight,.mCS-inset-dark.mCSB_scrollTools .mCSB_buttonRight{background-position:-120px -128px}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-inset-2.mCSB_scrollTools .mCSB_draggerRail{box-sizing:border-box;border-width:1px;border-style:solid;border-color:#fff;border-color:rgba(255,255,255,.2);background-color:transparent}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail{border-color:#000;border-color:rgba(0,0,0,.2)}.mCS-inset-3.mCSB_scrollTools .mCSB_draggerRail{background-color:#fff;background-color:rgba(255,255,255,.6)}.mCS-inset-3-dark.mCSB_scrollTools .mCSB_draggerRail{background-color:#000;background-color:rgba(0,0,0,.6)}.mCS-inset-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.75)}.mCS-inset-3.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.85)}.mCS-inset-3.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-inset-3.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.9)}.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.75)}.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.85)}.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.9)}@keyframes rotation{from{-webkit-transform:rotate(0)}to{-webkit-transform:rotate(359deg)}}@keyframes translatep1{0%{transform:translateY(35px)}100%{visibility:hidden;transform:translateY(5px)}}@keyframes translatep2{0%{transform:translateY(-20px)}100%{visibility:hidden;transform:translateY(40px)}}@keyframes translateright{0%{right:-50px;display:none;color:transparent}30%{right:150px;display:block;color:transparent}100%{right:200px;color:#fff}}@keyframes translateleft{0%{right:200px;display:block;color:#fff}30%{right:150px;color:transparent}100%{right:-200px;display:none;color:transparent}}@keyframes height{0%{height:50vh;min-height:250px}100%{height:0;min-height:100px}}@keyframes margin{0%{margin-top:25vh;transform:translateY(-50%)}100%{margin-top:0;transform:translateY(0)}}@keyframes h1{0%{font-size:11.2vw}100%{font-size:3rem}}@keyframes card{50%{transform:translateY(-40px)}100%{transform:translateY(-50px);opacity:0}}@keyframes textestroke{0%{-webkit-text-stroke:1px}100%{-webkit-text-stroke:0}}body{padding:0 2%}header .contact{position:fixed;z-index:9999;top:20px;right:2%;cursor:pointer;transition:transform .5s ease;transform:scale(1)}header .contact:hover{transition:transform .5s ease;transform:scale(.9)}header .contact.open{transition:transform .5s ease;transform:scale(15)}header .contact.open h3{left:500px;transition:visibility .5 ease left 1s ease;color:transparent}header .contact.open img{animation:rotation 12s infinite linear}header .contact.open+.txt{line-height:1.5rem;position:fixed;z-index:9999;top:190px;animation:translateright 1s ease forwards;text-align:center}header .contact.close{transition:transform .5s .1s ease;transform:scale(1)}header .contact.close+.txt{line-height:1.5rem;position:fixed;z-index:9999;top:190px;animation:translateleft 1s ease forwards;text-align:center}header .contact.close h3{left:20px;transition:color .5s .5s ease,left .5s ease;color:#fff}header .contact.close:hover{transition:transform .5s ease!important;transform:scale(.9)!important}header .contact img{animation:rotation 6s infinite linear}header .contact h3{position:absolute;top:33px;left:20px;transform:rotate(35deg)}header .contact+.txt{position:fixed;right:-200px}header h2{margin-left:10px}.card{line-height:.8!important;width:calc(100% / 3);padding:0 10px}.card .card-header{display:-ms-inline-flexbox;display:inline-flex;margin-top:10px}.card .card-header>div{margin-right:5px}.card .card-header>div>a{vertical-align:baseline}.card .card-header>div>time{vertical-align:baseline}body{height:100%}body .blog header{position:fixed;z-index:999;top:0;right:2%;left:2%;display:-ms-inline-flexbox;display:inline-flex;flex-direction:column;height:100vh;-ms-flex-direction:column}body .blog header>section{top:0;right:2%;left:2%;height:50vh;min-height:250px}body .blog header>section.works-mea{display:-ms-inline-flexbox;display:inline-flex;height:100%;margin-bottom:20px;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:end;align-items:flex-end}body .blog header>section.reduc{animation:reducfont 1s ease forwards}body .blog header h1{font-size:11.2vw;line-height:7vw!important;position:fixed;top:80px;transition:top 1s ease;transition:font-size 1.2s ease,color 1.2s ease,top 1.2s ease}body .blog header h2{font-size:3.7vw;position:fixed;top:220px;transition:opacity 1s ease 1s,transform 1s ease 1s;transform:translateX(0);opacity:1}body .blog header .scroll{position:fixed;bottom:0;left:50%;overflow:hidden;width:50px;height:15px;margin:0 auto 10px auto;transform:translate(-50%)}body .blog header .scroll>p:nth-child(1){position:absolute;top:-20px;animation:translatep1 2s infinite linear;text-align:center}body .blog header .scroll>p:nth-child(2){position:absolute;top:-20px;animation:translatep1 2s infinite linear;animation-delay:1s;text-align:center}body .blog header.hide>section:first-child{z-index:999;height:100px;min-height:100px;background:#fff}body .blog header.hide nav{margin-top:0;transition:margin-top 1s ease,transform 1s ease;transform:translate(0)}body .blog header.hide h1{font-size:3rem;position:relative;top:0;transition:font-size 1.2s ease .5s,color 1.2s ease 1s,top 1.2s ease .5s;animation:textestroke .5s ease 1.2s forwards;color:#000}body .blog header.hide h2{top:220px;transition:opacity .5s ease,transform .5s ease;transform:translateX(-10px);opacity:0}body .blog header.hide .card{animation:card 1s forwards ease}body .blog header.hide .scroll{transition:opacity .8s ease;opacity:0}body>.item header{position:relative;height:130px}body>.item header nav{position:absolute;margin-top:65px;transform:translateY(-50%)}body>.item header h1{color:#000}body>.item header h2{display:none}body>.item header .scroll{display:none}#body-wrapper #items .works-mea{display:-ms-inline-flexbox;display:inline-flex;width:100%;height:50vh;-ms-flex-align:end;align-items:flex-end}#body-wrapper #items .works-mea .card .card-image img{width:100%;height:auto}#body-wrapper #items .works-mea .card .card-header{margin-top:5px}#body-wrapper #items .works-mea .card .card-footer{margin-top:5px}#body-wrapper #items .cat-list{width:100%;height:auto;margin:20px 0;padding:10px 0;border-top:1px solid #000;border-bottom:1px solid #000}#body-wrapper #items .cat-list ul{display:-ms-inline-flexbox;display:inline-flex}#body-wrapper #items .cat-list ul li{margin-right:10px}#body-wrapper #items .full-works{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}body #start{width:100%;height:100%}body #page-wrapper.blog #start{position:relative;top:200vh;left:0;padding-top:110px;opacity:0}body #page-wrapper.blog #start.visible{opacity:1}#items .cat-list{padding:0 5px}#items .cat-list ul li{display:-ms-flexbox;display:flex}#items .cat-list ul li::after{width:5px;content:' '}#items .cat-list ul li:hover::after{content:'\002022'}#items .full-works{width:100%}#items .card{min-width:200px;margin-bottom:10px}body>.item #body-wrapper.blog-listing{padding-top:20px;border-top:1px solid #000}body>.item #body-wrapper.blog-listing .img-card{width:49%;margin-bottom:20px}body>.item #body-wrapper.blog-listing .header-item{position:relative}body>.item #body-wrapper.blog-listing .header-item .title{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:baseline;align-items:baseline}body>.item #body-wrapper.blog-listing .header-item .title h2{margin-right:5px}body>.item #body-wrapper.blog-listing .header-item .title time{margin-right:5px}body>.item #body-wrapper.blog-listing .header-item .title span{margin-right:5px}body>.item #body-wrapper.blog-listing .header-item .content{width:calc(100% / 3);margin:20px 0}body>.item #body-wrapper.blog-listing .header-item .prev-next{position:absolute;top:0;right:0;width:calc(100% / 3);padding-left:20px}body>.item #body-wrapper.blog-listing .header-item .prev-next .btn-info{position:absolute;margin-top:20px;margin-left:40px;transform:rotate(-15deg)}body>.item #body-wrapper.blog-listing .header-item .prev-next img{float:right;width:80%}body>.item .top{width:50px;margin:20px auto 20px auto;text-align:center}body>.item .top img{width:20px;margin-bottom:5px}body>.item .top p{transform:rotate(-10deg)}
\ No newline at end of file
+*{box-sizing:border-box}/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{font-size:18px;line-height:1.15;width:100%;height:100%;margin:0;padding:0;-webkit-text-size-adjust:100%}body{width:100%;height:100%;margin:0;padding:0}main{display:block}h1{font-size:3em;margin:0}a,em,h1,h2,h3,h4,h5,li,p,strong,ul{margin:0;padding:0}hr{overflow:visible;box-sizing:content-box;height:0}pre{font-family:monospace,monospace;font-size:1em}ul{list-style:none}li,ul{margin:0;padding:0}a{text-decoration:none;color:#000;background-color:transparent}abbr[title]{text-decoration:underline;text-decoration:underline dotted;border-bottom:none;-webkit-text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{width:100%;height:100%;vertical-align:bottom;border-style:none}iframe{border:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{display:table;box-sizing:border-box;max-width:100%;padding:0;white-space:normal;color:inherit}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}@font-face{font-family:Regular;src:url(../fonts/Secuela/Secuela-Regular.eot);src:url(../fonts/Secuela/Secuela-Regular.eot?#iefix) format('embedded-opentype'),url(../fonts/Secuela/Secuela-Regular.woff) format('woff'),url(../fonts/Secuela/Secuela-Regular.ttf) format('truetype'),url(../fonts/Secuela/Secuela-Regular.svg#Regular) format('svg')}@font-face{font-family:medium;src:url(../fonts/Secuela/Secuela-Medium.eot);src:url(../fonts/Secuela/Secuela-Medium.eot?#iefix) format('embedded-opentype'),url(../fonts/Secuela/Secuela-Medium.woff) format('woff'),url(../fonts/Secuela/Secuela-Medium.ttf) format('truetype'),url(../fonts/Secuela/Secuela-Medium.svg#medium) format('svg')}@font-face{font-family:bold;src:url(../fonts/Secuela/Secuela-Bold.eot);src:url(../fonts/Secuela/Secuela-Bold.eot?#iefix) format('embedded-opentype'),url(../fonts/Secuela/Secuela-Bold.woff) format('woff'),url(../fonts/Secuela/Secuela-Bold.ttf) format('truetype'),url(../fonts/Secuela/Secuela-Bold.svg#bold) format('svg')}@font-face{font-family:italic;src:url(../fonts/Secuela/Secuela-Italic.eot);src:url(../fonts/Secuela/Secuela-Italic.eot?#iefix) format('embedded-opentype'),url(../fonts/Secuela/Secuela-Italic.woff) format('woff'),url(../fonts/Secuela/Secuela-Italic.ttf) format('truetype'),url(../fonts/Secuela/Secuela-Italic.svg#italic) format('svg')}@font-face{font-family:light;src:url(../fonts/Secuela/Secuela-Light.eot);src:url(../fonts/Secuela/Secuela-Light.eot?#iefix) format('embedded-opentype'),url(../fonts/Secuela/Secuela-Light.woff) format('woff'),url(../fonts/Secuela/Secuela-Light.ttf) format('truetype'),url(../fonts/Secuela/Secuela-Light.svg#light) format('svg')}h1,h2{font-family:medium;font-weight:400}h2{font-size:4.5vw}h3{font-family:medium;font-size:1rem;font-weight:400}h5{font-family:bold;font-size:.8rem;font-weight:400}p{font-family:Regular;font-size:.9rem;font-weight:400;line-height:1.3rem}time{font-family:italic;font-weight:400}.blog h1{font-size:12vw;line-height:5rem;color:transparent;-webkit-text-stroke:2px #000}.blog h2{line-height:8rem}.blog.down h1{animation:reducH 1s .9s ease forwards}.item h2{font-size:1rem}.item .prev-next a .title{font-family:medium;font-size:1rem}.category a{font-family:Regular;font-size:1rem}time{font-size:1rem}.tags{font-family:Regular;font-size:1rem}#page-wrapper.blog #items .category a,#page-wrapper.blog .works-mea .category a{font-family:Regular;font-size:.8rem}#page-wrapper.blog #items time,#page-wrapper.blog .works-mea time{font-size:.8rem}#page-wrapper.blog #items .tags,#page-wrapper.blog .works-mea .tags{font-size:.8rem}.cat-list{font-family:Regular}.mCustomScrollbar{-ms-touch-action:pinch-zoom;touch-action:pinch-zoom}.mCustomScrollbar.mCS_no_scrollbar,.mCustomScrollbar.mCS_touch_action{-ms-touch-action:auto;touch-action:auto}.mCustomScrollBox{position:relative;overflow:hidden;max-width:100%;height:100%;outline:0;direction:ltr}.mCSB_container{overflow:hidden;width:auto;height:auto}.mCSB_inside>.mCSB_container{margin-right:30px}.mCSB_container.mCS_no_scrollbar_y.mCS_y_hidden{margin-right:0}.mCS-dir-rtl>.mCSB_inside>.mCSB_container{margin-right:0;margin-left:30px}.mCS-dir-rtl>.mCSB_inside>.mCSB_container.mCS_no_scrollbar_y.mCS_y_hidden{margin-left:0}.mCSB_scrollTools{position:absolute;top:0;right:0;bottom:0;left:auto;width:16px;height:auto;opacity:.75;filter:'alpha(opacity=75)';-ms-filter:'alpha(opacity=75)'}.mCSB_outside+.mCSB_scrollTools{right:-26px}.mCS-dir-rtl>.mCSB_inside>.mCSB_scrollTools,.mCS-dir-rtl>.mCSB_outside+.mCSB_scrollTools{right:auto;left:0}.mCS-dir-rtl>.mCSB_outside+.mCSB_scrollTools{left:-26px}.mCSB_scrollTools .mCSB_draggerContainer{position:absolute;top:0;right:0;bottom:0;left:0;height:auto}.mCSB_scrollTools a+.mCSB_draggerContainer{margin:20px 0}.mCSB_scrollTools .mCSB_draggerRail{width:2px;height:100%;margin:0 auto;border-radius:16px}.mCSB_scrollTools .mCSB_dragger{z-index:1;width:100%;height:30px;cursor:pointer}.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{position:relative;width:4px;height:100%;margin:0 auto;text-align:center;border-radius:16px}.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar,.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{width:12px}.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{width:8px}.mCSB_scrollTools .mCSB_buttonDown,.mCSB_scrollTools .mCSB_buttonUp{position:absolute;display:block;overflow:hidden;width:100%;height:20px;margin:0 auto;cursor:pointer}.mCSB_scrollTools .mCSB_buttonDown{bottom:0}.mCSB_horizontal.mCSB_inside>.mCSB_container{margin-right:0;margin-bottom:30px}.mCSB_horizontal.mCSB_outside>.mCSB_container{min-height:100%}.mCSB_horizontal>.mCSB_container.mCS_no_scrollbar_x.mCS_x_hidden{margin-bottom:0}.mCSB_scrollTools.mCSB_scrollTools_horizontal{top:auto;right:0;bottom:0;left:0;width:auto;height:16px}.mCustomScrollBox+.mCSB_scrollTools+.mCSB_scrollTools.mCSB_scrollTools_horizontal,.mCustomScrollBox+.mCSB_scrollTools.mCSB_scrollTools_horizontal{bottom:-26px}.mCSB_scrollTools.mCSB_scrollTools_horizontal a+.mCSB_draggerContainer{margin:0 20px}.mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_draggerRail{width:100%;height:2px;margin:7px 0}.mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_dragger{left:0;width:30px;height:100%}.mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{width:100%;height:4px;margin:6px auto}.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar,.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{height:12px;margin:2px auto}.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{height:8px;margin:4px 0}.mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonLeft,.mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonRight{position:absolute;display:block;overflow:hidden;width:20px;height:100%;margin:0 auto;cursor:pointer}.mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonLeft{left:0}.mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonRight{right:0}.mCSB_container_wrapper{position:absolute;top:0;right:0;bottom:0;left:0;overflow:hidden;width:auto;height:auto;margin-right:30px;margin-bottom:30px}.mCSB_container_wrapper>.mCSB_container{box-sizing:border-box;padding-right:30px;padding-bottom:30px}.mCSB_vertical_horizontal>.mCSB_scrollTools.mCSB_scrollTools_vertical{bottom:20px}.mCSB_vertical_horizontal>.mCSB_scrollTools.mCSB_scrollTools_horizontal{right:20px}.mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden+.mCSB_scrollTools.mCSB_scrollTools_vertical{bottom:0}.mCS-dir-rtl>.mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside>.mCSB_scrollTools.mCSB_scrollTools_horizontal,.mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden+.mCSB_scrollTools~.mCSB_scrollTools.mCSB_scrollTools_horizontal{right:0}.mCS-dir-rtl>.mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside>.mCSB_scrollTools.mCSB_scrollTools_horizontal{left:20px}.mCS-dir-rtl>.mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside>.mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden+.mCSB_scrollTools~.mCSB_scrollTools.mCSB_scrollTools_horizontal{left:0}.mCS-dir-rtl>.mCSB_inside>.mCSB_container_wrapper{margin-right:0;margin-left:30px}.mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden>.mCSB_container{padding-right:0}.mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden>.mCSB_container{padding-bottom:0}.mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside>.mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden{margin-right:0;margin-left:0}.mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside>.mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden{margin-bottom:0}.mCSB_scrollTools,.mCSB_scrollTools .mCSB_buttonDown,.mCSB_scrollTools .mCSB_buttonLeft,.mCSB_scrollTools .mCSB_buttonRight,.mCSB_scrollTools .mCSB_buttonUp,.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{transition:opacity .2s ease-in-out,background-color .2s ease-in-out}.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerRail,.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger_bar,.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerRail,.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger_bar{transition:width .2s ease-out .2s,height .2s ease-out .2s,margin-left .2s ease-out .2s,margin-right .2s ease-out .2s,margin-top .2s ease-out .2s,margin-bottom .2s ease-out .2s,opacity .2s ease-in-out,background-color .2s ease-in-out}.mCS-autoHide>.mCustomScrollBox>.mCSB_scrollTools,.mCS-autoHide>.mCustomScrollBox~.mCSB_scrollTools{opacity:0;filter:'alpha(opacity=0)';-ms-filter:'alpha(opacity=0)'}.mCS-autoHide:hover>.mCustomScrollBox>.mCSB_scrollTools,.mCS-autoHide:hover>.mCustomScrollBox~.mCSB_scrollTools,.mCustomScrollBox:hover>.mCSB_scrollTools,.mCustomScrollBox:hover~.mCSB_scrollTools,.mCustomScrollbar>.mCustomScrollBox>.mCSB_scrollTools.mCSB_scrollTools_onDrag,.mCustomScrollbar>.mCustomScrollBox~.mCSB_scrollTools.mCSB_scrollTools_onDrag{opacity:1;filter:'alpha(opacity=100)';-ms-filter:'alpha(opacity=100)'}.mCSB_scrollTools .mCSB_draggerRail{background-color:#000;background-color:rgba(0,0,0,.4);filter:'alpha(opacity=40)';-ms-filter:'alpha(opacity=40)'}.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.75);filter:'alpha(opacity=75)';-ms-filter:'alpha(opacity=75)'}.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.85);filter:'alpha(opacity=85)';-ms-filter:'alpha(opacity=85)'}.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.9);filter:'alpha(opacity=90)';-ms-filter:'alpha(opacity=90)'}.mCSB_scrollTools .mCSB_buttonDown,.mCSB_scrollTools .mCSB_buttonLeft,.mCSB_scrollTools .mCSB_buttonRight,.mCSB_scrollTools .mCSB_buttonUp{opacity:.4;background-image:url(mCSB_buttons.png);background-repeat:no-repeat;filter:'alpha(opacity=40)';-ms-filter:'alpha(opacity=40)'}.mCSB_scrollTools .mCSB_buttonUp{background-position:0 0}.mCSB_scrollTools .mCSB_buttonDown{background-position:0 -20px}.mCSB_scrollTools .mCSB_buttonLeft{background-position:0 -40px}.mCSB_scrollTools .mCSB_buttonRight{background-position:0 -56px}.mCSB_scrollTools .mCSB_buttonDown:hover,.mCSB_scrollTools .mCSB_buttonLeft:hover,.mCSB_scrollTools .mCSB_buttonRight:hover,.mCSB_scrollTools .mCSB_buttonUp:hover{opacity:.75;filter:'alpha(opacity=75)';-ms-filter:'alpha(opacity=75)'}.mCSB_scrollTools .mCSB_buttonDown:active,.mCSB_scrollTools .mCSB_buttonLeft:active,.mCSB_scrollTools .mCSB_buttonRight:active,.mCSB_scrollTools .mCSB_buttonUp:active{opacity:.9;filter:'alpha(opacity=90)';-ms-filter:'alpha(opacity=90)'}.mCS-dark.mCSB_scrollTools .mCSB_draggerRail{background-color:#000;background-color:rgba(0,0,0,.15)}.mCS-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.75)}.mCS-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:rgba(0,0,0,.85)}.mCS-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:rgba(0,0,0,.9)}.mCS-dark.mCSB_scrollTools .mCSB_buttonUp{background-position:-80px 0}.mCS-dark.mCSB_scrollTools .mCSB_buttonDown{background-position:-80px -20px}.mCS-dark.mCSB_scrollTools .mCSB_buttonLeft{background-position:-80px -40px}.mCS-dark.mCSB_scrollTools .mCSB_buttonRight{background-position:-80px -56px}.mCS-dark-2.mCSB_scrollTools .mCSB_draggerRail,.mCS-light-2.mCSB_scrollTools .mCSB_draggerRail{width:4px;border-radius:1px;background-color:#fff;background-color:rgba(255,255,255,.1)}.mCS-dark-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-light-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{width:4px;border-radius:1px;background-color:#fff;background-color:rgba(255,255,255,.75)}.mCS-dark-2.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-dark-2.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-light-2.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-light-2.mCSB_scrollTools_horizontal .mCSB_draggerRail{width:100%;height:4px;margin:6px auto}.mCS-light-2.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.85)}.mCS-light-2.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-light-2.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.9)}.mCS-light-2.mCSB_scrollTools .mCSB_buttonUp{background-position:-32px 0}.mCS-light-2.mCSB_scrollTools .mCSB_buttonDown{background-position:-32px -20px}.mCS-light-2.mCSB_scrollTools .mCSB_buttonLeft{background-position:-40px -40px}.mCS-light-2.mCSB_scrollTools .mCSB_buttonRight{background-position:-40px -56px}.mCS-dark-2.mCSB_scrollTools .mCSB_draggerRail{border-radius:1px;background-color:#000;background-color:rgba(0,0,0,.1)}.mCS-dark-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{border-radius:1px;background-color:#000;background-color:rgba(0,0,0,.75)}.mCS-dark-2.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.85)}.mCS-dark-2.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-dark-2.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.9)}.mCS-dark-2.mCSB_scrollTools .mCSB_buttonUp{background-position:-112px 0}.mCS-dark-2.mCSB_scrollTools .mCSB_buttonDown{background-position:-112px -20px}.mCS-dark-2.mCSB_scrollTools .mCSB_buttonLeft{background-position:-120px -40px}.mCS-dark-2.mCSB_scrollTools .mCSB_buttonRight{background-position:-120px -56px}.mCS-dark-thick.mCSB_scrollTools .mCSB_draggerRail,.mCS-light-thick.mCSB_scrollTools .mCSB_draggerRail{width:4px;border-radius:2px;background-color:#fff;background-color:rgba(255,255,255,.1)}.mCS-dark-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-light-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{width:6px;border-radius:2px;background-color:#fff;background-color:rgba(255,255,255,.75)}.mCS-dark-thick.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-light-thick.mCSB_scrollTools_horizontal .mCSB_draggerRail{width:100%;height:4px;margin:6px 0}.mCS-dark-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-light-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{width:100%;height:6px;margin:5px auto}.mCS-light-thick.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.85)}.mCS-light-thick.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-light-thick.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.9)}.mCS-light-thick.mCSB_scrollTools .mCSB_buttonUp{background-position:-16px 0}.mCS-light-thick.mCSB_scrollTools .mCSB_buttonDown{background-position:-16px -20px}.mCS-light-thick.mCSB_scrollTools .mCSB_buttonLeft{background-position:-20px -40px}.mCS-light-thick.mCSB_scrollTools .mCSB_buttonRight{background-position:-20px -56px}.mCS-dark-thick.mCSB_scrollTools .mCSB_draggerRail{border-radius:2px;background-color:#000;background-color:rgba(0,0,0,.1)}.mCS-dark-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{border-radius:2px;background-color:#000;background-color:rgba(0,0,0,.75)}.mCS-dark-thick.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.85)}.mCS-dark-thick.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-dark-thick.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.9)}.mCS-dark-thick.mCSB_scrollTools .mCSB_buttonUp{background-position:-96px 0}.mCS-dark-thick.mCSB_scrollTools .mCSB_buttonDown{background-position:-96px -20px}.mCS-dark-thick.mCSB_scrollTools .mCSB_buttonLeft{background-position:-100px -40px}.mCS-dark-thick.mCSB_scrollTools .mCSB_buttonRight{background-position:-100px -56px}.mCS-light-thin.mCSB_scrollTools .mCSB_draggerRail{background-color:#fff;background-color:rgba(255,255,255,.1)}.mCS-dark-thin.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-light-thin.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{width:2px}.mCS-dark-thin.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-light-thin.mCSB_scrollTools_horizontal .mCSB_draggerRail{width:100%}.mCS-dark-thin.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-light-thin.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{width:100%;height:2px;margin:7px auto}.mCS-dark-thin.mCSB_scrollTools .mCSB_draggerRail{background-color:#000;background-color:rgba(0,0,0,.15)}.mCS-dark-thin.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.75)}.mCS-dark-thin.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.85)}.mCS-dark-thin.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-dark-thin.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.9)}.mCS-dark-thin.mCSB_scrollTools .mCSB_buttonUp{background-position:-80px 0}.mCS-dark-thin.mCSB_scrollTools .mCSB_buttonDown{background-position:-80px -20px}.mCS-dark-thin.mCSB_scrollTools .mCSB_buttonLeft{background-position:-80px -40px}.mCS-dark-thin.mCSB_scrollTools .mCSB_buttonRight{background-position:-80px -56px}.mCS-rounded.mCSB_scrollTools .mCSB_draggerRail{background-color:#fff;background-color:rgba(255,255,255,.15)}.mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger,.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger,.mCS-rounded-dots.mCSB_scrollTools .mCSB_dragger,.mCS-rounded.mCSB_scrollTools .mCSB_dragger{height:14px}.mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded-dots.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{width:14px;margin:0 1px}.mCS-rounded-dark.mCSB_scrollTools_horizontal .mCSB_dragger,.mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_dragger,.mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_dragger,.mCS-rounded.mCSB_scrollTools_horizontal .mCSB_dragger{width:14px}.mCS-rounded-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{height:14px;margin:1px 0}.mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar,.mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar,.mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{width:16px;height:16px;margin:-1px 0}.mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail,.mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{width:4px}.mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar,.mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar,.mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{width:16px;height:16px;margin:0 -1px}.mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail,.mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{height:4px;margin:6px 0}.mCS-rounded.mCSB_scrollTools .mCSB_buttonUp{background-position:0 -72px}.mCS-rounded.mCSB_scrollTools .mCSB_buttonDown{background-position:0 -92px}.mCS-rounded.mCSB_scrollTools .mCSB_buttonLeft{background-position:0 -112px}.mCS-rounded.mCSB_scrollTools .mCSB_buttonRight{background-position:0 -128px}.mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.75)}.mCS-rounded-dark.mCSB_scrollTools .mCSB_draggerRail{background-color:#000;background-color:rgba(0,0,0,.15)}.mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar,.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.85)}.mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.9)}.mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonUp{background-position:-80px -72px}.mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonDown{background-position:-80px -92px}.mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonLeft{background-position:-80px -112px}.mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonRight{background-position:-80px -128px}.mCS-rounded-dots-dark.mCSB_scrollTools_vertical .mCSB_draggerRail,.mCS-rounded-dots.mCSB_scrollTools_vertical .mCSB_draggerRail{width:4px}.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-rounded-dots.mCSB_scrollTools .mCSB_draggerRail,.mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_draggerRail{background-color:transparent;background-position:center}.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-rounded-dots.mCSB_scrollTools .mCSB_draggerRail{opacity:.3;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAANElEQVQYV2NkIAAYiVbw//9/Y6DiM1ANJoyMjGdBbLgJQAX/kU0DKgDLkaQAvxW4HEvQFwCRcxIJK1XznAAAAABJRU5ErkJggg==);background-repeat:repeat-y;filter:'alpha(opacity=30)';-ms-filter:'alpha(opacity=30)'}.mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_draggerRail{height:4px;margin:6px 0;background-repeat:repeat-x}.mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonUp{background-position:-16px -72px}.mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonDown{background-position:-16px -92px}.mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonLeft{background-position:-20px -112px}.mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonRight{background-position:-20px -128px}.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_draggerRail{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAALElEQVQYV2NkIAAYSVFgDFR8BqrBBEifBbGRTfiPZhpYjiQFBK3A6l6CvgAAE9kGCd1mvgEAAAAASUVORK5CYII=)}.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonUp{background-position:-96px -72px}.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonDown{background-position:-96px -92px}.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonLeft{background-position:-100px -112px}.mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonRight{background-position:-100px -128px}.mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-image:linear-gradient(to right,rgba(255,255,255,.5) 0,rgba(255,255,255,0) 100%);background-repeat:repeat-y}.mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{background-image:linear-gradient(to bottom,rgba(255,255,255,.5) 0,rgba(255,255,255,0) 100%);background-repeat:repeat-x}.mCS-3d-dark.mCSB_scrollTools_vertical .mCSB_dragger,.mCS-3d.mCSB_scrollTools_vertical .mCSB_dragger{height:70px}.mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_dragger,.mCS-3d.mCSB_scrollTools_horizontal .mCSB_dragger{width:70px}.mCS-3d-dark.mCSB_scrollTools,.mCS-3d.mCSB_scrollTools{opacity:1;filter:'alpha(opacity=30)';-ms-filter:'alpha(opacity=30)'}.mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools .mCSB_draggerRail{border-radius:16px}.mCS-3d-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-3d.mCSB_scrollTools .mCSB_draggerRail{width:8px;background-color:#000;background-color:rgba(0,0,0,.2);box-shadow:inset 1px 0 1px rgba(0,0,0,.5),inset -1px 0 1px rgba(255,255,255,.2)}.mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-3d-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,.mCS-3d-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#555}.mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{width:8px}.mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-3d.mCSB_scrollTools_horizontal .mCSB_draggerRail{width:100%;height:8px;margin:4px 0;box-shadow:inset 0 1px 1px rgba(0,0,0,.5),inset 0 -1px 1px rgba(255,255,255,.2)}.mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-3d.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{width:100%;height:8px;margin:4px auto}.mCS-3d.mCSB_scrollTools .mCSB_buttonUp{background-position:-32px -72px}.mCS-3d.mCSB_scrollTools .mCSB_buttonDown{background-position:-32px -92px}.mCS-3d.mCSB_scrollTools .mCSB_buttonLeft{background-position:-40px -112px}.mCS-3d.mCSB_scrollTools .mCSB_buttonRight{background-position:-40px -128px}.mCS-3d-dark.mCSB_scrollTools .mCSB_draggerRail{background-color:#000;background-color:rgba(0,0,0,.1);box-shadow:inset 1px 0 1px rgba(0,0,0,.1)}.mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{box-shadow:inset 0 1px 1px rgba(0,0,0,.1)}.mCS-3d-dark.mCSB_scrollTools .mCSB_buttonUp{background-position:-112px -72px}.mCS-3d-dark.mCSB_scrollTools .mCSB_buttonDown{background-position:-112px -92px}.mCS-3d-dark.mCSB_scrollTools .mCSB_buttonLeft{background-position:-120px -112px}.mCS-3d-dark.mCSB_scrollTools .mCSB_buttonRight{background-position:-120px -128px}.mCS-3d-thick-dark.mCSB_scrollTools,.mCS-3d-thick.mCSB_scrollTools{opacity:1;filter:'alpha(opacity=30)';-ms-filter:'alpha(opacity=30)'}.mCS-3d-thick-dark.mCSB_scrollTools,.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_draggerContainer,.mCS-3d-thick.mCSB_scrollTools,.mCS-3d-thick.mCSB_scrollTools .mCSB_draggerContainer{border-radius:7px}.mCSB_inside+.mCS-3d-thick-dark.mCSB_scrollTools_vertical,.mCSB_inside+.mCS-3d-thick.mCSB_scrollTools_vertical{right:1px}.mCS-3d-thick-dark.mCSB_scrollTools_vertical,.mCS-3d-thick.mCSB_scrollTools_vertical{box-shadow:inset 1px 0 1px rgba(0,0,0,.1),inset 0 0 14px rgba(0,0,0,.5)}.mCS-3d-thick-dark.mCSB_scrollTools_horizontal,.mCS-3d-thick.mCSB_scrollTools_horizontal{bottom:1px;box-shadow:inset 0 1px 1px rgba(0,0,0,.1),inset 0 0 14px rgba(0,0,0,.5)}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{position:absolute;top:0;right:0;bottom:0;left:0;width:12px;height:auto;margin:2px;border-radius:5px;box-shadow:inset 1px 0 0 rgba(255,255,255,.4)}.mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{width:auto;height:12px;box-shadow:inset 0 1px 0 rgba(255,255,255,.4)}.mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-thick.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-3d-thick.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,.mCS-3d-thick.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#555}.mCS-3d-thick.mCSB_scrollTools .mCSB_draggerContainer{background-color:#000;background-color:rgba(0,0,0,.05);box-shadow:inset 1px 1px 16px rgba(0,0,0,.1)}.mCS-3d-thick.mCSB_scrollTools .mCSB_draggerRail{background-color:transparent}.mCS-3d-thick.mCSB_scrollTools .mCSB_buttonUp{background-position:-32px -72px}.mCS-3d-thick.mCSB_scrollTools .mCSB_buttonDown{background-position:-32px -92px}.mCS-3d-thick.mCSB_scrollTools .mCSB_buttonLeft{background-position:-40px -112px}.mCS-3d-thick.mCSB_scrollTools .mCSB_buttonRight{background-position:-40px -128px}.mCS-3d-thick-dark.mCSB_scrollTools{box-shadow:inset 0 0 14px rgba(0,0,0,.2)}.mCS-3d-thick-dark.mCSB_scrollTools_horizontal{box-shadow:inset 0 1px 1px rgba(0,0,0,.1),inset 0 0 14px rgba(0,0,0,.2)}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{box-shadow:inset 1px 0 0 rgba(255,255,255,.4),inset -1px 0 0 rgba(0,0,0,.2)}.mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{box-shadow:inset 0 1px 0 rgba(255,255,255,.4),inset 0 -1px 0 rgba(0,0,0,.2)}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#777}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_draggerContainer{background-color:#fff;background-color:rgba(0,0,0,.05);box-shadow:inset 1px 1px 16px rgba(0,0,0,.1)}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-minimal-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-minimal.mCSB_scrollTools .mCSB_draggerRail{background-color:transparent}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonUp{background-position:-112px -72px}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonDown{background-position:-112px -92px}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonLeft{background-position:-120px -112px}.mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonRight{background-position:-120px -128px}.mCSB_outside+.mCS-minimal-dark.mCSB_scrollTools_vertical,.mCSB_outside+.mCS-minimal.mCSB_scrollTools_vertical{right:0;margin:12px 0}.mCustomScrollBox.mCS-minimal+.mCSB_scrollTools+.mCSB_scrollTools.mCSB_scrollTools_horizontal,.mCustomScrollBox.mCS-minimal+.mCSB_scrollTools.mCSB_scrollTools_horizontal,.mCustomScrollBox.mCS-minimal-dark+.mCSB_scrollTools+.mCSB_scrollTools.mCSB_scrollTools_horizontal,.mCustomScrollBox.mCS-minimal-dark+.mCSB_scrollTools.mCSB_scrollTools_horizontal{bottom:0;margin:0 12px}.mCS-dir-rtl>.mCSB_outside+.mCS-minimal-dark.mCSB_scrollTools_vertical,.mCS-dir-rtl>.mCSB_outside+.mCS-minimal.mCSB_scrollTools_vertical{right:auto;left:0}.mCS-minimal-dark.mCSB_scrollTools_vertical .mCSB_dragger,.mCS-minimal.mCSB_scrollTools_vertical .mCSB_dragger{height:50px}.mCS-minimal-dark.mCSB_scrollTools_horizontal .mCSB_dragger,.mCS-minimal.mCSB_scrollTools_horizontal .mCSB_dragger{width:50px}.mCS-minimal.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.2);filter:'alpha(opacity=20)';-ms-filter:'alpha(opacity=20)'}.mCS-minimal.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-minimal.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.5);filter:'alpha(opacity=50)';-ms-filter:'alpha(opacity=50)'}.mCS-minimal-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.2);filter:'alpha(opacity=20)';-ms-filter:'alpha(opacity=20)'}.mCS-minimal-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-minimal-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.5);filter:'alpha(opacity=50)';-ms-filter:'alpha(opacity=50)'}.mCS-dark-3.mCSB_scrollTools .mCSB_draggerRail,.mCS-light-3.mCSB_scrollTools .mCSB_draggerRail{width:6px;background-color:#000;background-color:rgba(0,0,0,.2)}.mCS-dark-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-light-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{width:6px}.mCS-dark-3.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-dark-3.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-light-3.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-light-3.mCSB_scrollTools_horizontal .mCSB_draggerRail{width:100%;height:6px;margin:5px 0}.mCS-dark-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCS-dark-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail,.mCS-light-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCS-light-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{width:12px}.mCS-dark-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCS-dark-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail,.mCS-light-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded+.mCSB_draggerRail,.mCS-light-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{height:12px;margin:2px 0}.mCS-light-3.mCSB_scrollTools .mCSB_buttonUp{background-position:-32px -72px}.mCS-light-3.mCSB_scrollTools .mCSB_buttonDown{background-position:-32px -92px}.mCS-light-3.mCSB_scrollTools .mCSB_buttonLeft{background-position:-40px -112px}.mCS-light-3.mCSB_scrollTools .mCSB_buttonRight{background-position:-40px -128px}.mCS-dark-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.75)}.mCS-dark-3.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.85)}.mCS-dark-3.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-dark-3.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.9)}.mCS-dark-3.mCSB_scrollTools .mCSB_draggerRail{background-color:#000;background-color:rgba(0,0,0,.1)}.mCS-dark-3.mCSB_scrollTools .mCSB_buttonUp{background-position:-112px -72px}.mCS-dark-3.mCSB_scrollTools .mCSB_buttonDown{background-position:-112px -92px}.mCS-dark-3.mCSB_scrollTools .mCSB_buttonLeft{background-position:-120px -112px}.mCS-dark-3.mCSB_scrollTools .mCSB_buttonRight{background-position:-120px -128px}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-inset-2.mCSB_scrollTools .mCSB_draggerRail,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-inset-3.mCSB_scrollTools .mCSB_draggerRail,.mCS-inset-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-inset.mCSB_scrollTools .mCSB_draggerRail{width:12px;background-color:#000;background-color:rgba(0,0,0,.2)}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-inset.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{position:absolute;top:0;right:0;bottom:0;left:0;width:6px;height:auto;margin:3px 5px}.mCS-inset-2-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-2.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-3-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-3.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar,.mCS-inset.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{position:absolute;top:0;right:0;bottom:0;left:0;width:auto;height:6px;margin:5px 3px}.mCS-inset-2-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-inset-2.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-inset-3-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-inset-3.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-inset-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail,.mCS-inset.mCSB_scrollTools_horizontal .mCSB_draggerRail{width:100%;height:12px;margin:2px 0}.mCS-inset-2.mCSB_scrollTools .mCSB_buttonUp,.mCS-inset-3.mCSB_scrollTools .mCSB_buttonUp,.mCS-inset.mCSB_scrollTools .mCSB_buttonUp{background-position:-32px -72px}.mCS-inset-2.mCSB_scrollTools .mCSB_buttonDown,.mCS-inset-3.mCSB_scrollTools .mCSB_buttonDown,.mCS-inset.mCSB_scrollTools .mCSB_buttonDown{background-position:-32px -92px}.mCS-inset-2.mCSB_scrollTools .mCSB_buttonLeft,.mCS-inset-3.mCSB_scrollTools .mCSB_buttonLeft,.mCS-inset.mCSB_scrollTools .mCSB_buttonLeft{background-position:-40px -112px}.mCS-inset-2.mCSB_scrollTools .mCSB_buttonRight,.mCS-inset-3.mCSB_scrollTools .mCSB_buttonRight,.mCS-inset.mCSB_scrollTools .mCSB_buttonRight{background-position:-40px -128px}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar,.mCS-inset-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.75)}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar,.mCS-inset-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.85)}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,.mCS-inset-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-inset-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.9)}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-inset-dark.mCSB_scrollTools .mCSB_draggerRail{background-color:#000;background-color:rgba(0,0,0,.1)}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonUp,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonUp,.mCS-inset-dark.mCSB_scrollTools .mCSB_buttonUp{background-position:-112px -72px}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonDown,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonDown,.mCS-inset-dark.mCSB_scrollTools .mCSB_buttonDown{background-position:-112px -92px}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonLeft,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonLeft,.mCS-inset-dark.mCSB_scrollTools .mCSB_buttonLeft{background-position:-120px -112px}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonRight,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonRight,.mCS-inset-dark.mCSB_scrollTools .mCSB_buttonRight{background-position:-120px -128px}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail,.mCS-inset-2.mCSB_scrollTools .mCSB_draggerRail{box-sizing:border-box;border-width:1px;border-style:solid;border-color:#fff;border-color:rgba(255,255,255,.2);background-color:transparent}.mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail{border-color:#000;border-color:rgba(0,0,0,.2)}.mCS-inset-3.mCSB_scrollTools .mCSB_draggerRail{background-color:#fff;background-color:rgba(255,255,255,.6)}.mCS-inset-3-dark.mCSB_scrollTools .mCSB_draggerRail{background-color:#000;background-color:rgba(0,0,0,.6)}.mCS-inset-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.75)}.mCS-inset-3.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.85)}.mCS-inset-3.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-inset-3.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#000;background-color:rgba(0,0,0,.9)}.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.75)}.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.85)}.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar,.mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar{background-color:#fff;background-color:rgba(255,255,255,.9)}@keyframes rotation{from{-webkit-transform:rotate(0)}to{-webkit-transform:rotate(359deg)}}@keyframes translateright{0%{right:-50px;display:none;color:transparent}30%{right:150px;display:block;color:transparent}100%{right:200px;color:#fff}}@keyframes translateleft{0%{right:200px;display:block;color:#fff}30%{right:150px;color:transparent}100%{right:-200px;display:none;color:transparent}}@keyframes reducH{0%{font-size:12vw;line-height:5rem;color:transparent;-webkit-text-stroke:1px}70%{color:#000;-webkit-text-stroke:1px}100%{font-size:3rem;line-height:5rem;color:#000;-webkit-text-stroke:0}}@keyframes translateY{0%{transform:translateY(0);opacity:1}100%{transform:translateY(-20px);opacity:0}}@keyframes showcatlist{0%{width:0;opacity:1;box-shadow:0 0 0 0 #000}100%{width:100%;opacity:1;box-shadow:0 -2px 0 -2px #000}}@keyframes showborder{0%{width:0;background:#000}100%{width:100%;background:#000}}@keyframes ultranslate{0%{transform:translateX(0);opacity:0}100%{transform:translateX(10px);opacity:1}}@keyframes showcard{0%{opacity:0}100%{opacity:1}}body,html{overflow:hidden}.blog{height:100%}.blog.down{height:5rem}header .contact{position:fixed;z-index:999;top:0;right:0;width:90px;height:auto;cursor:pointer;transition:transform .5s ease;transform:scale(1)}header .contact:hover{transition:transform .5s ease;transform:scale(.9)}header .contact svg{cursor:pointer;animation:rotation 6s infinite linear}header .contact h3{position:absolute;top:35px;left:19px;transform:rotate(35deg);color:#fff}header .contact .txt{position:fixed;right:-200px;display:none}header .contact.open{height:auto;transition:transform .5s ease;transform:scale(15)}header .contact.open h3{left:500px;transition:visibility .5 ease left 1s ease;color:transparent}header .contact.open svg{animation:rotation 12s infinite linear}header .contact.open+.txt{line-height:1.5rem;position:fixed;z-index:9999;top:190px;animation:translateright 1s ease forwards;text-align:center}header .contact.close{transition:transform .5s .1s ease;transform:scale(1)}header .contact.close+.txt{line-height:1.5rem;z-index:9999;top:190px;animation:translateleft 1s ease forwards;text-align:center}header .contact.close h3{transition:color .5s .5s ease,left .5s ease}header .contact.close:hover{transition:transform .5s ease!important;transform:scale(.9)!important}.card{line-height:.8rem!important;width:calc((100% / 3))}.card .card-header{display:-ms-inline-flexbox;display:inline-flex;margin-top:10px}.card .card-header>div{margin-right:5px}@media screen and (max-width:960px){.works-mea>div:last-child{display:none}.card{width:calc((100% / 2) - 5px)}}@media screen and (max-width:600px){.works-mea>div:nth-child(2){display:none}.card{width:100%}}body .blog header{display:-ms-inline-flexbox;display:inline-flex;flex-direction:column;width:100%;height:100vh;-ms-flex-direction:column;-ms-flex-line-pack:distribute;align-content:space-around}body .blog header>div{position:fixed}body .blog header>div.head{z-index:999;width:100%;padding-top:70px}body .blog header>div.head h2{position:absolute;margin-left:8px}body .blog header>div.head::after{display:block;width:0;height:1px;content:' ';background:#000}body .blog header>div.works-mea{display:-ms-inline-flexbox;display:inline-flex;height:calc(100% - 20px);-ms-flex-item-align:end;align-self:flex-end;-ms-flex-align:end;align-items:flex-end}body .blog header>div.works-mea .card:nth-child(2){margin:0 10px}body .blog header>div.works-mea .card:not(:nth-child(2)){margin:0}body .blog header>div.scroll{bottom:0;width:100%;text-align:center}body .blog.down header{height:5rem}body .blog.down header .head{padding:0;transition:all 1s .9s ease}body .blog.down header .head::after{animation:showborder 2.5s 1.5s ease forwards}body .blog.down header .head h2{left:-50%;margin-left:-100px;transition:all 1s ease;opacity:0}body .blog.down header .card:nth-child(1){animation:translateY 1s ease forwards}body .blog.down header .card:nth-child(2){animation:translateY 1.3s ease forwards}body .blog.down header .card:nth-child(3){animation:translateY 1.6s ease forwards}body .blog.down header .scroll{transition:all 1s ease;opacity:0}body #page-wrapper.item header{position:fixed;z-index:999;top:0;right:10px;left:10px;height:112px;border-bottom:1px solid #000;background:#fff}body #page-wrapper.item header nav{margin-top:25px}body #page-wrapper.item header h1{color:#000}body #page-wrapper.item header .scroll,body #page-wrapper.item header h2{display:none}.blog #start .cat-list{z-index:999;left:-100%;width:100%}.blog #start .cat-list::before{display:block;width:0;height:1px;content:' ';background:#000}.blog #start .cat-list::after{display:block;width:0;height:1px;content:' ';background:#000}.blog #start .cat-list ul{display:-ms-inline-flexbox;display:inline-flex;margin:10px 0;opacity:0}.blog #start .cat-list ul .btn-group{display:-ms-flexbox;display:flex;margin-right:10px}.blog #start .cat-list ul .btn-group::after{width:5px;content:' '}.blog #start .cat-list ul .btn-group:hover::after{content:'\002022'}.blog #start .cat-list ul .btn-group input{display:none}.blog #start #items .full-works .card{width:calc((100vw / 3) - 6.5px)}.blog .down .cat-list{left:0}.blog .down .cat-list::after{animation:showborder 2.5s 1.6s ease forwards}.blog .down .cat-list ul{animation:ultranslate 1s 2.5s ease forwards}body #start{width:100%;height:100%}body #page-wrapper.blog #start{position:fixed;top:100vh;left:0;height:calc(100% - 5rem)}body #page-wrapper.down #start{top:5rem}body #page-wrapper.down #items .card{animation:showcard 1.5s 2.5s ease forwards}#items .full-works{width:100%;margin-top:30px}#items .card{min-width:200px;margin-bottom:10px;opacity:0}body #page-wrapper.item{margin:0 10px}body #page-wrapper.item .contact .txt{display:none}body #page-wrapper.item #start{margin-top:112px}body #page-wrapper.item #body-wrapper.blog-listing .img-card{width:calc((100% / 3) - 6.5px);margin-bottom:10px}body #page-wrapper.item #body-wrapper.blog-listing .header-item{position:relative;margin-bottom:20px;padding-top:20px}body #page-wrapper.item #body-wrapper.blog-listing .header-item .title{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:baseline;align-items:baseline}body #page-wrapper.item #body-wrapper.blog-listing .header-item .title h2{margin-right:5px;text-transform:uppercase}body #page-wrapper.item #body-wrapper.blog-listing .header-item .title time{margin-right:5px}body #page-wrapper.item #body-wrapper.blog-listing .header-item .title span{margin-right:5px}body #page-wrapper.item #body-wrapper.blog-listing .header-item .content{width:calc((100% / 3) - 15px);margin-top:20px}body #page-wrapper.item #body-wrapper.blog-listing .header-item .prev-next{position:absolute;top:calc(50% + 10px);right:0;width:calc(100% / 3);height:100%;transform:translateY(-50%)}body #page-wrapper.item #body-wrapper.blog-listing .header-item .prev-next .btn{display:block;width:auto;height:100%}body #page-wrapper.item #body-wrapper.blog-listing .header-item .prev-next .btn .btn-info{position:absolute;margin-top:20px;margin-left:40px;transform:rotate(-15deg)}body #page-wrapper.item #body-wrapper.blog-listing .header-item .prev-next .btn img{float:right;width:80%}body #page-wrapper.item .top{width:50px;margin:20px auto 20px auto;text-align:center}body #page-wrapper.item .top img{width:20px;margin-bottom:5px}body #page-wrapper.item .top p{transform:rotate(-10deg)}@media screen and (max-width:960px){body #page-wrapper.item #body-wrapper.blog-listing .header-item .content{width:calc((100% / 2) - 5px)}body #page-wrapper.item #body-wrapper.blog-listing .header-item .prev-next{width:calc((100% / 2) - 5px)}body #page-wrapper.item #body-wrapper.blog-listing .img-card{width:calc((100% / 2) - 5px)}}@media screen and (max-width:600px){body #page-wrapper.item #body-wrapper.blog-listing .header-item .content{width:calc((100%) - 15px)}body #page-wrapper.item #body-wrapper.blog-listing .header-item .prev-next{position:relative;width:calc((100%) - 15px);transform:translateY(0)}body #page-wrapper.item #body-wrapper.blog-listing .header-item .prev-next .btn .btn-info{display:none}body #page-wrapper.item #body-wrapper.blog-listing .header-item .prev-next .btn img{float:none;width:100%}body #page-wrapper.item #body-wrapper.blog-listing .img-card{width:100%}}
\ No newline at end of file
diff --git a/user/themes/gilles_acezat/images/arrow.png b/user/themes/gilles_acezat/images/arrow.png
new file mode 100644
index 0000000..fe39f2c
Binary files /dev/null and b/user/themes/gilles_acezat/images/arrow.png differ
diff --git a/user/themes/gilles_acezat/js/blockScroll.js b/user/themes/gilles_acezat/js/blockScroll.js
new file mode 100755
index 0000000..9e97fdc
--- /dev/null
+++ b/user/themes/gilles_acezat/js/blockScroll.js
@@ -0,0 +1,223 @@
+(function( $ ) {
+
+ $.fn.blockScroll = function(options) {
+ var settings = $.extend({
+ // These are the defaults.
+ startDiv : 1,
+ fadeDuration : "slow",
+ paddingRatio : 0.05,
+ triggerRatio : 0.005,
+ scrollDuration: "fast",
+ fadeBlocks: true
+ }, options );
+
+ if(settings.triggerRatio > settings.paddingRatio*.95) { settings.triggerRatio = settings.paddingRatio*.95 }
+
+ var theDivs = this.children().filter("div");
+ var activeDiv = settings.startDiv-1; //Active did is 0-index, settings is 1-index
+ var windowHeight;
+ var paddingHeight;
+ var triggerHeight;
+ var currentDownTrigger;
+ var currentUpTrigger;
+ var totalDivs = theDivs.length;
+ var lastScrollPos;
+ var activelyScrolling = false;
+ var activeBackground= 0;
+
+ // Ensure that all of the elements are hidden just in case the css is not setup properly
+ if(settings.fadeBlocks)
+ {
+ this.children().each(function() {
+ $(this).css('opacity','0');
+ });
+ }
+
+ arrange();
+ // Fade in the first div
+ $(theDivs[activeDiv]).animate({opacity: 1},settings.fadeDuration,'linear', function() {
+ $(window).scrollTop(0);
+ calcTriggers();
+ bindEvents();
+ lastScrollPos = $(window).scrollTop();
+ });
+
+ function bindEvents()
+ {
+ $(window).on('scroll', function(e) {
+ var scrollPosition = $(window).scrollTop();
+ var scrollDistance = $(window).height();
+ var indexOfClosest = 0;
+
+ theDivs.each(function(index, element) {
+ var $this = $(this);
+ var topPosition = $this.offset().top;
+ var newScrollDistance = Math.abs(scrollPosition - topPosition);
+ if(newScrollDistance < scrollDistance)
+ {
+ indexOfClosest = index;
+ scrollDistance = newScrollDistance;
+ }
+ });
+ gotoDiv(indexOfClosest);
+ }, 250);
+
+ $(window).resize(function() {
+ arrange();
+ });
+
+ $("#block-up-arrow").click(function() {
+ goUp();
+ });
+
+ $("#block-down-arrow").click(function() {
+ goDown();
+ });
+
+ $(document).keydown(function(e){
+ if (e.keyCode == 37 || e.keyCode == 38) {
+ goUp();
+ return false;
+ }
+
+ if (e.keyCode == 39 || e.keyCode == 40) {
+ goDown();
+ return false;
+ }
+ });
+ $(window).bind('mousewheel', function(e){
+ if(e.originalEvent.wheelDelta > 119) {
+ goUp();
+ }
+ else if (e.originalEvent.wheelDelta < -119) {
+ goDown();
+ }
+ });
+ $(window).bind('DOMMouseScroll', function(e){
+ if(e.originalEvent.detail < 0) {
+ goUp();
+ }
+ else if (e.originalEvent.detail > 0) {
+ goDown();
+ }
+ });
+ }
+
+ function goUp()
+ {
+ if(activeDiv > 0 && !activelyScrolling)
+ {
+ gotoDiv(activeDiv-1);
+ }
+ }
+
+ function goDown()
+ {
+ if(activeDiv < totalDivs - 1 && !activelyScrolling)
+ {
+ gotoDiv(activeDiv+1);
+ }
+ }
+
+ function gotoDiv(number)
+ {
+ if(number == 0)
+ $("#block-up-arrow").hide();
+ else
+ $("#block-up-arrow").show();
+ if(number == totalDivs-1)
+ $("#block-down-arrow").hide();
+ else
+ $("#block-down-arrow").show();
+ activeDiv = number;
+ activelyScrolling = true;
+ $('html, body').animate({scrollTop: $(theDivs[activeDiv]).offset().top}, settings.scrollDuration, 'linear', function() {
+ $(theDivs[activeDiv]).animate({opacity: 1}, settings.fadeDuration,'linear', function() {
+ setTimeout(function(){
+ activelyScrolling = false; lastScrollPos = $(window).scrollTop();
+ },50);
+ });
+ });
+ calcTriggers();
+ }
+
+ function calcTriggers()
+ {
+ if (activeDiv < totalDivs -1)
+ {
+ currentDownTrigger = $(theDivs[activeDiv+1]).offset().top;
+ } else {
+ currentDownTrigger = -1;
+ }
+
+ if (activeDiv > 0) {
+ currentUpTrigger = $(theDivs[activeDiv-1]).offset().top;
+ } else {
+ currentUpTrigger = -1;
+ }
+ }
+
+ function calcDims()
+ {
+ windowHeight = $(window).height();
+ paddingHeight = windowHeight * settings.paddingRatio;
+ triggerHeight = windowHeight * settings.triggerRatio;
+ }
+
+
+ function arrange()
+ {
+ calcDims();
+ theDivs.each(function(index, element) {
+ var $this = $(this);
+ $this.height('auto');
+ if($this.height() < windowHeight)
+ {
+ var margin = windowHeight/2 - $this.height()/2;
+ $this.height(windowHeight-margin);
+ $this.css('padding-top', margin + "px");
+ var $innerDiv = $($this.children().filter('div')[0]);
+ // $innerDiv.css('padding-top', margin + "px");
+ }
+ if(index != totalDivs - 1)
+ {
+ //$this.css('padding-bottom',paddingHeight + 'px');
+ }
+ });
+ gotoDiv(activeDiv);
+ }
+
+ var gotoView = function(number)
+ {
+ gotoDiv(number-1);
+ }
+
+ return {
+ goto: gotoView
+ };
+ }
+
+}( jQuery ));
+
+;(function ($) {
+ var on = $.fn.on, timer;
+ $.fn.on = function () {
+ var args = Array.apply(null, arguments);
+ var last = args[args.length - 1];
+
+ if (isNaN(last) || (last === 1 && args.pop())) return on.apply(this, args);
+
+ var delay = args.pop();
+ var fn = args.pop();
+
+ args.push(function () {
+ var self = this, params = arguments;
+ clearTimeout(timer);
+ timer = setTimeout(function () {
+ fn.apply(self, params);
+ }, delay);
+ });
+
+ return on.apply(this, args);
+ };
+}(this.jQuery || this.Zepto));
diff --git a/user/themes/gilles_acezat/js/jquery.mobile-1.4.5.min.js b/user/themes/gilles_acezat/js/jquery.mobile-1.4.5.min.js
new file mode 100644
index 0000000..5b1a9a1
--- /dev/null
+++ b/user/themes/gilles_acezat/js/jquery.mobile-1.4.5.min.js
@@ -0,0 +1,10 @@
+/*! jQuery Mobile 1.4.5 | Git HEADhash: 68e55e7 <> 2014-10-31T17:33:30Z | (c) 2010, 2014 jQuery Foundation, Inc. | jquery.org/license */
+
+!function(a,b,c){"function"==typeof define&&define.amd?define(["jquery"],function(d){return c(d,a,b),d.mobile}):c(a.jQuery,a,b)}(this,document,function(a,b,c){!function(a){a.mobile={}}(a),function(a,b){function d(b,c){var d,f,g,h=b.nodeName.toLowerCase();return"area"===h?(d=b.parentNode,f=d.name,b.href&&f&&"map"===d.nodeName.toLowerCase()?(g=a("img[usemap=#"+f+"]")[0],!!g&&e(g)):!1):(/input|select|textarea|button|object/.test(h)?!b.disabled:"a"===h?b.href||c:c)&&e(b)}function e(b){return a.expr.filters.visible(b)&&!a(b).parents().addBack().filter(function(){return"hidden"===a.css(this,"visibility")}).length}var f=0,g=/^ui-id-\d+$/;a.ui=a.ui||{},a.extend(a.ui,{version:"c0ab71056b936627e8a7821f03c044aec6280a40",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),a.fn.extend({focus:function(b){return function(c,d){return"number"==typeof c?this.each(function(){var b=this;setTimeout(function(){a(b).focus(),d&&d.call(b)},c)}):b.apply(this,arguments)}}(a.fn.focus),scrollParent:function(){var b;return b=a.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.css(this,"position"))&&/(auto|scroll)/.test(a.css(this,"overflow")+a.css(this,"overflow-y")+a.css(this,"overflow-x"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(a.css(this,"overflow")+a.css(this,"overflow-y")+a.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!b.length?a(this[0].ownerDocument||c):b},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++f)})},removeUniqueId:function(){return this.each(function(){g.test(this.id)&&a(this).removeAttr("id")})}}),a.extend(a.expr[":"],{data:a.expr.createPseudo?a.expr.createPseudo(function(b){return function(c){return!!a.data(c,b)}}):function(b,c,d){return!!a.data(b,d[3])},focusable:function(b){return d(b,!isNaN(a.attr(b,"tabindex")))},tabbable:function(b){var c=a.attr(b,"tabindex"),e=isNaN(c);return(e||c>=0)&&d(b,!e)}}),a("").outerWidth(1).jquery||a.each(["Width","Height"],function(c,d){function e(b,c,d,e){return a.each(f,function(){c-=parseFloat(a.css(b,"padding"+this))||0,d&&(c-=parseFloat(a.css(b,"border"+this+"Width"))||0),e&&(c-=parseFloat(a.css(b,"margin"+this))||0)}),c}var f="Width"===d?["Left","Right"]:["Top","Bottom"],g=d.toLowerCase(),h={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+d]=function(c){return c===b?h["inner"+d].call(this):this.each(function(){a(this).css(g,e(this,c)+"px")})},a.fn["outer"+d]=function(b,c){return"number"!=typeof b?h["outer"+d].call(this,b):this.each(function(){a(this).css(g,e(this,b,!0,c)+"px")})}}),a.fn.addBack||(a.fn.addBack=function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}),a("").data("a-b","a").removeData("a-b").data("a-b")&&(a.fn.removeData=function(b){return function(c){return arguments.length?b.call(this,a.camelCase(c)):b.call(this)}}(a.fn.removeData)),a.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),a.support.selectstart="onselectstart"in c.createElement("div"),a.fn.extend({disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")},zIndex:function(d){if(d!==b)return this.css("zIndex",d);if(this.length)for(var e,f,g=a(this[0]);g.length&&g[0]!==c;){if(e=g.css("position"),("absolute"===e||"relative"===e||"fixed"===e)&&(f=parseInt(g.css("zIndex"),10),!isNaN(f)&&0!==f))return f;g=g.parent()}return 0}}),a.ui.plugin={add:function(b,c,d){var e,f=a.ui[b].prototype;for(e in d)f.plugins[e]=f.plugins[e]||[],f.plugins[e].push([c,d[e]])},call:function(a,b,c,d){var e,f=a.plugins[b];if(f&&(d||a.element[0].parentNode&&11!==a.element[0].parentNode.nodeType))for(e=0;e0&&(e=e.concat(g.toArray())),0===j.length&&i.length>0&&(e=e.concat(i.toArray())),a.each(e,function(b,d){c-=a(d).outerHeight()}),Math.max(0,c)};a.extend(a.mobile,{window:a(b),document:a(c),keyCode:a.ui.keyCode,behaviors:{},silentScroll:function(c){"number"!==a.type(c)&&(c=a.mobile.defaultHomeScroll),a.event.special.scrollstart.enabled=!1,setTimeout(function(){b.scrollTo(0,c),a.mobile.document.trigger("silentscroll",{x:0,y:c})},20),setTimeout(function(){a.event.special.scrollstart.enabled=!0},150)},getClosestBaseUrl:function(b){var c=a(b).closest(".ui-page").jqmData("url"),d=a.mobile.path.documentBase.hrefNoHash;return a.mobile.dynamicBaseEnabled&&c&&a.mobile.path.isPath(c)||(c=d),a.mobile.path.makeUrlAbsolute(c,d)},removeActiveLinkClass:function(b){!a.mobile.activeClickedLink||a.mobile.activeClickedLink.closest("."+a.mobile.activePageClass).length&&!b||a.mobile.activeClickedLink.removeClass(a.mobile.activeBtnClass),a.mobile.activeClickedLink=null},getInheritedTheme:function(a,b){for(var c,d,e=a[0],f="",g=/ui-(bar|body|overlay)-([a-z])\b/;e&&(c=e.className||"",!(c&&(d=g.exec(c))&&(f=d[2])));)e=e.parentNode;return f||b||"a"},enhanceable:function(a){return this.haveParents(a,"enhance")},hijackable:function(a){return this.haveParents(a,"ajax")},haveParents:function(b,c){if(!a.mobile.ignoreContentEnabled)return b;var d,e,f,g,h,i=b.length,j=a();for(g=0;i>g;g++){for(e=b.eq(g),f=!1,d=b[g];d;){if(h=d.getAttribute?d.getAttribute("data-"+a.mobile.ns+c):"","false"===h){f=!0;break}d=d.parentNode}f||(j=j.add(e))}return j},getScreenHeight:function(){return b.innerHeight||a.mobile.window.height()},resetActivePageHeight:function(b){var c=a("."+a.mobile.activePageClass),e=c.height(),f=c.outerHeight(!0);b=d(c,"number"==typeof b?b:a.mobile.getScreenHeight()),c.css("min-height",""),c.height()0&&(g=g.not(d)),g.length>0&&(c[f.prototype.widgetName]=g)}});for(b in c)c[b][b]();return this},addDependents:function(b){a.addDependents(this,b)},getEncodedText:function(){return a("").text(this.text()).html()},jqmEnhanceable:function(){return a.mobile.enhanceable(this)},jqmHijackable:function(){return a.mobile.hijackable(this)}}),a.removeWithDependents=function(b){var c=a(b);(c.jqmData("dependents")||a()).remove(),c.remove()},a.addDependents=function(b,c){var d=a(b),e=d.jqmData("dependents")||a();d.jqmData("dependents",a(e).add(c))},a.find.matches=function(b,c){return a.find(b,null,null,c)},a.find.matchesSelector=function(b,c){return a.find(c,null,null,[b]).length>0}}(a,this),function(a){a.extend(a.mobile,{version:"1.4.5",subPageUrlKey:"ui-page",hideUrlBar:!0,keepNative:":jqmData(role='none'), :jqmData(role='nojs')",activePageClass:"ui-page-active",activeBtnClass:"ui-btn-active",focusClass:"ui-focus",ajaxEnabled:!0,hashListeningEnabled:!0,linkBindingEnabled:!0,defaultPageTransition:"fade",maxTransitionWidth:!1,minScrollBack:0,defaultDialogTransition:"pop",pageLoadErrorMessage:"Error Loading Page",pageLoadErrorMessageTheme:"a",phonegapNavigationEnabled:!1,autoInitializePage:!0,pushStateEnabled:!0,ignoreContentEnabled:!1,buttonMarkup:{hoverDelay:200},dynamicBaseEnabled:!0,pageContainer:a(),allowCrossDomainPages:!1,dialogHashKey:"&ui-state=dialog"})}(a,this),function(a,b){var c=0,d=Array.prototype.slice,e=a.cleanData;a.cleanData=function(b){for(var c,d=0;null!=(c=b[d]);d++)try{a(c).triggerHandler("remove")}catch(f){}e(b)},a.widget=function(b,c,d){var e,f,g,h,i={},j=b.split(".")[0];return b=b.split(".")[1],e=j+"-"+b,d||(d=c,c=a.Widget),a.expr[":"][e.toLowerCase()]=function(b){return!!a.data(b,e)},a[j]=a[j]||{},f=a[j][b],g=a[j][b]=function(a,b){return this._createWidget?void(arguments.length&&this._createWidget(a,b)):new g(a,b)},a.extend(g,f,{version:d.version,_proto:a.extend({},d),_childConstructors:[]}),h=new c,h.options=a.widget.extend({},h.options),a.each(d,function(b,d){return a.isFunction(d)?void(i[b]=function(){var a=function(){return c.prototype[b].apply(this,arguments)},e=function(a){return c.prototype[b].apply(this,a)};return function(){var b,c=this._super,f=this._superApply;return this._super=a,this._superApply=e,b=d.apply(this,arguments),this._super=c,this._superApply=f,b}}()):void(i[b]=d)}),g.prototype=a.widget.extend(h,{widgetEventPrefix:f?h.widgetEventPrefix||b:b},i,{constructor:g,namespace:j,widgetName:b,widgetFullName:e}),f?(a.each(f._childConstructors,function(b,c){var d=c.prototype;a.widget(d.namespace+"."+d.widgetName,g,c._proto)}),delete f._childConstructors):c._childConstructors.push(g),a.widget.bridge(b,g),g},a.widget.extend=function(c){for(var e,f,g=d.call(arguments,1),h=0,i=g.length;i>h;h++)for(e in g[h])f=g[h][e],g[h].hasOwnProperty(e)&&f!==b&&(c[e]=a.isPlainObject(f)?a.isPlainObject(c[e])?a.widget.extend({},c[e],f):a.widget.extend({},f):f);return c},a.widget.bridge=function(c,e){var f=e.prototype.widgetFullName||c;a.fn[c]=function(g){var h="string"==typeof g,i=d.call(arguments,1),j=this;return g=!h&&i.length?a.widget.extend.apply(null,[g].concat(i)):g,this.each(h?function(){var d,e=a.data(this,f);return"instance"===g?(j=e,!1):e?a.isFunction(e[g])&&"_"!==g.charAt(0)?(d=e[g].apply(e,i),d!==e&&d!==b?(j=d&&d.jquery?j.pushStack(d.get()):d,!1):void 0):a.error("no such method '"+g+"' for "+c+" widget instance"):a.error("cannot call methods on "+c+" prior to initialization; attempted to call method '"+g+"'")}:function(){var b=a.data(this,f);b?b.option(g||{})._init():a.data(this,f,new e(g,this))}),j}},a.Widget=function(){},a.Widget._childConstructors=[],a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"",options:{disabled:!1,create:null},_createWidget:function(b,d){d=a(d||this.defaultElement||this)[0],this.element=a(d),this.uuid=c++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=a.widget.extend({},this.options,this._getCreateOptions(),b),this.bindings=a(),this.hoverable=a(),this.focusable=a(),d!==this&&(a.data(d,this.widgetFullName,this),this._on(!0,this.element,{remove:function(a){a.target===d&&this.destroy()}}),this.document=a(d.style?d.ownerDocument:d.document||d),this.window=a(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:a.noop,_getCreateEventData:a.noop,_create:a.noop,_init:a.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetFullName).removeData(a.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:a.noop,widget:function(){return this.element},option:function(c,d){var e,f,g,h=c;if(0===arguments.length)return a.widget.extend({},this.options);if("string"==typeof c)if(h={},e=c.split("."),c=e.shift(),e.length){for(f=h[c]=a.widget.extend({},this.options[c]),g=0;g
-1&&(b=b.replace(g,"[data-"+(a.mobile.ns||"")+"$1]")),e.call(this,b,c,d,f)},a.extend(a.find,e)}(a,this),function(a){var b=/[A-Z]/g,c=function(a){return"-"+a.toLowerCase()};a.extend(a.Widget.prototype,{_getCreateOptions:function(){var d,e,f=this.element[0],g={};if(!a.mobile.getAttribute(f,"defaults"))for(d in this.options)e=a.mobile.getAttribute(f,d.replace(b,c)),null!=e&&(g[d]=e);return g}}),a.mobile.widget=a.Widget}(a),function(a){var b="ui-loader",c=a("html");a.widget("mobile.loader",{options:{theme:"a",textVisible:!1,html:"",text:"loading"},defaultHtml:"
",fakeFixLoader:function(){var b=a("."+a.mobile.activeBtnClass).first();this.element.css({top:a.support.scrollTop&&this.window.scrollTop()+this.window.height()/2||b.length&&b.offset().top||100})},checkLoaderPosition:function(){var b=this.element.offset(),c=this.window.scrollTop(),d=a.mobile.getScreenHeight();(b.topd)&&(this.element.addClass("ui-loader-fakefix"),this.fakeFixLoader(),this.window.unbind("scroll",this.checkLoaderPosition).bind("scroll",a.proxy(this.fakeFixLoader,this)))},resetHtml:function(){this.element.html(a(this.defaultHtml).html())},show:function(d,e,f){var g,h,i;this.resetHtml(),"object"===a.type(d)?(i=a.extend({},this.options,d),d=i.theme):(i=this.options,d=d||i.theme),h=e||(i.text===!1?"":i.text),c.addClass("ui-loading"),g=i.textVisible,this.element.attr("class",b+" ui-corner-all ui-body-"+d+" ui-loader-"+(g||e||d.text?"verbose":"default")+(i.textonly||f?" ui-loader-textonly":"")),i.html?this.element.html(i.html):this.element.find("h1").text(h),this.element.appendTo(a(a.mobile.pagecontainer?":mobile-pagecontainer":"body")),this.checkLoaderPosition(),this.window.bind("scroll",a.proxy(this.checkLoaderPosition,this))},hide:function(){c.removeClass("ui-loading"),this.options.text&&this.element.removeClass("ui-loader-fakefix"),this.window.unbind("scroll",this.fakeFixLoader),this.window.unbind("scroll",this.checkLoaderPosition)}})}(a,this),function(a,b,d){"$:nomunge";function e(a){return a=a||location.href,"#"+a.replace(/^[^#]*#?(.*)$/,"$1")}var f,g="hashchange",h=c,i=a.event.special,j=h.documentMode,k="on"+g in b&&(j===d||j>7);a.fn[g]=function(a){return a?this.bind(g,a):this.trigger(g)},a.fn[g].delay=50,i[g]=a.extend(i[g],{setup:function(){return k?!1:void a(f.start)},teardown:function(){return k?!1:void a(f.stop)}}),f=function(){function c(){var d=e(),h=n(j);d!==j?(m(j=d,h),a(b).trigger(g)):h!==j&&(location.href=location.href.replace(/#.*/,"")+h),f=setTimeout(c,a.fn[g].delay)}var f,i={},j=e(),l=function(a){return a},m=l,n=l;return i.start=function(){f||c()},i.stop=function(){f&&clearTimeout(f),f=d},b.attachEvent&&!b.addEventListener&&!k&&function(){var b,d;i.start=function(){b||(d=a.fn[g].src,d=d&&d+e(),b=a('').hide().one("load",function(){d||m(e()),c()}).attr("src",d||"javascript:0").insertAfter("body")[0].contentWindow,h.onpropertychange=function(){try{"title"===event.propertyName&&(b.document.title=h.title)}catch(a){}})},i.stop=l,n=function(){return e(b.location.href)},m=function(c,d){var e=b.document,f=a.fn[g].domain;c!==d&&(e.title=h.title,e.open(),f&&e.write(''),e.close(),b.location.hash=c)}}(),i}()}(a,this),function(a){b.matchMedia=b.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(c),a.mobile.media=function(a){return b.matchMedia(a).matches}}(a),function(a){var b={touch:"ontouchend"in c};a.mobile.support=a.mobile.support||{},a.extend(a.support,b),a.extend(a.mobile.support,b)}(a),function(a){a.extend(a.support,{orientation:"orientation"in b&&"onorientationchange"in b})}(a),function(a,d){function e(a){var b,c=a.charAt(0).toUpperCase()+a.substr(1),e=(a+" "+o.join(c+" ")+c).split(" ");for(b in e)if(n[e[b]]!==d)return!0}function f(){var c=b,d=!(!c.document.createElementNS||!c.document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect||c.opera&&-1===navigator.userAgent.indexOf("Chrome")),e=function(b){b&&d||a("html").addClass("ui-nosvg")},f=new c.Image;f.onerror=function(){e(!1)},f.onload=function(){e(1===f.width&&1===f.height)},f.src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="}function g(){var e,f,g,h="transform-3d",i=a.mobile.media("(-"+o.join("-"+h+"),(-")+"-"+h+"),("+h+")");if(i)return!!i;e=c.createElement("div"),f={MozTransform:"-moz-transform",transform:"transform"},m.append(e);for(g in f)e.style[g]!==d&&(e.style[g]="translate3d( 100px, 1px, 1px )",i=b.getComputedStyle(e).getPropertyValue(f[g]));return!!i&&"none"!==i}function h(){var b,c,d=location.protocol+"//"+location.host+location.pathname+"ui-dir/",e=a("head base"),f=null,g="";return e.length?g=e.attr("href"):e=f=a("",{href:d}).appendTo("head"),b=a("").prependTo(m),c=b[0].href,e[0].href=g||location.pathname,f&&f.remove(),0===c.indexOf(d)}function i(){var a,d=c.createElement("x"),e=c.documentElement,f=b.getComputedStyle;return"pointerEvents"in d.style?(d.style.pointerEvents="auto",d.style.pointerEvents="x",e.appendChild(d),a=f&&"auto"===f(d,"").pointerEvents,e.removeChild(d),!!a):!1}function j(){var a=c.createElement("div");return"undefined"!=typeof a.getBoundingClientRect}function k(){var a=b,c=navigator.userAgent,d=navigator.platform,e=c.match(/AppleWebKit\/([0-9]+)/),f=!!e&&e[1],g=c.match(/Fennec\/([0-9]+)/),h=!!g&&g[1],i=c.match(/Opera Mobi\/([0-9]+)/),j=!!i&&i[1];return(d.indexOf("iPhone")>-1||d.indexOf("iPad")>-1||d.indexOf("iPod")>-1)&&f&&534>f||a.operamini&&"[object OperaMini]"==={}.toString.call(a.operamini)||i&&7458>j||c.indexOf("Android")>-1&&f&&533>f||h&&6>h||"palmGetResource"in b&&f&&534>f||c.indexOf("MeeGo")>-1&&c.indexOf("NokiaBrowser/8.5.0")>-1?!1:!0}var l,m=a("").prependTo("html"),n=m[0].style,o=["Webkit","Moz","O"],p="palmGetResource"in b,q=b.operamini&&"[object OperaMini]"==={}.toString.call(b.operamini),r=b.blackberry&&!e("-webkit-transform");a.extend(a.mobile,{browser:{}}),a.mobile.browser.oldIE=function(){var a=3,b=c.createElement("div"),d=b.all||[];do b.innerHTML="";while(d[0]);return a>4?a:!a}(),a.extend(a.support,{pushState:"pushState"in history&&"replaceState"in history&&!(b.navigator.userAgent.indexOf("Firefox")>=0&&b.top!==b)&&-1===b.navigator.userAgent.search(/CriOS/),mediaquery:a.mobile.media("only all"),cssPseudoElement:!!e("content"),touchOverflow:!!e("overflowScrolling"),cssTransform3d:g(),boxShadow:!!e("boxShadow")&&!r,fixedPosition:k(),scrollTop:("pageXOffset"in b||"scrollTop"in c.documentElement||"scrollTop"in m[0])&&!p&&!q,dynamicBaseTag:h(),cssPointerEvents:i(),boundingRect:j(),inlineSVG:f}),m.remove(),l=function(){var a=b.navigator.userAgent;return a.indexOf("Nokia")>-1&&(a.indexOf("Symbian/3")>-1||a.indexOf("Series60/5")>-1)&&a.indexOf("AppleWebKit")>-1&&a.match(/(BrowserNG|NokiaBrowser)\/7\.[0-3]/)}(),a.mobile.gradeA=function(){return(a.support.mediaquery&&a.support.cssPseudoElement||a.mobile.browser.oldIE&&a.mobile.browser.oldIE>=8)&&(a.support.boundingRect||null!==a.fn.jquery.match(/1\.[0-7+]\.[0-9+]?/))},a.mobile.ajaxBlacklist=b.blackberry&&!b.WebKitPoint||q||l,l&&a(function(){a("head link[rel='stylesheet']").attr("rel","alternate stylesheet").attr("rel","stylesheet")}),a.support.boxShadow||a("html").addClass("ui-noboxshadow")}(a),function(a,b){var c,d=a.mobile.window,e=function(){};a.event.special.beforenavigate={setup:function(){d.on("navigate",e)},teardown:function(){d.off("navigate",e)}},a.event.special.navigate=c={bound:!1,pushStateEnabled:!0,originalEventName:b,isPushStateEnabled:function(){return a.support.pushState&&a.mobile.pushStateEnabled===!0&&this.isHashChangeEnabled()},isHashChangeEnabled:function(){return a.mobile.hashListeningEnabled===!0},popstate:function(b){var c=new a.Event("navigate"),e=new a.Event("beforenavigate"),f=b.originalEvent.state||{};e.originalEvent=b,d.trigger(e),e.isDefaultPrevented()||(b.historyState&&a.extend(f,b.historyState),c.originalEvent=b,setTimeout(function(){d.trigger(c,{state:f})},0))},hashchange:function(b){var c=new a.Event("navigate"),e=new a.Event("beforenavigate");e.originalEvent=b,d.trigger(e),e.isDefaultPrevented()||(c.originalEvent=b,d.trigger(c,{state:b.hashchangeState||{}}))},setup:function(){c.bound||(c.bound=!0,c.isPushStateEnabled()?(c.originalEventName="popstate",d.bind("popstate.navigate",c.popstate)):c.isHashChangeEnabled()&&(c.originalEventName="hashchange",d.bind("hashchange.navigate",c.hashchange)))}}}(a),function(a,c){var d,e,f="&ui-state=dialog";a.mobile.path=d={uiStateKey:"&ui-state",urlParseRE:/^\s*(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,getLocation:function(a){var b=this.parseUrl(a||location.href),c=a?b:location,d=b.hash;return d="#"===d?"":d,c.protocol+b.doubleSlash+c.host+(""!==c.protocol&&"/"!==c.pathname.substring(0,1)?"/":"")+c.pathname+c.search+d},getDocumentUrl:function(b){return b?a.extend({},d.documentUrl):d.documentUrl.href},parseLocation:function(){return this.parseUrl(this.getLocation())},parseUrl:function(b){if("object"===a.type(b))return b;var c=d.urlParseRE.exec(b||"")||[];return{href:c[0]||"",hrefNoHash:c[1]||"",hrefNoSearch:c[2]||"",domain:c[3]||"",protocol:c[4]||"",doubleSlash:c[5]||"",authority:c[6]||"",username:c[8]||"",password:c[9]||"",host:c[10]||"",hostname:c[11]||"",port:c[12]||"",pathname:c[13]||"",directory:c[14]||"",filename:c[15]||"",search:c[16]||"",hash:c[17]||""}},makePathAbsolute:function(a,b){var c,d,e,f;if(a&&"/"===a.charAt(0))return a;for(a=a||"",b=b?b.replace(/^\/|(\/[^\/]*|[^\/]+)$/g,""):"",c=b?b.split("/"):[],d=a.split("/"),e=0;e-1&&(l=e.slice(g),e=e.slice(0,g)),c=d.makeUrlAbsolute(e,b),f=this.parseUrl(c).search,i?((d.isPath(k)||0===k.replace("#","").indexOf(this.uiStateKey))&&(k=""),l&&-1===k.indexOf(this.uiStateKey)&&(k+=l),-1===k.indexOf("#")&&""!==k&&(k="#"+k),c=d.parseUrl(c),c=c.protocol+c.doubleSlash+c.host+c.pathname+f+k):c+=c.indexOf("#")>-1?l:"#"+l,c},isPreservableHash:function(a){return 0===a.replace("#","").indexOf(this.uiStateKey)},hashToSelector:function(a){var b="#"===a.substring(0,1);return b&&(a=a.substring(1)),(b?"#":"")+a.replace(/([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g,"\\$1")},getFilePath:function(a){return a&&a.split(f)[0]},isFirstPageUrl:function(b){var e=d.parseUrl(d.makeUrlAbsolute(b,this.documentBase)),f=e.hrefNoHash===this.documentUrl.hrefNoHash||this.documentBaseDiffers&&e.hrefNoHash===this.documentBase.hrefNoHash,g=a.mobile.firstPage,h=g&&g[0]?g[0].id:c;return f&&(!e.hash||"#"===e.hash||h&&e.hash.replace(/^#/,"")===h)},isPermittedCrossDomainRequest:function(b,c){return a.mobile.allowCrossDomainPages&&("file:"===b.protocol||"content:"===b.protocol)&&-1!==c.search(/^https?:/)}},d.documentUrl=d.parseLocation(),e=a("head").find("base"),d.documentBase=e.length?d.parseUrl(d.makeUrlAbsolute(e.attr("href"),d.documentUrl.href)):d.documentUrl,d.documentBaseDiffers=d.documentUrl.hrefNoHash!==d.documentBase.hrefNoHash,d.getDocumentBase=function(b){return b?a.extend({},d.documentBase):d.documentBase.href},a.extend(a.mobile,{getDocumentUrl:d.getDocumentUrl,getDocumentBase:d.getDocumentBase})}(a),function(a,b){a.mobile.History=function(a,b){this.stack=a||[],this.activeIndex=b||0},a.extend(a.mobile.History.prototype,{getActive:function(){return this.stack[this.activeIndex]},getLast:function(){return this.stack[this.previousIndex]},getNext:function(){return this.stack[this.activeIndex+1]},getPrev:function(){return this.stack[this.activeIndex-1]},add:function(a,b){b=b||{},this.getNext()&&this.clearForward(),b.hash&&-1===b.hash.indexOf("#")&&(b.hash="#"+b.hash),b.url=a,this.stack.push(b),this.activeIndex=this.stack.length-1},clearForward:function(){this.stack=this.stack.slice(0,this.activeIndex+1)},find:function(a,b,c){b=b||this.stack;var d,e,f,g=b.length;for(e=0;g>e;e++)if(d=b[e],(decodeURIComponent(a)===decodeURIComponent(d.url)||decodeURIComponent(a)===decodeURIComponent(d.hash))&&(f=e,c))return f;return f},closest:function(a){var c,d=this.activeIndex;return c=this.find(a,this.stack.slice(0,d)),c===b&&(c=this.find(a,this.stack.slice(d),!0),c=c===b?c:c+d),c},direct:function(c){var d=this.closest(c.url),e=this.activeIndex;d!==b&&(this.activeIndex=d,this.previousIndex=e),e>d?(c.present||c.back||a.noop)(this.getActive(),"back"):d>e?(c.present||c.forward||a.noop)(this.getActive(),"forward"):d===b&&c.missing&&c.missing(this.getActive())}})}(a),function(a){var d=a.mobile.path,e=location.href;a.mobile.Navigator=function(b){this.history=b,this.ignoreInitialHashChange=!0,a.mobile.window.bind({"popstate.history":a.proxy(this.popstate,this),"hashchange.history":a.proxy(this.hashchange,this)})},a.extend(a.mobile.Navigator.prototype,{squash:function(e,f){var g,h,i=d.isPath(e)?d.stripHash(e):e;return h=d.squash(e),g=a.extend({hash:i,url:h},f),b.history.replaceState(g,g.title||c.title,h),g},hash:function(a,b){var c,e,f,g;return c=d.parseUrl(a),e=d.parseLocation(),e.pathname+e.search===c.pathname+c.search?f=c.hash?c.hash:c.pathname+c.search:d.isPath(a)?(g=d.parseUrl(b),f=g.pathname+g.search+(d.isPreservableHash(g.hash)?g.hash.replace("#",""):"")):f=a,f},go:function(e,f,g){var h,i,j,k,l=a.event.special.navigate.isPushStateEnabled();
+i=d.squash(e),j=this.hash(e,i),g&&j!==d.stripHash(d.parseLocation().hash)&&(this.preventNextHashChange=g),this.preventHashAssignPopState=!0,b.location.hash=j,this.preventHashAssignPopState=!1,h=a.extend({url:i,hash:j,title:c.title},f),l&&(k=new a.Event("popstate"),k.originalEvent={type:"popstate",state:null},this.squash(e,h),g||(this.ignorePopState=!0,a.mobile.window.trigger(k))),this.history.add(h.url,h)},popstate:function(b){var c,f;if(a.event.special.navigate.isPushStateEnabled())return this.preventHashAssignPopState?(this.preventHashAssignPopState=!1,void b.stopImmediatePropagation()):this.ignorePopState?void(this.ignorePopState=!1):!b.originalEvent.state&&1===this.history.stack.length&&this.ignoreInitialHashChange&&(this.ignoreInitialHashChange=!1,location.href===e)?void b.preventDefault():(c=d.parseLocation().hash,!b.originalEvent.state&&c?(f=this.squash(c),this.history.add(f.url,f),void(b.historyState=f)):void this.history.direct({url:(b.originalEvent.state||{}).url||c,present:function(c,d){b.historyState=a.extend({},c),b.historyState.direction=d}}))},hashchange:function(b){var e,f;if(a.event.special.navigate.isHashChangeEnabled()&&!a.event.special.navigate.isPushStateEnabled()){if(this.preventNextHashChange)return this.preventNextHashChange=!1,void b.stopImmediatePropagation();e=this.history,f=d.parseLocation().hash,this.history.direct({url:f,present:function(c,d){b.hashchangeState=a.extend({},c),b.hashchangeState.direction=d},missing:function(){e.add(f,{hash:f,title:c.title})}})}}})}(a),function(a){a.mobile.navigate=function(b,c,d){a.mobile.navigate.navigator.go(b,c,d)},a.mobile.navigate.history=new a.mobile.History,a.mobile.navigate.navigator=new a.mobile.Navigator(a.mobile.navigate.history);var b=a.mobile.path.parseLocation();a.mobile.navigate.history.add(b.href,{hash:b.hash})}(a),function(a,b){var d={animation:{},transition:{}},e=c.createElement("a"),f=["","webkit-","moz-","o-"];a.each(["animation","transition"],function(c,g){var h=0===c?g+"-name":g;a.each(f,function(c,f){return e.style[a.camelCase(f+h)]!==b?(d[g].prefix=f,!1):void 0}),d[g].duration=a.camelCase(d[g].prefix+g+"-duration"),d[g].event=a.camelCase(d[g].prefix+g+"-end"),""===d[g].prefix&&(d[g].event=d[g].event.toLowerCase())}),a.support.cssTransitions=d.transition.prefix!==b,a.support.cssAnimations=d.animation.prefix!==b,a(e).remove(),a.fn.animationComplete=function(e,f,g){var h,i,j=this,k=function(){clearTimeout(h),e.apply(this,arguments)},l=f&&"animation"!==f?"transition":"animation";return a.support.cssTransitions&&"transition"===l||a.support.cssAnimations&&"animation"===l?(g===b&&(a(this).context!==c&&(i=3e3*parseFloat(a(this).css(d[l].duration))),(0===i||i===b||isNaN(i))&&(i=a.fn.animationComplete.defaultDuration)),h=setTimeout(function(){a(j).off(d[l].event,k),e.apply(j)},i),a(this).one(d[l].event,k)):(setTimeout(a.proxy(e,this),0),a(this))},a.fn.animationComplete.defaultDuration=1e3}(a),function(a,b,c,d){function e(a){for(;a&&"undefined"!=typeof a.originalEvent;)a=a.originalEvent;return a}function f(b,c){var f,g,h,i,j,k,l,m,n,o=b.type;if(b=a.Event(b),b.type=c,f=b.originalEvent,g=a.event.props,o.search(/^(mouse|click)/)>-1&&(g=E),f)for(l=g.length,i;l;)i=g[--l],b[i]=f[i];if(o.search(/mouse(down|up)|click/)>-1&&!b.which&&(b.which=1),-1!==o.search(/^touch/)&&(h=e(f),o=h.touches,j=h.changedTouches,k=o&&o.length?o[0]:j&&j.length?j[0]:d))for(m=0,n=C.length;n>m;m++)i=C[m],b[i]=k[i];return b}function g(b){for(var c,d,e={};b;){c=a.data(b,z);for(d in c)c[d]&&(e[d]=e.hasVirtualBinding=!0);b=b.parentNode}return e}function h(b,c){for(var d;b;){if(d=a.data(b,z),d&&(!c||d[c]))return b;b=b.parentNode}return null}function i(){M=!1}function j(){M=!0}function k(){Q=0,K.length=0,L=!1,j()}function l(){i()}function m(){n(),G=setTimeout(function(){G=0,k()},a.vmouse.resetTimerDuration)}function n(){G&&(clearTimeout(G),G=0)}function o(b,c,d){var e;return(d&&d[b]||!d&&h(c.target,b))&&(e=f(c,b),a(c.target).trigger(e)),e}function p(b){var c,d=a.data(b.target,A);L||Q&&Q===d||(c=o("v"+b.type,b),c&&(c.isDefaultPrevented()&&b.preventDefault(),c.isPropagationStopped()&&b.stopPropagation(),c.isImmediatePropagationStopped()&&b.stopImmediatePropagation()))}function q(b){var c,d,f,h=e(b).touches;h&&1===h.length&&(c=b.target,d=g(c),d.hasVirtualBinding&&(Q=P++,a.data(c,A,Q),n(),l(),J=!1,f=e(b).touches[0],H=f.pageX,I=f.pageY,o("vmouseover",b,d),o("vmousedown",b,d)))}function r(a){M||(J||o("vmousecancel",a,g(a.target)),J=!0,m())}function s(b){if(!M){var c=e(b).touches[0],d=J,f=a.vmouse.moveDistanceThreshold,h=g(b.target);J=J||Math.abs(c.pageX-H)>f||Math.abs(c.pageY-I)>f,J&&!d&&o("vmousecancel",b,h),o("vmousemove",b,h),m()}}function t(a){if(!M){j();var b,c,d=g(a.target);o("vmouseup",a,d),J||(b=o("vclick",a,d),b&&b.isDefaultPrevented()&&(c=e(a).changedTouches[0],K.push({touchID:Q,x:c.clientX,y:c.clientY}),L=!0)),o("vmouseout",a,d),J=!1,m()}}function u(b){var c,d=a.data(b,z);if(d)for(c in d)if(d[c])return!0;return!1}function v(){}function w(b){var c=b.substr(1);return{setup:function(){u(this)||a.data(this,z,{});var d=a.data(this,z);d[b]=!0,F[b]=(F[b]||0)+1,1===F[b]&&O.bind(c,p),a(this).bind(c,v),N&&(F.touchstart=(F.touchstart||0)+1,1===F.touchstart&&O.bind("touchstart",q).bind("touchend",t).bind("touchmove",s).bind("scroll",r))},teardown:function(){--F[b],F[b]||O.unbind(c,p),N&&(--F.touchstart,F.touchstart||O.unbind("touchstart",q).unbind("touchmove",s).unbind("touchend",t).unbind("scroll",r));var d=a(this),e=a.data(this,z);e&&(e[b]=!1),d.unbind(c,v),u(this)||d.removeData(z)}}}var x,y,z="virtualMouseBindings",A="virtualTouchID",B="vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split(" "),C="clientX clientY pageX pageY screenX screenY".split(" "),D=a.event.mouseHooks?a.event.mouseHooks.props:[],E=a.event.props.concat(D),F={},G=0,H=0,I=0,J=!1,K=[],L=!1,M=!1,N="addEventListener"in c,O=a(c),P=1,Q=0;for(a.vmouse={moveDistanceThreshold:10,clickDistanceThreshold:10,resetTimerDuration:1500},y=0;yf;f++)if(g=K[f],h=0,e===j&&Math.abs(g.x-c)Math.floor(a.pageY)||0===a.pageX&&Math.floor(e)>Math.floor(a.pageX)?(e-=c,f-=d):(fa.event.special.swipe.horizontalDistanceThreshold&&Math.abs(b.coords[1]-c.coords[1])c.coords[0]?"swipeleft":"swiperight";return e(d,"swipe",a.Event("swipe",{target:f,swipestart:b,swipestop:c}),!0),e(d,g,a.Event(g,{target:f,swipestart:b,swipestop:c}),!0),!0}return!1},eventInProgress:!1,setup:function(){var b,c=this,d=a(c),e={};b=a.data(this,"mobile-events"),b||(b={length:0},a.data(this,"mobile-events",b)),b.length++,b.swipe=e,e.start=function(b){if(!a.event.special.swipe.eventInProgress){a.event.special.swipe.eventInProgress=!0;var d,g=a.event.special.swipe.start(b),h=b.target,i=!1;e.move=function(b){g&&!b.isDefaultPrevented()&&(d=a.event.special.swipe.stop(b),i||(i=a.event.special.swipe.handleSwipe(g,d,c,h),i&&(a.event.special.swipe.eventInProgress=!1)),Math.abs(g.coords[0]-d.coords[0])>a.event.special.swipe.scrollSupressionThreshold&&b.preventDefault())},e.stop=function(){i=!0,a.event.special.swipe.eventInProgress=!1,f.off(k,e.move),e.move=null},f.on(k,e.move).one(j,e.stop)}},d.on(i,e.start)},teardown:function(){var b,c;b=a.data(this,"mobile-events"),b&&(c=b.swipe,delete b.swipe,b.length--,0===b.length&&a.removeData(this,"mobile-events")),c&&(c.start&&a(this).off(i,c.start),c.move&&f.off(k,c.move),c.stop&&f.off(j,c.stop))}},a.each({scrollstop:"scrollstart",taphold:"tap",swipeleft:"swipe.left",swiperight:"swipe.right"},function(b,c){a.event.special[b]={setup:function(){a(this).bind(c,a.noop)},teardown:function(){a(this).unbind(c)}}})}(a,this),function(a){a.event.special.throttledresize={setup:function(){a(this).bind("resize",f)},teardown:function(){a(this).unbind("resize",f)}};var b,c,d,e=250,f=function(){c=(new Date).getTime(),d=c-g,d>=e?(g=c,a(this).trigger("throttledresize")):(b&&clearTimeout(b),b=setTimeout(f,e-d))},g=0}(a),function(a,b){function d(){var a=e();a!==f&&(f=a,l.trigger(m))}var e,f,g,h,i,j,k,l=a(b),m="orientationchange",n={0:!0,180:!0};a.support.orientation&&(i=b.innerWidth||l.width(),j=b.innerHeight||l.height(),k=50,g=i>j&&i-j>k,h=n[b.orientation],(g&&h||!g&&!h)&&(n={"-90":!0,90:!0})),a.event.special.orientationchange=a.extend({},a.event.special.orientationchange,{setup:function(){return a.support.orientation&&!a.event.special.orientationchange.disabled?!1:(f=e(),void l.bind("throttledresize",d))},teardown:function(){return a.support.orientation&&!a.event.special.orientationchange.disabled?!1:void l.unbind("throttledresize",d)},add:function(a){var b=a.handler;a.handler=function(a){return a.orientation=e(),b.apply(this,arguments)}}}),a.event.special.orientationchange.orientation=e=function(){var d=!0,e=c.documentElement;return d=a.support.orientation?n[b.orientation]:e&&e.clientWidth/e.clientHeight<1.1,d?"portrait":"landscape"},a.fn[m]=function(a){return a?this.bind(m,a):this.trigger(m)},a.attrFn&&(a.attrFn[m]=!0)}(a,this),function(a){var b=a("head").children("base"),c={element:b.length?b:a("",{href:a.mobile.path.documentBase.hrefNoHash}).prependTo(a("head")),linkSelector:"[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]",set:function(b){a.mobile.dynamicBaseEnabled&&a.support.dynamicBaseTag&&c.element.attr("href",a.mobile.path.makeUrlAbsolute(b,a.mobile.path.documentBase))},rewrite:function(b,d){var e=a.mobile.path.get(b);d.find(c.linkSelector).each(function(b,c){var d=a(c).is("[href]")?"href":a(c).is("[src]")?"src":"action",f=a.mobile.path.parseLocation(),g=a(c).attr(d);g=g.replace(f.protocol+f.doubleSlash+f.host+f.pathname,""),/^(\w+:|#|\/)/.test(g)||a(c).attr(d,e+g)})},reset:function(){c.element.attr("href",a.mobile.path.documentBase.hrefNoSearch)}};a.mobile.base=c}(a),function(a,b){a.mobile.widgets={};var c=a.widget,d=a.mobile.keepNative;a.widget=function(c){return function(){var d=c.apply(this,arguments),e=d.prototype.widgetName;return d.initSelector=d.prototype.initSelector!==b?d.prototype.initSelector:":jqmData(role='"+e+"')",a.mobile.widgets[e]=d,d}}(a.widget),a.extend(a.widget,c),a.mobile.document.on("create",function(b){a(b.target).enhanceWithin()}),a.widget("mobile.page",{options:{theme:"a",domCache:!1,keepNativeDefault:a.mobile.keepNative,contentTheme:null,enhanced:!1},_createWidget:function(){a.Widget.prototype._createWidget.apply(this,arguments),this._trigger("init")},_create:function(){return this._trigger("beforecreate")===!1?!1:(this.options.enhanced||this._enhance(),this._on(this.element,{pagebeforehide:"removeContainerBackground",pagebeforeshow:"_handlePageBeforeShow"}),this.element.enhanceWithin(),void("dialog"===a.mobile.getAttribute(this.element[0],"role")&&a.mobile.dialog&&this.element.dialog()))},_enhance:function(){var c="data-"+a.mobile.ns,d=this;this.options.role&&this.element.attr("data-"+a.mobile.ns+"role",this.options.role),this.element.attr("tabindex","0").addClass("ui-page ui-page-theme-"+this.options.theme),this.element.find("["+c+"role='content']").each(function(){var e=a(this),f=this.getAttribute(c+"theme")||b;d.options.contentTheme=f||d.options.contentTheme||d.options.dialog&&d.options.theme||"dialog"===d.element.jqmData("role")&&d.options.theme,e.addClass("ui-content"),d.options.contentTheme&&e.addClass("ui-body-"+d.options.contentTheme),e.attr("role","main").addClass("ui-content")})},bindRemove:function(b){var c=this.element;!c.data("mobile-page").options.domCache&&c.is(":jqmData(external-page='true')")&&c.bind("pagehide.remove",b||function(b,c){if(!c.samePage){var d=a(this),e=new a.Event("pageremove");d.trigger(e),e.isDefaultPrevented()||d.removeWithDependents()}})},_setOptions:function(c){c.theme!==b&&this.element.removeClass("ui-page-theme-"+this.options.theme).addClass("ui-page-theme-"+c.theme),c.contentTheme!==b&&this.element.find("[data-"+a.mobile.ns+"='content']").removeClass("ui-body-"+this.options.contentTheme).addClass("ui-body-"+c.contentTheme)},_handlePageBeforeShow:function(){this.setContainerBackground()},removeContainerBackground:function(){this.element.closest(":mobile-pagecontainer").pagecontainer({theme:"none"})},setContainerBackground:function(a){this.element.parent().pagecontainer({theme:a||this.options.theme})},keepNativeSelector:function(){var b=this.options,c=a.trim(b.keepNative||""),e=a.trim(a.mobile.keepNative),f=a.trim(b.keepNativeDefault),g=d===e?"":e,h=""===g?f:"";return(c?[c]:[]).concat(g?[g]:[]).concat(h?[h]:[]).join(", ")}})}(a),function(a,d){a.widget("mobile.pagecontainer",{options:{theme:"a"},initSelector:!1,_create:function(){this._trigger("beforecreate"),this.setLastScrollEnabled=!0,this._on(this.window,{navigate:"_disableRecordScroll",scrollstop:"_delayedRecordScroll"}),this._on(this.window,{navigate:"_filterNavigateEvents"}),this._on({pagechange:"_afterContentChange"}),this.window.one("navigate",a.proxy(function(){this.setLastScrollEnabled=!0},this))},_setOptions:function(a){a.theme!==d&&"none"!==a.theme?this.element.removeClass("ui-overlay-"+this.options.theme).addClass("ui-overlay-"+a.theme):a.theme!==d&&this.element.removeClass("ui-overlay-"+this.options.theme),this._super(a)},_disableRecordScroll:function(){this.setLastScrollEnabled=!1},_enableRecordScroll:function(){this.setLastScrollEnabled=!0},_afterContentChange:function(){this.setLastScrollEnabled=!0,this._off(this.window,"scrollstop"),this._on(this.window,{scrollstop:"_delayedRecordScroll"})},_recordScroll:function(){if(this.setLastScrollEnabled){var a,b,c,d=this._getActiveHistory();d&&(a=this._getScroll(),b=this._getMinScroll(),c=this._getDefaultScroll(),d.lastScroll=b>a?c:a)}},_delayedRecordScroll:function(){setTimeout(a.proxy(this,"_recordScroll"),100)},_getScroll:function(){return this.window.scrollTop()},_getMinScroll:function(){return a.mobile.minScrollBack},_getDefaultScroll:function(){return a.mobile.defaultHomeScroll},_filterNavigateEvents:function(b,c){var d;b.originalEvent&&b.originalEvent.isDefaultPrevented()||(d=b.originalEvent.type.indexOf("hashchange")>-1?c.state.hash:c.state.url,d||(d=this._getHash()),d&&"#"!==d&&0!==d.indexOf("#"+a.mobile.path.uiStateKey)||(d=location.href),this._handleNavigate(d,c.state))},_getHash:function(){return a.mobile.path.parseLocation().hash},getActivePage:function(){return this.activePage},_getInitialContent:function(){return a.mobile.firstPage},_getHistory:function(){return a.mobile.navigate.history},_getActiveHistory:function(){return this._getHistory().getActive()},_getDocumentBase:function(){return a.mobile.path.documentBase},back:function(){this.go(-1)},forward:function(){this.go(1)},go:function(c){if(a.mobile.hashListeningEnabled)b.history.go(c);else{var d=a.mobile.navigate.history.activeIndex,e=d+parseInt(c,10),f=a.mobile.navigate.history.stack[e].url,g=c>=1?"forward":"back";a.mobile.navigate.history.activeIndex=e,a.mobile.navigate.history.previousIndex=d,this.change(f,{direction:g,changeHash:!1,fromHashChange:!0})}},_handleDestination:function(b){var c;return"string"===a.type(b)&&(b=a.mobile.path.stripHash(b)),b&&(c=this._getHistory(),b=a.mobile.path.isPath(b)?b:a.mobile.path.makeUrlAbsolute("#"+b,this._getDocumentBase())),b||this._getInitialContent()},_transitionFromHistory:function(a,b){var c=this._getHistory(),d="back"===a?c.getLast():c.getActive();return d&&d.transition||b},_handleDialog:function(b,c){var d,e,f=this.getActivePage();return f&&!f.data("mobile-dialog")?("back"===c.direction?this.back():this.forward(),!1):(d=c.pageUrl,e=this._getActiveHistory(),a.extend(b,{role:e.role,transition:this._transitionFromHistory(c.direction,b.transition),reverse:"back"===c.direction}),d)},_handleNavigate:function(b,c){var d=a.mobile.path.stripHash(b),e=this._getHistory(),f=0===e.stack.length?"none":this._transitionFromHistory(c.direction),g={changeHash:!1,fromHashChange:!0,reverse:"back"===c.direction};a.extend(g,c,{transition:f}),e.activeIndex>0&&d.indexOf(a.mobile.dialogHashKey)>-1&&(d=this._handleDialog(g,c),d===!1)||this._changeContent(this._handleDestination(d),g)},_changeContent:function(b,c){a.mobile.changePage(b,c)},_getBase:function(){return a.mobile.base},_getNs:function(){return a.mobile.ns},_enhance:function(a,b){return a.page({role:b})},_include:function(a,b){a.appendTo(this.element),this._enhance(a,b.role),a.page("bindRemove")},_find:function(b){var c,d=this._createFileUrl(b),e=this._createDataUrl(b),f=this._getInitialContent();return c=this.element.children("[data-"+this._getNs()+"url='"+a.mobile.path.hashToSelector(e)+"']"),0===c.length&&e&&!a.mobile.path.isPath(e)&&(c=this.element.children(a.mobile.path.hashToSelector("#"+e)).attr("data-"+this._getNs()+"url",e).jqmData("url",e)),0===c.length&&a.mobile.path.isFirstPageUrl(d)&&f&&f.parent().length&&(c=a(f)),c},_getLoader:function(){return a.mobile.loading()},_showLoading:function(b,c,d,e){this._loadMsg||(this._loadMsg=setTimeout(a.proxy(function(){this._getLoader().loader("show",c,d,e),this._loadMsg=0},this),b))},_hideLoading:function(){clearTimeout(this._loadMsg),this._loadMsg=0,this._getLoader().loader("hide")},_showError:function(){this._hideLoading(),this._showLoading(0,a.mobile.pageLoadErrorMessageTheme,a.mobile.pageLoadErrorMessage,!0),setTimeout(a.proxy(this,"_hideLoading"),1500)},_parse:function(b,c){var d,e=a("");return e.get(0).innerHTML=b,d=e.find(":jqmData(role='page'), :jqmData(role='dialog')").first(),d.length||(d=a(""+(b.split(/<\/?body[^>]*>/gim)[1]||"")+"
")),d.attr("data-"+this._getNs()+"url",this._createDataUrl(c)).attr("data-"+this._getNs()+"external-page",!0),d},_setLoadedTitle:function(b,c){var d=c.match(/]*>([^<]*)/)&&RegExp.$1;d&&!b.jqmData("title")&&(d=a(""+d+"
").text(),b.jqmData("title",d))},_isRewritableBaseTag:function(){return a.mobile.dynamicBaseEnabled&&!a.support.dynamicBaseTag},_createDataUrl:function(b){return a.mobile.path.convertUrlToDataUrl(b)},_createFileUrl:function(b){return a.mobile.path.getFilePath(b)},_triggerWithDeprecated:function(b,c,d){var e=a.Event("page"+b),f=a.Event(this.widgetName+b);return(d||this.element).trigger(e,c),this._trigger(b,f,c),{deprecatedEvent:e,event:f}},_loadSuccess:function(b,c,e,f){var g=this._createFileUrl(b);return a.proxy(function(h,i,j){var k,l=new RegExp("(<[^>]+\\bdata-"+this._getNs()+"role=[\"']?page[\"']?[^>]*>)"),m=new RegExp("\\bdata-"+this._getNs()+"url=[\"']?([^\"'>]*)[\"']?");l.test(h)&&RegExp.$1&&m.test(RegExp.$1)&&RegExp.$1&&(g=a.mobile.path.getFilePath(a(""+RegExp.$1+"
").text()),g=this.window[0].encodeURIComponent(g)),e.prefetch===d&&this._getBase().set(g),k=this._parse(h,g),this._setLoadedTitle(k,h),c.xhr=j,c.textStatus=i,c.page=k,c.content=k,c.toPage=k,this._triggerWithDeprecated("load",c).event.isDefaultPrevented()||(this._isRewritableBaseTag()&&k&&this._getBase().rewrite(g,k),this._include(k,e),e.showLoadMsg&&this._hideLoading(),f.resolve(b,e,k))},this)},_loadDefaults:{type:"get",data:d,reloadPage:!1,reload:!1,role:d,showLoadMsg:!1,loadMsgDelay:50},load:function(b,c){var e,f,g,h,i=c&&c.deferred||a.Deferred(),j=c&&c.reload===d&&c.reloadPage!==d?{reload:c.reloadPage}:{},k=a.extend({},this._loadDefaults,c,j),l=null,m=a.mobile.path.makeUrlAbsolute(b,this._findBaseWithDefault());return k.data&&"get"===k.type&&(m=a.mobile.path.addSearchParams(m,k.data),k.data=d),k.data&&"post"===k.type&&(k.reload=!0),e=this._createFileUrl(m),f=this._createDataUrl(m),l=this._find(m),0===l.length&&a.mobile.path.isEmbeddedPage(e)&&!a.mobile.path.isFirstPageUrl(e)?(i.reject(m,k),i.promise()):(this._getBase().reset(),l.length&&!k.reload?(this._enhance(l,k.role),i.resolve(m,k,l),k.prefetch||this._getBase().set(b),i.promise()):(h={url:b,absUrl:m,toPage:b,prevPage:c?c.fromPage:d,dataUrl:f,deferred:i,options:k},g=this._triggerWithDeprecated("beforeload",h),g.deprecatedEvent.isDefaultPrevented()||g.event.isDefaultPrevented()?i.promise():(k.showLoadMsg&&this._showLoading(k.loadMsgDelay),k.prefetch===d&&this._getBase().reset(),a.mobile.allowCrossDomainPages||a.mobile.path.isSameDomain(a.mobile.path.documentUrl,m)?(a.ajax({url:e,type:k.type,data:k.data,contentType:k.contentType,dataType:"html",success:this._loadSuccess(m,h,k,i),error:this._loadError(m,h,k,i)}),i.promise()):(i.reject(m,k),i.promise()))))},_loadError:function(b,c,d,e){return a.proxy(function(f,g,h){this._getBase().set(a.mobile.path.get()),c.xhr=f,c.textStatus=g,c.errorThrown=h;var i=this._triggerWithDeprecated("loadfailed",c);i.deprecatedEvent.isDefaultPrevented()||i.event.isDefaultPrevented()||(d.showLoadMsg&&this._showError(),e.reject(b,d))},this)},_getTransitionHandler:function(b){return b=a.mobile._maybeDegradeTransition(b),a.mobile.transitionHandlers[b]||a.mobile.defaultTransitionHandler},_triggerCssTransitionEvents:function(b,c,d){var e=!1;d=d||"",c&&(b[0]===c[0]&&(e=!0),this._triggerWithDeprecated(d+"hide",{nextPage:b,toPage:b,prevPage:c,samePage:e},c)),this._triggerWithDeprecated(d+"show",{prevPage:c||a(""),toPage:b},b)},_cssTransition:function(b,c,d){var e,f,g=d.transition,h=d.reverse,i=d.deferred;this._triggerCssTransitionEvents(b,c,"before"),this._hideLoading(),e=this._getTransitionHandler(g),f=new e(g,h,b,c).transition(),f.done(a.proxy(function(){this._triggerCssTransitionEvents(b,c)},this)),f.done(function(){i.resolve.apply(i,arguments)})},_releaseTransitionLock:function(){f=!1,e.length>0&&a.mobile.changePage.apply(null,e.pop())},_removeActiveLinkClass:function(b){a.mobile.removeActiveLinkClass(b)},_loadUrl:function(b,c,d){d.target=b,d.deferred=a.Deferred(),this.load(b,d),d.deferred.done(a.proxy(function(a,b,d){f=!1,b.absUrl=c.absUrl,this.transition(d,c,b)},this)),d.deferred.fail(a.proxy(function(){this._removeActiveLinkClass(!0),this._releaseTransitionLock(),this._triggerWithDeprecated("changefailed",c)},this))},_triggerPageBeforeChange:function(b,c,d){var e;return c.prevPage=this.activePage,a.extend(c,{toPage:b,options:d}),c.absUrl="string"===a.type(b)?a.mobile.path.makeUrlAbsolute(b,this._findBaseWithDefault()):d.absUrl,e=this._triggerWithDeprecated("beforechange",c),e.event.isDefaultPrevented()||e.deprecatedEvent.isDefaultPrevented()?!1:!0},change:function(b,c){if(f)return void e.unshift(arguments);var d=a.extend({},a.mobile.changePage.defaults,c),g={};d.fromPage=d.fromPage||this.activePage,this._triggerPageBeforeChange(b,g,d)&&(b=g.toPage,"string"===a.type(b)?(f=!0,this._loadUrl(b,g,d)):this.transition(b,g,d))},transition:function(b,g,h){var i,j,k,l,m,n,o,p,q,r,s,t,u,v;if(f)return void e.unshift([b,h]);if(this._triggerPageBeforeChange(b,g,h)&&(g.prevPage=h.fromPage,v=this._triggerWithDeprecated("beforetransition",g),!v.deprecatedEvent.isDefaultPrevented()&&!v.event.isDefaultPrevented())){if(f=!0,b[0]!==a.mobile.firstPage[0]||h.dataUrl||(h.dataUrl=a.mobile.path.documentUrl.hrefNoHash),i=h.fromPage,j=h.dataUrl&&a.mobile.path.convertUrlToDataUrl(h.dataUrl)||b.jqmData("url"),k=j,l=a.mobile.path.getFilePath(j),m=a.mobile.navigate.history.getActive(),n=0===a.mobile.navigate.history.activeIndex,o=0,p=c.title,q=("dialog"===h.role||"dialog"===b.jqmData("role"))&&b.jqmData("dialog")!==!0,i&&i[0]===b[0]&&!h.allowSamePageTransition)return f=!1,this._triggerWithDeprecated("transition",g),this._triggerWithDeprecated("change",g),void(h.fromHashChange&&a.mobile.navigate.history.direct({url:j}));b.page({role:h.role}),h.fromHashChange&&(o="back"===h.direction?-1:1);try{c.activeElement&&"body"!==c.activeElement.nodeName.toLowerCase()?a(c.activeElement).blur():a("input:focus, textarea:focus, select:focus").blur()}catch(w){}r=!1,q&&m&&(m.url&&m.url.indexOf(a.mobile.dialogHashKey)>-1&&this.activePage&&!this.activePage.hasClass("ui-dialog")&&a.mobile.navigate.history.activeIndex>0&&(h.changeHash=!1,r=!0),j=m.url||"",j+=!r&&j.indexOf("#")>-1?a.mobile.dialogHashKey:"#"+a.mobile.dialogHashKey),s=m?b.jqmData("title")||b.children(":jqmData(role='header')").find(".ui-title").text():p,s&&p===c.title&&(p=s),b.jqmData("title")||b.jqmData("title",p),h.transition=h.transition||(o&&!n?m.transition:d)||(q?a.mobile.defaultDialogTransition:a.mobile.defaultPageTransition),!o&&r&&(a.mobile.navigate.history.getActive().pageUrl=k),j&&!h.fromHashChange&&(!a.mobile.path.isPath(j)&&j.indexOf("#")<0&&(j="#"+j),t={transition:h.transition,title:p,pageUrl:k,role:h.role},h.changeHash!==!1&&a.mobile.hashListeningEnabled?a.mobile.navigate(this.window[0].encodeURI(j),t,!0):b[0]!==a.mobile.firstPage[0]&&a.mobile.navigate.history.add(j,t)),c.title=p,a.mobile.activePage=b,this.activePage=b,h.reverse=h.reverse||0>o,u=a.Deferred(),this._cssTransition(b,i,{transition:h.transition,reverse:h.reverse,deferred:u}),u.done(a.proxy(function(c,d,e,f,i){a.mobile.removeActiveLinkClass(),h.duplicateCachedPage&&h.duplicateCachedPage.remove(),i||a.mobile.focusPage(b),this._releaseTransitionLock(),this._triggerWithDeprecated("transition",g),this._triggerWithDeprecated("change",g)},this))}},_findBaseWithDefault:function(){var b=this.activePage&&a.mobile.getClosestBaseUrl(this.activePage);return b||a.mobile.path.documentBase.hrefNoHash}}),a.mobile.navreadyDeferred=a.Deferred();var e=[],f=!1}(a),function(a,d){function e(a){for(;a&&("string"!=typeof a.nodeName||"a"!==a.nodeName.toLowerCase());)a=a.parentNode;return a}var f=a.Deferred(),g=a.Deferred(),h=function(){g.resolve(),g=null},i=a.mobile.path.documentUrl,j=null;a.mobile.loadPage=function(b,c){var d;return c=c||{},d=c.pageContainer||a.mobile.pageContainer,c.deferred=a.Deferred(),d.pagecontainer("load",b,c),c.deferred.promise()},a.mobile.back=function(){var c=b.navigator;this.phonegapNavigationEnabled&&c&&c.app&&c.app.backHistory?c.app.backHistory():a.mobile.pageContainer.pagecontainer("back")},a.mobile.focusPage=function(a){var b=a.find("[autofocus]"),c=a.find(".ui-title:eq(0)");return b.length?void b.focus():void(c.length?c.focus():a.focus())},a.mobile._maybeDegradeTransition=a.mobile._maybeDegradeTransition||function(a){return a},a.mobile.changePage=function(b,c){a.mobile.pageContainer.pagecontainer("change",b,c)},a.mobile.changePage.defaults={transition:d,reverse:!1,changeHash:!0,fromHashChange:!1,role:d,duplicateCachedPage:d,pageContainer:d,showLoadMsg:!0,dataUrl:d,fromPage:d,allowSamePageTransition:!1},a.mobile._registerInternalEvents=function(){var c=function(b,c){var d,e,f,g,h=!0;return!a.mobile.ajaxEnabled||b.is(":jqmData(ajax='false')")||!b.jqmHijackable().length||b.attr("target")?!1:(d=j&&j.attr("formaction")||b.attr("action"),g=(b.attr("method")||"get").toLowerCase(),d||(d=a.mobile.getClosestBaseUrl(b),"get"===g&&(d=a.mobile.path.parseUrl(d).hrefNoSearch),d===a.mobile.path.documentBase.hrefNoHash&&(d=i.hrefNoSearch)),d=a.mobile.path.makeUrlAbsolute(d,a.mobile.getClosestBaseUrl(b)),a.mobile.path.isExternal(d)&&!a.mobile.path.isPermittedCrossDomainRequest(i,d)?!1:(c||(e=b.serializeArray(),j&&j[0].form===b[0]&&(f=j.attr("name"),f&&(a.each(e,function(a,b){return b.name===f?(f="",!1):void 0}),f&&e.push({name:f,value:j.attr("value")}))),h={url:d,options:{type:g,data:a.param(e),transition:b.jqmData("transition"),reverse:"reverse"===b.jqmData("direction"),reloadPage:!0}}),h))};a.mobile.document.delegate("form","submit",function(b){var d;b.isDefaultPrevented()||(d=c(a(this)),d&&(a.mobile.changePage(d.url,d.options),b.preventDefault()))}),a.mobile.document.bind("vclick",function(b){var d,f,g=b.target,h=!1;if(!(b.which>1)&&a.mobile.linkBindingEnabled){if(j=a(g),a.data(g,"mobile-button")){if(!c(a(g).closest("form"),!0))return;g.parentNode&&(g=g.parentNode)}else{if(g=e(g),!g||"#"===a.mobile.path.parseUrl(g.getAttribute("href")||"#").hash)return;if(!a(g).jqmHijackable().length)return}~g.className.indexOf("ui-link-inherit")?g.parentNode&&(f=a.data(g.parentNode,"buttonElements")):f=a.data(g,"buttonElements"),f?g=f.outer:h=!0,d=a(g),h&&(d=d.closest(".ui-btn")),d.length>0&&!d.hasClass("ui-state-disabled")&&(a.mobile.removeActiveLinkClass(!0),a.mobile.activeClickedLink=d,a.mobile.activeClickedLink.addClass(a.mobile.activeBtnClass))}}),a.mobile.document.bind("click",function(c){if(a.mobile.linkBindingEnabled&&!c.isDefaultPrevented()){var f,g,h,j,k,l,m,n=e(c.target),o=a(n),p=function(){b.setTimeout(function(){a.mobile.removeActiveLinkClass(!0)},200)};if(a.mobile.activeClickedLink&&a.mobile.activeClickedLink[0]===c.target.parentNode&&p(),n&&!(c.which>1)&&o.jqmHijackable().length){if(o.is(":jqmData(rel='back')"))return a.mobile.back(),!1;if(f=a.mobile.getClosestBaseUrl(o),g=a.mobile.path.makeUrlAbsolute(o.attr("href")||"#",f),!a.mobile.ajaxEnabled&&!a.mobile.path.isEmbeddedPage(g))return void p();if(!(-1===g.search("#")||a.mobile.path.isExternal(g)&&a.mobile.path.isAbsoluteUrl(g))){if(g=g.replace(/[^#]*#/,""),!g)return void c.preventDefault();g=a.mobile.path.isPath(g)?a.mobile.path.makeUrlAbsolute(g,f):a.mobile.path.makeUrlAbsolute("#"+g,i.hrefNoHash)}if(h=o.is("[rel='external']")||o.is(":jqmData(ajax='false')")||o.is("[target]"),j=h||a.mobile.path.isExternal(g)&&!a.mobile.path.isPermittedCrossDomainRequest(i,g))return void p();k=o.jqmData("transition"),l="reverse"===o.jqmData("direction")||o.jqmData("back"),m=o.attr("data-"+a.mobile.ns+"rel")||d,a.mobile.changePage(g,{transition:k,reverse:l,role:m,link:o}),c.preventDefault()}}}),a.mobile.document.delegate(".ui-page","pageshow.prefetch",function(){var b=[];a(this).find("a:jqmData(prefetch)").each(function(){var c=a(this),d=c.attr("href");d&&-1===a.inArray(d,b)&&(b.push(d),a.mobile.loadPage(d,{role:c.attr("data-"+a.mobile.ns+"rel"),prefetch:!0}))})}),a.mobile.pageContainer.pagecontainer(),a.mobile.document.bind("pageshow",function(){g?g.done(a.mobile.resetActivePageHeight):a.mobile.resetActivePageHeight()
+}),a.mobile.window.bind("throttledresize",a.mobile.resetActivePageHeight)},a(function(){f.resolve()}),"complete"===c.readyState?h():a.mobile.window.load(h),a.when(f,a.mobile.navreadyDeferred).done(function(){a.mobile._registerInternalEvents()})}(a),function(a,b){a.mobile.Transition=function(){this.init.apply(this,arguments)},a.extend(a.mobile.Transition.prototype,{toPreClass:" ui-page-pre-in",init:function(b,c,d,e){a.extend(this,{name:b,reverse:c,$to:d,$from:e,deferred:new a.Deferred})},cleanFrom:function(){this.$from.removeClass(a.mobile.activePageClass+" out in reverse "+this.name).height("")},beforeDoneIn:function(){},beforeDoneOut:function(){},beforeStartOut:function(){},doneIn:function(){this.beforeDoneIn(),this.$to.removeClass("out in reverse "+this.name).height(""),this.toggleViewportClass(),a.mobile.window.scrollTop()!==this.toScroll&&this.scrollPage(),this.sequential||this.$to.addClass(a.mobile.activePageClass),this.deferred.resolve(this.name,this.reverse,this.$to,this.$from,!0)},doneOut:function(a,b,c,d){this.beforeDoneOut(),this.startIn(a,b,c,d)},hideIn:function(a){this.$to.css("z-index",-10),a.call(this),this.$to.css("z-index","")},scrollPage:function(){a.event.special.scrollstart.enabled=!1,(a.mobile.hideUrlBar||this.toScroll!==a.mobile.defaultHomeScroll)&&b.scrollTo(0,this.toScroll),setTimeout(function(){a.event.special.scrollstart.enabled=!0},150)},startIn:function(b,c,d,e){this.hideIn(function(){this.$to.addClass(a.mobile.activePageClass+this.toPreClass),e||a.mobile.focusPage(this.$to),this.$to.height(b+this.toScroll),d||this.scrollPage()}),this.$to.removeClass(this.toPreClass).addClass(this.name+" in "+c),d?this.doneIn():this.$to.animationComplete(a.proxy(function(){this.doneIn()},this))},startOut:function(b,c,d){this.beforeStartOut(b,c,d),this.$from.height(b+a.mobile.window.scrollTop()).addClass(this.name+" out"+c)},toggleViewportClass:function(){a.mobile.pageContainer.toggleClass("ui-mobile-viewport-transitioning viewport-"+this.name)},transition:function(){var b,c=this.reverse?" reverse":"",d=a.mobile.getScreenHeight(),e=a.mobile.maxTransitionWidth!==!1&&a.mobile.window.width()>a.mobile.maxTransitionWidth;return this.toScroll=a.mobile.navigate.history.getActive().lastScroll||a.mobile.defaultHomeScroll,b=!a.support.cssTransitions||!a.support.cssAnimations||e||!this.name||"none"===this.name||Math.max(a.mobile.window.scrollTop(),this.toScroll)>a.mobile.getMaxScrollForTransition(),this.toggleViewportClass(),this.$from&&!b?this.startOut(d,c,b):this.doneOut(d,c,b,!0),this.deferred.promise()}})}(a,this),function(a){a.mobile.SerialTransition=function(){this.init.apply(this,arguments)},a.extend(a.mobile.SerialTransition.prototype,a.mobile.Transition.prototype,{sequential:!0,beforeDoneOut:function(){this.$from&&this.cleanFrom()},beforeStartOut:function(b,c,d){this.$from.animationComplete(a.proxy(function(){this.doneOut(b,c,d)},this))}})}(a),function(a){a.mobile.ConcurrentTransition=function(){this.init.apply(this,arguments)},a.extend(a.mobile.ConcurrentTransition.prototype,a.mobile.Transition.prototype,{sequential:!1,beforeDoneIn:function(){this.$from&&this.cleanFrom()},beforeStartOut:function(a,b,c){this.doneOut(a,b,c)}})}(a),function(a){var b=function(){return 3*a.mobile.getScreenHeight()};a.mobile.transitionHandlers={sequential:a.mobile.SerialTransition,simultaneous:a.mobile.ConcurrentTransition},a.mobile.defaultTransitionHandler=a.mobile.transitionHandlers.sequential,a.mobile.transitionFallbacks={},a.mobile._maybeDegradeTransition=function(b){return b&&!a.support.cssTransform3d&&a.mobile.transitionFallbacks[b]&&(b=a.mobile.transitionFallbacks[b]),b},a.mobile.getMaxScrollForTransition=a.mobile.getMaxScrollForTransition||b}(a),function(a){a.mobile.transitionFallbacks.flip="fade"}(a,this),function(a){a.mobile.transitionFallbacks.flow="fade"}(a,this),function(a){a.mobile.transitionFallbacks.pop="fade"}(a,this),function(a){a.mobile.transitionHandlers.slide=a.mobile.transitionHandlers.simultaneous,a.mobile.transitionFallbacks.slide="fade"}(a,this),function(a){a.mobile.transitionFallbacks.slidedown="fade"}(a,this),function(a){a.mobile.transitionFallbacks.slidefade="fade"}(a,this),function(a){a.mobile.transitionFallbacks.slideup="fade"}(a,this),function(a){a.mobile.transitionFallbacks.turn="fade"}(a,this),function(a){a.mobile.degradeInputs={color:!1,date:!1,datetime:!1,"datetime-local":!1,email:!1,month:!1,number:!1,range:"number",search:"text",tel:!1,time:!1,url:!1,week:!1},a.mobile.page.prototype.options.degradeInputs=a.mobile.degradeInputs,a.mobile.degradeInputsWithin=function(b){b=a(b),b.find("input").not(a.mobile.page.prototype.keepNativeSelector()).each(function(){var b,c,d,e,f=a(this),g=this.getAttribute("type"),h=a.mobile.degradeInputs[g]||"text";a.mobile.degradeInputs[g]&&(b=a("").html(f.clone()).html(),c=b.indexOf(" type=")>-1,d=c?/\s+type=["']?\w+['"]?/:/\/?>/,e=' type="'+h+'" data-'+a.mobile.ns+'type="'+g+'"'+(c?"":">"),f.replaceWith(b.replace(d,e)))})}}(a),function(a,b,c){a.widget("mobile.page",a.mobile.page,{options:{closeBtn:"left",closeBtnText:"Close",overlayTheme:"a",corners:!0,dialog:!1},_create:function(){this._super(),this.options.dialog&&(a.extend(this,{_inner:this.element.children(),_headerCloseButton:null}),this.options.enhanced||this._setCloseBtn(this.options.closeBtn))},_enhance:function(){this._super(),this.options.dialog&&this.element.addClass("ui-dialog").wrapInner(a("
",{role:"dialog","class":"ui-dialog-contain ui-overlay-shadow"+(this.options.corners?" ui-corner-all":"")}))},_setOptions:function(b){var d,e,f=this.options;b.corners!==c&&this._inner.toggleClass("ui-corner-all",!!b.corners),b.overlayTheme!==c&&a.mobile.activePage[0]===this.element[0]&&(f.overlayTheme=b.overlayTheme,this._handlePageBeforeShow()),b.closeBtnText!==c&&(d=f.closeBtn,e=b.closeBtnText),b.closeBtn!==c&&(d=b.closeBtn),d&&this._setCloseBtn(d,e),this._super(b)},_handlePageBeforeShow:function(){this.options.overlayTheme&&this.options.dialog?(this.removeContainerBackground(),this.setContainerBackground(this.options.overlayTheme)):this._super()},_setCloseBtn:function(b,c){var d,e=this._headerCloseButton;b="left"===b?"left":"right"===b?"right":"none","none"===b?e&&(e.remove(),e=null):e?(e.removeClass("ui-btn-left ui-btn-right").addClass("ui-btn-"+b),c&&e.text(c)):(d=this._inner.find(":jqmData(role='header')").first(),e=a("
",{href:"#","class":"ui-btn ui-corner-all ui-icon-delete ui-btn-icon-notext ui-btn-"+b}).attr("data-"+a.mobile.ns+"rel","back").text(c||this.options.closeBtnText||"").prependTo(d)),this._headerCloseButton=e}})}(a,this),function(a,b,c){a.widget("mobile.dialog",{options:{closeBtn:"left",closeBtnText:"Close",overlayTheme:"a",corners:!0},_handlePageBeforeShow:function(){this._isCloseable=!0,this.options.overlayTheme&&this.element.page("removeContainerBackground").page("setContainerBackground",this.options.overlayTheme)},_handlePageBeforeHide:function(){this._isCloseable=!1},_handleVClickSubmit:function(b){var c,d=a(b.target).closest("vclick"===b.type?"a":"form");d.length&&!d.jqmData("transition")&&(c={},c["data-"+a.mobile.ns+"transition"]=(a.mobile.navigate.history.getActive()||{}).transition||a.mobile.defaultDialogTransition,c["data-"+a.mobile.ns+"direction"]="reverse",d.attr(c))},_create:function(){var b=this.element,c=this.options;b.addClass("ui-dialog").wrapInner(a("
",{role:"dialog","class":"ui-dialog-contain ui-overlay-shadow"+(c.corners?" ui-corner-all":"")})),a.extend(this,{_isCloseable:!1,_inner:b.children(),_headerCloseButton:null}),this._on(b,{vclick:"_handleVClickSubmit",submit:"_handleVClickSubmit",pagebeforeshow:"_handlePageBeforeShow",pagebeforehide:"_handlePageBeforeHide"}),this._setCloseBtn(c.closeBtn)},_setOptions:function(b){var d,e,f=this.options;b.corners!==c&&this._inner.toggleClass("ui-corner-all",!!b.corners),b.overlayTheme!==c&&a.mobile.activePage[0]===this.element[0]&&(f.overlayTheme=b.overlayTheme,this._handlePageBeforeShow()),b.closeBtnText!==c&&(d=f.closeBtn,e=b.closeBtnText),b.closeBtn!==c&&(d=b.closeBtn),d&&this._setCloseBtn(d,e),this._super(b)},_setCloseBtn:function(b,c){var d,e=this._headerCloseButton;b="left"===b?"left":"right"===b?"right":"none","none"===b?e&&(e.remove(),e=null):e?(e.removeClass("ui-btn-left ui-btn-right").addClass("ui-btn-"+b),c&&e.text(c)):(d=this._inner.find(":jqmData(role='header')").first(),e=a("
",{role:"button",href:"#","class":"ui-btn ui-corner-all ui-icon-delete ui-btn-icon-notext ui-btn-"+b}).text(c||this.options.closeBtnText||"").prependTo(d),this._on(e,{click:"close"})),this._headerCloseButton=e},close:function(){var b=a.mobile.navigate.history;this._isCloseable&&(this._isCloseable=!1,a.mobile.hashListeningEnabled&&b.activeIndex>0?a.mobile.back():a.mobile.pageContainer.pagecontainer("back"))}})}(a,this),function(a,b){var c=/([A-Z])/g,d=function(a){return"ui-btn-icon-"+(null===a?"left":a)};a.widget("mobile.collapsible",{options:{enhanced:!1,expandCueText:null,collapseCueText:null,collapsed:!0,heading:"h1,h2,h3,h4,h5,h6,legend",collapsedIcon:null,expandedIcon:null,iconpos:null,theme:null,contentTheme:null,inset:null,corners:null,mini:null},_create:function(){var b=this.element,c={accordion:b.closest(":jqmData(role='collapsible-set'),:jqmData(role='collapsibleset')"+(a.mobile.collapsibleset?", :mobile-collapsibleset":"")).addClass("ui-collapsible-set")};this._ui=c,this._renderedOptions=this._getOptions(this.options),this.options.enhanced?(c.heading=this.element.children(".ui-collapsible-heading"),c.content=c.heading.next(),c.anchor=c.heading.children(),c.status=c.anchor.children(".ui-collapsible-heading-status")):this._enhance(b,c),this._on(c.heading,{tap:function(){c.heading.find("a").first().addClass(a.mobile.activeBtnClass)},click:function(a){this._handleExpandCollapse(!c.heading.hasClass("ui-collapsible-heading-collapsed")),a.preventDefault(),a.stopPropagation()}})},_getOptions:function(b){var d,e=this._ui.accordion,f=this._ui.accordionWidget;b=a.extend({},b),e.length&&!f&&(this._ui.accordionWidget=f=e.data("mobile-collapsibleset"));for(d in b)b[d]=null!=b[d]?b[d]:f?f.options[d]:e.length?a.mobile.getAttribute(e[0],d.replace(c,"-$1").toLowerCase()):null,null==b[d]&&(b[d]=a.mobile.collapsible.defaults[d]);return b},_themeClassFromOption:function(a,b){return b?"none"===b?"":a+b:""},_enhance:function(b,c){var e,f=this._renderedOptions,g=this._themeClassFromOption("ui-body-",f.contentTheme);return b.addClass("ui-collapsible "+(f.inset?"ui-collapsible-inset ":"")+(f.inset&&f.corners?"ui-corner-all ":"")+(g?"ui-collapsible-themed-content ":"")),c.originalHeading=b.children(this.options.heading).first(),c.content=b.wrapInner("
").children(".ui-collapsible-content"),c.heading=c.originalHeading,c.heading.is("legend")&&(c.heading=a("
"+c.heading.html()+"
"),c.placeholder=a("
").insertBefore(c.originalHeading),c.originalHeading.remove()),e=f.collapsed?f.collapsedIcon?"ui-icon-"+f.collapsedIcon:"":f.expandedIcon?"ui-icon-"+f.expandedIcon:"",c.status=a("
"),c.anchor=c.heading.detach().addClass("ui-collapsible-heading").append(c.status).wrapInner("
").find("a").first().addClass("ui-btn "+(e?e+" ":"")+(e?d(f.iconpos)+" ":"")+this._themeClassFromOption("ui-btn-",f.theme)+" "+(f.mini?"ui-mini ":"")),c.heading.insertBefore(c.content),this._handleExpandCollapse(this.options.collapsed),c},refresh:function(){this._applyOptions(this.options),this._renderedOptions=this._getOptions(this.options)},_applyOptions:function(a){var c,e,f,g,h,i=this.element,j=this._renderedOptions,k=this._ui,l=k.anchor,m=k.status,n=this._getOptions(a);a.collapsed!==b&&this._handleExpandCollapse(a.collapsed),c=i.hasClass("ui-collapsible-collapsed"),c?n.expandCueText!==b&&m.text(n.expandCueText):n.collapseCueText!==b&&m.text(n.collapseCueText),h=n.collapsedIcon!==b?n.collapsedIcon!==!1:j.collapsedIcon!==!1,(n.iconpos!==b||n.collapsedIcon!==b||n.expandedIcon!==b)&&(l.removeClass([d(j.iconpos)].concat(j.expandedIcon?["ui-icon-"+j.expandedIcon]:[]).concat(j.collapsedIcon?["ui-icon-"+j.collapsedIcon]:[]).join(" ")),h&&l.addClass([d(n.iconpos!==b?n.iconpos:j.iconpos)].concat(c?["ui-icon-"+(n.collapsedIcon!==b?n.collapsedIcon:j.collapsedIcon)]:["ui-icon-"+(n.expandedIcon!==b?n.expandedIcon:j.expandedIcon)]).join(" "))),n.theme!==b&&(f=this._themeClassFromOption("ui-btn-",j.theme),e=this._themeClassFromOption("ui-btn-",n.theme),l.removeClass(f).addClass(e)),n.contentTheme!==b&&(f=this._themeClassFromOption("ui-body-",j.contentTheme),e=this._themeClassFromOption("ui-body-",n.contentTheme),k.content.removeClass(f).addClass(e)),n.inset!==b&&(i.toggleClass("ui-collapsible-inset",n.inset),g=!(!n.inset||!n.corners&&!j.corners)),n.corners!==b&&(g=!(!n.corners||!n.inset&&!j.inset)),g!==b&&i.toggleClass("ui-corner-all",g),n.mini!==b&&l.toggleClass("ui-mini",n.mini)},_setOptions:function(a){this._applyOptions(a),this._super(a),this._renderedOptions=this._getOptions(this.options)},_handleExpandCollapse:function(b){var c=this._renderedOptions,d=this._ui;d.status.text(b?c.expandCueText:c.collapseCueText),d.heading.toggleClass("ui-collapsible-heading-collapsed",b).find("a").first().toggleClass("ui-icon-"+c.expandedIcon,!b).toggleClass("ui-icon-"+c.collapsedIcon,b||c.expandedIcon===c.collapsedIcon).removeClass(a.mobile.activeBtnClass),this.element.toggleClass("ui-collapsible-collapsed",b),d.content.toggleClass("ui-collapsible-content-collapsed",b).attr("aria-hidden",b).trigger("updatelayout"),this.options.collapsed=b,this._trigger(b?"collapse":"expand")},expand:function(){this._handleExpandCollapse(!1)},collapse:function(){this._handleExpandCollapse(!0)},_destroy:function(){var a=this._ui,b=this.options;b.enhanced||(a.placeholder?(a.originalHeading.insertBefore(a.placeholder),a.placeholder.remove(),a.heading.remove()):(a.status.remove(),a.heading.removeClass("ui-collapsible-heading ui-collapsible-heading-collapsed").children().contents().unwrap()),a.anchor.contents().unwrap(),a.content.contents().unwrap(),this.element.removeClass("ui-collapsible ui-collapsible-collapsed ui-collapsible-themed-content ui-collapsible-inset ui-corner-all"))}}),a.mobile.collapsible.defaults={expandCueText:" click to expand contents",collapseCueText:" click to collapse contents",collapsedIcon:"plus",contentTheme:"inherit",expandedIcon:"minus",iconpos:"left",inset:!0,corners:!0,theme:"inherit",mini:!1}}(a),function(a){function b(b){var d,e=b.length,f=[];for(d=0;e>d;d++)b[d].className.match(c)||f.push(b[d]);return a(f)}var c=/\bui-screen-hidden\b/;a.mobile.behaviors.addFirstLastClasses={_getVisibles:function(a,c){var d;return c?d=b(a):(d=a.filter(":visible"),0===d.length&&(d=b(a))),d},_addFirstLastClasses:function(a,b,c){a.removeClass("ui-first-child ui-last-child"),b.eq(0).addClass("ui-first-child").end().last().addClass("ui-last-child"),c||this.element.trigger("updatelayout")},_removeFirstLastClasses:function(a){a.removeClass("ui-first-child ui-last-child")}}}(a),function(a,b){var c=":mobile-collapsible, "+a.mobile.collapsible.initSelector;a.widget("mobile.collapsibleset",a.extend({initSelector:":jqmData(role='collapsible-set'),:jqmData(role='collapsibleset')",options:a.extend({enhanced:!1},a.mobile.collapsible.defaults),_handleCollapsibleExpand:function(b){var c=a(b.target).closest(".ui-collapsible");c.parent().is(":mobile-collapsibleset, :jqmData(role='collapsible-set')")&&c.siblings(".ui-collapsible:not(.ui-collapsible-collapsed)").collapsible("collapse")},_create:function(){var b=this.element,c=this.options;a.extend(this,{_classes:""}),c.enhanced||(b.addClass("ui-collapsible-set "+this._themeClassFromOption("ui-group-theme-",c.theme)+" "+(c.corners&&c.inset?"ui-corner-all ":"")),this.element.find(a.mobile.collapsible.initSelector).collapsible()),this._on(b,{collapsibleexpand:"_handleCollapsibleExpand"})},_themeClassFromOption:function(a,b){return b?"none"===b?"":a+b:""},_init:function(){this._refresh(!0),this.element.children(c).filter(":jqmData(collapsed='false')").collapsible("expand")},_setOptions:function(a){var c,d,e=this.element,f=this._themeClassFromOption("ui-group-theme-",a.theme);return f&&e.removeClass(this._themeClassFromOption("ui-group-theme-",this.options.theme)).addClass(f),a.inset!==b&&(d=!(!a.inset||!a.corners&&!this.options.corners)),a.corners!==b&&(d=!(!a.corners||!a.inset&&!this.options.inset)),d!==b&&e.toggleClass("ui-corner-all",d),c=this._super(a),this.element.children(":mobile-collapsible").collapsible("refresh"),c},_destroy:function(){var a=this.element;this._removeFirstLastClasses(a.children(c)),a.removeClass("ui-collapsible-set ui-corner-all "+this._themeClassFromOption("ui-group-theme-",this.options.theme)).children(":mobile-collapsible").collapsible("destroy")},_refresh:function(b){var d=this.element.children(c);this.element.find(a.mobile.collapsible.initSelector).not(".ui-collapsible").collapsible(),this._addFirstLastClasses(d,this._getVisibles(d,b),b)},refresh:function(){this._refresh(!1)}},a.mobile.behaviors.addFirstLastClasses))}(a),function(a){a.fn.fieldcontain=function(){return this.addClass("ui-field-contain")}}(a),function(a){a.fn.grid=function(b){return this.each(function(){var c,d,e=a(this),f=a.extend({grid:null},b),g=e.children(),h={solo:1,a:2,b:3,c:4,d:5},i=f.grid;if(!i)if(g.length<=5)for(d in h)h[d]===g.length&&(i=d);else i="a",e.addClass("ui-grid-duo");c=h[i],e.addClass("ui-grid-"+i),g.filter(":nth-child("+c+"n+1)").addClass("ui-block-a"),c>1&&g.filter(":nth-child("+c+"n+2)").addClass("ui-block-b"),c>2&&g.filter(":nth-child("+c+"n+3)").addClass("ui-block-c"),c>3&&g.filter(":nth-child("+c+"n+4)").addClass("ui-block-d"),c>4&&g.filter(":nth-child("+c+"n+5)").addClass("ui-block-e")})}}(a),function(a,b){a.widget("mobile.navbar",{options:{iconpos:"top",grid:null},_create:function(){var d=this.element,e=d.find("a, button"),f=e.filter(":jqmData(icon)").length?this.options.iconpos:b;d.addClass("ui-navbar").attr("role","navigation").find("ul").jqmEnhanceable().grid({grid:this.options.grid}),e.each(function(){var b=a.mobile.getAttribute(this,"icon"),c=a.mobile.getAttribute(this,"theme"),d="ui-btn";c&&(d+=" ui-btn-"+c),b&&(d+=" ui-icon-"+b+" ui-btn-icon-"+f),a(this).addClass(d)}),d.delegate("a","vclick",function(){var b=a(this);b.hasClass("ui-state-disabled")||b.hasClass("ui-disabled")||b.hasClass(a.mobile.activeBtnClass)||(e.removeClass(a.mobile.activeBtnClass),b.addClass(a.mobile.activeBtnClass),a(c).one("pagehide",function(){b.removeClass(a.mobile.activeBtnClass)}))}),d.closest(".ui-page").bind("pagebeforeshow",function(){e.filter(".ui-state-persist").addClass(a.mobile.activeBtnClass)})}})}(a),function(a){var b=a.mobile.getAttribute;a.widget("mobile.listview",a.extend({options:{theme:null,countTheme:null,dividerTheme:null,icon:"carat-r",splitIcon:"carat-r",splitTheme:null,corners:!0,shadow:!0,inset:!1},_create:function(){var a=this,b="";b+=a.options.inset?" ui-listview-inset":"",a.options.inset&&(b+=a.options.corners?" ui-corner-all":"",b+=a.options.shadow?" ui-shadow":""),a.element.addClass(" ui-listview"+b),a.refresh(!0)},_findFirstElementByTagName:function(a,b,c,d){var e={};for(e[c]=e[d]=!0;a;){if(e[a.nodeName])return a;a=a[b]}return null},_addThumbClasses:function(b){var c,d,e=b.length;for(c=0;e>c;c++)d=a(this._findFirstElementByTagName(b[c].firstChild,"nextSibling","img","IMG")),d.length&&a(this._findFirstElementByTagName(d[0].parentNode,"parentNode","li","LI")).addClass(d.hasClass("ui-li-icon")?"ui-li-has-icon":"ui-li-has-thumb")},_getChildrenByTagName:function(b,c,d){var e=[],f={};for(f[c]=f[d]=!0,b=b.firstChild;b;)f[b.nodeName]&&e.push(b),b=b.nextSibling;return a(e)},_beforeListviewRefresh:a.noop,_afterListviewRefresh:a.noop,refresh:function(c){var d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x=this.options,y=this.element,z=!!a.nodeName(y[0],"ol"),A=y.attr("start"),B={},C=y.find(".ui-li-count"),D=b(y[0],"counttheme")||this.options.countTheme,E=D?"ui-body-"+D:"ui-body-inherit";for(x.theme&&y.addClass("ui-group-theme-"+x.theme),z&&(A||0===A)&&(n=parseInt(A,10)-1,y.css("counter-reset","listnumbering "+n)),this._beforeListviewRefresh(),w=this._getChildrenByTagName(y[0],"li","LI"),e=0,f=w.length;f>e;e++)g=w.eq(e),h="",(c||g[0].className.search(/\bui-li-static\b|\bui-li-divider\b/)<0)&&(l=this._getChildrenByTagName(g[0],"a","A"),m="list-divider"===b(g[0],"role"),p=g.attr("value"),i=b(g[0],"theme"),l.length&&l[0].className.search(/\bui-btn\b/)<0&&!m?(j=b(g[0],"icon"),k=j===!1?!1:j||x.icon,l.removeClass("ui-link"),d="ui-btn",i&&(d+=" ui-btn-"+i),l.length>1?(h="ui-li-has-alt",q=l.last(),r=b(q[0],"theme")||x.splitTheme||b(g[0],"theme",!0),s=r?" ui-btn-"+r:"",t=b(q[0],"icon")||b(g[0],"icon")||x.splitIcon,u="ui-btn ui-btn-icon-notext ui-icon-"+t+s,q.attr("title",a.trim(q.getEncodedText())).addClass(u).empty(),l=l.first()):k&&(d+=" ui-btn-icon-right ui-icon-"+k),l.addClass(d)):m?(v=b(g[0],"theme")||x.dividerTheme||x.theme,h="ui-li-divider ui-bar-"+(v?v:"inherit"),g.attr("role","heading")):l.length<=0&&(h="ui-li-static ui-body-"+(i?i:"inherit")),z&&p&&(o=parseInt(p,10)-1,g.css("counter-reset","listnumbering "+o))),B[h]||(B[h]=[]),B[h].push(g[0]);for(h in B)a(B[h]).addClass(h);C.each(function(){a(this).closest("li").addClass("ui-li-has-count")}),E&&C.not("[class*='ui-body-']").addClass(E),this._addThumbClasses(w),this._addThumbClasses(w.find(".ui-btn")),this._afterListviewRefresh(),this._addFirstLastClasses(w,this._getVisibles(w,c),c)}},a.mobile.behaviors.addFirstLastClasses))}(a),function(a){function b(b){var c=a.trim(b.text())||null;return c?c=c.slice(0,1).toUpperCase():null}a.widget("mobile.listview",a.mobile.listview,{options:{autodividers:!1,autodividersSelector:b},_beforeListviewRefresh:function(){this.options.autodividers&&(this._replaceDividers(),this._superApply(arguments))},_replaceDividers:function(){var b,d,e,f,g,h=null,i=this.element;for(i.children("li:jqmData(role='list-divider')").remove(),d=i.children("li"),b=0;b
-1;d--)e=a[d],e.className.match(b)?(f&&(e.className=e.className+" ui-screen-hidden"),f=!0):e.className.match(c)||(f=!1)}})}(a),function(a){a.mobile.nojs=function(b){a(":jqmData(role='nojs')",b).addClass("ui-nojs")}}(a),function(a){a.mobile.behaviors.formReset={_handleFormReset:function(){this._on(this.element.closest("form"),{reset:function(){this._delay("_reset")}})}}}(a),function(a,b){var c=a.mobile.path.hashToSelector;a.widget("mobile.checkboxradio",a.extend({initSelector:"input:not( :jqmData(role='flipswitch' ) )[type='checkbox'],input[type='radio']:not( :jqmData(role='flipswitch' ))",options:{theme:"inherit",mini:!1,wrapperClass:null,enhanced:!1,iconpos:"left"},_create:function(){var b=this.element,c=this.options,d=function(a,b){return a.jqmData(b)||a.closest("form, fieldset").jqmData(b)},e=this.options.enhanced?{element:this.element.siblings("label"),isParent:!1}:this._findLabel(),f=b[0].type,g="ui-"+f+"-on",h="ui-"+f+"-off";("checkbox"===f||"radio"===f)&&(this.element[0].disabled&&(this.options.disabled=!0),c.iconpos=d(b,"iconpos")||e.element.attr("data-"+a.mobile.ns+"iconpos")||c.iconpos,c.mini=d(b,"mini")||c.mini,a.extend(this,{input:b,label:e.element,labelIsParent:e.isParent,inputtype:f,checkedClass:g,uncheckedClass:h}),this.options.enhanced||this._enhance(),this._on(e.element,{vmouseover:"_handleLabelVMouseOver",vclick:"_handleLabelVClick"}),this._on(b,{vmousedown:"_cacheVals",vclick:"_handleInputVClick",focus:"_handleInputFocus",blur:"_handleInputBlur"}),this._handleFormReset(),this.refresh())},_findLabel:function(){var b,d,e,f=this.element,g=f[0].labels;return g&&g.length>0?(d=a(g[0]),e=a.contains(d[0],f[0])):(b=f.closest("label"),e=b.length>0,d=e?b:a(this.document[0].getElementsByTagName("label")).filter("[for='"+c(f[0].id)+"']").first()),{element:d,isParent:e}},_enhance:function(){this.label.addClass("ui-btn ui-corner-all"),this.labelIsParent?this.input.add(this.label).wrapAll(this._wrapper()):(this.element.wrap(this._wrapper()),this.element.parent().prepend(this.label)),this._setOptions({theme:this.options.theme,iconpos:this.options.iconpos,mini:this.options.mini})},_wrapper:function(){return a("")},_handleInputFocus:function(){this.label.addClass(a.mobile.focusClass)},_handleInputBlur:function(){this.label.removeClass(a.mobile.focusClass)},_handleInputVClick:function(){this.element.prop("checked",this.element.is(":checked")),this._getInputSet().not(this.element).prop("checked",!1),this._updateAll(!0)},_handleLabelVMouseOver:function(a){this.label.parent().hasClass("ui-state-disabled")&&a.stopPropagation()},_handleLabelVClick:function(a){var b=this.element;return b.is(":disabled")?void a.preventDefault():(this._cacheVals(),b.prop("checked","radio"===this.inputtype&&!0||!b.prop("checked")),b.triggerHandler("click"),this._getInputSet().not(b).prop("checked",!1),this._updateAll(),!1)},_cacheVals:function(){this._getInputSet().each(function(){a(this).attr("data-"+a.mobile.ns+"cacheVal",this.checked)})},_getInputSet:function(){var b,d,e=this.element[0],f=e.name,g=e.form,h=this.element.parents().last().get(0),i=this.element;return f&&"radio"===this.inputtype&&h&&(b="input[type='radio'][name='"+c(f)+"']",g?(d=g.getAttribute("id"),d&&(i=a(b+"[form='"+c(d)+"']",h)),i=a(g).find(b).filter(function(){return this.form===g}).add(i)):i=a(b,h).filter(function(){return!this.form})),i},_updateAll:function(b){var c=this;this._getInputSet().each(function(){var d=a(this);!this.checked&&"checkbox"!==c.inputtype||b||d.trigger("change")}).checkboxradio("refresh")},_reset:function(){this.refresh()},_hasIcon:function(){var b,c,d=a.mobile.controlgroup;return d&&(b=this.element.closest(":mobile-controlgroup,"+d.prototype.initSelector),b.length>0)?(c=a.data(b[0],"mobile-controlgroup"),"horizontal"!==(c?c.options.type:b.attr("data-"+a.mobile.ns+"type"))):!0},refresh:function(){var b=this.element[0].checked,c=a.mobile.activeBtnClass,d="ui-btn-icon-"+this.options.iconpos,e=[],f=[];this._hasIcon()?(f.push(c),e.push(d)):(f.push(d),(b?e:f).push(c)),b?(e.push(this.checkedClass),f.push(this.uncheckedClass)):(e.push(this.uncheckedClass),f.push(this.checkedClass)),this.widget().toggleClass("ui-state-disabled",this.element.prop("disabled")),this.label.addClass(e.join(" ")).removeClass(f.join(" "))},widget:function(){return this.label.parent()},_setOptions:function(a){var c=this.label,d=this.options,e=this.widget(),f=this._hasIcon();a.disabled!==b&&(this.input.prop("disabled",!!a.disabled),e.toggleClass("ui-state-disabled",!!a.disabled)),a.mini!==b&&e.toggleClass("ui-mini",!!a.mini),a.theme!==b&&c.removeClass("ui-btn-"+d.theme).addClass("ui-btn-"+a.theme),a.wrapperClass!==b&&e.removeClass(d.wrapperClass).addClass(a.wrapperClass),a.iconpos!==b&&f?c.removeClass("ui-btn-icon-"+d.iconpos).addClass("ui-btn-icon-"+a.iconpos):f||c.removeClass("ui-btn-icon-"+d.iconpos),this._super(a)}},a.mobile.behaviors.formReset))}(a),function(a,b){a.widget("mobile.button",{initSelector:"input[type='button'], input[type='submit'], input[type='reset']",options:{theme:null,icon:null,iconpos:"left",iconshadow:!1,corners:!0,shadow:!0,inline:null,mini:null,wrapperClass:null,enhanced:!1},_create:function(){this.element.is(":disabled")&&(this.options.disabled=!0),this.options.enhanced||this._enhance(),a.extend(this,{wrapper:this.element.parent()}),this._on({focus:function(){this.widget().addClass(a.mobile.focusClass)},blur:function(){this.widget().removeClass(a.mobile.focusClass)}}),this.refresh(!0)},_enhance:function(){this.element.wrap(this._button())},_button:function(){var b=this.options,c=this._getIconClasses(this.options);return a(""+this.element.val()+"
")},widget:function(){return this.wrapper},_destroy:function(){this.element.insertBefore(this.wrapper),this.wrapper.remove()},_getIconClasses:function(a){return a.icon?"ui-icon-"+a.icon+(a.iconshadow?" ui-shadow-icon":"")+" ui-btn-icon-"+a.iconpos:""},_setOptions:function(c){var d=this.widget();c.theme!==b&&d.removeClass(this.options.theme).addClass("ui-btn-"+c.theme),c.corners!==b&&d.toggleClass("ui-corner-all",c.corners),c.shadow!==b&&d.toggleClass("ui-shadow",c.shadow),c.inline!==b&&d.toggleClass("ui-btn-inline",c.inline),c.mini!==b&&d.toggleClass("ui-mini",c.mini),c.disabled!==b&&(this.element.prop("disabled",c.disabled),d.toggleClass("ui-state-disabled",c.disabled)),(c.icon!==b||c.iconshadow!==b||c.iconpos!==b)&&d.removeClass(this._getIconClasses(this.options)).addClass(this._getIconClasses(a.extend({},this.options,c))),this._super(c)},refresh:function(b){var c,d=this.element.prop("disabled");this.options.icon&&"notext"===this.options.iconpos&&this.element.attr("title")&&this.element.attr("title",this.element.val()),b||(c=this.element.detach(),a(this.wrapper).text(this.element.val()).append(c)),this.options.disabled!==d&&this._setOptions({disabled:d})}})}(a),function(a){var b=a("meta[name=viewport]"),c=b.attr("content"),d=c+",maximum-scale=1, user-scalable=no",e=c+",maximum-scale=10, user-scalable=yes",f=/(user-scalable[\s]*=[\s]*no)|(maximum-scale[\s]*=[\s]*1)[$,\s]/.test(c);a.mobile.zoom=a.extend({},{enabled:!f,locked:!1,disable:function(c){f||a.mobile.zoom.locked||(b.attr("content",d),a.mobile.zoom.enabled=!1,a.mobile.zoom.locked=c||!1)},enable:function(c){f||a.mobile.zoom.locked&&c!==!0||(b.attr("content",e),a.mobile.zoom.enabled=!0,a.mobile.zoom.locked=!1)},restore:function(){f||(b.attr("content",c),a.mobile.zoom.enabled=!0)}})}(a),function(a,b){a.widget("mobile.textinput",{initSelector:"input[type='text'],input[type='search'],:jqmData(type='search'),input[type='number'],:jqmData(type='number'),input[type='password'],input[type='email'],input[type='url'],input[type='tel'],textarea,input[type='time'],input[type='date'],input[type='month'],input[type='week'],input[type='datetime'],input[type='datetime-local'],input[type='color'],input:not([type]),input[type='file']",options:{theme:null,corners:!0,mini:!1,preventFocusZoom:/iPhone|iPad|iPod/.test(navigator.platform)&&navigator.userAgent.indexOf("AppleWebKit")>-1,wrapperClass:"",enhanced:!1},_create:function(){var b=this.options,c=this.element.is("[type='search'], :jqmData(type='search')"),d="TEXTAREA"===this.element[0].tagName,e=this.element.is("[data-"+(a.mobile.ns||"")+"type='range']"),f=(this.element.is("input")||this.element.is("[data-"+(a.mobile.ns||"")+"type='search']"))&&!e;this.element.prop("disabled")&&(b.disabled=!0),a.extend(this,{classes:this._classesFromOptions(),isSearch:c,isTextarea:d,isRange:e,inputNeedsWrap:f}),this._autoCorrect(),b.enhanced||this._enhance(),this._on({focus:"_handleFocus",blur:"_handleBlur"})},refresh:function(){this.setOptions({disabled:this.element.is(":disabled")})},_enhance:function(){var a=[];this.isTextarea&&a.push("ui-input-text"),(this.isTextarea||this.isRange)&&a.push("ui-shadow-inset"),this.inputNeedsWrap?this.element.wrap(this._wrap()):a=a.concat(this.classes),this.element.addClass(a.join(" "))},widget:function(){return this.inputNeedsWrap?this.element.parent():this.element},_classesFromOptions:function(){var a=this.options,b=[];return b.push("ui-body-"+(null===a.theme?"inherit":a.theme)),a.corners&&b.push("ui-corner-all"),a.mini&&b.push("ui-mini"),a.disabled&&b.push("ui-state-disabled"),a.wrapperClass&&b.push(a.wrapperClass),b
+},_wrap:function(){return a("")},_autoCorrect:function(){"undefined"==typeof this.element[0].autocorrect||a.support.touchOverflow||(this.element[0].setAttribute("autocorrect","off"),this.element[0].setAttribute("autocomplete","off"))},_handleBlur:function(){this.widget().removeClass(a.mobile.focusClass),this.options.preventFocusZoom&&a.mobile.zoom.enable(!0)},_handleFocus:function(){this.options.preventFocusZoom&&a.mobile.zoom.disable(!0),this.widget().addClass(a.mobile.focusClass)},_setOptions:function(a){var c=this.widget();this._super(a),(a.disabled!==b||a.mini!==b||a.corners!==b||a.theme!==b||a.wrapperClass!==b)&&(c.removeClass(this.classes.join(" ")),this.classes=this._classesFromOptions(),c.addClass(this.classes.join(" "))),a.disabled!==b&&this.element.prop("disabled",!!a.disabled)},_destroy:function(){this.options.enhanced||(this.inputNeedsWrap&&this.element.unwrap(),this.element.removeClass("ui-input-text "+this.classes.join(" ")))}})}(a),function(a,d){a.widget("mobile.slider",a.extend({initSelector:"input[type='range'], :jqmData(type='range'), :jqmData(role='slider')",widgetEventPrefix:"slide",options:{theme:null,trackTheme:null,corners:!0,mini:!1,highlight:!1},_create:function(){var e,f,g,h,i,j,k,l,m,n,o=this,p=this.element,q=this.options.trackTheme||a.mobile.getAttribute(p[0],"theme"),r=q?" ui-bar-"+q:" ui-bar-inherit",s=this.options.corners||p.jqmData("corners")?" ui-corner-all":"",t=this.options.mini||p.jqmData("mini")?" ui-mini":"",u=p[0].nodeName.toLowerCase(),v="select"===u,w=p.parent().is(":jqmData(role='rangeslider')"),x=v?"ui-slider-switch":"",y=p.attr("id"),z=a("[for='"+y+"']"),A=z.attr("id")||y+"-label",B=v?0:parseFloat(p.attr("min")),C=v?p.find("option").length-1:parseFloat(p.attr("max")),D=b.parseFloat(p.attr("step")||1),E=c.createElement("a"),F=a(E),G=c.createElement("div"),H=a(G),I=this.options.highlight&&!v?function(){var b=c.createElement("div");return b.className="ui-slider-bg "+a.mobile.activeBtnClass,a(b).prependTo(H)}():!1;if(z.attr("id",A),this.isToggleSwitch=v,E.setAttribute("href","#"),G.setAttribute("role","application"),G.className=[this.isToggleSwitch?"ui-slider ui-slider-track ui-shadow-inset ":"ui-slider-track ui-shadow-inset ",x,r,s,t].join(""),E.className="ui-slider-handle",G.appendChild(E),F.attr({role:"slider","aria-valuemin":B,"aria-valuemax":C,"aria-valuenow":this._value(),"aria-valuetext":this._value(),title:this._value(),"aria-labelledby":A}),a.extend(this,{slider:H,handle:F,control:p,type:u,step:D,max:C,min:B,valuebg:I,isRangeslider:w,dragging:!1,beforeStart:null,userModified:!1,mouseMoved:!1}),v){for(k=p.attr("tabindex"),k&&F.attr("tabindex",k),p.attr("tabindex","-1").focus(function(){a(this).blur(),F.focus()}),f=c.createElement("div"),f.className="ui-slider-inneroffset",g=0,h=G.childNodes.length;h>g;g++)f.appendChild(G.childNodes[g]);for(G.appendChild(f),F.addClass("ui-slider-handle-snapping"),e=p.find("option"),i=0,j=e.length;j>i;i++)l=i?"a":"b",m=i?" "+a.mobile.activeBtnClass:"",n=c.createElement("span"),n.className=["ui-slider-label ui-slider-label-",l,m].join(""),n.setAttribute("role","img"),n.appendChild(c.createTextNode(e[i].innerHTML)),a(n).prependTo(H);o._labels=a(".ui-slider-label",H)}p.addClass(v?"ui-slider-switch":"ui-slider-input"),this._on(p,{change:"_controlChange",keyup:"_controlKeyup",blur:"_controlBlur",vmouseup:"_controlVMouseUp"}),H.bind("vmousedown",a.proxy(this._sliderVMouseDown,this)).bind("vclick",!1),this._on(c,{vmousemove:"_preventDocumentDrag"}),this._on(H.add(c),{vmouseup:"_sliderVMouseUp"}),H.insertAfter(p),v||w||(f=this.options.mini?"":"
",p.add(H).wrapAll(f)),this._on(this.handle,{vmousedown:"_handleVMouseDown",keydown:"_handleKeydown",keyup:"_handleKeyup"}),this.handle.bind("vclick",!1),this._handleFormReset(),this.refresh(d,d,!0)},_setOptions:function(a){a.theme!==d&&this._setTheme(a.theme),a.trackTheme!==d&&this._setTrackTheme(a.trackTheme),a.corners!==d&&this._setCorners(a.corners),a.mini!==d&&this._setMini(a.mini),a.highlight!==d&&this._setHighlight(a.highlight),a.disabled!==d&&this._setDisabled(a.disabled),this._super(a)},_controlChange:function(a){return this._trigger("controlchange",a)===!1?!1:void(this.mouseMoved||this.refresh(this._value(),!0))},_controlKeyup:function(){this.refresh(this._value(),!0,!0)},_controlBlur:function(){this.refresh(this._value(),!0)},_controlVMouseUp:function(){this._checkedRefresh()},_handleVMouseDown:function(){this.handle.focus()},_handleKeydown:function(b){var c=this._value();if(!this.options.disabled){switch(b.keyCode){case a.mobile.keyCode.HOME:case a.mobile.keyCode.END:case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:b.preventDefault(),this._keySliding||(this._keySliding=!0,this.handle.addClass("ui-state-active"))}switch(b.keyCode){case a.mobile.keyCode.HOME:this.refresh(this.min);break;case a.mobile.keyCode.END:this.refresh(this.max);break;case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:this.refresh(c+this.step);break;case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:this.refresh(c-this.step)}}},_handleKeyup:function(){this._keySliding&&(this._keySliding=!1,this.handle.removeClass("ui-state-active"))},_sliderVMouseDown:function(a){return this.options.disabled||1!==a.which&&0!==a.which&&a.which!==d?!1:this._trigger("beforestart",a)===!1?!1:(this.dragging=!0,this.userModified=!1,this.mouseMoved=!1,this.isToggleSwitch&&(this.beforeStart=this.element[0].selectedIndex),this.refresh(a),this._trigger("start"),!1)},_sliderVMouseUp:function(){return this.dragging?(this.dragging=!1,this.isToggleSwitch&&(this.handle.addClass("ui-slider-handle-snapping"),this.refresh(this.mouseMoved?this.userModified?0===this.beforeStart?1:0:this.beforeStart:0===this.beforeStart?1:0)),this.mouseMoved=!1,this._trigger("stop"),!1):void 0},_preventDocumentDrag:function(a){return this._trigger("drag",a)===!1?!1:this.dragging&&!this.options.disabled?(this.mouseMoved=!0,this.isToggleSwitch&&this.handle.removeClass("ui-slider-handle-snapping"),this.refresh(a),this.userModified=this.beforeStart!==this.element[0].selectedIndex,!1):void 0},_checkedRefresh:function(){this.value!==this._value()&&this.refresh(this._value())},_value:function(){return this.isToggleSwitch?this.element[0].selectedIndex:parseFloat(this.element.val())},_reset:function(){this.refresh(d,!1,!0)},refresh:function(b,d,e){var f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z=this,A=a.mobile.getAttribute(this.element[0],"theme"),B=this.options.theme||A,C=B?" ui-btn-"+B:"",D=this.options.trackTheme||A,E=D?" ui-bar-"+D:" ui-bar-inherit",F=this.options.corners?" ui-corner-all":"",G=this.options.mini?" ui-mini":"";if(z.slider[0].className=[this.isToggleSwitch?"ui-slider ui-slider-switch ui-slider-track ui-shadow-inset":"ui-slider-track ui-shadow-inset",E,F,G].join(""),(this.options.disabled||this.element.prop("disabled"))&&this.disable(),this.value=this._value(),this.options.highlight&&!this.isToggleSwitch&&0===this.slider.find(".ui-slider-bg").length&&(this.valuebg=function(){var b=c.createElement("div");return b.className="ui-slider-bg "+a.mobile.activeBtnClass,a(b).prependTo(z.slider)}()),this.handle.addClass("ui-btn"+C+" ui-shadow"),l=this.element,m=!this.isToggleSwitch,n=m?[]:l.find("option"),o=m?parseFloat(l.attr("min")):0,p=m?parseFloat(l.attr("max")):n.length-1,q=m&&parseFloat(l.attr("step"))>0?parseFloat(l.attr("step")):1,"object"==typeof b){if(h=b,i=8,f=this.slider.offset().left,g=this.slider.width(),j=g/((p-o)/q),!this.dragging||h.pageX
f+g+i)return;k=j>1?(h.pageX-f)/g*100:Math.round((h.pageX-f)/g*100)}else null==b&&(b=m?parseFloat(l.val()||0):l[0].selectedIndex),k=(parseFloat(b)-o)/(p-o)*100;if(!isNaN(k)&&(r=k/100*(p-o)+o,s=(r-o)%q,t=r-s,2*Math.abs(s)>=q&&(t+=s>0?q:-q),u=100/((p-o)/q),r=parseFloat(t.toFixed(5)),"undefined"==typeof j&&(j=g/((p-o)/q)),j>1&&m&&(k=(r-o)*u*(1/q)),0>k&&(k=0),k>100&&(k=100),o>r&&(r=o),r>p&&(r=p),this.handle.css("left",k+"%"),this.handle[0].setAttribute("aria-valuenow",m?r:n.eq(r).attr("value")),this.handle[0].setAttribute("aria-valuetext",m?r:n.eq(r).getEncodedText()),this.handle[0].setAttribute("title",m?r:n.eq(r).getEncodedText()),this.valuebg&&this.valuebg.css("width",k+"%"),this._labels&&(v=this.handle.width()/this.slider.width()*100,w=k&&v+(100-v)*k/100,x=100===k?0:Math.min(v+100-w,100),this._labels.each(function(){var b=a(this).hasClass("ui-slider-label-a");a(this).width((b?w:x)+"%")})),!e)){if(y=!1,m?(y=parseFloat(l.val())!==r,l.val(r)):(y=l[0].selectedIndex!==r,l[0].selectedIndex=r),this._trigger("beforechange",b)===!1)return!1;!d&&y&&l.trigger("change")}},_setHighlight:function(a){a=!!a,a?(this.options.highlight=!!a,this.refresh()):this.valuebg&&(this.valuebg.remove(),this.valuebg=!1)},_setTheme:function(a){this.handle.removeClass("ui-btn-"+this.options.theme).addClass("ui-btn-"+a);var b=this.options.theme?this.options.theme:"inherit",c=a?a:"inherit";this.control.removeClass("ui-body-"+b).addClass("ui-body-"+c)},_setTrackTheme:function(a){var b=this.options.trackTheme?this.options.trackTheme:"inherit",c=a?a:"inherit";this.slider.removeClass("ui-body-"+b).addClass("ui-body-"+c)},_setMini:function(a){a=!!a,this.isToggleSwitch||this.isRangeslider||(this.slider.parent().toggleClass("ui-mini",a),this.element.toggleClass("ui-mini",a)),this.slider.toggleClass("ui-mini",a)},_setCorners:function(a){this.slider.toggleClass("ui-corner-all",a),this.isToggleSwitch||this.control.toggleClass("ui-corner-all",a)},_setDisabled:function(a){a=!!a,this.element.prop("disabled",a),this.slider.toggleClass("ui-state-disabled",a).attr("aria-disabled",a),this.element.toggleClass("ui-state-disabled",a)}},a.mobile.behaviors.formReset))}(a),function(a){function b(){return c||(c=a("",{"class":"ui-slider-popup ui-shadow ui-corner-all"})),c.clone()}var c;a.widget("mobile.slider",a.mobile.slider,{options:{popupEnabled:!1,showValue:!1},_create:function(){this._super(),a.extend(this,{_currentValue:null,_popup:null,_popupVisible:!1}),this._setOption("popupEnabled",this.options.popupEnabled),this._on(this.handle,{vmousedown:"_showPopup"}),this._on(this.slider.add(this.document),{vmouseup:"_hidePopup"}),this._refresh()},_positionPopup:function(){var a=this.handle.offset();this._popup.offset({left:a.left+(this.handle.width()-this._popup.width())/2,top:a.top-this._popup.outerHeight()-5})},_setOption:function(a,c){this._super(a,c),"showValue"===a?this.handle.html(c&&!this.options.mini?this._value():""):"popupEnabled"===a&&c&&!this._popup&&(this._popup=b().addClass("ui-body-"+(this.options.theme||"a")).hide().insertBefore(this.element))},refresh:function(){this._super.apply(this,arguments),this._refresh()},_refresh:function(){var a,b=this.options;b.popupEnabled&&this.handle.removeAttr("title"),a=this._value(),a!==this._currentValue&&(this._currentValue=a,b.popupEnabled&&this._popup&&(this._positionPopup(),this._popup.html(a)),b.showValue&&!this.options.mini&&this.handle.html(a))},_showPopup:function(){this.options.popupEnabled&&!this._popupVisible&&(this.handle.html(""),this._popup.show(),this._positionPopup(),this._popupVisible=!0)},_hidePopup:function(){var a=this.options;a.popupEnabled&&this._popupVisible&&(a.showValue&&!a.mini&&this.handle.html(this._value()),this._popup.hide(),this._popupVisible=!1)}})}(a),function(a,b){a.widget("mobile.flipswitch",a.extend({options:{onText:"On",offText:"Off",theme:null,enhanced:!1,wrapperClass:null,corners:!0,mini:!1},_create:function(){this.options.enhanced?a.extend(this,{flipswitch:this.element.parent(),on:this.element.find(".ui-flipswitch-on").eq(0),off:this.element.find(".ui-flipswitch-off").eq(0),type:this.element.get(0).tagName}):this._enhance(),this._handleFormReset(),this._originalTabIndex=this.element.attr("tabindex"),null!=this._originalTabIndex&&this.on.attr("tabindex",this._originalTabIndex),this.element.attr("tabindex","-1"),this._on({focus:"_handleInputFocus"}),this.element.is(":disabled")&&this._setOptions({disabled:!0}),this._on(this.flipswitch,{click:"_toggle",swipeleft:"_left",swiperight:"_right"}),this._on(this.on,{keydown:"_keydown"}),this._on({change:"refresh"})},_handleInputFocus:function(){this.on.focus()},widget:function(){return this.flipswitch},_left:function(){this.flipswitch.removeClass("ui-flipswitch-active"),"SELECT"===this.type?this.element.get(0).selectedIndex=0:this.element.prop("checked",!1),this.element.trigger("change")},_right:function(){this.flipswitch.addClass("ui-flipswitch-active"),"SELECT"===this.type?this.element.get(0).selectedIndex=1:this.element.prop("checked",!0),this.element.trigger("change")},_enhance:function(){var b=a(""),c=this.options,d=this.element,e=c.theme?c.theme:"inherit",f=a("
",{href:"#"}),g=a("
"),h=d.get(0).tagName,i="INPUT"===h?c.onText:d.find("option").eq(1).text(),j="INPUT"===h?c.offText:d.find("option").eq(0).text();f.addClass("ui-flipswitch-on ui-btn ui-shadow ui-btn-inherit").text(i),g.addClass("ui-flipswitch-off").text(j),b.addClass("ui-flipswitch ui-shadow-inset ui-bar-"+e+" "+(c.wrapperClass?c.wrapperClass:"")+" "+(d.is(":checked")||d.find("option").eq(1).is(":selected")?"ui-flipswitch-active":"")+(d.is(":disabled")?" ui-state-disabled":"")+(c.corners?" ui-corner-all":"")+(c.mini?" ui-mini":"")).append(f,g),d.addClass("ui-flipswitch-input").after(b).appendTo(b),a.extend(this,{flipswitch:b,on:f,off:g,type:h})},_reset:function(){this.refresh()},refresh:function(){var a,b=this.flipswitch.hasClass("ui-flipswitch-active")?"_right":"_left";a="SELECT"===this.type?this.element.get(0).selectedIndex>0?"_right":"_left":this.element.prop("checked")?"_right":"_left",a!==b&&this[a]()},_toggle:function(){var a=this.flipswitch.hasClass("ui-flipswitch-active")?"_left":"_right";this[a]()},_keydown:function(b){b.which===a.mobile.keyCode.LEFT?this._left():b.which===a.mobile.keyCode.RIGHT?this._right():b.which===a.mobile.keyCode.SPACE&&(this._toggle(),b.preventDefault())},_setOptions:function(a){if(a.theme!==b){var c=a.theme?a.theme:"inherit",d=a.theme?a.theme:"inherit";this.widget().removeClass("ui-bar-"+c).addClass("ui-bar-"+d)}a.onText!==b&&this.on.text(a.onText),a.offText!==b&&this.off.text(a.offText),a.disabled!==b&&this.widget().toggleClass("ui-state-disabled",a.disabled),a.mini!==b&&this.widget().toggleClass("ui-mini",a.mini),a.corners!==b&&this.widget().toggleClass("ui-corner-all",a.corners),this._super(a)},_destroy:function(){this.options.enhanced||(null!=this._originalTabIndex?this.element.attr("tabindex",this._originalTabIndex):this.element.removeAttr("tabindex"),this.on.remove(),this.off.remove(),this.element.unwrap(),this.flipswitch.remove(),this.removeClass("ui-flipswitch-input"))}},a.mobile.behaviors.formReset))}(a),function(a,b){a.widget("mobile.rangeslider",a.extend({options:{theme:null,trackTheme:null,corners:!0,mini:!1,highlight:!0},_create:function(){var b=this.element,c=this.options.mini?"ui-rangeslider ui-mini":"ui-rangeslider",d=b.find("input").first(),e=b.find("input").last(),f=b.find("label").first(),g=a.data(d.get(0),"mobile-slider")||a.data(d.slider().get(0),"mobile-slider"),h=a.data(e.get(0),"mobile-slider")||a.data(e.slider().get(0),"mobile-slider"),i=g.slider,j=h.slider,k=g.handle,l=a("
").appendTo(b);d.addClass("ui-rangeslider-first"),e.addClass("ui-rangeslider-last"),b.addClass(c),i.appendTo(l),j.appendTo(l),f.insertBefore(b),k.prependTo(j),a.extend(this,{_inputFirst:d,_inputLast:e,_sliderFirst:i,_sliderLast:j,_label:f,_targetVal:null,_sliderTarget:!1,_sliders:l,_proxy:!1}),this.refresh(),this._on(this.element.find("input.ui-slider-input"),{slidebeforestart:"_slidebeforestart",slidestop:"_slidestop",slidedrag:"_slidedrag",slidebeforechange:"_change",blur:"_change",keyup:"_change"}),this._on({mousedown:"_change"}),this._on(this.element.closest("form"),{reset:"_handleReset"}),this._on(k,{vmousedown:"_dragFirstHandle"})},_handleReset:function(){var a=this;setTimeout(function(){a._updateHighlight()},0)},_dragFirstHandle:function(b){return a.data(this._inputFirst.get(0),"mobile-slider").dragging=!0,a.data(this._inputFirst.get(0),"mobile-slider").refresh(b),a.data(this._inputFirst.get(0),"mobile-slider")._trigger("start"),!1},_slidedrag:function(b){var c=a(b.target).is(this._inputFirst),d=c?this._inputLast:this._inputFirst;return this._sliderTarget=!1,"first"===this._proxy&&c||"last"===this._proxy&&!c?(a.data(d.get(0),"mobile-slider").dragging=!0,a.data(d.get(0),"mobile-slider").refresh(b),!1):void 0},_slidestop:function(b){var c=a(b.target).is(this._inputFirst);this._proxy=!1,this.element.find("input").trigger("vmouseup"),this._sliderFirst.css("z-index",c?1:"")},_slidebeforestart:function(b){this._sliderTarget=!1,a(b.originalEvent.target).hasClass("ui-slider-track")&&(this._sliderTarget=!0,this._targetVal=a(b.target).val())},_setOptions:function(a){a.theme!==b&&this._setTheme(a.theme),a.trackTheme!==b&&this._setTrackTheme(a.trackTheme),a.mini!==b&&this._setMini(a.mini),a.highlight!==b&&this._setHighlight(a.highlight),a.disabled!==b&&this._setDisabled(a.disabled),this._super(a),this.refresh()},refresh:function(){var a=this.element,b=this.options;(this._inputFirst.is(":disabled")||this._inputLast.is(":disabled"))&&(this.options.disabled=!0),a.find("input").slider({theme:b.theme,trackTheme:b.trackTheme,disabled:b.disabled,corners:b.corners,mini:b.mini,highlight:b.highlight}).slider("refresh"),this._updateHighlight()},_change:function(b){if("keyup"===b.type)return this._updateHighlight(),!1;var c=this,d=parseFloat(this._inputFirst.val(),10),e=parseFloat(this._inputLast.val(),10),f=a(b.target).hasClass("ui-rangeslider-first"),g=f?this._inputFirst:this._inputLast,h=f?this._inputLast:this._inputFirst;if(this._inputFirst.val()>this._inputLast.val()&&"mousedown"===b.type&&!a(b.target).hasClass("ui-slider-handle"))g.blur();else if("mousedown"===b.type)return;return d>e&&!this._sliderTarget?(g.val(f?e:d).slider("refresh"),this._trigger("normalize")):d>e&&(g.val(this._targetVal).slider("refresh"),setTimeout(function(){h.val(f?d:e).slider("refresh"),a.data(h.get(0),"mobile-slider").handle.focus(),c._sliderFirst.css("z-index",f?"":1),c._trigger("normalize")},0),this._proxy=f?"first":"last"),d===e?(a.data(g.get(0),"mobile-slider").handle.css("z-index",1),a.data(h.get(0),"mobile-slider").handle.css("z-index",0)):(a.data(h.get(0),"mobile-slider").handle.css("z-index",""),a.data(g.get(0),"mobile-slider").handle.css("z-index","")),this._updateHighlight(),d>=e?!1:void 0},_updateHighlight:function(){var b=parseInt(a.data(this._inputFirst.get(0),"mobile-slider").handle.get(0).style.left,10),c=parseInt(a.data(this._inputLast.get(0),"mobile-slider").handle.get(0).style.left,10),d=c-b;this.element.find(".ui-slider-bg").css({"margin-left":b+"%",width:d+"%"})},_setTheme:function(a){this._inputFirst.slider("option","theme",a),this._inputLast.slider("option","theme",a)},_setTrackTheme:function(a){this._inputFirst.slider("option","trackTheme",a),this._inputLast.slider("option","trackTheme",a)},_setMini:function(a){this._inputFirst.slider("option","mini",a),this._inputLast.slider("option","mini",a),this.element.toggleClass("ui-mini",!!a)},_setHighlight:function(a){this._inputFirst.slider("option","highlight",a),this._inputLast.slider("option","highlight",a)},_setDisabled:function(a){this._inputFirst.prop("disabled",a),this._inputLast.prop("disabled",a)},_destroy:function(){this._label.prependTo(this.element),this.element.removeClass("ui-rangeslider ui-mini"),this._inputFirst.after(this._sliderFirst),this._inputLast.after(this._sliderLast),this._sliders.remove(),this.element.find("input").removeClass("ui-rangeslider-first ui-rangeslider-last").slider("destroy")}},a.mobile.behaviors.formReset))}(a),function(a,b){a.widget("mobile.textinput",a.mobile.textinput,{options:{clearBtn:!1,clearBtnText:"Clear text"},_create:function(){this._super(),this.isSearch&&(this.options.clearBtn=!0),this.options.clearBtn&&this.inputNeedsWrap&&this._addClearBtn()},clearButton:function(){return a("
").attr("title",this.options.clearBtnText).text(this.options.clearBtnText)},_clearBtnClick:function(a){this.element.val("").focus().trigger("change"),this._clearBtn.addClass("ui-input-clear-hidden"),a.preventDefault()},_addClearBtn:function(){this.options.enhanced||this._enhanceClear(),a.extend(this,{_clearBtn:this.widget().find("a.ui-input-clear")}),this._bindClearEvents(),this._toggleClear()},_enhanceClear:function(){this.clearButton().appendTo(this.widget()),this.widget().addClass("ui-input-has-clear")},_bindClearEvents:function(){this._on(this._clearBtn,{click:"_clearBtnClick"}),this._on({keyup:"_toggleClear",change:"_toggleClear",input:"_toggleClear",focus:"_toggleClear",blur:"_toggleClear",cut:"_toggleClear",paste:"_toggleClear"})},_unbindClear:function(){this._off(this._clearBtn,"click"),this._off(this.element,"keyup change input focus blur cut paste")},_setOptions:function(a){this._super(a),a.clearBtn===b||this.element.is("textarea, :jqmData(type='range')")||(a.clearBtn?this._addClearBtn():this._destroyClear()),a.clearBtnText!==b&&this._clearBtn!==b&&this._clearBtn.text(a.clearBtnText).attr("title",a.clearBtnText)},_toggleClear:function(){this._delay("_toggleClearClass",0)},_toggleClearClass:function(){this._clearBtn.toggleClass("ui-input-clear-hidden",!this.element.val())},_destroyClear:function(){this.widget().removeClass("ui-input-has-clear"),this._unbindClear(),this._clearBtn.remove()},_destroy:function(){this._super(),this.options.clearBtn&&this._destroyClear()}})}(a),function(a,b){a.widget("mobile.textinput",a.mobile.textinput,{options:{autogrow:!0,keyupTimeoutBuffer:100},_create:function(){this._super(),this.options.autogrow&&this.isTextarea&&this._autogrow()},_autogrow:function(){this.element.addClass("ui-textinput-autogrow"),this._on({keyup:"_timeout",change:"_timeout",input:"_timeout",paste:"_timeout"}),this._on(!0,this.document,{pageshow:"_handleShow",popupbeforeposition:"_handleShow",updatelayout:"_handleShow",panelopen:"_handleShow"})},_handleShow:function(b){a.contains(b.target,this.element[0])&&this.element.is(":visible")&&("popupbeforeposition"!==b.type&&this.element.addClass("ui-textinput-autogrow-resize").animationComplete(a.proxy(function(){this.element.removeClass("ui-textinput-autogrow-resize")},this),"transition"),this._prepareHeightUpdate())},_unbindAutogrow:function(){this.element.removeClass("ui-textinput-autogrow"),this._off(this.element,"keyup change input paste"),this._off(this.document,"pageshow popupbeforeposition updatelayout panelopen")},keyupTimeout:null,_prepareHeightUpdate:function(a){this.keyupTimeout&&clearTimeout(this.keyupTimeout),a===b?this._updateHeight():this.keyupTimeout=this._delay("_updateHeight",a)},_timeout:function(){this._prepareHeightUpdate(this.options.keyupTimeoutBuffer)},_updateHeight:function(){var a,b,c,d,e,f,g,h,i,j=this.window.scrollTop();this.keyupTimeout=0,"onpage"in this.element[0]||this.element.css({height:0,"min-height":0,"max-height":0}),d=this.element[0].scrollHeight,e=this.element[0].clientHeight,f=parseFloat(this.element.css("border-top-width")),g=parseFloat(this.element.css("border-bottom-width")),h=f+g,i=d+h+15,0===e&&(a=parseFloat(this.element.css("padding-top")),b=parseFloat(this.element.css("padding-bottom")),c=a+b,i+=c),this.element.css({height:i,"min-height":"","max-height":""}),this.window.scrollTop(j)},refresh:function(){this.options.autogrow&&this.isTextarea&&this._updateHeight()},_setOptions:function(a){this._super(a),a.autogrow!==b&&this.isTextarea&&(a.autogrow?this._autogrow():this._unbindAutogrow())}})}(a),function(a){a.widget("mobile.selectmenu",a.extend({initSelector:"select:not( :jqmData(role='slider')):not( :jqmData(role='flipswitch') )",options:{theme:null,icon:"carat-d",iconpos:"right",inline:!1,corners:!0,shadow:!0,iconshadow:!1,overlayTheme:null,dividerTheme:null,hidePlaceholderMenuItems:!0,closeText:"Close",nativeMenu:!0,preventFocusZoom:/iPhone|iPad|iPod/.test(navigator.platform)&&navigator.userAgent.indexOf("AppleWebKit")>-1,mini:!1},_button:function(){return a("
")},_setDisabled:function(a){return this.element.attr("disabled",a),this.button.attr("aria-disabled",a),this._setOption("disabled",a)},_focusButton:function(){var a=this;setTimeout(function(){a.button.focus()},40)},_selectOptions:function(){return this.select.find("option")},_preExtension:function(){var b=this.options.inline||this.element.jqmData("inline"),c=this.options.mini||this.element.jqmData("mini"),d="";~this.element[0].className.indexOf("ui-btn-left")&&(d=" ui-btn-left"),~this.element[0].className.indexOf("ui-btn-right")&&(d=" ui-btn-right"),b&&(d+=" ui-btn-inline"),c&&(d+=" ui-mini"),this.select=this.element.removeClass("ui-btn-left ui-btn-right").wrap("
"),this.selectId=this.select.attr("id")||"select-"+this.uuid,this.buttonId=this.selectId+"-button",this.label=a("label[for='"+this.selectId+"']"),this.isMultiple=this.select[0].multiple},_destroy:function(){var a=this.element.parents(".ui-select");a.length>0&&(a.is(".ui-btn-left, .ui-btn-right")&&this.element.addClass(a.hasClass("ui-btn-left")?"ui-btn-left":"ui-btn-right"),this.element.insertAfter(a),a.remove())},_create:function(){this._preExtension(),this.button=this._button();var c=this,d=this.options,e=d.icon?d.iconpos||this.select.jqmData("iconpos"):!1,f=this.button.insertBefore(this.select).attr("id",this.buttonId).addClass("ui-btn"+(d.icon?" ui-icon-"+d.icon+" ui-btn-icon-"+e+(d.iconshadow?" ui-shadow-icon":""):"")+(d.theme?" ui-btn-"+d.theme:"")+(d.corners?" ui-corner-all":"")+(d.shadow?" ui-shadow":""));this.setButtonText(),d.nativeMenu&&b.opera&&b.opera.version&&f.addClass("ui-select-nativeonly"),this.isMultiple&&(this.buttonCount=a("
").addClass("ui-li-count ui-body-inherit").hide().appendTo(f.addClass("ui-li-has-count"))),(d.disabled||this.element.attr("disabled"))&&this.disable(),this.select.change(function(){c.refresh(),d.nativeMenu&&c._delay(function(){c.select.blur()})}),this._handleFormReset(),this._on(this.button,{keydown:"_handleKeydown"}),this.build()},build:function(){var b=this;this.select.appendTo(b.button).bind("vmousedown",function(){b.button.addClass(a.mobile.activeBtnClass)}).bind("focus",function(){b.button.addClass(a.mobile.focusClass)}).bind("blur",function(){b.button.removeClass(a.mobile.focusClass)}).bind("focus vmouseover",function(){b.button.trigger("vmouseover")}).bind("vmousemove",function(){b.button.removeClass(a.mobile.activeBtnClass)}).bind("change blur vmouseout",function(){b.button.trigger("vmouseout").removeClass(a.mobile.activeBtnClass)}),b.button.bind("vmousedown",function(){b.options.preventFocusZoom&&a.mobile.zoom.disable(!0)}),b.label.bind("click focus",function(){b.options.preventFocusZoom&&a.mobile.zoom.disable(!0)}),b.select.bind("focus",function(){b.options.preventFocusZoom&&a.mobile.zoom.disable(!0)}),b.button.bind("mouseup",function(){b.options.preventFocusZoom&&setTimeout(function(){a.mobile.zoom.enable(!0)},0)}),b.select.bind("blur",function(){b.options.preventFocusZoom&&a.mobile.zoom.enable(!0)})},selected:function(){return this._selectOptions().filter(":selected")},selectedIndices:function(){var a=this;return this.selected().map(function(){return a._selectOptions().index(this)}).get()},setButtonText:function(){var b=this,d=this.selected(),e=this.placeholder,f=a(c.createElement("span"));this.button.children("span").not(".ui-li-count").remove().end().end().prepend(function(){return e=d.length?d.map(function(){return a(this).text()}).get().join(", "):b.placeholder,e?f.text(e):f.html(" "),f.addClass(b.select.attr("class")).addClass(d.attr("class")).removeClass("ui-screen-hidden")}())},setButtonCount:function(){var a=this.selected();this.isMultiple&&this.buttonCount[a.length>1?"show":"hide"]().text(a.length)},_handleKeydown:function(){this._delay("_refreshButton")},_reset:function(){this.refresh()},_refreshButton:function(){this.setButtonText(),this.setButtonCount()},refresh:function(){this._refreshButton()},open:a.noop,close:a.noop,disable:function(){this._setDisabled(!0),this.button.addClass("ui-state-disabled")},enable:function(){this._setDisabled(!1),this.button.removeClass("ui-state-disabled")}},a.mobile.behaviors.formReset))}(a),function(a){a.mobile.links=function(b){a(b).find("a").jqmEnhanceable().filter(":jqmData(rel='popup')[href][href!='']").each(function(){var a=this,b=a.getAttribute("href").substring(1);b&&(a.setAttribute("aria-haspopup",!0),a.setAttribute("aria-owns",b),a.setAttribute("aria-expanded",!1))}).end().not(".ui-btn, :jqmData(role='none'), :jqmData(role='nojs')").addClass("ui-link")}}(a),function(a,c){function d(a,b,c,d){var e=d;return e=b>a?c+(a-b)/2:Math.min(Math.max(c,d-b/2),c+a-b)}function e(a){return{x:a.scrollLeft(),y:a.scrollTop(),cx:a[0].innerWidth||a.width(),cy:a[0].innerHeight||a.height()}}a.widget("mobile.popup",{options:{wrapperClass:null,theme:null,overlayTheme:null,shadow:!0,corners:!0,transition:"none",positionTo:"origin",tolerance:null,closeLinkSelector:"a:jqmData(rel='back')",closeLinkEvents:"click.popup",navigateEvents:"navigate.popup",closeEvents:"navigate.popup pagebeforechange.popup",dismissible:!0,enhanced:!1,history:!a.mobile.browser.oldIE},_handleDocumentVmousedown:function(b){this._isOpen&&a.contains(this._ui.container[0],b.target)&&this._ignoreResizeEvents()},_create:function(){var b=this.element,c=b.attr("id"),d=this.options;d.history=d.history&&a.mobile.ajaxEnabled&&a.mobile.hashListeningEnabled,this._on(this.document,{vmousedown:"_handleDocumentVmousedown"}),a.extend(this,{_scrollTop:0,_page:b.closest(".ui-page"),_ui:null,_fallbackTransition:"",_currentTransition:!1,_prerequisites:null,_isOpen:!1,_tolerance:null,_resizeData:null,_ignoreResizeTo:0,_orientationchangeInProgress:!1}),0===this._page.length&&(this._page=a("body")),d.enhanced?this._ui={container:b.parent(),screen:b.parent().prev(),placeholder:a(this.document[0].getElementById(c+"-placeholder"))}:(this._ui=this._enhance(b,c),this._applyTransition(d.transition)),this._setTolerance(d.tolerance)._ui.focusElement=this._ui.container,this._on(this._ui.screen,{vclick:"_eatEventAndClose"}),this._on(this.window,{orientationchange:a.proxy(this,"_handleWindowOrientationchange"),resize:a.proxy(this,"_handleWindowResize"),keyup:a.proxy(this,"_handleWindowKeyUp")}),this._on(this.document,{focusin:"_handleDocumentFocusIn"})},_enhance:function(b,c){var d=this.options,e=d.wrapperClass,f={screen:a(""),placeholder:a(""),container:a("")},g=this.document[0].createDocumentFragment();return g.appendChild(f.screen[0]),g.appendChild(f.container[0]),c&&(f.screen.attr("id",c+"-screen"),f.container.attr("id",c+"-popup"),f.placeholder.attr("id",c+"-placeholder").html("")),this._page[0].appendChild(g),f.placeholder.insertAfter(b),b.detach().addClass("ui-popup "+this._themeClassFromOption("ui-body-",d.theme)+" "+(d.shadow?"ui-overlay-shadow ":"")+(d.corners?"ui-corner-all ":"")).appendTo(f.container),f},_eatEventAndClose:function(a){return a.preventDefault(),a.stopImmediatePropagation(),this.options.dismissible&&this.close(),!1},_resizeScreen:function(){var a=this._ui.screen,b=this._ui.container.outerHeight(!0),c=a.removeAttr("style").height(),d=this.document.height()-1;d>c?a.height(d):b>c&&a.height(b)},_handleWindowKeyUp:function(b){return this._isOpen&&b.keyCode===a.mobile.keyCode.ESCAPE?this._eatEventAndClose(b):void 0},_expectResizeEvent:function(){var a=e(this.window);
+if(this._resizeData){if(a.x===this._resizeData.windowCoordinates.x&&a.y===this._resizeData.windowCoordinates.y&&a.cx===this._resizeData.windowCoordinates.cx&&a.cy===this._resizeData.windowCoordinates.cy)return!1;clearTimeout(this._resizeData.timeoutId)}return this._resizeData={timeoutId:this._delay("_resizeTimeout",200),windowCoordinates:a},!0},_resizeTimeout:function(){this._isOpen?this._expectResizeEvent()||(this._ui.container.hasClass("ui-popup-hidden")&&(this._ui.container.removeClass("ui-popup-hidden ui-popup-truncate"),this.reposition({positionTo:"window"}),this._ignoreResizeEvents()),this._resizeScreen(),this._resizeData=null,this._orientationchangeInProgress=!1):(this._resizeData=null,this._orientationchangeInProgress=!1)},_stopIgnoringResizeEvents:function(){this._ignoreResizeTo=0},_ignoreResizeEvents:function(){this._ignoreResizeTo&&clearTimeout(this._ignoreResizeTo),this._ignoreResizeTo=this._delay("_stopIgnoringResizeEvents",1e3)},_handleWindowResize:function(){this._isOpen&&0===this._ignoreResizeTo&&(!this._expectResizeEvent()&&!this._orientationchangeInProgress||this._ui.container.hasClass("ui-popup-hidden")||this._ui.container.addClass("ui-popup-hidden ui-popup-truncate").removeAttr("style"))},_handleWindowOrientationchange:function(){!this._orientationchangeInProgress&&this._isOpen&&0===this._ignoreResizeTo&&(this._expectResizeEvent(),this._orientationchangeInProgress=!0)},_handleDocumentFocusIn:function(b){var c,d=b.target,e=this._ui;if(this._isOpen){if(d!==e.container[0]){if(c=a(d),!a.contains(e.container[0],d))return a(this.document[0].activeElement).one("focus",a.proxy(function(){this._safelyBlur(d)},this)),e.focusElement.focus(),b.preventDefault(),b.stopImmediatePropagation(),!1;e.focusElement[0]===e.container[0]&&(e.focusElement=c)}this._ignoreResizeEvents()}},_themeClassFromOption:function(a,b){return b?"none"===b?"":a+b:a+"inherit"},_applyTransition:function(b){return b&&(this._ui.container.removeClass(this._fallbackTransition),"none"!==b&&(this._fallbackTransition=a.mobile._maybeDegradeTransition(b),"none"===this._fallbackTransition&&(this._fallbackTransition=""),this._ui.container.addClass(this._fallbackTransition))),this},_setOptions:function(a){var b=this.options,d=this.element,e=this._ui.screen;return a.wrapperClass!==c&&this._ui.container.removeClass(b.wrapperClass).addClass(a.wrapperClass),a.theme!==c&&d.removeClass(this._themeClassFromOption("ui-body-",b.theme)).addClass(this._themeClassFromOption("ui-body-",a.theme)),a.overlayTheme!==c&&(e.removeClass(this._themeClassFromOption("ui-overlay-",b.overlayTheme)).addClass(this._themeClassFromOption("ui-overlay-",a.overlayTheme)),this._isOpen&&e.addClass("in")),a.shadow!==c&&d.toggleClass("ui-overlay-shadow",a.shadow),a.corners!==c&&d.toggleClass("ui-corner-all",a.corners),a.transition!==c&&(this._currentTransition||this._applyTransition(a.transition)),a.tolerance!==c&&this._setTolerance(a.tolerance),a.disabled!==c&&a.disabled&&this.close(),this._super(a)},_setTolerance:function(b){var d,e={t:30,r:15,b:30,l:15};if(b!==c)switch(d=String(b).split(","),a.each(d,function(a,b){d[a]=parseInt(b,10)}),d.length){case 1:isNaN(d[0])||(e.t=e.r=e.b=e.l=d[0]);break;case 2:isNaN(d[0])||(e.t=e.b=d[0]),isNaN(d[1])||(e.l=e.r=d[1]);break;case 4:isNaN(d[0])||(e.t=d[0]),isNaN(d[1])||(e.r=d[1]),isNaN(d[2])||(e.b=d[2]),isNaN(d[3])||(e.l=d[3])}return this._tolerance=e,this},_clampPopupWidth:function(a){var b,c=e(this.window),d={x:this._tolerance.l,y:c.y+this._tolerance.t,cx:c.cx-this._tolerance.l-this._tolerance.r,cy:c.cy-this._tolerance.t-this._tolerance.b};return a||this._ui.container.css("max-width",d.cx),b={cx:this._ui.container.outerWidth(!0),cy:this._ui.container.outerHeight(!0)},{rc:d,menuSize:b}},_calculateFinalLocation:function(a,b){var c,e=b.rc,f=b.menuSize;return c={left:d(e.cx,f.cx,e.x,a.x),top:d(e.cy,f.cy,e.y,a.y)},c.top=Math.max(0,c.top),c.top-=Math.min(c.top,Math.max(0,c.top+f.cy-this.document.height())),c},_placementCoords:function(a){return this._calculateFinalLocation(a,this._clampPopupWidth())},_createPrerequisites:function(b,c,d){var e,f=this;e={screen:a.Deferred(),container:a.Deferred()},e.screen.then(function(){e===f._prerequisites&&b()}),e.container.then(function(){e===f._prerequisites&&c()}),a.when(e.screen,e.container).done(function(){e===f._prerequisites&&(f._prerequisites=null,d())}),f._prerequisites=e},_animate:function(b){return this._ui.screen.removeClass(b.classToRemove).addClass(b.screenClassToAdd),b.prerequisites.screen.resolve(),b.transition&&"none"!==b.transition&&(b.applyTransition&&this._applyTransition(b.transition),this._fallbackTransition)?void this._ui.container.addClass(b.containerClassToAdd).removeClass(b.classToRemove).animationComplete(a.proxy(b.prerequisites.container,"resolve")):(this._ui.container.removeClass(b.classToRemove),void b.prerequisites.container.resolve())},_desiredCoords:function(b){var c,d=null,f=e(this.window),g=b.x,h=b.y,i=b.positionTo;if(i&&"origin"!==i)if("window"===i)g=f.cx/2+f.x,h=f.cy/2+f.y;else{try{d=a(i)}catch(j){d=null}d&&(d.filter(":visible"),0===d.length&&(d=null))}return d&&(c=d.offset(),g=c.left+d.outerWidth()/2,h=c.top+d.outerHeight()/2),("number"!==a.type(g)||isNaN(g))&&(g=f.cx/2+f.x),("number"!==a.type(h)||isNaN(h))&&(h=f.cy/2+f.y),{x:g,y:h}},_reposition:function(a){a={x:a.x,y:a.y,positionTo:a.positionTo},this._trigger("beforeposition",c,a),this._ui.container.offset(this._placementCoords(this._desiredCoords(a)))},reposition:function(a){this._isOpen&&this._reposition(a)},_safelyBlur:function(b){b!==this.window[0]&&"body"!==b.nodeName.toLowerCase()&&a(b).blur()},_openPrerequisitesComplete:function(){var b=this.element.attr("id"),c=this._ui.container.find(":focusable").first();this._ui.container.addClass("ui-popup-active"),this._isOpen=!0,this._resizeScreen(),a.contains(this._ui.container[0],this.document[0].activeElement)||this._safelyBlur(this.document[0].activeElement),c.length>0&&(this._ui.focusElement=c),this._ignoreResizeEvents(),b&&this.document.find("[aria-haspopup='true'][aria-owns='"+b+"']").attr("aria-expanded",!0),this._trigger("afteropen")},_open:function(b){var c=a.extend({},this.options,b),d=function(){var a=navigator.userAgent,b=a.match(/AppleWebKit\/([0-9\.]+)/),c=!!b&&b[1],d=a.match(/Android (\d+(?:\.\d+))/),e=!!d&&d[1],f=a.indexOf("Chrome")>-1;return null!==d&&"4.0"===e&&c&&c>534.13&&!f?!0:!1}();this._createPrerequisites(a.noop,a.noop,a.proxy(this,"_openPrerequisitesComplete")),this._currentTransition=c.transition,this._applyTransition(c.transition),this._ui.screen.removeClass("ui-screen-hidden"),this._ui.container.removeClass("ui-popup-truncate"),this._reposition(c),this._ui.container.removeClass("ui-popup-hidden"),this.options.overlayTheme&&d&&this.element.closest(".ui-page").addClass("ui-popup-open"),this._animate({additionalCondition:!0,transition:c.transition,classToRemove:"",screenClassToAdd:"in",containerClassToAdd:"in",applyTransition:!1,prerequisites:this._prerequisites})},_closePrerequisiteScreen:function(){this._ui.screen.removeClass("out").addClass("ui-screen-hidden")},_closePrerequisiteContainer:function(){this._ui.container.removeClass("reverse out").addClass("ui-popup-hidden ui-popup-truncate").removeAttr("style")},_closePrerequisitesDone:function(){var b=this._ui.container,d=this.element.attr("id");a.mobile.popup.active=c,a(":focus",b[0]).add(b[0]).blur(),d&&this.document.find("[aria-haspopup='true'][aria-owns='"+d+"']").attr("aria-expanded",!1),this._trigger("afterclose")},_close:function(b){this._ui.container.removeClass("ui-popup-active"),this._page.removeClass("ui-popup-open"),this._isOpen=!1,this._createPrerequisites(a.proxy(this,"_closePrerequisiteScreen"),a.proxy(this,"_closePrerequisiteContainer"),a.proxy(this,"_closePrerequisitesDone")),this._animate({additionalCondition:this._ui.screen.hasClass("in"),transition:b?"none":this._currentTransition,classToRemove:"in",screenClassToAdd:"out",containerClassToAdd:"reverse out",applyTransition:!0,prerequisites:this._prerequisites})},_unenhance:function(){this.options.enhanced||(this._setOptions({theme:a.mobile.popup.prototype.options.theme}),this.element.detach().insertAfter(this._ui.placeholder).removeClass("ui-popup ui-overlay-shadow ui-corner-all ui-body-inherit"),this._ui.screen.remove(),this._ui.container.remove(),this._ui.placeholder.remove())},_destroy:function(){return a.mobile.popup.active===this?(this.element.one("popupafterclose",a.proxy(this,"_unenhance")),this.close()):this._unenhance(),this},_closePopup:function(c,d){var e,f,g=this.options,h=!1;c&&c.isDefaultPrevented()||a.mobile.popup.active!==this||(b.scrollTo(0,this._scrollTop),c&&"pagebeforechange"===c.type&&d&&(e="string"==typeof d.toPage?d.toPage:d.toPage.jqmData("url"),e=a.mobile.path.parseUrl(e),f=e.pathname+e.search+e.hash,this._myUrl!==a.mobile.path.makeUrlAbsolute(f)?h=!0:c.preventDefault()),this.window.off(g.closeEvents),this.element.undelegate(g.closeLinkSelector,g.closeLinkEvents),this._close(h))},_bindContainerClose:function(){this.window.on(this.options.closeEvents,a.proxy(this,"_closePopup"))},widget:function(){return this._ui.container},open:function(b){var c,d,e,f,g,h,i=this,j=this.options;return a.mobile.popup.active||j.disabled?this:(a.mobile.popup.active=this,this._scrollTop=this.window.scrollTop(),j.history?(h=a.mobile.navigate.history,d=a.mobile.dialogHashKey,e=a.mobile.activePage,f=e?e.hasClass("ui-dialog"):!1,this._myUrl=c=h.getActive().url,(g=c.indexOf(d)>-1&&!f&&h.activeIndex>0)?(i._open(b),i._bindContainerClose(),this):(-1!==c.indexOf(d)||f?c=a.mobile.path.parseLocation().hash+d:c+=c.indexOf("#")>-1?d:"#"+d,this.window.one("beforenavigate",function(a){a.preventDefault(),i._open(b),i._bindContainerClose()}),this.urlAltered=!0,a.mobile.navigate(c,{role:"dialog"}),this)):(i._open(b),i._bindContainerClose(),i.element.delegate(j.closeLinkSelector,j.closeLinkEvents,function(a){i.close(),a.preventDefault()}),this))},close:function(){return a.mobile.popup.active!==this?this:(this._scrollTop=this.window.scrollTop(),this.options.history&&this.urlAltered?(a.mobile.back(),this.urlAltered=!1):this._closePopup(),this)}}),a.mobile.popup.handleLink=function(b){var c,d=a.mobile.path,e=a(d.hashToSelector(d.parseUrl(b.attr("href")).hash)).first();e.length>0&&e.data("mobile-popup")&&(c=b.offset(),e.popup("open",{x:c.left+b.outerWidth()/2,y:c.top+b.outerHeight()/2,transition:b.jqmData("transition"),positionTo:b.jqmData("position-to")})),setTimeout(function(){b.removeClass(a.mobile.activeBtnClass)},300)},a.mobile.document.on("pagebeforechange",function(b,c){"popup"===c.options.role&&(a.mobile.popup.handleLink(c.options.link),b.preventDefault())})}(a),function(a,b){var d=".ui-disabled,.ui-state-disabled,.ui-li-divider,.ui-screen-hidden,:jqmData(role='placeholder')",e=function(a,b,c){var e=a[c+"All"]().not(d).first();e.length&&(b.blur().attr("tabindex","-1"),e.find("a").first().focus())};a.widget("mobile.selectmenu",a.mobile.selectmenu,{_create:function(){var a=this.options;return a.nativeMenu=a.nativeMenu||this.element.parents(":jqmData(role='popup'),:mobile-popup").length>0,this._super()},_handleSelectFocus:function(){this.element.blur(),this.button.focus()},_handleKeydown:function(a){this._super(a),this._handleButtonVclickKeydown(a)},_handleButtonVclickKeydown:function(b){this.options.disabled||this.isOpen||this.options.nativeMenu||("vclick"===b.type||b.keyCode&&(b.keyCode===a.mobile.keyCode.ENTER||b.keyCode===a.mobile.keyCode.SPACE))&&(this._decideFormat(),"overlay"===this.menuType?this.button.attr("href","#"+this.popupId).attr("data-"+(a.mobile.ns||"")+"rel","popup"):this.button.attr("href","#"+this.dialogId).attr("data-"+(a.mobile.ns||"")+"rel","dialog"),this.isOpen=!0)},_handleListFocus:function(b){var c="focusin"===b.type?{tabindex:"0",event:"vmouseover"}:{tabindex:"-1",event:"vmouseout"};a(b.target).attr("tabindex",c.tabindex).trigger(c.event)},_handleListKeydown:function(b){var c=a(b.target),d=c.closest("li");switch(b.keyCode){case 38:return e(d,c,"prev"),!1;case 40:return e(d,c,"next"),!1;case 13:case 32:return c.trigger("click"),!1}},_handleMenuPageHide:function(){this._delayedTrigger(),this.thisPage.page("bindRemove")},_handleHeaderCloseClick:function(){return"overlay"===this.menuType?(this.close(),!1):void 0},_handleListItemClick:function(b){var c=a(b.target).closest("li"),d=this.select[0].selectedIndex,e=a.mobile.getAttribute(c,"option-index"),f=this._selectOptions().eq(e)[0];f.selected=this.isMultiple?!f.selected:!0,this.isMultiple&&c.find("a").toggleClass("ui-checkbox-on",f.selected).toggleClass("ui-checkbox-off",!f.selected),this.isMultiple||d===e||(this._triggerChange=!0),this.isMultiple?(this.select.trigger("change"),this.list.find("li:not(.ui-li-divider)").eq(e).find("a").first().focus()):this.close(),b.preventDefault()},build:function(){var c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v=this.options;return v.nativeMenu?this._super():(c=this.selectId,d=c+"-listbox",e=c+"-dialog",f=this.label,g=this.element.closest(".ui-page"),h=this.element[0].multiple,i=c+"-menu",j=v.theme?" data-"+a.mobile.ns+"theme='"+v.theme+"'":"",k=v.overlayTheme||v.theme||null,l=k?" data-"+a.mobile.ns+"overlay-theme='"+k+"'":"",m=v.dividerTheme&&h?" data-"+a.mobile.ns+"divider-theme='"+v.dividerTheme+"'":"",n=a(""),o=a("").insertAfter(this.select).popup(),p=a("").appendTo(o),q=a("").prependTo(o),r=a("").appendTo(q),this.isMultiple&&(u=a("",{role:"button",text:v.closeText,href:"#","class":"ui-btn ui-corner-all ui-btn-left ui-btn-icon-notext ui-icon-delete"}).appendTo(q)),a.extend(this,{selectId:c,menuId:i,popupId:d,dialogId:e,thisPage:g,menuPage:n,label:f,isMultiple:h,theme:v.theme,listbox:o,list:p,header:q,headerTitle:r,headerClose:u,menuPageContent:s,menuPageClose:t,placeholder:""}),this.refresh(),this._origTabIndex===b&&(this._origTabIndex=null===this.select[0].getAttribute("tabindex")?!1:this.select.attr("tabindex")),this.select.attr("tabindex","-1"),this._on(this.select,{focus:"_handleSelectFocus"}),this._on(this.button,{vclick:"_handleButtonVclickKeydown"}),this.list.attr("role","listbox"),this._on(this.list,{focusin:"_handleListFocus",focusout:"_handleListFocus",keydown:"_handleListKeydown","click li:not(.ui-disabled,.ui-state-disabled,.ui-li-divider)":"_handleListItemClick"}),this._on(this.menuPage,{pagehide:"_handleMenuPageHide"}),this._on(this.listbox,{popupafterclose:"_popupClosed"}),this.isMultiple&&this._on(this.headerClose,{click:"_handleHeaderCloseClick"}),this)},_popupClosed:function(){this.close(),this._delayedTrigger()},_delayedTrigger:function(){this._triggerChange&&this.element.trigger("change"),this._triggerChange=!1},_isRebuildRequired:function(){var a=this.list.find("li"),b=this._selectOptions().not(".ui-screen-hidden");return b.text()!==a.text()},selected:function(){return this._selectOptions().filter(":selected:not( :jqmData(placeholder='true') )")},refresh:function(b){var c,d;return this.options.nativeMenu?this._super(b):(c=this,(b||this._isRebuildRequired())&&c._buildList(),d=this.selectedIndices(),c.setButtonText(),c.setButtonCount(),void c.list.find("li:not(.ui-li-divider)").find("a").removeClass(a.mobile.activeBtnClass).end().attr("aria-selected",!1).each(function(b){var e=a(this);a.inArray(b,d)>-1?(e.attr("aria-selected",!0),c.isMultiple?e.find("a").removeClass("ui-checkbox-off").addClass("ui-checkbox-on"):e.hasClass("ui-screen-hidden")?e.next().find("a").addClass(a.mobile.activeBtnClass):e.find("a").addClass(a.mobile.activeBtnClass)):c.isMultiple&&e.find("a").removeClass("ui-checkbox-on").addClass("ui-checkbox-off")}))},close:function(){if(!this.options.disabled&&this.isOpen){var a=this;"page"===a.menuType?(a.menuPage.dialog("close"),a.list.appendTo(a.listbox)):a.listbox.popup("close"),a._focusButton(),a.isOpen=!1}},open:function(){this.button.click()},_focusMenuItem:function(){var b=this.list.find("a."+a.mobile.activeBtnClass);0===b.length&&(b=this.list.find("li:not("+d+") a.ui-btn")),b.first().focus()},_decideFormat:function(){var b=this,c=this.window,d=b.list.parent(),e=d.outerHeight(),f=c.scrollTop(),g=b.button.offset().top,h=c.height();e>h-80||!a.support.scrollTop?(b.menuPage.appendTo(a.mobile.pageContainer).page(),b.menuPageContent=b.menuPage.find(".ui-content"),b.menuPageClose=b.menuPage.find(".ui-header a"),b.thisPage.unbind("pagehide.remove"),0===f&&g>h&&b.thisPage.one("pagehide",function(){a(this).jqmData("lastScroll",g)}),b.menuPage.one({pageshow:a.proxy(this,"_focusMenuItem"),pagehide:a.proxy(this,"close")}),b.menuType="page",b.menuPageContent.append(b.list),b.menuPage.find("div .ui-title").text(b.label.getEncodedText()||b.placeholder)):(b.menuType="overlay",b.listbox.one({popupafteropen:a.proxy(this,"_focusMenuItem")}))},_buildList:function(){var b,d,e,f,g,h,i,j,k,l,m,n,o,p,q=this,r=this.options,s=this.placeholder,t=!0,u="false",v="data-"+a.mobile.ns,w=v+"option-index",x=v+"icon",y=v+"role",z=v+"placeholder",A=c.createDocumentFragment(),B=!1;for(q.list.empty().filter(".ui-listview").listview("destroy"),b=this._selectOptions(),d=b.length,e=this.select[0],g=0;d>g;g++,B=!1)h=b[g],i=a(h),i.hasClass("ui-screen-hidden")||(j=h.parentNode,m=[],k=i.text(),l=c.createElement("a"),l.setAttribute("href","#"),l.appendChild(c.createTextNode(k)),j!==e&&"optgroup"===j.nodeName.toLowerCase()&&(n=j.getAttribute("label"),n!==f&&(o=c.createElement("li"),o.setAttribute(y,"list-divider"),o.setAttribute("role","option"),o.setAttribute("tabindex","-1"),o.appendChild(c.createTextNode(n)),A.appendChild(o),f=n)),!t||h.getAttribute("value")&&0!==k.length&&!i.jqmData("placeholder")||(t=!1,B=!0,null===h.getAttribute(z)&&(this._removePlaceholderAttr=!0),h.setAttribute(z,!0),r.hidePlaceholderMenuItems&&m.push("ui-screen-hidden"),s!==k&&(s=q.placeholder=k)),p=c.createElement("li"),h.disabled&&(m.push("ui-state-disabled"),p.setAttribute("aria-disabled",!0)),p.setAttribute(w,g),p.setAttribute(x,u),B&&p.setAttribute(z,!0),p.className=m.join(" "),p.setAttribute("role","option"),l.setAttribute("tabindex","-1"),this.isMultiple&&a(l).addClass("ui-btn ui-checkbox-off ui-btn-icon-right"),p.appendChild(l),A.appendChild(p));q.list[0].appendChild(A),this.isMultiple||s.length?this.headerTitle.text(this.placeholder):this.header.addClass("ui-screen-hidden"),q.list.listview()},_button:function(){return this.options.nativeMenu?this._super():a("",{href:"#",role:"button",id:this.buttonId,"aria-haspopup":"true","aria-owns":this.menuId})},_destroy:function(){this.options.nativeMenu||(this.close(),this._origTabIndex!==b&&(this._origTabIndex!==!1?this.select.attr("tabindex",this._origTabIndex):this.select.removeAttr("tabindex")),this._removePlaceholderAttr&&this._selectOptions().removeAttr("data-"+a.mobile.ns+"placeholder"),this.listbox.remove(),this.menuPage.remove()),this._super()}})}(a),function(a,b){function c(a,b){var c=b?b:[];return c.push("ui-btn"),a.theme&&c.push("ui-btn-"+a.theme),a.icon&&(c=c.concat(["ui-icon-"+a.icon,"ui-btn-icon-"+a.iconpos]),a.iconshadow&&c.push("ui-shadow-icon")),a.inline&&c.push("ui-btn-inline"),a.shadow&&c.push("ui-shadow"),a.corners&&c.push("ui-corner-all"),a.mini&&c.push("ui-mini"),c}function d(a){var c,d,e,g=!1,h=!0,i={icon:"",inline:!1,shadow:!1,corners:!1,iconshadow:!1,mini:!1},j=[];for(a=a.split(" "),c=0;c a, .ui-bar > :jqmData(role='controlgroup') > a, button:not(:jqmData(role='navbar') button)"})}(a),function(a,b){a.widget("mobile.controlgroup",a.extend({options:{enhanced:!1,theme:null,shadow:!1,corners:!0,excludeInvisible:!0,type:"vertical",mini:!1},_create:function(){var b=this.element,c=this.options,d=a.mobile.page.prototype.keepNativeSelector();a.fn.buttonMarkup&&this.element.find(a.fn.buttonMarkup.initSelector).not(d).buttonMarkup(),a.each(this._childWidgets,a.proxy(function(b,c){a.mobile[c]&&this.element.find(a.mobile[c].initSelector).not(d)[c]()},this)),a.extend(this,{_ui:null,_initialRefresh:!0}),this._ui=c.enhanced?{groupLegend:b.children(".ui-controlgroup-label").children(),childWrapper:b.children(".ui-controlgroup-controls")}:this._enhance()},_childWidgets:["checkboxradio","selectmenu","button"],_themeClassFromOption:function(a){return a?"none"===a?"":"ui-group-theme-"+a:""},_enhance:function(){var b=this.element,c=this.options,d={groupLegend:b.children("legend"),childWrapper:b.addClass("ui-controlgroup ui-controlgroup-"+("horizontal"===c.type?"horizontal":"vertical")+" "+this._themeClassFromOption(c.theme)+" "+(c.corners?"ui-corner-all ":"")+(c.mini?"ui-mini ":"")).wrapInner("").children()};return d.groupLegend.length>0&&a("").append(d.groupLegend).prependTo(b),d},_init:function(){this.refresh()},_setOptions:function(a){var c,d,e=this.element;return a.type!==b&&(e.removeClass("ui-controlgroup-horizontal ui-controlgroup-vertical").addClass("ui-controlgroup-"+("horizontal"===a.type?"horizontal":"vertical")),c=!0),a.theme!==b&&e.removeClass(this._themeClassFromOption(this.options.theme)).addClass(this._themeClassFromOption(a.theme)),a.corners!==b&&e.toggleClass("ui-corner-all",a.corners),a.mini!==b&&e.toggleClass("ui-mini",a.mini),a.shadow!==b&&this._ui.childWrapper.toggleClass("ui-shadow",a.shadow),a.excludeInvisible!==b&&(this.options.excludeInvisible=a.excludeInvisible,c=!0),d=this._super(a),c&&this.refresh(),d},container:function(){return this._ui.childWrapper},refresh:function(){var b=this.container(),c=b.find(".ui-btn").not(".ui-slider-handle"),d=this._initialRefresh;a.mobile.checkboxradio&&b.find(":mobile-checkboxradio").checkboxradio("refresh"),this._addFirstLastClasses(c,this.options.excludeInvisible?this._getVisibles(c,d):c,d),this._initialRefresh=!1},_destroy:function(){var a,b,c=this.options;return c.enhanced?this:(a=this._ui,b=this.element.removeClass("ui-controlgroup ui-controlgroup-horizontal ui-controlgroup-vertical ui-corner-all ui-mini "+this._themeClassFromOption(c.theme)).find(".ui-btn").not(".ui-slider-handle"),this._removeFirstLastClasses(b),a.groupLegend.unwrap(),void a.childWrapper.children().unwrap())}},a.mobile.behaviors.addFirstLastClasses))}(a),function(a,b){a.widget("mobile.toolbar",{initSelector:":jqmData(role='footer'), :jqmData(role='header')",options:{theme:null,addBackBtn:!1,backBtnTheme:null,backBtnText:"Back"},_create:function(){var b,c,d=this.element.is(":jqmData(role='header')")?"header":"footer",e=this.element.closest(".ui-page");0===e.length&&(e=!1,this._on(this.document,{pageshow:"refresh"})),a.extend(this,{role:d,page:e,leftbtn:b,rightbtn:c}),this.element.attr("role","header"===d?"banner":"contentinfo").addClass("ui-"+d),this.refresh(),this._setOptions(this.options)},_setOptions:function(a){if(a.addBackBtn!==b&&this._updateBackButton(),null!=a.backBtnTheme&&this.element.find(".ui-toolbar-back-btn").addClass("ui-btn ui-btn-"+a.backBtnTheme),a.backBtnText!==b&&this.element.find(".ui-toolbar-back-btn .ui-btn-text").text(a.backBtnText),a.theme!==b){var c=this.options.theme?this.options.theme:"inherit",d=a.theme?a.theme:"inherit";this.element.removeClass("ui-bar-"+c).addClass("ui-bar-"+d)}this._super(a)},refresh:function(){"header"===this.role&&this._addHeaderButtonClasses(),this.page||(this._setRelative(),"footer"===this.role?this.element.appendTo("body"):"header"===this.role&&this._updateBackButton()),this._addHeadingClasses(),this._btnMarkup()},_setRelative:function(){a("[data-"+a.mobile.ns+"role='page']").css({position:"relative"})},_btnMarkup:function(){this.element.children("a").filter(":not([data-"+a.mobile.ns+"role='none'])").attr("data-"+a.mobile.ns+"role","button"),this.element.trigger("create")},_addHeaderButtonClasses:function(){var a=this.element.children("a, button");this.leftbtn=a.hasClass("ui-btn-left")&&!a.hasClass("ui-toolbar-back-btn"),this.rightbtn=a.hasClass("ui-btn-right"),this.leftbtn=this.leftbtn||a.eq(0).not(".ui-btn-right,.ui-toolbar-back-btn").addClass("ui-btn-left").length,this.rightbtn=this.rightbtn||a.eq(1).addClass("ui-btn-right").length},_updateBackButton:function(){var b,c=this.options,d=c.backBtnTheme||c.theme;b=this._backButton=this._backButton||{},this.options.addBackBtn&&"header"===this.role&&a(".ui-page").length>1&&(this.page?this.page[0].getAttribute("data-"+a.mobile.ns+"url")!==a.mobile.path.stripHash(location.hash):a.mobile.navigate&&a.mobile.navigate.history&&a.mobile.navigate.history.activeIndex>0)&&!this.leftbtn?b.attached||(this.backButton=b.element=(b.element||a(""+c.backBtnText+"")).prependTo(this.element),b.attached=!0):b.element&&(b.element.detach(),b.attached=!1)},_addHeadingClasses:function(){this.element.children("h1, h2, h3, h4, h5, h6").addClass("ui-title").attr({role:"heading","aria-level":"1"})},_destroy:function(){var a;this.element.children("h1, h2, h3, h4, h5, h6").removeClass("ui-title").removeAttr("role").removeAttr("aria-level"),"header"===this.role&&(this.element.children("a, button").removeClass("ui-btn-left ui-btn-right ui-btn ui-shadow ui-corner-all"),this.backButton&&this.backButton.remove()),a=this.options.theme?this.options.theme:"inherit",this.element.removeClass("ui-bar-"+a),this.element.removeClass("ui-"+this.role).removeAttr("role")}})}(a),function(a,b){a.widget("mobile.toolbar",a.mobile.toolbar,{options:{position:null,visibleOnPageShow:!0,disablePageZoom:!0,transition:"slide",fullscreen:!1,tapToggle:!0,tapToggleBlacklist:"a, button, input, select, textarea, .ui-header-fixed, .ui-footer-fixed, .ui-flipswitch, .ui-popup, .ui-panel, .ui-panel-dismiss-open",hideDuringFocus:"input, textarea, select",updatePagePadding:!0,trackPersistentToolbars:!0,supportBlacklist:function(){return!a.support.fixedPosition}},_create:function(){this._super(),this.pagecontainer=a(":mobile-pagecontainer"),"fixed"!==this.options.position||this.options.supportBlacklist()||this._makeFixed()},_makeFixed:function(){this.element.addClass("ui-"+this.role+"-fixed"),this.updatePagePadding(),this._addTransitionClass(),this._bindPageEvents(),this._bindToggleHandlers()},_setOptions:function(c){if("fixed"===c.position&&"fixed"!==this.options.position&&this._makeFixed(),"fixed"===this.options.position&&!this.options.supportBlacklist()){var d=this.page?this.page:a(".ui-page-active").length>0?a(".ui-page-active"):a(".ui-page").eq(0);c.fullscreen!==b&&(c.fullscreen?(this.element.addClass("ui-"+this.role+"-fullscreen"),d.addClass("ui-page-"+this.role+"-fullscreen")):(this.element.removeClass("ui-"+this.role+"-fullscreen"),d.removeClass("ui-page-"+this.role+"-fullscreen").addClass("ui-page-"+this.role+"-fixed")))}this._super(c)},_addTransitionClass:function(){var a=this.options.transition;a&&"none"!==a&&("slide"===a&&(a=this.element.hasClass("ui-header")?"slidedown":"slideup"),this.element.addClass(a))},_bindPageEvents:function(){var a=this.page?this.element.closest(".ui-page"):this.document;this._on(a,{pagebeforeshow:"_handlePageBeforeShow",webkitAnimationStart:"_handleAnimationStart",animationstart:"_handleAnimationStart",updatelayout:"_handleAnimationStart",pageshow:"_handlePageShow",pagebeforehide:"_handlePageBeforeHide"})},_handlePageBeforeShow:function(){var b=this.options;b.disablePageZoom&&a.mobile.zoom.disable(!0),b.visibleOnPageShow||this.hide(!0)},_handleAnimationStart:function(){this.options.updatePagePadding&&this.updatePagePadding(this.page?this.page:".ui-page-active")},_handlePageShow:function(){this.updatePagePadding(this.page?this.page:".ui-page-active"),this.options.updatePagePadding&&this._on(this.window,{throttledresize:"updatePagePadding"})},_handlePageBeforeHide:function(b,c){var d,e,f,g,h=this.options;h.disablePageZoom&&a.mobile.zoom.enable(!0),h.updatePagePadding&&this._off(this.window,"throttledresize"),h.trackPersistentToolbars&&(d=a(".ui-footer-fixed:jqmData(id)",this.page),e=a(".ui-header-fixed:jqmData(id)",this.page),f=d.length&&c.nextPage&&a(".ui-footer-fixed:jqmData(id='"+d.jqmData("id")+"')",c.nextPage)||a(),g=e.length&&c.nextPage&&a(".ui-header-fixed:jqmData(id='"+e.jqmData("id")+"')",c.nextPage)||a(),(f.length||g.length)&&(f.add(g).appendTo(a.mobile.pageContainer),c.nextPage.one("pageshow",function(){g.prependTo(this),f.appendTo(this)})))},_visible:!0,updatePagePadding:function(c){var d=this.element,e="header"===this.role,f=parseFloat(d.css(e?"top":"bottom"));this.options.fullscreen||(c=c&&c.type===b&&c||this.page||d.closest(".ui-page"),c=this.page?this.page:".ui-page-active",a(c).css("padding-"+(e?"top":"bottom"),d.outerHeight()+f))},_useTransition:function(b){var c=this.window,d=this.element,e=c.scrollTop(),f=d.height(),g=this.page?d.closest(".ui-page").height():a(".ui-page-active").height(),h=a.mobile.getScreenHeight();return!b&&(this.options.transition&&"none"!==this.options.transition&&("header"===this.role&&!this.options.fullscreen&&e>f||"footer"===this.role&&!this.options.fullscreen&&g-f>e+h)||this.options.fullscreen)},show:function(a){var b="ui-fixed-hidden",c=this.element;this._useTransition(a)?c.removeClass("out "+b).addClass("in").animationComplete(function(){c.removeClass("in")}):c.removeClass(b),this._visible=!0},hide:function(a){var b="ui-fixed-hidden",c=this.element,d="out"+("slide"===this.options.transition?" reverse":"");this._useTransition(a)?c.addClass(d).removeClass("in").animationComplete(function(){c.addClass(b).removeClass(d)}):c.addClass(b).removeClass(d),this._visible=!1},toggle:function(){this[this._visible?"hide":"show"]()},_bindToggleHandlers:function(){var b,c,d=this,e=d.options,f=!0,g=this.page?this.page:a(".ui-page");g.bind("vclick",function(b){e.tapToggle&&!a(b.target).closest(e.tapToggleBlacklist).length&&d.toggle()}).bind("focusin focusout",function(g){screen.width<1025&&a(g.target).is(e.hideDuringFocus)&&!a(g.target).closest(".ui-header-fixed, .ui-footer-fixed").length&&("focusout"!==g.type||f?"focusin"===g.type&&f&&(clearTimeout(b),f=!1,c=setTimeout(function(){d.hide()},0)):(f=!0,clearTimeout(c),b=setTimeout(function(){d.show()},0)))})},_setRelative:function(){"fixed"!==this.options.position&&a("[data-"+a.mobile.ns+"role='page']").css({position:"relative"})},_destroy:function(){var b,c,d,e,f,g=this.pagecontainer.pagecontainer("getActivePage");this._super(),"fixed"===this.options.position&&(d=a("body>.ui-"+this.role+"-fixed").add(g.find(".ui-"+this.options.role+"-fixed")).not(this.element).length>0,f=a("body>.ui-"+this.role+"-fixed").add(g.find(".ui-"+this.options.role+"-fullscreen")).not(this.element).length>0,c="ui-header-fixed ui-footer-fixed ui-header-fullscreen in out ui-footer-fullscreen fade slidedown slideup ui-fixed-hidden",this.element.removeClass(c),f||(b="ui-page-"+this.role+"-fullscreen"),d||(e="header"===this.role,b+=" ui-page-"+this.role+"-fixed",g.css("padding-"+(e?"top":"bottom"),"")),g.removeClass(b))
+}})}(a),function(a){a.widget("mobile.toolbar",a.mobile.toolbar,{_makeFixed:function(){this._super(),this._workarounds()},_workarounds:function(){var a=navigator.userAgent,b=navigator.platform,c=a.match(/AppleWebKit\/([0-9]+)/),d=!!c&&c[1],e=null,f=this;if(b.indexOf("iPhone")>-1||b.indexOf("iPad")>-1||b.indexOf("iPod")>-1)e="ios";else{if(!(a.indexOf("Android")>-1))return;e="android"}if("ios"===e)f._bindScrollWorkaround();else{if(!("android"===e&&d&&534>d))return;f._bindScrollWorkaround(),f._bindListThumbWorkaround()}},_viewportOffset:function(){var a=this.element,b=a.hasClass("ui-header"),c=Math.abs(a.offset().top-this.window.scrollTop());return b||(c=Math.round(c-this.window.height()+a.outerHeight())-60),c},_bindScrollWorkaround:function(){var a=this;this._on(this.window,{scrollstop:function(){var b=a._viewportOffset();b>2&&a._visible&&a._triggerRedraw()}})},_bindListThumbWorkaround:function(){this.element.closest(".ui-page").addClass("ui-android-2x-fixed")},_triggerRedraw:function(){var b=parseFloat(a(".ui-page-active").css("padding-bottom"));a(".ui-page-active").css("padding-bottom",b+1+"px"),setTimeout(function(){a(".ui-page-active").css("padding-bottom",b+"px")},0)},destroy:function(){this._super(),this.element.closest(".ui-page-active").removeClass("ui-android-2x-fix")}})}(a),function(a,b){function c(){var a=e.clone(),b=a.eq(0),c=a.eq(1),d=c.children();return{arEls:c.add(b),gd:b,ct:c,ar:d}}var d=a.mobile.browser.oldIE&&a.mobile.browser.oldIE<=8,e=a("");a.widget("mobile.popup",a.mobile.popup,{options:{arrow:""},_create:function(){var a,b=this._super();return this.options.arrow&&(this._ui.arrow=a=this._addArrow()),b},_addArrow:function(){var a,b=this.options,d=c();return a=this._themeClassFromOption("ui-body-",b.theme),d.ar.addClass(a+(b.shadow?" ui-overlay-shadow":"")),d.arEls.hide().appendTo(this.element),d},_unenhance:function(){var a=this._ui.arrow;return a&&a.arEls.remove(),this._super()},_tryAnArrow:function(a,b,c,d,e){var f,g,h,i={},j={};return d.arFull[a.dimKey]>d.guideDims[a.dimKey]?e:(i[a.fst]=c[a.fst]+(d.arHalf[a.oDimKey]+d.menuHalf[a.oDimKey])*a.offsetFactor-d.contentBox[a.fst]+(d.clampInfo.menuSize[a.oDimKey]-d.contentBox[a.oDimKey])*a.arrowOffsetFactor,i[a.snd]=c[a.snd],f=d.result||this._calculateFinalLocation(i,d.clampInfo),g={x:f.left,y:f.top},j[a.fst]=g[a.fst]+d.contentBox[a.fst]+a.tipOffset,j[a.snd]=Math.max(f[a.prop]+d.guideOffset[a.prop]+d.arHalf[a.dimKey],Math.min(f[a.prop]+d.guideOffset[a.prop]+d.guideDims[a.dimKey]-d.arHalf[a.dimKey],c[a.snd])),h=Math.abs(c.x-j.x)+Math.abs(c.y-j.y),(!e||h0?c:!1,_openedPage:null,_page:this._getPage,_panelInner:this._getPanelInner(),_fixedToolbars:this._getFixedToolbars}),"overlay"!==this.options.display&&this._getWrapper(),this._addPanelClasses(),a.support.cssTransform3d&&this.options.animate&&this.element.addClass(this.options.classes.animate),this._bindUpdateLayout(),this._bindCloseEvents(),this._bindLinkListeners(),this._bindPageEvents(),this.options.dismissible&&this._createModal(),this._bindSwipeEvents()},_getPanelInner:function(){var a=this.element.find("."+this.options.classes.panelInner);return 0===a.length&&(a=this.element.children().wrapAll("").parent()),a},_createModal:function(){var b=this,c=b._parentPage?b._parentPage.parent():b.element.parent();b._modal=a("").on("mousedown",function(){b.close()}).appendTo(c)},_getPage:function(){var b=this._openedPage||this._parentPage||a("."+a.mobile.activePageClass);return b},_getWrapper:function(){var a=this._page().find("."+this.options.classes.pageWrapper);0===a.length&&(a=this._page().children(".ui-header:not(.ui-header-fixed), .ui-content:not(.ui-popup), .ui-footer:not(.ui-footer-fixed)").wrapAll("").parent()),this._wrapper=a},_getFixedToolbars:function(){var b=a("body").children(".ui-header-fixed, .ui-footer-fixed"),c=this._page().find(".ui-header-fixed, .ui-footer-fixed"),d=b.add(c).addClass(this.options.classes.pageFixedToolbar);return d},_getPosDisplayClasses:function(a){return a+"-position-"+this.options.position+" "+a+"-display-"+this.options.display},_getPanelClasses:function(){var a=this.options.classes.panel+" "+this._getPosDisplayClasses(this.options.classes.panel)+" "+this.options.classes.panelClosed+" ui-body-"+(this.options.theme?this.options.theme:"inherit");return this.options.positionFixed&&(a+=" "+this.options.classes.panelFixed),a},_addPanelClasses:function(){this.element.addClass(this._getPanelClasses())},_handleCloseClick:function(a){a.isDefaultPrevented()||this.close()},_bindCloseEvents:function(){this._on(this._closeLink,{click:"_handleCloseClick"}),this._on({"click a:jqmData(ajax='false')":"_handleCloseClick"})},_positionPanel:function(b){var c=this,d=c._panelInner.outerHeight(),e=d>a.mobile.getScreenHeight();e||!c.options.positionFixed?(e&&(c._unfixPanel(),a.mobile.resetActivePageHeight(d)),b&&this.window[0].scrollTo(0,a.mobile.defaultHomeScroll)):c._fixPanel()},_bindFixListener:function(){this._on(a(b),{throttledresize:"_positionPanel"})},_unbindFixListener:function(){this._off(a(b),"throttledresize")},_unfixPanel:function(){this.options.positionFixed&&a.support.fixedPosition&&this.element.removeClass(this.options.classes.panelFixed)},_fixPanel:function(){this.options.positionFixed&&a.support.fixedPosition&&this.element.addClass(this.options.classes.panelFixed)},_bindUpdateLayout:function(){var a=this;a.element.on("updatelayout",function(){a._open&&a._positionPanel()})},_bindLinkListeners:function(){this._on("body",{"click a":"_handleClick"})},_handleClick:function(b){var d,e=this.element.attr("id");b.currentTarget.href.split("#")[1]===e&&e!==c&&(b.preventDefault(),d=a(b.target),d.hasClass("ui-btn")&&(d.addClass(a.mobile.activeBtnClass),this.element.one("panelopen panelclose",function(){d.removeClass(a.mobile.activeBtnClass)})),this.toggle())},_bindSwipeEvents:function(){var a=this,b=a._modal?a.element.add(a._modal):a.element;a.options.swipeClose&&("left"===a.options.position?b.on("swipeleft.panel",function(){a.close()}):b.on("swiperight.panel",function(){a.close()}))},_bindPageEvents:function(){var a=this;this.document.on("panelbeforeopen",function(b){a._open&&b.target!==a.element[0]&&a.close()}).on("keyup.panel",function(b){27===b.keyCode&&a._open&&a.close()}),this._parentPage||"overlay"===this.options.display||this._on(this.document,{pageshow:function(){this._openedPage=null,this._getWrapper()}}),a._parentPage?this.document.on("pagehide",":jqmData(role='page')",function(){a._open&&a.close(!0)}):this.document.on("pagebeforehide",function(){a._open&&a.close(!0)})},_open:!1,_pageContentOpenClasses:null,_modalOpenClasses:null,open:function(b){if(!this._open){var c=this,d=c.options,e=function(){c._off(c.document,"panelclose"),c._page().jqmData("panel","open"),a.support.cssTransform3d&&d.animate&&"overlay"!==d.display&&(c._wrapper.addClass(d.classes.animate),c._fixedToolbars().addClass(d.classes.animate)),!b&&a.support.cssTransform3d&&d.animate?(c._wrapper||c.element).animationComplete(f,"transition"):setTimeout(f,0),d.theme&&"overlay"!==d.display&&c._page().parent().addClass(d.classes.pageContainer+"-themed "+d.classes.pageContainer+"-"+d.theme),c.element.removeClass(d.classes.panelClosed).addClass(d.classes.panelOpen),c._positionPanel(!0),c._pageContentOpenClasses=c._getPosDisplayClasses(d.classes.pageContentPrefix),"overlay"!==d.display&&(c._page().parent().addClass(d.classes.pageContainer),c._wrapper.addClass(c._pageContentOpenClasses),c._fixedToolbars().addClass(c._pageContentOpenClasses)),c._modalOpenClasses=c._getPosDisplayClasses(d.classes.modal)+" "+d.classes.modalOpen,c._modal&&c._modal.addClass(c._modalOpenClasses).height(Math.max(c._modal.height(),c.document.height()))},f=function(){c._open&&("overlay"!==d.display&&(c._wrapper.addClass(d.classes.pageContentPrefix+"-open"),c._fixedToolbars().addClass(d.classes.pageContentPrefix+"-open")),c._bindFixListener(),c._trigger("open"),c._openedPage=c._page())};c._trigger("beforeopen"),"open"===c._page().jqmData("panel")?c._on(c.document,{panelclose:e}):e(),c._open=!0}},close:function(b){if(this._open){var c=this,d=this.options,e=function(){c.element.removeClass(d.classes.panelOpen),"overlay"!==d.display&&(c._wrapper.removeClass(c._pageContentOpenClasses),c._fixedToolbars().removeClass(c._pageContentOpenClasses)),!b&&a.support.cssTransform3d&&d.animate?(c._wrapper||c.element).animationComplete(f,"transition"):setTimeout(f,0),c._modal&&c._modal.removeClass(c._modalOpenClasses).height("")},f=function(){d.theme&&"overlay"!==d.display&&c._page().parent().removeClass(d.classes.pageContainer+"-themed "+d.classes.pageContainer+"-"+d.theme),c.element.addClass(d.classes.panelClosed),"overlay"!==d.display&&(c._page().parent().removeClass(d.classes.pageContainer),c._wrapper.removeClass(d.classes.pageContentPrefix+"-open"),c._fixedToolbars().removeClass(d.classes.pageContentPrefix+"-open")),a.support.cssTransform3d&&d.animate&&"overlay"!==d.display&&(c._wrapper.removeClass(d.classes.animate),c._fixedToolbars().removeClass(d.classes.animate)),c._fixPanel(),c._unbindFixListener(),a.mobile.resetActivePageHeight(),c._page().jqmRemoveData("panel"),c._trigger("close"),c._openedPage=null};c._trigger("beforeclose"),e(),c._open=!1}},toggle:function(){this[this._open?"close":"open"]()},_destroy:function(){var b,c=this.options,d=a("body > :mobile-panel").length+a.mobile.activePage.find(":mobile-panel").length>1;"overlay"!==c.display&&(b=a("body > :mobile-panel").add(a.mobile.activePage.find(":mobile-panel")),0===b.not(".ui-panel-display-overlay").not(this.element).length&&this._wrapper.children().unwrap(),this._open&&(this._fixedToolbars().removeClass(c.classes.pageContentPrefix+"-open"),a.support.cssTransform3d&&c.animate&&this._fixedToolbars().removeClass(c.classes.animate),this._page().parent().removeClass(c.classes.pageContainer),c.theme&&this._page().parent().removeClass(c.classes.pageContainer+"-themed "+c.classes.pageContainer+"-"+c.theme))),d||this.document.off("panelopen panelclose"),this._open&&this._page().jqmRemoveData("panel"),this._panelInner.children().unwrap(),this.element.removeClass([this._getPanelClasses(),c.classes.panelOpen,c.classes.animate].join(" ")).off("swipeleft.panel swiperight.panel").off("panelbeforeopen").off("panelhide").off("keyup.panel").off("updatelayout"),this._modal&&this._modal.remove()}})}(a),function(a,b){a.widget("mobile.table",{options:{classes:{table:"ui-table"},enhanced:!1},_create:function(){this.options.enhanced||this.element.addClass(this.options.classes.table),a.extend(this,{headers:b,allHeaders:b}),this._refresh(!0)},_setHeaders:function(){var a=this.element.find("thead tr");this.headers=this.element.find("tr:eq(0)").children(),this.allHeaders=this.headers.add(a.children())},refresh:function(){this._refresh()},rebuild:a.noop,_refresh:function(){var b=this.element,c=b.find("thead tr");this._setHeaders(),c.each(function(){var d=0;a(this).children().each(function(){var e,f=parseInt(this.getAttribute("colspan"),10),g=":nth-child("+(d+1)+")";if(this.setAttribute("data-"+a.mobile.ns+"colstart",d+1),f)for(e=0;f-1>e;e++)d++,g+=", :nth-child("+(d+1)+")";a(this).jqmData("cells",b.find("tr").not(c.eq(0)).not(this).children(g)),d++})})}})}(a),function(a){a.widget("mobile.table",a.mobile.table,{options:{mode:"columntoggle",columnBtnTheme:null,columnPopupTheme:null,columnBtnText:"Columns...",classes:a.extend(a.mobile.table.prototype.options.classes,{popup:"ui-table-columntoggle-popup",columnBtn:"ui-table-columntoggle-btn",priorityPrefix:"ui-table-priority-",columnToggleTable:"ui-table-columntoggle"})},_create:function(){this._super(),"columntoggle"===this.options.mode&&(a.extend(this,{_menu:null}),this.options.enhanced?(this._menu=a(this.document[0].getElementById(this._id()+"-popup")).children().first(),this._addToggles(this._menu,!0)):(this._menu=this._enhanceColToggle(),this.element.addClass(this.options.classes.columnToggleTable)),this._setupEvents(),this._setToggleState())},_id:function(){return this.element.attr("id")||this.widgetName+this.uuid},_setupEvents:function(){this._on(this.window,{throttledresize:"_setToggleState"}),this._on(this._menu,{"change input":"_menuInputChange"})},_addToggles:function(b,c){var d,e=0,f=this.options,g=b.controlgroup("container");c?d=b.find("input"):g.empty(),this.headers.not("td").each(function(){var b,h,i=a(this),j=a.mobile.getAttribute(this,"priority");j&&(h=i.add(i.jqmData("cells")),h.addClass(f.classes.priorityPrefix+j),b=(c?d.eq(e++):a("").appendTo(g).children(0).checkboxradio({theme:f.columnPopupTheme})).jqmData("header",i).jqmData("cells",h),i.jqmData("input",b))}),c||b.controlgroup("refresh")},_menuInputChange:function(b){var c=a(b.target),d=c[0].checked;c.jqmData("cells").toggleClass("ui-table-cell-hidden",!d).toggleClass("ui-table-cell-visible",d)},_unlockCells:function(a){a.removeClass("ui-table-cell-hidden ui-table-cell-visible")},_enhanceColToggle:function(){var b,c,d,e,f=this.element,g=this.options,h=a.mobile.ns,i=this.document[0].createDocumentFragment();return b=this._id()+"-popup",c=a(""+g.columnBtnText+""),d=a(""),e=a("").controlgroup(),this._addToggles(e,!1),e.appendTo(d),i.appendChild(d[0]),i.appendChild(c[0]),f.before(i),d.popup(),e},rebuild:function(){this._super(),"columntoggle"===this.options.mode&&this._refresh(!1)},_refresh:function(b){var c,d,e;if(this._super(b),!b&&"columntoggle"===this.options.mode)for(c=this.headers,d=[],this._menu.find("input").each(function(){var b=a(this),e=b.jqmData("header"),f=c.index(e[0]);f>-1&&!b.prop("checked")&&d.push(f)}),this._unlockCells(this.element.find(".ui-table-cell-hidden, .ui-table-cell-visible")),this._addToggles(this._menu,b),e=d.length-1;e>-1;e--)c.eq(d[e]).jqmData("input").prop("checked",!1).checkboxradio("refresh").trigger("change")},_setToggleState:function(){this._menu.find("input").each(function(){var b=a(this);this.checked="table-cell"===b.jqmData("cells").eq(0).css("display"),b.checkboxradio("refresh")})},_destroy:function(){this._super()}})}(a),function(a){a.widget("mobile.table",a.mobile.table,{options:{mode:"reflow",classes:a.extend(a.mobile.table.prototype.options.classes,{reflowTable:"ui-table-reflow",cellLabels:"ui-table-cell-label"})},_create:function(){this._super(),"reflow"===this.options.mode&&(this.options.enhanced||(this.element.addClass(this.options.classes.reflowTable),this._updateReflow()))},rebuild:function(){this._super(),"reflow"===this.options.mode&&this._refresh(!1)},_refresh:function(a){this._super(a),a||"reflow"!==this.options.mode||this._updateReflow()},_updateReflow:function(){var b=this,c=this.options;a(b.allHeaders.get().reverse()).each(function(){var d,e,f=a(this).jqmData("cells"),g=a.mobile.getAttribute(this,"colstart"),h=f.not(this).filter("thead th").length&&" ui-table-cell-label-top",i=a(this).clone().contents();i.length>0&&(h?(d=parseInt(this.getAttribute("colspan"),10),e="",d&&(e="td:nth-child("+d+"n + "+g+")"),b._addLabels(f.filter(e),c.classes.cellLabels+h,i)):b._addLabels(f,c.classes.cellLabels,i))})},_addLabels:function(b,c,d){1===d.length&&"abbr"===d[0].nodeName.toLowerCase()&&(d=d.eq(0).attr("title")),b.not(":has(b."+c+")").prepend(a("").append(d))}})}(a),function(a,c){var d=function(b,c){return-1===(""+(a.mobile.getAttribute(this,"filtertext")||a(this).text())).toLowerCase().indexOf(c)};a.widget("mobile.filterable",{initSelector:":jqmData(filter='true')",options:{filterReveal:!1,filterCallback:d,enhanced:!1,input:null,children:"> li, > option, > optgroup option, > tbody tr, > .ui-controlgroup-controls > .ui-btn, > .ui-controlgroup-controls > .ui-checkbox, > .ui-controlgroup-controls > .ui-radio"},_create:function(){var b=this.options;a.extend(this,{_search:null,_timer:0}),this._setInput(b.input),b.enhanced||this._filterItems((this._search&&this._search.val()||"").toLowerCase())},_onKeyUp:function(){var c,d,e=this._search;if(e){if(c=e.val().toLowerCase(),d=a.mobile.getAttribute(e[0],"lastval")+"",d&&d===c)return;this._timer&&(b.clearTimeout(this._timer),this._timer=0),this._timer=this._delay(function(){return this._trigger("beforefilter",null,{input:e})===!1?!1:(e[0].setAttribute("data-"+a.mobile.ns+"lastval",c),this._filterItems(c),void(this._timer=0))},250)}},_getFilterableItems:function(){var b=this.element,c=this.options.children,d=c?a.isFunction(c)?c():c.nodeName?a(c):c.jquery?c:this.element.find(c):{length:0};return 0===d.length&&(d=b.children()),d},_filterItems:function(b){var c,e,f,g,h=[],i=[],j=this.options,k=this._getFilterableItems();if(null!=b)for(e=j.filterCallback||d,f=k.length,c=0;f>c;c++)g=e.call(k[c],c,b)?i:h,g.push(k[c]);0===i.length?k[j.filterReveal&&0===b.length?"addClass":"removeClass"]("ui-screen-hidden"):(a(i).addClass("ui-screen-hidden"),a(h).removeClass("ui-screen-hidden")),this._refreshChildWidget(),this._trigger("filter",null,{items:k})},_refreshChildWidget:function(){var b,c,d=["collapsibleset","selectmenu","controlgroup","listview"];for(c=d.length-1;c>-1;c--)b=d[c],a.mobile[b]&&(b=this.element.data("mobile-"+b),b&&a.isFunction(b.refresh)&&b.refresh())},_setInput:function(c){var d=this._search;this._timer&&(b.clearTimeout(this._timer),this._timer=0),d&&(this._off(d,"keyup change input"),d=null),c&&(d=c.jquery?c:c.nodeName?a(c):this.document.find(c),this._on(d,{keydown:"_onKeyDown",keypress:"_onKeyPress",keyup:"_onKeyUp",change:"_onKeyUp",input:"_onKeyUp"})),this._search=d},_onKeyDown:function(b){b.keyCode===a.ui.keyCode.ENTER&&(b.preventDefault(),this._preventKeyPress=!0)},_onKeyPress:function(a){this._preventKeyPress&&(a.preventDefault(),this._preventKeyPress=!1)},_setOptions:function(a){var b=!(a.filterReveal===c&&a.filterCallback===c&&a.children===c);this._super(a),a.input!==c&&(this._setInput(a.input),b=!0),b&&this.refresh()},_destroy:function(){var a=this.options,b=this._getFilterableItems();a.enhanced?b.toggleClass("ui-screen-hidden",a.filterReveal):b.removeClass("ui-screen-hidden")},refresh:function(){this._timer&&(b.clearTimeout(this._timer),this._timer=0),this._filterItems((this._search&&this._search.val()||"").toLowerCase())}})}(a),function(a,b){var c=function(a,b){return function(c){b.call(this,c),a._syncTextInputOptions(c)}},d=/(^|\s)ui-li-divider(\s|$)/,e=a.mobile.filterable.prototype.options.filterCallback;a.mobile.filterable.prototype.options.filterCallback=function(a,b){return!this.className.match(d)&&e.call(this,a,b)},a.widget("mobile.filterable",a.mobile.filterable,{options:{filterPlaceholder:"Filter items...",filterTheme:null},_create:function(){var b,c,d=this.element,e=["collapsibleset","selectmenu","controlgroup","listview"],f={};for(this._super(),a.extend(this,{_widget:null}),b=e.length-1;b>-1;b--)if(c=e[b],a.mobile[c]){if(this._setWidget(d.data("mobile-"+c)))break;f[c+"create"]="_handleCreate"}this._widget||this._on(d,f)},_handleCreate:function(a){this._setWidget(this.element.data("mobile-"+a.type.substring(0,a.type.length-6)))},_trigger:function(a,b,c){return this._widget&&"mobile-listview"===this._widget.widgetFullName&&"beforefilter"===a&&this._widget._trigger("beforefilter",b,c),this._super(a,b,c)},_setWidget:function(a){return!this._widget&&a&&(this._widget=a,this._widget._setOptions=c(this,this._widget._setOptions)),this._widget&&(this._syncTextInputOptions(this._widget.options),"listview"===this._widget.widgetName&&(this._widget.options.hideDividers=!0,this._widget.element.listview("refresh"))),!!this._widget},_isSearchInternal:function(){return this._search&&this._search.jqmData("ui-filterable-"+this.uuid+"-internal")},_setInput:function(b){var c=this.options,d=!0,e={};if(!b){if(this._isSearchInternal())return;d=!1,b=a("").jqmData("ui-filterable-"+this.uuid+"-internal",!0),a("").append(b).submit(function(a){a.preventDefault(),b.blur()}).insertBefore(this.element),a.mobile.textinput&&(null!=this.options.filterTheme&&(e.theme=c.filterTheme),b.textinput(e))}this._super(b),this._isSearchInternal()&&d&&this._search.attr("placeholder",this.options.filterPlaceholder)},_setOptions:function(c){var d=this._super(c);return c.filterPlaceholder!==b&&this._isSearchInternal()&&this._search.attr("placeholder",c.filterPlaceholder),c.filterTheme!==b&&this._search&&a.mobile.textinput&&this._search.textinput("option","theme",c.filterTheme),d},_refreshChildWidget:function(){this._refreshingChildWidget=!0,this._superApply(arguments),this._refreshingChildWidget=!1},refresh:function(){this._refreshingChildWidget||this._superApply(arguments)},_destroy:function(){this._isSearchInternal()&&this._search.remove(),this._super()},_syncTextInputOptions:function(c){var d,e={};if(this._isSearchInternal()&&a.mobile.textinput){for(d in a.mobile.textinput.prototype.options)c[d]!==b&&(e[d]="theme"===d&&null!=this.options.filterTheme?this.options.filterTheme:c[d]);this._search.textinput("option",e)}}}),a.widget("mobile.listview",a.mobile.listview,{options:{filter:!1},_create:function(){return this.options.filter!==!0||this.element.data("mobile-filterable")||this.element.filterable(),this._super()},refresh:function(){var a;this._superApply(arguments),this.options.filter===!0&&(a=this.element.data("mobile-filterable"),a&&a.refresh())}})}(a),function(a,b){function c(){return++e}function d(a){return a.hash.length>1&&decodeURIComponent(a.href.replace(f,""))===decodeURIComponent(location.href.replace(f,""))}var e=0,f=/#.*$/;a.widget("ui.tabs",{version:"fadf2b312a05040436451c64bbfaf4814bc62c56",delay:300,options:{active:null,collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_create:function(){var b=this,c=this.options;this.running=!1,this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all").toggleClass("ui-tabs-collapsible",c.collapsible).delegate(".ui-tabs-nav > li","mousedown"+this.eventNamespace,function(b){a(this).is(".ui-state-disabled")&&b.preventDefault()}).delegate(".ui-tabs-anchor","focus"+this.eventNamespace,function(){a(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this._processTabs(),c.active=this._initialActive(),a.isArray(c.disabled)&&(c.disabled=a.unique(c.disabled.concat(a.map(this.tabs.filter(".ui-state-disabled"),function(a){return b.tabs.index(a)}))).sort()),this.active=this.options.active!==!1&&this.anchors.length?this._findActive(c.active):a(),this._refresh(),this.active.length&&this.load(c.active)},_initialActive:function(){var b=this.options.active,c=this.options.collapsible,d=location.hash.substring(1);return null===b&&(d&&this.tabs.each(function(c,e){return a(e).attr("aria-controls")===d?(b=c,!1):void 0}),null===b&&(b=this.tabs.index(this.tabs.filter(".ui-tabs-active"))),(null===b||-1===b)&&(b=this.tabs.length?0:!1)),b!==!1&&(b=this.tabs.index(this.tabs.eq(b)),-1===b&&(b=c?!1:0)),!c&&b===!1&&this.anchors.length&&(b=0),b},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):a()}},_tabKeydown:function(b){var c=a(this.document[0].activeElement).closest("li"),d=this.tabs.index(c),e=!0;if(!this._handlePageNav(b)){switch(b.keyCode){case a.ui.keyCode.RIGHT:case a.ui.keyCode.DOWN:d++;break;case a.ui.keyCode.UP:case a.ui.keyCode.LEFT:e=!1,d--;break;case a.ui.keyCode.END:d=this.anchors.length-1;break;case a.ui.keyCode.HOME:d=0;break;case a.ui.keyCode.SPACE:return b.preventDefault(),clearTimeout(this.activating),void this._activate(d);case a.ui.keyCode.ENTER:return b.preventDefault(),clearTimeout(this.activating),void this._activate(d===this.options.active?!1:d);default:return}b.preventDefault(),clearTimeout(this.activating),d=this._focusNextTab(d,e),b.ctrlKey||(c.attr("aria-selected","false"),this.tabs.eq(d).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",d)},this.delay))}},_panelKeydown:function(b){this._handlePageNav(b)||b.ctrlKey&&b.keyCode===a.ui.keyCode.UP&&(b.preventDefault(),this.active.focus())},_handlePageNav:function(b){return b.altKey&&b.keyCode===a.ui.keyCode.PAGE_UP?(this._activate(this._focusNextTab(this.options.active-1,!1)),!0):b.altKey&&b.keyCode===a.ui.keyCode.PAGE_DOWN?(this._activate(this._focusNextTab(this.options.active+1,!0)),!0):void 0},_findNextTab:function(b,c){function d(){return b>e&&(b=0),0>b&&(b=e),b}for(var e=this.tabs.length-1;-1!==a.inArray(d(),this.options.disabled);)b=c?b+1:b-1;return b},_focusNextTab:function(a,b){return a=this._findNextTab(a,b),this.tabs.eq(a).focus(),a},_setOption:function(a,b){return"active"===a?void this._activate(b):"disabled"===a?void this._setupDisabled(b):(this._super(a,b),"collapsible"===a&&(this.element.toggleClass("ui-tabs-collapsible",b),b||this.options.active!==!1||this._activate(0)),"event"===a&&this._setupEvents(b),void("heightStyle"===a&&this._setupHeightStyle(b)))},_tabId:function(a){return a.attr("aria-controls")||"ui-tabs-"+c()},_sanitizeSelector:function(a){return a?a.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var b=this.options,c=this.tablist.children(":has(a[href])");b.disabled=a.map(c.filter(".ui-state-disabled"),function(a){return c.index(a)}),this._processTabs(),b.active!==!1&&this.anchors.length?this.active.length&&!a.contains(this.tablist[0],this.active[0])?this.tabs.length===b.disabled.length?(b.active=!1,this.active=a()):this._activate(this._findNextTab(Math.max(0,b.active-1),!1)):b.active=this.tabs.index(this.active):(b.active=!1,this.active=a()),this._refresh()},_refresh:function(){this._setupDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-expanded":"false","aria-hidden":"true"}),this.active.length?(this.active.addClass("ui-tabs-active ui-state-active").attr({"aria-selected":"true",tabIndex:0}),this._getPanelForTab(this.active).show().attr({"aria-expanded":"true","aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var b=this;this.tablist=this._getList().addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").attr("role","tablist"),this.tabs=this.tablist.find("> li:has(a[href])").addClass("ui-state-default ui-corner-top").attr({role:"tab",tabIndex:-1}),this.anchors=this.tabs.map(function(){return a("a",this)[0]}).addClass("ui-tabs-anchor").attr({role:"presentation",tabIndex:-1}),this.panels=a(),this.anchors.each(function(c,e){var f,g,h,i=a(e).uniqueId().attr("id"),j=a(e).closest("li"),k=j.attr("aria-controls");d(e)?(f=e.hash,g=b.element.find(b._sanitizeSelector(f))):(h=b._tabId(j),f="#"+h,g=b.element.find(f),g.length||(g=b._createPanel(h),g.insertAfter(b.panels[c-1]||b.tablist)),g.attr("aria-live","polite")),g.length&&(b.panels=b.panels.add(g)),k&&j.data("ui-tabs-aria-controls",k),j.attr({"aria-controls":f.substring(1),"aria-labelledby":i}),g.attr("aria-labelledby",i)}),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel")},_getList:function(){return this.element.find("ol,ul").eq(0)},_createPanel:function(b){return a("").attr("id",b).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)},_setupDisabled:function(b){a.isArray(b)&&(b.length?b.length===this.anchors.length&&(b=!0):b=!1);for(var c,d=0;c=this.tabs[d];d++)b===!0||-1!==a.inArray(d,b)?a(c).addClass("ui-state-disabled").attr("aria-disabled","true"):a(c).removeClass("ui-state-disabled").removeAttr("aria-disabled");this.options.disabled=b},_setupEvents:function(b){var c={click:function(a){a.preventDefault()}};b&&a.each(b.split(" "),function(a,b){c[b]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(this.anchors,c),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(b){var c,d=this.element.parent();"fill"===b?(c=d.height(),c-=this.element.outerHeight()-this.element.height(),this.element.siblings(":visible").each(function(){var b=a(this),d=b.css("position");"absolute"!==d&&"fixed"!==d&&(c-=b.outerHeight(!0))}),this.element.children().not(this.panels).each(function(){c-=a(this).outerHeight(!0)
+}),this.panels.each(function(){a(this).height(Math.max(0,c-a(this).innerHeight()+a(this).height()))}).css("overflow","auto")):"auto"===b&&(c=0,this.panels.each(function(){c=Math.max(c,a(this).height("").height())}).height(c))},_eventHandler:function(b){var c=this.options,d=this.active,e=a(b.currentTarget),f=e.closest("li"),g=f[0]===d[0],h=g&&c.collapsible,i=h?a():this._getPanelForTab(f),j=d.length?this._getPanelForTab(d):a(),k={oldTab:d,oldPanel:j,newTab:h?a():f,newPanel:i};b.preventDefault(),f.hasClass("ui-state-disabled")||f.hasClass("ui-tabs-loading")||this.running||g&&!c.collapsible||this._trigger("beforeActivate",b,k)===!1||(c.active=h?!1:this.tabs.index(f),this.active=g?a():f,this.xhr&&this.xhr.abort(),j.length||i.length||a.error("jQuery UI Tabs: Mismatching fragment identifier."),i.length&&this.load(this.tabs.index(f),b),this._toggle(b,k))},_toggle:function(b,c){function d(){f.running=!1,f._trigger("activate",b,c)}function e(){c.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),g.length&&f.options.show?f._show(g,f.options.show,d):(g.show(),d())}var f=this,g=c.newPanel,h=c.oldPanel;this.running=!0,h.length&&this.options.hide?this._hide(h,this.options.hide,function(){c.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),e()}):(c.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),h.hide(),e()),h.attr({"aria-expanded":"false","aria-hidden":"true"}),c.oldTab.attr("aria-selected","false"),g.length&&h.length?c.oldTab.attr("tabIndex",-1):g.length&&this.tabs.filter(function(){return 0===a(this).attr("tabIndex")}).attr("tabIndex",-1),g.attr({"aria-expanded":"true","aria-hidden":"false"}),c.newTab.attr({"aria-selected":"true",tabIndex:0})},_activate:function(b){var c,d=this._findActive(b);d[0]!==this.active[0]&&(d.length||(d=this.active),c=d.find(".ui-tabs-anchor")[0],this._eventHandler({target:c,currentTarget:c,preventDefault:a.noop}))},_findActive:function(b){return b===!1?a():this.tabs.eq(b)},_getIndex:function(a){return"string"==typeof a&&(a=this.anchors.index(this.anchors.filter("[href$='"+a+"']"))),a},_destroy:function(){this.xhr&&this.xhr.abort(),this.element.removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible"),this.tablist.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").removeAttr("role"),this.anchors.removeClass("ui-tabs-anchor").removeAttr("role").removeAttr("tabIndex").removeUniqueId(),this.tabs.add(this.panels).each(function(){a.data(this,"ui-tabs-destroy")?a(this).remove():a(this).removeClass("ui-state-default ui-state-active ui-state-disabled ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel").removeAttr("tabIndex").removeAttr("aria-live").removeAttr("aria-busy").removeAttr("aria-selected").removeAttr("aria-labelledby").removeAttr("aria-hidden").removeAttr("aria-expanded").removeAttr("role")}),this.tabs.each(function(){var b=a(this),c=b.data("ui-tabs-aria-controls");c?b.attr("aria-controls",c).removeData("ui-tabs-aria-controls"):b.removeAttr("aria-controls")}),this.panels.show(),"content"!==this.options.heightStyle&&this.panels.css("height","")},enable:function(c){var d=this.options.disabled;d!==!1&&(c===b?d=!1:(c=this._getIndex(c),d=a.isArray(d)?a.map(d,function(a){return a!==c?a:null}):a.map(this.tabs,function(a,b){return b!==c?b:null})),this._setupDisabled(d))},disable:function(c){var d=this.options.disabled;if(d!==!0){if(c===b)d=!0;else{if(c=this._getIndex(c),-1!==a.inArray(c,d))return;d=a.isArray(d)?a.merge([c],d).sort():[c]}this._setupDisabled(d)}},load:function(b,c){b=this._getIndex(b);var e=this,f=this.tabs.eq(b),g=f.find(".ui-tabs-anchor"),h=this._getPanelForTab(f),i={tab:f,panel:h};d(g[0])||(this.xhr=a.ajax(this._ajaxSettings(g,c,i)),this.xhr&&"canceled"!==this.xhr.statusText&&(f.addClass("ui-tabs-loading"),h.attr("aria-busy","true"),this.xhr.success(function(a){setTimeout(function(){h.html(a),e._trigger("load",c,i)},1)}).complete(function(a,b){setTimeout(function(){"abort"===b&&e.panels.stop(!1,!0),f.removeClass("ui-tabs-loading"),h.removeAttr("aria-busy"),a===e.xhr&&delete e.xhr},1)})))},_ajaxSettings:function(b,c,d){var e=this;return{url:b.attr("href"),beforeSend:function(b,f){return e._trigger("beforeLoad",c,a.extend({jqXHR:b,ajaxSettings:f},d))}}},_getPanelForTab:function(b){var c=a(b).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+c))}})}(a),function(){}(a),function(a,b){function c(a){e=a.originalEvent,i=e.accelerationIncludingGravity,f=Math.abs(i.x),g=Math.abs(i.y),h=Math.abs(i.z),!b.orientation&&(f>7||(h>6&&8>g||8>h&&g>6)&&f>5)?d.enabled&&d.disable():d.enabled||d.enable()}a.mobile.iosorientationfixEnabled=!0;var d,e,f,g,h,i,j=navigator.userAgent;return/iPhone|iPad|iPod/.test(navigator.platform)&&/OS [1-5]_[0-9_]* like Mac OS X/i.test(j)&&j.indexOf("AppleWebKit")>-1?(d=a.mobile.zoom,void a.mobile.document.on("mobileinit",function(){a.mobile.iosorientationfixEnabled&&a.mobile.window.bind("orientationchange.iosorientationfix",d.enable).bind("devicemotion.iosorientationfix",c)})):void(a.mobile.iosorientationfixEnabled=!1)}(a,this),function(a,b,d){function e(){f.removeClass("ui-mobile-rendering")}var f=a("html"),g=a.mobile.window;a(b.document).trigger("mobileinit"),a.mobile.gradeA()&&(a.mobile.ajaxBlacklist&&(a.mobile.ajaxEnabled=!1),f.addClass("ui-mobile ui-mobile-rendering"),setTimeout(e,5e3),a.extend(a.mobile,{initializePage:function(){var b=a.mobile.path,f=a(":jqmData(role='page'), :jqmData(role='dialog')"),h=b.stripHash(b.stripQueryParams(b.parseLocation().hash)),i=a.mobile.path.parseLocation(),j=h?c.getElementById(h):d;f.length||(f=a("body").wrapInner("
").children(0)),f.each(function(){var c=a(this);c[0].getAttribute("data-"+a.mobile.ns+"url")||c.attr("data-"+a.mobile.ns+"url",c.attr("id")||b.convertUrlToDataUrl(i.pathname+i.search))}),a.mobile.firstPage=f.first(),a.mobile.pageContainer=a.mobile.firstPage.parent().addClass("ui-mobile-viewport").pagecontainer(),a.mobile.navreadyDeferred.resolve(),g.trigger("pagecontainercreate"),a.mobile.loading("show"),e(),a.mobile.hashListeningEnabled&&a.mobile.path.isHashValid(location.hash)&&(a(j).is(":jqmData(role='page')")||a.mobile.path.isPath(h)||h===a.mobile.dialogHashKey)?a.event.special.navigate.isPushStateEnabled()?(a.mobile.navigate.history.stack=[],a.mobile.navigate(a.mobile.path.isPath(location.hash)?location.hash:location.href)):g.trigger("hashchange",[!0]):(a.event.special.navigate.isPushStateEnabled()&&a.mobile.navigate.navigator.squash(b.parseLocation().href),a.mobile.changePage(a.mobile.firstPage,{transition:"none",reverse:!0,changeHash:!1,fromHashChange:!0}))}}),a(function(){a.support.inlineSVG(),a.mobile.hideUrlBar&&b.scrollTo(0,1),a.mobile.defaultHomeScroll=a.support.scrollTop&&1!==a.mobile.window.scrollTop()?1:0,a.mobile.autoInitializePage&&a.mobile.initializePage(),a.mobile.hideUrlBar&&g.load(a.mobile.silentScroll),a.support.cssPointerEvents||a.mobile.document.delegate(".ui-state-disabled,.ui-disabled","vclick",function(a){a.preventDefault(),a.stopImmediatePropagation()})}))}(a,this)});
+//# sourceMappingURL=jquery.mobile-1.4.5.min.map
\ No newline at end of file
diff --git a/user/themes/gilles_acezat/js/jquery.mousewheel.min.js b/user/themes/gilles_acezat/js/jquery.mousewheel.min.js
new file mode 100644
index 0000000..03bfd60
--- /dev/null
+++ b/user/themes/gilles_acezat/js/jquery.mousewheel.min.js
@@ -0,0 +1,8 @@
+/*!
+ * jQuery Mousewheel 3.1.13
+ *
+ * Copyright 2015 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?module.exports=a:a(jQuery)}(function(a){function b(b){var g=b||window.event,h=i.call(arguments,1),j=0,l=0,m=0,n=0,o=0,p=0;if(b=a.event.fix(g),b.type="mousewheel","detail"in g&&(m=-1*g.detail),"wheelDelta"in g&&(m=g.wheelDelta),"wheelDeltaY"in g&&(m=g.wheelDeltaY),"wheelDeltaX"in g&&(l=-1*g.wheelDeltaX),"axis"in g&&g.axis===g.HORIZONTAL_AXIS&&(l=-1*m,m=0),j=0===m?l:m,"deltaY"in g&&(m=-1*g.deltaY,j=m),"deltaX"in g&&(l=g.deltaX,0===m&&(j=-1*l)),0!==m||0!==l){if(1===g.deltaMode){var q=a.data(this,"mousewheel-line-height");j*=q,m*=q,l*=q}else if(2===g.deltaMode){var r=a.data(this,"mousewheel-page-height");j*=r,m*=r,l*=r}if(n=Math.max(Math.abs(m),Math.abs(l)),(!f||f>n)&&(f=n,d(g,n)&&(f/=40)),d(g,n)&&(j/=40,l/=40,m/=40),j=Math[j>=1?"floor":"ceil"](j/f),l=Math[l>=1?"floor":"ceil"](l/f),m=Math[m>=1?"floor":"ceil"](m/f),k.settings.normalizeOffset&&this.getBoundingClientRect){var s=this.getBoundingClientRect();o=b.clientX-s.left,p=b.clientY-s.top}return b.deltaX=l,b.deltaY=m,b.deltaFactor=f,b.offsetX=o,b.offsetY=p,b.deltaMode=0,h.unshift(b,j,l,m),e&&clearTimeout(e),e=setTimeout(c,200),(a.event.dispatch||a.event.handle).apply(this,h)}}function c(){f=null}function d(a,b){return k.settings.adjustOldDeltas&&"mousewheel"===a.type&&b%120===0}var e,f,g=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],h="onwheel"in document||document.documentMode>=9?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],i=Array.prototype.slice;if(a.event.fixHooks)for(var j=g.length;j;)a.event.fixHooks[g[--j]]=a.event.mouseHooks;var k=a.event.special.mousewheel={version:"3.1.12",setup:function(){if(this.addEventListener)for(var c=h.length;c;)this.addEventListener(h[--c],b,!1);else this.onmousewheel=b;a.data(this,"mousewheel-line-height",k.getLineHeight(this)),a.data(this,"mousewheel-page-height",k.getPageHeight(this))},teardown:function(){if(this.removeEventListener)for(var c=h.length;c;)this.removeEventListener(h[--c],b,!1);else this.onmousewheel=null;a.removeData(this,"mousewheel-line-height"),a.removeData(this,"mousewheel-page-height")},getLineHeight:function(b){var c=a(b),d=c["offsetParent"in a.fn?"offsetParent":"parent"]();return d.length||(d=a("body")),parseInt(d.css("fontSize"),10)||parseInt(c.css("fontSize"),10)||16},getPageHeight:function(b){return a(b).height()},settings:{adjustOldDeltas:!0,normalizeOffset:!0}};a.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})});
\ No newline at end of file
diff --git a/user/themes/gilles_acezat/js/multipleFilterMasonry.js b/user/themes/gilles_acezat/js/multipleFilterMasonry.js
new file mode 100644
index 0000000..d8bf4e5
--- /dev/null
+++ b/user/themes/gilles_acezat/js/multipleFilterMasonry.js
@@ -0,0 +1,97 @@
+(function($){
+ 'use strict';
+ $.fn.multipleFilterMasonry = function(options){
+ var cache=[];
+ var filters = [];
+
+ if(options.selectorType === 'list') {
+ $(options.filtersGroupSelector).children().each(function() {
+ filters.push($(this).data('filter'));
+ });
+ }
+
+ //the main job of the function is to cache the item,because we are going to filter the items later
+ var init = function($container){
+ $container.find(options.itemSelector).each(function(){
+ cache.push($(this));
+ });
+ $container.masonry(options);
+ };
+
+ //filter items in cache
+ var filterItems = function(selector){
+ var result=[];
+ $(cache).each(function(item){
+ $(selector).each(function(index,sel) {
+ if(cache[item].is(sel)){
+ if($.inArray(cache[item], result) === -1) result.push(cache[item]);
+ }
+ });
+ });
+ return result;
+ };
+
+ //reload masonry
+ var reload = function($container,items){
+ $container.empty();
+ $(items).each(function(){
+ $($container).append($(this));
+ });
+ $container.masonry('reloadItems');
+ $container.masonry();
+ };
+
+ // Hash filter
+ var hashFilter = function($container) {
+ var hash = window.location.hash.replace("#", "");
+ if($.inArray(hash, filters) !== -1) {
+ reload($container, $('.' + hash));
+ }
+ };
+
+ var proc = function($container){
+ $(options.filtersGroupSelector).find('input[type=checkbox]').each(function(){
+ $(this).change(function(){
+ var selector = [];
+ $(options.filtersGroupSelector).find('input[type=checkbox]').each( function() {
+ if ( $(this).is(':checked') ) {
+ selector.push( '.' + $(this).val() );
+ }
+ });
+ var items = cache;
+ if (selector.length > 0) {
+ items = filterItems(selector);
+ }
+ reload($container,items);
+ });
+ });
+ };
+
+ var procUL = function($container){
+ $(options.filtersGroupSelector).children().each(function(){
+ $(this).click(function(){
+ $(options.filtersGroupSelector).children().removeClass('selected');
+ window.location.hash = $(this).data('filter');
+ var selector = [];
+ selector.push( '.' + $(this).data('filter') );
+ $(this).addClass('selected');
+ var items = cache;
+ if (selector.length > 0) {
+ items = filterItems(selector);
+ }
+ reload($container,items);
+ });
+ });
+
+ hashFilter($container);
+ $(options.filtersGroupSelector).children().removeClass('selected');
+ $('.filters li[data-filter='+window.location.hash.replace("#", "")+']').addClass('selected');
+ };
+
+ return this.each(function() {
+ var $$ = $(this);
+ init($$);
+ options.selectorType === 'list' ? procUL($$) : proc($$);
+ });
+ };
+}(window.jQuery));
diff --git a/user/themes/gilles_acezat/js/site.js b/user/themes/gilles_acezat/js/site.js
index 2dbc5e9..6fc66cc 100644
--- a/user/themes/gilles_acezat/js/site.js
+++ b/user/themes/gilles_acezat/js/site.js
@@ -4,128 +4,97 @@ var $cat = $('.cat-list .cat a');
var $img = $('.item #items .img');
function masonry() {
- var $grid = $cards.masonry({
- itemSelector: '.card',
- columnWidth: '.card',
- percentPosition: true,
- transitionDuration: '0.2s'
- });
+ var $grid = $cards.masonry({
+ itemSelector: '.card',
+ columnWidth: '.card',
+ percentPosition: true,
+ gutter: 10,
+ transitionDuration: '0.2s'
+ });
+ var $grid2 = $img.masonry({
+ itemSelector: '.img-card',
+ columnWidth: '.img-card',
+ percentPosition: true,
+ gutter: 10,
+ transitionDuration: '0.2s'
+ });
+ $grid.imagesLoaded().progress(function() {
+ $grid.masonry();
+ });
+ $grid2.imagesLoaded().progress(function() {
+ $grid2.masonry();
+ });
+}
- var $grid2 = $img.masonry({
- itemSelector: '.img-card',
- columnWidth: '.img-card',
- percentPosition: true,
- gutter: 20,
- transitionDuration: '0.2s'
- });
+function filteritem() {
+ var $container = $('#masonryContainer');
- $grid.imagesLoaded().progress( function() {
- $grid.masonry();
- });
-
- $grid2.imagesLoaded().progress( function() {
- $grid2.masonry();
- });
+ $cards.multipleFilterMasonry({
+ itemSelector: '.card',
+ filtersGroupSelector: '.cat-list'
+ });
}
function contact() {
- var $contact = $('header .contact');
+ var $contact = $('header .contact');
+
+ $contact.on('click', function() {
+ if ($(this).hasClass('open')) {
+ $(this).removeClass('open');
+ $(this).addClass('close');
+ } else {
+ $(this).addClass('open');
+ $(this).removeClass('close');
+ }
+ })
+}
+
+function customscroll() {
+ var $page = $('#page-wrapper.blog')
+ var $page_item = $("body.blog #start, body.item");
+
+ $("body").mCustomScrollbar({
+ theme: "dark",
+ scrollbarPosition: "outside",
+ callbacks: {
+ whileScrolling: function() {
+ var pct = this.mcs.topPct;
+ if (pct < 1) {
+ $page.addClass("up");
+ $page.removeClass("down");
+ } else {
+ $page.removeClass("up");
+ $page.addClass("down")
+ }
+ }
+ }
+ });
+
+ $page_item.mCustomScrollbar({
+ theme: "dark",
+ scrollbarPosition: "outside",
+ scrollInertia: 1000,
+ });
+}
+
+function scrolltop() {
+ var $top = $('.top');
+
+ $top.on('click', function(e) {
+ // e.preventDefault()
- $contact.on('click',function() {
- if ($(this).hasClass('open')) {
- $(this).removeClass('open');
- $(this).addClass('close');
- } else {
- $(this).addClass('open');
- $(this).removeClass('close');
- }
})
}
-function scroll() {
- // left: 37, up: 38, right: 39, down: 40,
- // spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36
- if ($('#page-wrapper.blog').length > 0){
+jQuery(document).ready(function($) {
+ masonry();
+ filteritem();
+ contact();
+ customscroll();
+ // scrolltop();
- $(window).bind('mousewheel DOMMouseScroll', function(event){
- var $height = $(window).scrollTop();
- var $heightW = $(window).height();
- var wh = $heightW - $height;
-
- var keys = {32: 1, 37: 1, 38: 1, 39: 1, 40: 1};
-
- function preventDefault(e) {
- e = e || window.event;
- if (e.preventDefault)
- e.preventDefault();
- e.returnValue = false;
- }
-
- function preventDefaultForScrollKeys(e) {
- if (keys[e.keyCode]) {
- preventDefault(e);
- return false;
- }
- }
-
- function disablescroll() {
- if (window.addEventListener) // older FF
- window.addEventListener('DOMMouseScroll', preventDefault, false);
- window.onwheel = preventDefault; // modern standard
- window.onmousewheel = document.onmousewheel = preventDefault; // older browsers, IE
- window.ontouchmove = preventDefault; // mobile
- document.ontouchmove = function(e) {
- var target = e.currentTarget;
- while(target) {
- if(checkIfElementShouldScroll(target))
- return;
- target = target.parentNode;
- }
- e.preventDefault();
- };
- document.onkeydown = preventDefaultForScrollKeys;
- }
-
- function enableScroll() {
- if (window.removeEventListener)
- window.removeEventListener('DOMMouseScroll', preventDefault, false);
- window.onmousewheel = document.onmousewheel = null;
- window.onwheel = null;
- window.ontouchmove = null;
- document.onkeydown = null;
- var aTag = $("#start");
- $('html,body').animate({scrollTop: aTag.offset().top},'slow');
- }
-
- if (event.originalEvent.wheelDelta > 0 || event.originalEvent.detail < 0) {
- if (wh > 0) {
- console.log('up');
- $('.blog header').removeClass('hide');
- $('.blog #start').removeClass('visible');
- // disablescroll();
- // setTimeout(enableScroll, 500);
- $('.blog #start #items').removeClass('fixed');
- }
- }else {
- if (wh > 0) {
- console.log('down');
- $('.blog header').addClass('hide');
- $('.blog #start').addClass('visible');
- disablescroll();
- setTimeout(enableScroll, 500);
- $('.blog #start #items').addClass('fixed');
- }
- }
- });
- }
-}
-
-jQuery(document).ready(function($){
- masonry();
- contact();
- scroll();
- $(window).load(function(){
- $('html, body').scrollTop(0);
-});
+ // $(window).load(function() {
+ // $('html, body').scrollTop(0);
+ // });
});
diff --git a/user/themes/gilles_acezat/scss/theme/_animation.scss b/user/themes/gilles_acezat/scss/configurations/_animation.scss
similarity index 53%
rename from user/themes/gilles_acezat/scss/theme/_animation.scss
rename to user/themes/gilles_acezat/scss/configurations/_animation.scss
index fbf3b2f..fcd847a 100644
--- a/user/themes/gilles_acezat/scss/theme/_animation.scss
+++ b/user/themes/gilles_acezat/scss/configurations/_animation.scss
@@ -7,29 +7,6 @@
}
}
-
-@keyframes translatep1{
- 0%{
- transform: translateY(35px);
- }
-
- 100%{
- transform: translateY(5px);
- visibility: hidden;
- }
-}
-
-@keyframes translatep2{
- 0%{
- transform: translateY(-20px);
- }
-
- 100%{
- transform: translateY(40px);
- visibility: hidden;
- }
-}
-
@keyframes translateright{
0%{
right: -50px;
@@ -67,59 +44,85 @@
}
}
-
-@keyframes height{
+@keyframes reducH{
0%{
- height: 50vh;
- min-height: 250px;
+ font-size: $fh1;
+ -webkit-text-stroke : 1px;
+ color: transparent;
+ line-height: 5rem;
+ }
+
+ 70%{
+ -webkit-text-stroke : 1px;
+ color: black;
+ // line-height: 6.5rem;
+
}
100%{
- height: 0;
- min-height: 100px;
+ font-size: $min-h1;
+ -webkit-text-stroke : 0px;
+ color: black;
+ line-height: 5rem;
}
}
-@keyframes margin{
+@keyframes translateY{
0%{
- margin-top: 25vh;
- transform: translateY(-50%);
- }
- 100%{
- margin-top: 0px;
- transform: translateY(0%);
- }
-}
-
-@keyframes h1{
- 0%{
- font-size: 11.2vw;
- }
- 100%{
- font-size: 3rem;
- }
-}
-
-
-@keyframes card{
- 0%{}
-
- 50%{
- transform: translateY(-40px);
+ transform: translateY(0px);
+ opacity: 1;
}
100%{
- transform: translateY(-50px);
+ transform: translateY(-20px);
opacity: 0;
}
}
-@keyframes textestroke{
+@keyframes showcatlist {
0%{
- -webkit-text-stroke : 1px;
+ width: 0;
+ box-shadow: 0px 0px 0px 0px black;
+ opacity: 1;
}
100%{
- -webkit-text-stroke : 0px;
+ width: 100%;
+ box-shadow: 0px -2px 0px -2px black;
+ opacity: 1;
+ }
+}
+
+@keyframes showborder {
+ 0%{
+ width: 0;
+ background: black;
+ }
+ 100%{
+ width: 100%;
+ background: black;
+ }
+}
+
+@keyframes ultranslate {
+ 0%{
+ opacity: 0;
+ transform: translateX(0px);
+ }
+ 100%{
+ opacity: 1;
+ transform: translateX(10px);
+ }
+}
+
+
+@keyframes showcard {
+ 0%{
+ opacity: 0;
+
+ }
+ 100%{
+ opacity: 1;
+
}
}
diff --git a/user/themes/gilles_acezat/scss/theme/_fonts.scss b/user/themes/gilles_acezat/scss/configurations/_fonts.scss
similarity index 100%
rename from user/themes/gilles_acezat/scss/theme/_fonts.scss
rename to user/themes/gilles_acezat/scss/configurations/_fonts.scss
diff --git a/user/themes/gilles_acezat/scss/configurations/_mixins.scss b/user/themes/gilles_acezat/scss/configurations/_mixins.scss
new file mode 100644
index 0000000..e69de29
diff --git a/user/themes/gilles_acezat/scss/theme/_reset.scss b/user/themes/gilles_acezat/scss/configurations/_reset.scss
similarity index 98%
rename from user/themes/gilles_acezat/scss/theme/_reset.scss
rename to user/themes/gilles_acezat/scss/configurations/_reset.scss
index 1689a34..f4b45f2 100644
--- a/user/themes/gilles_acezat/scss/theme/_reset.scss
+++ b/user/themes/gilles_acezat/scss/configurations/_reset.scss
@@ -18,6 +18,8 @@ html {
font-size: 18px;
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
+ height: 100%;
+ width: 100%;
}
/* Sections
@@ -30,6 +32,8 @@ html {
body {
margin: 0;
padding: 0;
+ height: 100%;
+ width: 100%;
}
/**
@@ -46,7 +50,7 @@ main {
*/
h1 {
- font-size: 2em;
+ font-size: 3em;
margin:0;
}
@@ -175,6 +179,9 @@ img {
vertical-align: bottom;
}
+iframe{
+ border: none;
+}
/* Forms
========================================================================== */
diff --git a/user/themes/gilles_acezat/scss/theme/_variables.scss b/user/themes/gilles_acezat/scss/configurations/_variables.scss
similarity index 82%
rename from user/themes/gilles_acezat/scss/theme/_variables.scss
rename to user/themes/gilles_acezat/scss/configurations/_variables.scss
index 760a2a8..fb76c5a 100644
--- a/user/themes/gilles_acezat/scss/theme/_variables.scss
+++ b/user/themes/gilles_acezat/scss/configurations/_variables.scss
@@ -6,15 +6,16 @@
@include font-face($light, $file5, $typo, $category);//1
// font-size
-
-$fh1: 11rem;
-$fh2:3rem;
-$fh2-i: 1.1rem;
+$fh1: 12vw;
+$fh2: 4.5vw;
+$fh2-i: 1rem;
$fh3: 1rem;
-$fh5:0.8rem;
+$fh5: 0.8rem;
$fp: 0.9rem;
-$fs:0.8rem;
-$fs-i: 1.1rem;
+$fs: 0.8rem;
+$fs-i: 1rem;
+$min-h1: 3rem;
+
// interline
//interlettre
diff --git a/user/themes/gilles_acezat/scss/theme.scss b/user/themes/gilles_acezat/scss/theme.scss
index e4f2038..1a72b87 100644
--- a/user/themes/gilles_acezat/scss/theme.scss
+++ b/user/themes/gilles_acezat/scss/theme.scss
@@ -1,16 +1,16 @@
-@import 'theme/reset';
-@import 'theme/fonts';
-@import 'theme/variables';
+@import 'configurations/reset';
+@import 'configurations/fonts';
+@import 'configurations/variables';
-@import 'theme/mixins';
+@import 'configurations/mixins';
@import 'theme/typography';
@import 'theme/mCustomScrollbar';
-@import 'theme/animation';
+@import 'configurations/animation';
@import 'theme/all';
@import 'theme/header';
@import 'theme/home';
@import 'theme/blog';
@import 'theme/item';
-@import 'theme/footer';
+// @import 'theme/footer';
diff --git a/user/themes/gilles_acezat/scss/theme/_blog.scss b/user/themes/gilles_acezat/scss/theme/_blog.scss
index 4274c3b..9d6fc64 100644
--- a/user/themes/gilles_acezat/scss/theme/_blog.scss
+++ b/user/themes/gilles_acezat/scss/theme/_blog.scss
@@ -5,52 +5,32 @@ body{
}
#page-wrapper.blog{
#start{
- position: relative;
- top: 200vh;
+ height: calc(100% - 5rem);
+ position: fixed;
+ top: 100vh;
left: 0;
- padding-top: 110px;
- opacity: 0;
- &.visible{
- opacity: 1;
+ }
+ }
+ #page-wrapper.down{
+ #start{
+ top: 5rem;
+ }
+ #items{
+ .card{
+ animation: showcard 1.5s 2.5s ease forwards;
}
}
}
}
#items{
- .cat-list{
- padding: 0 5px;
- ul{
- li{
- display: flex;
- &::after{
- width: 5px;
- content: " ";
- }
- &:hover{
- &::after{
- content: "\002022";
- }
- }
- }
- }
- }
.full-works{
width: 100%;
+ margin-top: 30px;
}
.card{
+ opacity: 0;
margin-bottom: 10px;
min-width: 200px;
}
- &.fixed{
- // .cat-list{
- // position: fixed;
- // top: 80px;
- // z-index: 999;
- // background: white;
- // }
- // .full-works{
- // margin-top: 60px;
- // }
- }
}
diff --git a/user/themes/gilles_acezat/scss/theme/_header.scss b/user/themes/gilles_acezat/scss/theme/_header.scss
index 493def8..219abb4 100644
--- a/user/themes/gilles_acezat/scss/theme/_header.scss
+++ b/user/themes/gilles_acezat/scss/theme/_header.scss
@@ -1,175 +1,114 @@
// home
-body{
- height: 100%;
- .blog{
- header{
- z-index: 999;
- position: fixed;
- top: 0;
- left: 2%;
- right: 2%;
- height: 100vh;
- display: -webkit-inline-box;
- display: -ms-inline-flexbox;
- display: inline-flex;
- -webkit-box-orient: vertical;
- -webkit-box-direction: normal;
- -ms-flex-direction: column;
- flex-direction: column;
- & > section{
- height: 50vh;
- min-height: 250px;
- top: 0;
- left: 2%;
- right: 2%;
- &.works-mea{
- display: -webkit-inline-box;
- display: -ms-inline-flexbox;
- display: inline-flex;
- -ms-flex-wrap: wrap;
- flex-wrap: wrap;
- -webkit-box-align: end;
- -ms-flex-align: end;
- align-items: flex-end;
- height: 100%;
- margin-bottom: 20px;
+body {
+ .blog {
+ header {
+ height:100vh;
+ width: 100%;
+ display: inline-flex;
+ flex-direction: column;
+ align-content: space-around;
+ & > div{
+ position: fixed;
+ &.head{
+ width: 100%;
+ z-index: 999;
+ padding-top: 70px;
+ h2 {
+ margin-left: 8px;
+ position: absolute;
+ }
+ &::after {
+ display: block;
+ content: " ";
+ width: 0%;
+ height: 1px;
+ background: black;
+ }
+
+ }
+ &.works-mea {
+ display: -webkit-inline-box;
+ display: -ms-inline-flexbox;
+ display: inline-flex;
+ align-self: flex-end;
+ height: calc(100% - 20px);
+ align-items: flex-end;
+ .card{
+ &:nth-child(2){
+ margin:0 10px;
+ }
+ &:not(:nth-child(2)){
+ margin: 0;
+ }
+ }
+ }
+ &.scroll{
+ width: 100%;
+ bottom: 0;
+ text-align: center;
+ }
+ }
}
- &.reduc{
- -webkit-animation: reducfont 1s ease forwards;
- animation: reducfont 1s ease forwards;
- }
- }
- h1{
- position: fixed;
- top: 80px;
- font-size: 11.2vw;
- line-height: 7vw!important;
- transition: top 1s ease;
- transition: font-size 1.2s ease, color 1.2s ease , top 1.2s ease;
- }
- h2{
- position: fixed;
- top:220px;
- font-size: 3.7vw;
- opacity: 1;
- transform: translateX(0);
- transition: opacity 1s ease 1s, transform 1s ease 1s;
- }
- nav{
- // margin-top: 25vh; /* poussé de la moitié de hauteur de viewport */
- // -webkit-transform: translateY(-50%);
- // transform: translateY(-50%); /* tiré de la moitié de sa propre hauteur */
- }
- .scroll{
- width: 50px;
- height: 15px;
- margin: 0 auto 10px auto;
- overflow: hidden;
- position: fixed;
- bottom: 0;
- left: 50%;
- -webkit-transform: translate(-50%);
- transform: translate(-50%);
- & > p:nth-child(1){
- text-align: center;
- position: absolute;
- top: -20px;
- -webkit-animation: translatep1 2s infinite linear;
- animation: translatep1 2s infinite linear;
- }
- & > p:nth-child(2){
- text-align: center;
- position: absolute;
- top: -20px;
- -webkit-animation: translatep1 2s infinite linear;
- animation: translatep1 2s infinite linear;
- -webkit-animation-delay: 1s;
- animation-delay: 1s;
- }
- }
- &.hide{
- & > section:first-child{
- height: 100px;
- min-height: 100px;
- background: white;
- z-index: 999;
- }
- nav{
- margin-top: 0;
- -webkit-transform: translate(0);
- transform: translate(0);
- -webkit-transition: margin-top 1s ease, -webkit-transform 1s ease;
- transition: margin-top 1s ease, -webkit-transform 1s ease;
- transition: margin-top 1s ease, transform 1s ease;
- transition: margin-top 1s ease, transform 1s ease, -webkit-transform 1s ease;
- }
- h1{
- position: relative;
- font-size: 3rem;
- top: 0px;
- color: black;
- transition: font-size 1.2s ease 0.5s, color 1.2s ease 1s, top 1.2s ease 0.5s;
- animation: textestroke 0.5s ease 1.2s forwards;
- }
- h2{
- opacity: 0;
- transform: translateX(-10px);
- top:220px;
- -webkit-transition: opacity 0.5s ease, transform 0.5s ease;
- transition: opacity 0.5s ease, transform 0.5s ease;
- }
- .card{
- -webkit-animation: card 1s forwards ease;
- animation: card 1s forwards ease;
- }
- .scroll{
- opacity: 0;
- -webkit-transition: opacity 0.8s ease;
- transition: opacity 0.8s ease;
+ &.down{
+ header{
+ height: 5rem;
+ .head{
+ padding:0;
+ transition: all 1s 0.9s ease;
+ &::after{
+ animation: showborder 2.5s 1.5s ease forwards;
+ }
+ h2{
+ opacity: 0;
+ margin-left: -100px;
+ transition: all 1s ease;
+ left: -50%;
+ }
+ }
+ .card{
+ &:nth-child(1){
+ animation: translateY 1s ease forwards;
+ }
+ &:nth-child(2){
+ animation: translateY 1.3s ease forwards;
+ }
+ &:nth-child(3){
+ animation: translateY 1.6s ease forwards;
+ }
+ }
+ .scroll{
+ opacity: 0;
+ transition: all 1s ease;
+ }
}
}
}
- }
}
-// html{
-// overflow: hidden;
-// }
-// html, body {
-// padding: 0;
-// margin: 0;
-//
-// }
-// body {
-// position: absolute;
-// left: 0;
-// top: 0;
-// right: -30px;
-// bottom: 0;
-// padding-right: 15px;
-// overflow-y: scroll;
-// }
+
// page
-body{
- & > .item{
- header{
- height: 130px;
- position: relative;
- nav{
- position: absolute;
- margin-top: 65px; /* poussé de la moitié de hauteur de viewport */
- -webkit-transform: translateY(-50%);
- transform: translateY(-50%); /* tiré de la moitié de sa propre hauteur */
- }
- h1{
- color: black;
- }
- h2{
- display: none;
- }
- .scroll{
- display: none;
- }
+body {
+ #page-wrapper.item {
+ header {
+ border-bottom: 1px solid black;
+ position: fixed;
+ height: 112px;
+ top: 0;
+ left: 10px;
+ right: 10px;
+ background: white;
+ z-index: 999;
+ nav {
+ margin-top: 25px;
+ }
+
+ h1 {
+ color: black;
+ }
+
+ h2, .scroll {
+ display: none;
+ }
+ }
}
- }
}
diff --git a/user/themes/gilles_acezat/scss/theme/_home.scss b/user/themes/gilles_acezat/scss/theme/_home.scss
index 6679a8b..f6494bf 100644
--- a/user/themes/gilles_acezat/scss/theme/_home.scss
+++ b/user/themes/gilles_acezat/scss/theme/_home.scss
@@ -1,42 +1,62 @@
-#body-wrapper{
- #items{
- .works-mea{
- display: inline-flex;
+.blog{
+ #start{
+ .cat-list {
width: 100%;
- height: 50vh;
- align-items: flex-end;
- .card{
- .card-image{
- img{
- width: 100%;
- height: auto;
+ z-index: 999;
+ left: -100%;
+ &::before{
+ display: block;
+ content: " ";
+ width: 0%;
+ height: 1px;
+ background: black;
+ }
+ &::after {
+ display: block;
+ content: " ";
+ width: 0%;
+ height: 1px;
+ background: black;
+ }
+ ul {
+ opacity: 0;
+ display: inline-flex;
+ margin: 10px 0;
+ .btn-group{
+ display: flex;
+ margin-right: 10px;
+ &::after{
+ width: 5px;
+ content: " ";
+ }
+ &:hover{
+ &::after{
+ content: "\002022";
+ }
+ }
+ input{
+ display: none;
}
}
- .card-header{
- margin-top: 5px;
- }
- .card-footer{
- margin-top: 5px;
- }
- }
- }
- .cat-list{
- width: 100%;
- height: auto;
- border-top: 1px solid black;
- border-bottom: 1px solid black;
- padding: 10px 0;
- margin: 20px 0;
- ul{
- display: inline-flex;
- li{
- margin-right: 10px;
- }
}
}
+ #items{
.full-works{
- display: inline-flex;
- flex-wrap: wrap;
+ .card{
+ width: calc((100vw / 3) - 6.5px);
+ }
}
+ }
+ }
+ .down{
+ .cat-list{
+ left: 0;
+ &::after{
+ animation: showborder 2.5s 1.6s ease forwards;
+ }
+ ul{
+ animation: ultranslate 1s 2.5s ease forwards;
+ }
+ }
}
}
diff --git a/user/themes/gilles_acezat/scss/theme/_typography.scss b/user/themes/gilles_acezat/scss/theme/_typography.scss
index 20ad8df..c2dd40e 100644
--- a/user/themes/gilles_acezat/scss/theme/_typography.scss
+++ b/user/themes/gilles_acezat/scss/theme/_typography.scss
@@ -3,7 +3,6 @@ h1, h2{
font-weight: normal;
}
-
h2{
font-size: $fh2;
}
@@ -24,6 +23,7 @@ p{
font-family: $Regular;
font-weight: normal;
font-size: $fp;
+ line-height: 1.3rem;
}
time{
@@ -31,127 +31,70 @@ time{
font-weight: normal;
}
-header{
- .contact{
- h3{
- text-transform: lowercase;
- color:white;
+.blog{
+ h1{
+ font-size: $fh1;
+ line-height: 5rem;
+ -webkit-text-stroke: 2px black;
+ color: transparent;
+ }
+ h2{
+ line-height: 8rem;
+ }
+ &.down{
+ h1{
+ animation: reducH 1s 0.9s ease forwards;
}
}
}
-.scroll{
- p{
- font-size: $fs;
- font-family: $bold;
- font-weight: normal;
+.item{
+ h2{
+ font-size: $fh2-i;
+ }
+ .prev-next{
+ a{
+ .title{
+ font-family: $medium;
+ font-size: $fh2-i;
+ }
+ }
+ }
+}
+
+.category{
+ a{
+ font-family: $Regular;
+ font-size: $fs-i;
+ }
+}
+
+time{
+ font-size: $fs-i;
+}
+
+.tags{
+ font-family: $Regular;
+ font-size: $fs-i;
+}
+
+#page-wrapper.blog{
+ .works-mea, #items{
+ .category{
+ a{
+ font-family: $Regular;
+ font-size: $fs;
+ }
+ }
+ time{
+ font-size: $fs;
+ }
+ .tags{
+ font-size: $fs;
+ }
}
}
.cat-list{
- .cat{
- a{
- font-family: $light;
- font-weight: normal;
- }
- }
-}
-
-body{
- &>.blog{
- h1{
- font-size: $fh1;
- -webkit-text-stroke:.1rem #111;
- color: transparent;
- line-height: 7rem;
- }
- .card{
- h5{
- text-transform: uppercase;
- }
- .card-category{
- font-family: $italic;
- font-weight: normal;
- font-size: $fs;
- }
- .card-date{
- font-size: $fs;
- }
- .card-footer{
- span{
- font-family: $medium;
- font-weight: normal;
- font-size: $fs;
- text-transform: capitalize;
- a{
- color: $grey;
- }
- }
- }
- }
- }
-}
-
-body{
- & >.item{
- h1{
- font-size: $fh2;
- }
- h2{
- font-size: $fh2-i;
- }
- .content-item{
- .content-title{
- & > a{
- font-family: $italic;
- }
- }
- }
- .category{
- font-family: $italic;
- font-weight: normal;
- font-size: $fs-i;
- }
- time{
- font-size: $fs;
- font-size: $fs-i;
- }
- .tags{
- font-family: $medium;
- font-weight: normal;
- font-size: 0.8rem;
- text-transform: capitalize;
- a{
- color: $grey;
- }
- }
- .top{
- p{
- font-family: $bold;
- }
- }
- }
-}
-
-.prev-next{
- .title{
- font-family: $bold;
- font-size: $fs;
- }
- .tags{
- font-family: $italic;
- font-size: $fs!important;
- }
- .blog-date{
- time{
- font-family: $italic;
- font-size: $fs!important;
- }
- }
-}
-
-.top{
- p{
- font-size: 0.8rem;
- }
+ font-family: $Regular;
}
diff --git a/user/themes/gilles_acezat/scss/theme/all.scss b/user/themes/gilles_acezat/scss/theme/all.scss
index adc898d..a606ed5 100644
--- a/user/themes/gilles_acezat/scss/theme/all.scss
+++ b/user/themes/gilles_acezat/scss/theme/all.scss
@@ -1,30 +1,72 @@
-body{
- padding: 0 2%;
+html, body{
+ overflow: hidden;
+}
+
+.blog{
+ height: 100%;
+ &.down{
+ height: 5rem;
+ }
}
header{
.contact{
- z-index: 9999;
+ width: 90px;
+ height: auto;
+ z-index: 999;
position: fixed;
- top: 20px;
- right: 2%;
- transform: scale(1);
+ top: 0px;
+ right: 0px;
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ -webkit-transition: -webkit-transform 0.5s ease;
+ transition: -webkit-transform 0.5s ease;
transition: transform 0.5s ease;
+ transition: transform 0.5s ease, -webkit-transform 0.5s ease;
cursor: pointer;
&:hover{
+ -webkit-transition: -webkit-transform 0.5s ease;
+ transition: -webkit-transform 0.5s ease;
transition: transform 0.5s ease;
- transform: scale(0.9);
+ transition: transform 0.5s ease, -webkit-transform 0.5s ease;
+ -webkit-transform: scale(0.9);
+ transform: scale(0.9);
+ }
+ svg{
+ cursor: pointer;
+ -webkit-animation: rotation 6s infinite linear;
+ animation: rotation 6s infinite linear;
+ }
+ h3{
+ color: white;
+ -webkit-transform: rotate(35deg);
+ transform: rotate(35deg);
+ position: absolute;
+ top: 35px;
+ left: 19px;
+ }
+ .txt{
+ position: fixed;
+ right: -200px;
+ display: none;
}
&.open{
- transform: scale(15);
+ -webkit-transform: scale(15);
+ transform: scale(15);
+ height: auto;
+ -webkit-transition: -webkit-transform 0.5s ease;
+ transition: -webkit-transform 0.5s ease;
transition: transform 0.5s ease;
+ transition: transform 0.5s ease, -webkit-transform 0.5s ease;
h3{
color: transparent;
left: 500px;
+ -webkit-transition: visibility 0.5 ease left 1s ease;
transition: visibility 0.5 ease left 1s ease;
}
- img{
- animation: rotation 12s infinite linear;
+ svg{
+ -webkit-animation: rotation 12s infinite linear;
+ animation: rotation 12s infinite linear;
}
& + .txt{
position: fixed;
@@ -32,66 +74,77 @@ header{
text-align: center;
line-height: 1.5rem;
z-index: 9999;
- animation: translateright 1s ease forwards;
+ -webkit-animation: translateright 1s ease forwards;
+ animation: translateright 1s ease forwards;
}
}
&.close{
- transform: scale(1);
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ -webkit-transition: -webkit-transform 0.5s 0.1s ease;
+ transition: -webkit-transform 0.5s 0.1s ease;
transition: transform 0.5s 0.1s ease;
+ transition: transform 0.5s 0.1s ease, -webkit-transform 0.5s 0.1s ease;
& + .txt{
- position: fixed;
top: 190px;
text-align: center;
line-height: 1.5rem;
z-index: 9999;
- animation: translateleft 1s ease forwards;
+ -webkit-animation: translateleft 1s ease forwards;
+ animation: translateleft 1s ease forwards;
}
h3{
- color: white;
- left: 20px;
+
+
+ -webkit-transition: color 0.5s 0.5s ease, left 0.5s ease;
transition: color 0.5s 0.5s ease, left 0.5s ease;
}
&:hover{
+ -webkit-transition: -webkit-transform 0.5s ease!important;
+ transition: -webkit-transform 0.5s ease!important;
transition: transform 0.5s ease!important;
- transform: scale(0.9)!important;
+ transition: transform 0.5s ease, -webkit-transform 0.5s ease!important;
+ -webkit-transform: scale(0.9)!important;
+ transform: scale(0.9)!important;
}
}
- img{
- animation: rotation 6s infinite linear;
- }
- h3{
- transform: rotate(35deg);
- position: absolute;
- top: 33px;
- left: 20px;
- }
- & + .txt{
- position: fixed;
- right: -200px;
- p{
- }
- }
- }
- h2{
- margin-left: 10px;
}
}
.card{
- width: calc(100% / 3);
- padding: 0 10px;
- line-height: 0.8!important;
+ width: calc((100% / 3));
+ line-height: 0.8rem!important;
.card-header{
margin-top: 10px;
+ display: -webkit-inline-box;
+ display: -ms-inline-flexbox;
display: inline-flex;
& > div{
margin-right: 5px;
- & > a{
- vertical-align: baseline;
- }
- & > time{
- vertical-align: baseline;
- }
}
}
}
+
+
+@media screen and (max-width: 960px) {
+ .works-mea{
+ & > div:last-child{
+ display: none;
+ }
+ }
+ .card{
+ width: calc((100% / 2) - 5px );
+ }
+
+}
+
+@media screen and (max-width: 600px) {
+ .works-mea{
+ & > div:nth-child(2){
+ display: none;
+ }
+ }
+ .card{
+ width: 100%;
+ }
+}
diff --git a/user/themes/gilles_acezat/scss/theme/item.scss b/user/themes/gilles_acezat/scss/theme/item.scss
index fd9fb06..921fb17 100644
--- a/user/themes/gilles_acezat/scss/theme/item.scss
+++ b/user/themes/gilles_acezat/scss/theme/item.scss
@@ -1,19 +1,27 @@
body{
- & >.item{
+ #page-wrapper.item{
+ margin: 0 10px;
+ .contact{
+ .txt{display: none}
+ }
+ #start{
+ margin-top: 112px;
+ }
#body-wrapper.blog-listing{
- border-top: 1px solid black;
- padding-top: 20px;
.img-card{
- width: 49%;
- margin-bottom: 20px;
+ width: calc((100% / 3) - 6.5px);
+ margin-bottom: 10px;
}
.header-item{
position: relative;
+ margin-bottom: 20px;
+ padding-top: 20px;
.title{
display: inline-flex;
align-items: baseline;
h2{
margin-right: 5px;
+ text-transform: uppercase;
}
time{
margin-right: 5px;
@@ -23,15 +31,20 @@ body{
}
}
.content{
- width: calc(100% / 3);
- margin: 20px 0;
+ width: calc((100% / 3) - 15px);
+ margin-top: 20px;
}
.prev-next{
width: calc(100% / 3);
- padding-left: 20px;
+ height: 100%;
position: absolute;
- top: 0;
- right: 0;
+ top: calc( 50% + 10px );
+ transform: translateY(-50%);
+ right: 0px;
+ .btn{
+ display: block;
+ width: auto;
+ height: 100%;
.btn-info{
position: absolute;
transform: rotate(-15deg);
@@ -43,6 +56,7 @@ body{
width: 80%;
}
}
+ }
}
}
.top{
@@ -59,3 +73,54 @@ body{
}
}
}
+
+@media screen and (max-width: 960px) {
+ body{
+ #page-wrapper.item{
+ #body-wrapper.blog-listing{
+ .header-item{
+ .content{
+ width: calc((100% / 2) - 5px);
+ }
+ .prev-next{
+ width: calc((100% / 2) - 5px);
+ }
+ }
+ .img-card{
+ width: calc((100% / 2) - 5px);
+ }
+ }
+ }
+ }
+}
+
+@media screen and (max-width: 600px) {
+ body{
+ #page-wrapper.item{
+ #body-wrapper.blog-listing{
+ .header-item{
+ .content{
+ width: calc((100%) - 15px);
+ }
+ .prev-next{
+ width: calc((100%) - 15px);
+ position: relative;
+ transform: translateY(0%);
+ .btn{
+ .btn-info{
+ display: none;
+ }
+ img{
+ float: none;
+ width: 100%;
+ }
+ }
+ }
+ }
+ .img-card{
+ width: 100%;
+ }
+ }
+ }
+ }
+}
diff --git a/user/themes/gilles_acezat/templates/blog.html.twig b/user/themes/gilles_acezat/templates/blog.html.twig
index 4170966..0698490 100644
--- a/user/themes/gilles_acezat/templates/blog.html.twig
+++ b/user/themes/gilles_acezat/templates/blog.html.twig
@@ -13,11 +13,6 @@
{% embed 'partials/layout.html.twig' with {blog: page} %}
{% block item %}
-
-
- {% include 'partials/taxonomylist.html.twig' with {base_url: my_url, taxonomy: 'category', children_only: true} %}
-
-
{% for child in collection if child.template == 'item' %}
diff --git a/user/themes/gilles_acezat/templates/item.html.twig b/user/themes/gilles_acezat/templates/item.html.twig
index 4452ca9..561248a 100644
--- a/user/themes/gilles_acezat/templates/item.html.twig
+++ b/user/themes/gilles_acezat/templates/item.html.twig
@@ -19,7 +19,6 @@
{% endif %}
{% endblock %}
-
{% block body %}
@@ -36,7 +35,6 @@
{% include 'partials/sidebar.html.twig' %}
{% endblock %}
{% endembed %}
-
{% endblock %}
diff --git a/user/themes/gilles_acezat/templates/partials/base.html.twig b/user/themes/gilles_acezat/templates/partials/base.html.twig
index 127361e..bafdb39 100644
--- a/user/themes/gilles_acezat/templates/partials/base.html.twig
+++ b/user/themes/gilles_acezat/templates/partials/base.html.twig
@@ -25,11 +25,12 @@
{% do assets.addJs('theme://js/jquery.easing.1.3.js', {group:'bottom'}) %}
{% do assets.addJs('theme://js/imagesloaded.pkgd.min.js', {group:'bottom'}) %}
{% do assets.addJs('theme://js/masonry.pkgd.min.js', {group:'bottom'}) %}
- {% do assets.addJs('theme://js/scrollreveal.js', {group:'bottom'}) %}
+ {% do assets.addJs('theme://js/multipleFilterMasonry.js', {group:'bottom'}) %}
+ {% do assets.addJs('theme://js/jquery.mCustomScrollbar.js', {group:'bottom'}) %}
+ {% do assets.addJs('theme://js/jquery.mousewheel.min.js', {group:'bottom'}) %}
{% do assets.addJs('theme://js/site.js', {group:'bottom'}) %}
-
{% endblock %}
{{ assets.js()|raw }}
@@ -37,37 +38,45 @@
-
+
{% block header %}
-
-
{% endblock %}
+
+
{% block body %}
diff --git a/user/themes/gilles_acezat/templates/partials/blog-item.html.twig b/user/themes/gilles_acezat/templates/partials/blog-item.html.twig
index f446b3d..28db959 100644
--- a/user/themes/gilles_acezat/templates/partials/blog-item.html.twig
+++ b/user/themes/gilles_acezat/templates/partials/blog-item.html.twig
@@ -1,5 +1,11 @@
{% if not hero_image_name %}
+
+
+