commit e64b18b9a9a345a8c5148523ce90cf6faa67e8e5
Author: ouidade
Date: Tue Oct 20 09:32:09 2020 +0200
first commit
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..e84f52b
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,8 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: grav
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+custom: # Replace with a single custom sponsorship URL
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..ef79a4b
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,75 @@
+
+
+RewriteEngine On
+
+## Begin RewriteBase
+# If you are getting 500 or 404 errors on subpages, you may have to uncomment the RewriteBase entry
+# You should change the '/' to your appropriate subfolder. For example if you have
+# your Grav install at the root of your site '/' should work, else it might be something
+# along the lines of: RewriteBase /
+##
+
+# RewriteBase /
+
+## End - RewriteBase
+
+## Begin - X-Forwarded-Proto
+# In some hosted or load balanced environments, SSL negotiation happens upstream.
+# In order for Grav to recognize the connection as secure, you need to uncomment
+# the following lines.
+#
+# RewriteCond %{HTTP:X-Forwarded-Proto} https
+# RewriteRule .* - [E=HTTPS:on]
+#
+## End - X-Forwarded-Proto
+
+## Begin - Exploits
+# If you experience problems on your site block out the operations listed below
+# This attempts to block the most common type of exploit `attempts` to Grav
+#
+# Block out any script trying to base64_encode data within the URL.
+RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
+# Block out any script that includes a \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..7f91c6d
--- /dev/null
+++ b/system/src/Grav/Common/Assets/Traits/LegacyAssetsTrait.php
@@ -0,0 +1,129 @@
+ 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'];
+ if (is_array($old_attributes)) {
+ $arguments = array_merge($arguments, $old_attributes);
+ } else {
+ $arguments['type'] = $old_attributes;
+ }
+ }
+ unset($arguments['attributes']);
+
+ break;
+
+ case(Assets::INLINE_CSS_TYPE):
+ $defaults = ['priority' => null, 'group' => null];
+ $arguments = $this->createArgumentsFromLegacy($args, $defaults);
+ 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/Browser.php b/system/src/Grav/Common/Browser.php
new file mode 100644
index 0000000..8d0d687
--- /dev/null
+++ b/system/src/Grav/Common/Browser.php
@@ -0,0 +1,151 @@
+useragent = parse_user_agent();
+ } catch (\InvalidArgumentException $e) {
+ $this->useragent = parse_user_agent("Mozilla/5.0 (compatible; Unknown;)");
+ }
+ }
+
+ /**
+ * Get the current browser identifier
+ *
+ * Currently detected browsers:
+ *
+ * Android Browser
+ * BlackBerry Browser
+ * Camino
+ * Kindle / Silk
+ * Firefox / Iceweasel
+ * Safari
+ * Internet Explorer
+ * IEMobile
+ * Chrome
+ * Opera
+ * Midori
+ * Vivaldi
+ * TizenBrowser
+ * Lynx
+ * Wget
+ * Curl
+ *
+ * @return string the lowercase browser name
+ */
+ public function getBrowser()
+ {
+ return strtolower($this->useragent['browser']);
+ }
+
+ /**
+ * Get the current platform identifier
+ *
+ * Currently detected platforms:
+ *
+ * Desktop
+ * -> Windows
+ * -> Linux
+ * -> Macintosh
+ * -> Chrome OS
+ * Mobile
+ * -> Android
+ * -> iPhone
+ * -> iPad / iPod Touch
+ * -> Windows Phone OS
+ * -> Kindle
+ * -> Kindle Fire
+ * -> BlackBerry
+ * -> Playbook
+ * -> Tizen
+ * Console
+ * -> Nintendo 3DS
+ * -> New Nintendo 3DS
+ * -> Nintendo Wii
+ * -> Nintendo WiiU
+ * -> PlayStation 3
+ * -> PlayStation 4
+ * -> PlayStation Vita
+ * -> Xbox 360
+ * -> Xbox One
+ *
+ * @return string the lowercase platform name
+ */
+ public function getPlatform()
+ {
+ return strtolower($this->useragent['platform']);
+ }
+
+ /**
+ * Get the current full version identifier
+ *
+ * @return string the browser full version identifier
+ */
+ public function getLongVersion()
+ {
+ return $this->useragent['version'];
+ }
+
+ /**
+ * Get the current major version identifier
+ *
+ * @return string the browser major version identifier
+ */
+ public function getVersion()
+ {
+ $version = explode('.', $this->getLongVersion());
+
+ return (int)$version[0];
+ }
+
+ /**
+ * Determine if the request comes from a human, or from a bot/crawler
+ *
+ * @return bool
+ */
+ public function isHuman()
+ {
+ $browser = $this->getBrowser();
+ if (empty($browser)) {
+ return false;
+ }
+
+ if (preg_match('~(bot|crawl)~i', $browser)) {
+ return false;
+ }
+
+ 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
new file mode 100644
index 0000000..94eae59
--- /dev/null
+++ b/system/src/Grav/Common/Cache.php
@@ -0,0 +1,656 @@
+init($grav);
+ }
+
+ /**
+ * Initialization that sets a base key and the driver based on configuration settings
+ *
+ * @param Grav $grav
+ *
+ * @return void
+ */
+ public function init(Grav $grav)
+ {
+ /** @var Config $config */
+ $this->config = $grav['config'];
+ $this->now = time();
+
+ 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');
+ $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') . '-' . $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();
+ $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 bool|int $enabled
+ */
+ public function setEnabled($enabled)
+ {
+ $this->enabled = (bool)$enabled;
+ }
+
+ /**
+ * Returns the current enabled state
+ *
+ * @return bool
+ */
+ public function getEnabled()
+ {
+ return $this->enabled;
+ }
+
+ /**
+ * Get cache state
+ *
+ * @return string
+ */
+ public function getCacheStatus()
+ {
+ return 'Cache: [' . ($this->enabled ? 'true' : 'false') . '] Setting: [' . $this->driver_setting . '] Driver: [' . $this->driver_name . ']';
+ }
+
+ /**
+ * Automatically picks the cache mechanism to use. If you pick one manually it will use that
+ * If there is no config option for $driver in the config, or it's set to 'auto', it will
+ * pick the best option based on which cache extensions are installed.
+ *
+ * @return DoctrineCache\CacheProvider The cache driver to use
+ */
+ public function getCacheDriver()
+ {
+ $setting = $this->driver_setting;
+ $driver_name = 'file';
+
+ // CLI compatibility requires a non-volatile cache driver
+ if ($this->config->get('system.cache.cli_compatibility') && (
+ $setting === 'auto' || $this->isVolatileDriver($setting))) {
+ $setting = $driver_name;
+ }
+
+ if (!$setting || $setting === 'auto') {
+ if (extension_loaded('apcu')) {
+ $driver_name = 'apcu';
+ } elseif (extension_loaded('wincache')) {
+ $driver_name = 'wincache';
+ }
+ } else {
+ $driver_name = $setting;
+ }
+
+ $this->driver_name = $driver_name;
+
+ switch ($driver_name) {
+ case 'apc':
+ case 'apcu':
+ $driver = new DoctrineCache\ApcuCache();
+ break;
+
+ case 'wincache':
+ $driver = new DoctrineCache\WinCacheCache();
+ break;
+
+ case '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':
+ 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':
+ 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);
+ } 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 {
+ throw new \LogicException('Redis PHP extension has not been installed');
+ }
+ break;
+
+ default:
+ $driver = new DoctrineCache\FilesystemCache($this->cache_dir);
+ break;
+ }
+
+ return $driver;
+ }
+
+ /**
+ * Gets a cached entry if it exists based on an id. If it does not exist, it returns false
+ *
+ * @param string $id the id of the cached entry
+ *
+ * @return object|bool returns the cached entry, can be any type, or false if doesn't exist
+ */
+ public function fetch($id)
+ {
+ if ($this->enabled) {
+ return $this->driver->fetch($id);
+ }
+
+ return false;
+ }
+
+ /**
+ * Stores a new cached entry.
+ *
+ * @param string $id the id of the cached entry
+ * @param array|object $data the data for the cached entry to store
+ * @param int $lifetime the lifetime to store the entry in seconds
+ */
+ public function save($id, $data, $lifetime = null)
+ {
+ if ($this->enabled) {
+ if ($lifetime === null) {
+ $lifetime = $this->getLifetime();
+ }
+ $this->driver->save($id, $data, $lifetime);
+ }
+ }
+
+ /**
+ * Deletes an item in the cache based on the id
+ *
+ * @param string $id the id of the cached data entry
+ * @return bool true if the item was deleted successfully
+ */
+ public function delete($id)
+ {
+ 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;
+ }
+
+ /**
+ * Returns a boolean state of whether or not the item exists in the cache based on id key
+ *
+ * @param string $id the id of the cached data entry
+ * @return bool true if the cached items exists
+ */
+ public function contains($id)
+ {
+ if ($this->enabled) {
+ return $this->driver->contains(($id));
+ }
+
+ return false;
+ }
+
+ /**
+ * Getter method to get the cache key
+ */
+ public function getKey()
+ {
+ return $this->key;
+ }
+
+ /**
+ * Setter method to set key (Advanced)
+ */
+ public function setKey($key)
+ {
+ $this->key = $key;
+ $this->driver->setNamespace($this->key);
+ }
+
+ /**
+ * Helper method to clear all Grav caches
+ *
+ * @param string $remove standard|all|assets-only|images-only|cache-only
+ *
+ * @return array
+ */
+ public static function clearCache($remove = 'standard')
+ {
+ $locator = Grav::instance()['locator'];
+ $output = [];
+ $user_config = USER_DIR . 'config/system.yaml';
+
+ switch ($remove) {
+ case 'all':
+ $remove_paths = self::$all_remove;
+ break;
+ case 'assets-only':
+ $remove_paths = self::$assets_remove;
+ break;
+ case 'images-only':
+ $remove_paths = self::$images_remove;
+ break;
+ case 'cache-only':
+ $remove_paths = self::$cache_remove;
+ break;
+ case 'tmp-only':
+ $remove_paths = self::$tmp_remove;
+ break;
+ case 'invalidate':
+ $remove_paths = [];
+ break;
+ default:
+ if (Grav::instance()['config']->get('system.cache.clear_images_by_default')) {
+ $remove_paths = self::$standard_remove;
+ } else {
+ $remove_paths = self::$standard_remove_no_images;
+ }
+
+ }
+
+ // 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]));
+
+ foreach ($remove_paths as $stream) {
+
+ // Convert stream to a real path
+ try {
+ $path = $locator->findResource($stream, true, true);
+ if($path === false) continue;
+
+ $anything = false;
+ $files = glob($path . '/*');
+
+ if (is_array($files)) {
+ foreach ($files as $file) {
+ if (is_link($file)) {
+ $output[] = 'Skipping symlink: ' . $file;
+ } elseif (is_file($file)) {
+ if (@unlink($file)) {
+ $anything = true;
+ }
+ } elseif (is_dir($file)) {
+ if (Folder::delete($file)) {
+ $anything = true;
+ }
+ }
+ }
+ }
+
+ if ($anything) {
+ $output[] = 'Cleared: ' . $path . '/*';
+ }
+ } catch (\Exception $e) {
+ // stream not found or another error while deleting files.
+ $output[] = 'ERROR: ' . $e->getMessage();
+ }
+ }
+
+ $output[] = '';
+
+ if (($remove === 'all' || $remove === 'standard') && file_exists($user_config)) {
+ touch($user_config);
+
+ $output[] = 'Touched: ' . $user_config;
+ $output[] = '';
+ }
+
+ // Clear stat cache
+ @clearstatcache();
+
+ // Clear opcache
+ if (function_exists('opcache_reset')) {
+ @opcache_reset();
+ }
+
+ 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
+ *
+ * @param int $future timestamp
+ */
+ public function setLifetime($future)
+ {
+ if (!$future) {
+ return;
+ }
+
+ $interval = (int)($future - $this->now);
+ if ($interval > 0 && $interval < $this->getLifetime()) {
+ $this->lifetime = $interval;
+ }
+ }
+
+
+ /**
+ * Retrieve the cache lifetime (in seconds)
+ *
+ * @return mixed
+ */
+ public function getLifetime()
+ {
+ if ($this->lifetime === null) {
+ $this->lifetime = (int)($this->config->get('system.cache.lifetime') ?: 604800); // 1 week default
+ }
+
+ return $this->lifetime;
+ }
+
+ /**
+ * Returns the current driver name
+ *
+ * @return mixed
+ */
+ public function getDriverName()
+ {
+ return $this->driver_name;
+ }
+
+ /**
+ * Returns the current driver setting
+ *
+ * @return mixed
+ */
+ public function getDriverSetting()
+ {
+ return $this->driver_setting;
+ }
+
+ /**
+ * is this driver a volatile driver in that it resides in PHP process memory
+ *
+ * @param string $setting
+ * @return bool
+ */
+ public function isVolatileDriver($setting)
+ {
+ if (in_array($setting, ['apc', 'apcu', 'xcache', 'wincache'])) {
+ return true;
+ }
+
+ 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
new file mode 100644
index 0000000..c6abb2c
--- /dev/null
+++ b/system/src/Grav/Common/Composer.php
@@ -0,0 +1,61 @@
+path = $path ? rtrim($path, '\\/') . '/' : '';
+ $this->cacheFolder = $cacheFolder;
+ $this->files = $files;
+ $this->timestamp = 0;
+ }
+
+ /**
+ * Get filename for the compiled PHP file.
+ *
+ * @param string $name
+ * @return $this
+ */
+ public function name($name = null)
+ {
+ if (!$this->name) {
+ $this->name = $name ?: md5(json_encode(array_keys($this->files)));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Function gets called when cached configuration is saved.
+ */
+ public function modified() {}
+
+ /**
+ * Get timestamp of compiled configuration
+ *
+ * @return int Timestamp of compiled configuration
+ */
+ public function timestamp()
+ {
+ return $this->timestamp ?: time();
+ }
+
+ /**
+ * Load the configuration.
+ *
+ * @return mixed
+ */
+ public function load()
+ {
+ if ($this->object) {
+ return $this->object;
+ }
+
+ $filename = $this->createFilename();
+ if (!$this->loadCompiledFile($filename) && $this->loadFiles()) {
+ $this->saveCompiledFile($filename);
+ }
+
+ return $this->object;
+ }
+
+ /**
+ * Returns checksum from the configuration files.
+ *
+ * You can set $this->checksum = false to disable this check.
+ *
+ * @return bool|string
+ */
+ public function checksum()
+ {
+ if (null === $this->checksum) {
+ $this->checksum = md5(json_encode($this->files) . $this->version);
+ }
+
+ return $this->checksum;
+ }
+
+ protected function createFilename()
+ {
+ return "{$this->cacheFolder}/{$this->name()->name}.php";
+ }
+
+ /**
+ * Create configuration object.
+ *
+ * @param array $data
+ */
+ abstract protected function createObject(array $data = []);
+
+ /**
+ * Finalize configuration object.
+ */
+ abstract protected function finalizeObject();
+
+ /**
+ * Load single configuration file and append it to the correct position.
+ *
+ * @param string $name Name of the position.
+ * @param string $filename File to be loaded.
+ */
+ abstract protected function loadFile($name, $filename);
+
+ /**
+ * Load and join all configuration files.
+ *
+ * @return bool
+ * @internal
+ */
+ protected function loadFiles()
+ {
+ $this->createObject();
+
+ $list = array_reverse($this->files);
+ foreach ($list as $files) {
+ foreach ($files as $name => $item) {
+ $this->loadFile($name, $this->path . $item['file']);
+ }
+ }
+
+ $this->finalizeObject();
+
+ return true;
+ }
+
+ /**
+ * Load compiled file.
+ *
+ * @param string $filename
+ * @return bool
+ * @internal
+ */
+ protected function loadCompiledFile($filename)
+ {
+ if (!file_exists($filename)) {
+ return false;
+ }
+
+ $cache = include $filename;
+ if (
+ !\is_array($cache)
+ || !isset($cache['checksum'], $cache['data'], $cache['@class'])
+ || $cache['@class'] !== \get_class($this)
+ ) {
+ return false;
+ }
+
+ // Load real file if cache isn't up to date (or is invalid).
+ if ($cache['checksum'] !== $this->checksum()) {
+ return false;
+ }
+
+ $this->createObject($cache['data']);
+ $this->timestamp = $cache['timestamp'] ?? 0;
+
+ $this->finalizeObject();
+
+ return true;
+ }
+
+ /**
+ * Save compiled file.
+ *
+ * @param string $filename
+ * @throws \RuntimeException
+ * @internal
+ */
+ protected function saveCompiledFile($filename)
+ {
+ $file = PhpFile::instance($filename);
+
+ // Attempt to lock the file for writing.
+ try {
+ $file->lock(false);
+ } catch (\Exception $e) {
+ // Another process has locked the file; we will check this in a bit.
+ }
+
+ if ($file->locked() === false) {
+ // File was already locked by another process.
+ return;
+ }
+
+ $cache = [
+ '@class' => \get_class($this),
+ 'timestamp' => time(),
+ 'checksum' => $this->checksum(),
+ 'files' => $this->files,
+ 'data' => $this->getState()
+ ];
+
+ $file->save($cache);
+ $file->unlock();
+ $file->free();
+
+ $this->modified();
+ }
+
+ protected function getState()
+ {
+ return $this->object->toArray();
+ }
+}
diff --git a/system/src/Grav/Common/Config/CompiledBlueprints.php b/system/src/Grav/Common/Config/CompiledBlueprints.php
new file mode 100644
index 0000000..9ad7be6
--- /dev/null
+++ b/system/src/Grav/Common/Config/CompiledBlueprints.php
@@ -0,0 +1,119 @@
+version = 2;
+ }
+
+ /**
+ * Returns checksum from the configuration files.
+ *
+ * You can set $this->checksum = false to disable this check.
+ *
+ * @return bool|string
+ */
+ public function checksum()
+ {
+ if (null === $this->checksum) {
+ $this->checksum = md5(json_encode($this->files) . json_encode($this->getTypes()) . $this->version);
+ }
+
+ return $this->checksum;
+ }
+
+ /**
+ * Create configuration object.
+ *
+ * @param array $data
+ */
+ protected function createObject(array $data = [])
+ {
+ $this->object = (new BlueprintSchema($data))->setTypes($this->getTypes());
+ }
+
+ /**
+ * Get list of form field types.
+ *
+ * @return array
+ */
+ protected function getTypes()
+ {
+ return Grav::instance()['plugins']->formFieldTypes ?: [];
+ }
+
+ /**
+ * Finalize configuration object.
+ */
+ protected function finalizeObject()
+ {
+ }
+
+ /**
+ * Load single configuration file and append it to the correct position.
+ *
+ * @param string $name Name of the position.
+ * @param array $files Files to be loaded.
+ */
+ protected function loadFile($name, $files)
+ {
+ // Load blueprint file.
+ $blueprint = new Blueprint($files);
+
+ $this->object->embed($name, $blueprint->load()->toArray(), '/', true);
+ }
+
+ /**
+ * Load and join all configuration files.
+ *
+ * @return bool
+ * @internal
+ */
+ protected function loadFiles()
+ {
+ $this->createObject();
+
+ // Convert file list into parent list.
+ $list = [];
+ /** @var array $files */
+ foreach ($this->files as $files) {
+ foreach ($files as $name => $item) {
+ $list[$name][] = $this->path . $item['file'];
+ }
+ }
+
+ // Load files.
+ foreach ($list as $name => $files) {
+ $this->loadFile($name, $files);
+ }
+
+ $this->finalizeObject();
+
+ return true;
+ }
+
+ protected function getState()
+ {
+ return $this->object->getState();
+ }
+}
diff --git a/system/src/Grav/Common/Config/CompiledConfig.php b/system/src/Grav/Common/Config/CompiledConfig.php
new file mode 100644
index 0000000..1c92edd
--- /dev/null
+++ b/system/src/Grav/Common/Config/CompiledConfig.php
@@ -0,0 +1,101 @@
+version = 1;
+ }
+
+ /**
+ * Set blueprints for the configuration.
+ *
+ * @param callable $blueprints
+ * @return $this
+ */
+ public function setBlueprints(callable $blueprints)
+ {
+ $this->callable = $blueprints;
+
+ return $this;
+ }
+
+ /**
+ * @param bool $withDefaults
+ * @return mixed
+ */
+ public function load($withDefaults = false)
+ {
+ $this->withDefaults = $withDefaults;
+
+ return parent::load();
+ }
+
+ /**
+ * Create configuration object.
+ *
+ * @param array $data
+ */
+ protected function createObject(array $data = [])
+ {
+ if ($this->withDefaults && empty($data) && \is_callable($this->callable)) {
+ $blueprints = $this->callable;
+ $data = $blueprints()->getDefaults();
+ }
+
+ $this->object = new Config($data, $this->callable);
+ }
+
+ /**
+ * Finalize configuration object.
+ */
+ protected function finalizeObject()
+ {
+ $this->object->checksum($this->checksum());
+ $this->object->timestamp($this->timestamp());
+ }
+
+ /**
+ * Function gets called when cached configuration is saved.
+ */
+ public function modified()
+ {
+ $this->object->modified(true);
+ }
+
+ /**
+ * Load single configuration file and append it to the correct position.
+ *
+ * @param string $name Name of the position.
+ * @param string $filename File to be loaded.
+ */
+ protected function loadFile($name, $filename)
+ {
+ $file = CompiledYamlFile::instance($filename);
+ $this->object->join($name, $file->content(), '/');
+ $file->free();
+ }
+}
diff --git a/system/src/Grav/Common/Config/CompiledLanguages.php b/system/src/Grav/Common/Config/CompiledLanguages.php
new file mode 100644
index 0000000..ef18145
--- /dev/null
+++ b/system/src/Grav/Common/Config/CompiledLanguages.php
@@ -0,0 +1,67 @@
+version = 1;
+ }
+
+ /**
+ * Create configuration object.
+ *
+ * @param array $data
+ */
+ protected function createObject(array $data = [])
+ {
+ $this->object = new Languages($data);
+ }
+
+ /**
+ * Finalize configuration object.
+ */
+ protected function finalizeObject()
+ {
+ $this->object->checksum($this->checksum());
+ $this->object->timestamp($this->timestamp());
+ }
+
+
+ /**
+ * Function gets called when cached configuration is saved.
+ */
+ public function modified()
+ {
+ $this->object->modified(true);
+ }
+
+ /**
+ * Load single configuration file and append it to the correct position.
+ *
+ * @param string $name Name of the position.
+ * @param string $filename File to be loaded.
+ */
+ protected function loadFile($name, $filename)
+ {
+ $file = CompiledYamlFile::instance($filename);
+ if (preg_match('|languages\.yaml$|', $filename)) {
+ $this->object->mergeRecursive((array) $file->content());
+ } else {
+ $this->object->mergeRecursive([$name => $file->content()]);
+ }
+ $file->free();
+ }
+}
diff --git a/system/src/Grav/Common/Config/Config.php b/system/src/Grav/Common/Config/Config.php
new file mode 100644
index 0000000..7b80846
--- /dev/null
+++ b/system/src/Grav/Common/Config/Config.php
@@ -0,0 +1,126 @@
+key) {
+ $this->key = md5($this->checksum . $this->timestamp);
+ }
+
+ return $this->key;
+ }
+
+ public function checksum($checksum = null)
+ {
+ if ($checksum !== null) {
+ $this->checksum = $checksum;
+ }
+
+ return $this->checksum;
+ }
+
+ public function modified($modified = null)
+ {
+ if ($modified !== null) {
+ $this->modified = $modified;
+ }
+
+ return $this->modified;
+ }
+
+ public function timestamp($timestamp = null)
+ {
+ if ($timestamp !== null) {
+ $this->timestamp = $timestamp;
+ }
+
+ return $this->timestamp;
+ }
+
+ public function reload()
+ {
+ $grav = Grav::instance();
+
+ // Load new configuration.
+ $config = ConfigServiceProvider::load($grav);
+
+ /** @var Debugger $debugger */
+ $debugger = $grav['debugger'];
+
+ if ($config->modified()) {
+ // Update current configuration.
+ $this->items = $config->toArray();
+ $this->checksum($config->checksum());
+ $this->modified(true);
+
+ $debugger->addMessage('Configuration was changed and saved.');
+ }
+
+ return $this;
+ }
+
+ public function debug()
+ {
+ /** @var Debugger $debugger */
+ $debugger = Grav::instance()['debugger'];
+
+ $debugger->addMessage('Environment Name: ' . $this->environment);
+ if ($this->modified()) {
+ $debugger->addMessage('Configuration reloaded and cached.');
+ }
+ }
+
+ public function init()
+ {
+ $setup = Grav::instance()['setup']->toArray();
+ foreach ($setup as $key => $value) {
+ if ($key === 'streams' || !\is_array($value)) {
+ // Optimized as streams and simple values are fully defined in setup.
+ $this->items[$key] = $value;
+ } else {
+ $this->joinDefaults($key, $value);
+ }
+ }
+
+ // Legacy value - Override the media.upload_limit based on PHP values
+ $this->items['system']['media']['upload_limit'] = Utils::getUploadLimit();
+ }
+
+ /**
+ * @return mixed
+ * @deprecated 1.5 Use Grav::instance()['languages'] instead.
+ */
+ public function getLanguages()
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use Grav::instance()[\'languages\'] instead', E_USER_DEPRECATED);
+
+ return Grav::instance()['languages'];
+ }
+}
diff --git a/system/src/Grav/Common/Config/ConfigFileFinder.php b/system/src/Grav/Common/Config/ConfigFileFinder.php
new file mode 100644
index 0000000..1e7adca
--- /dev/null
+++ b/system/src/Grav/Common/Config/ConfigFileFinder.php
@@ -0,0 +1,263 @@
+base = $base ? "{$base}/" : '';
+
+ return $this;
+ }
+
+ /**
+ * Return all locations for all the files with a timestamp.
+ *
+ * @param array $paths List of folders to look from.
+ * @param string $pattern Pattern to match the file. Pattern will also be removed from the key.
+ * @param int $levels Maximum number of recursive directories.
+ * @return array
+ */
+ public function locateFiles(array $paths, $pattern = '|\.yaml$|', $levels = -1)
+ {
+ $list = [];
+ foreach ($paths as $folder) {
+ $list += $this->detectRecursive($folder, $pattern, $levels);
+ }
+ return $list;
+ }
+
+ /**
+ * Return all locations for all the files with a timestamp.
+ *
+ * @param array $paths List of folders to look from.
+ * @param string $pattern Pattern to match the file. Pattern will also be removed from the key.
+ * @param int $levels Maximum number of recursive directories.
+ * @return array
+ */
+ public function getFiles(array $paths, $pattern = '|\.yaml$|', $levels = -1)
+ {
+ $list = [];
+ foreach ($paths as $folder) {
+ $path = trim(Folder::getRelativePath($folder), '/');
+
+ $files = $this->detectRecursive($folder, $pattern, $levels);
+
+ $list += $files[trim($path, '/')];
+ }
+ return $list;
+ }
+
+ /**
+ * Return all paths for all the files with a timestamp.
+ *
+ * @param array $paths List of folders to look from.
+ * @param string $pattern Pattern to match the file. Pattern will also be removed from the key.
+ * @param int $levels Maximum number of recursive directories.
+ * @return array
+ */
+ public function listFiles(array $paths, $pattern = '|\.yaml$|', $levels = -1)
+ {
+ $list = [];
+ foreach ($paths as $folder) {
+ $list = array_merge_recursive($list, $this->detectAll($folder, $pattern, $levels));
+ }
+ return $list;
+ }
+
+ /**
+ * Find filename from a list of folders.
+ *
+ * Note: Only finds the last override.
+ *
+ * @param string $filename
+ * @param array $folders
+ * @return array
+ */
+ public function locateFileInFolder($filename, array $folders)
+ {
+ $list = [];
+ foreach ($folders as $folder) {
+ $list += $this->detectInFolder($folder, $filename);
+ }
+ return $list;
+ }
+
+ /**
+ * Find filename from a list of folders.
+ *
+ * @param array $folders
+ * @param string $filename
+ * @return array
+ */
+ public function locateInFolders(array $folders, $filename = null)
+ {
+ $list = [];
+ foreach ($folders as $folder) {
+ $path = trim(Folder::getRelativePath($folder), '/');
+ $list[$path] = $this->detectInFolder($folder, $filename);
+ }
+ return $list;
+ }
+
+ /**
+ * Return all existing locations for a single file with a timestamp.
+ *
+ * @param array $paths Filesystem paths to look up from.
+ * @param string $name Configuration file to be located.
+ * @param string $ext File extension (optional, defaults to .yaml).
+ * @return array
+ */
+ public function locateFile(array $paths, $name, $ext = '.yaml')
+ {
+ $filename = preg_replace('|[.\/]+|', '/', $name) . $ext;
+
+ $list = [];
+ foreach ($paths as $folder) {
+ $path = trim(Folder::getRelativePath($folder), '/');
+
+ if (is_file("{$folder}/{$filename}")) {
+ $modified = filemtime("{$folder}/{$filename}");
+ } else {
+ $modified = 0;
+ }
+ $basename = $this->base . $name;
+ $list[$path] = [$basename => ['file' => "{$path}/{$filename}", 'modified' => $modified]];
+ }
+
+ return $list;
+ }
+
+ /**
+ * Detects all directories with a configuration file and returns them with last modification time.
+ *
+ * @param string $folder Location to look up from.
+ * @param string $pattern Pattern to match the file. Pattern will also be removed from the key.
+ * @param int $levels Maximum number of recursive directories.
+ * @return array
+ * @internal
+ */
+ protected function detectRecursive($folder, $pattern, $levels)
+ {
+ $path = trim(Folder::getRelativePath($folder), '/');
+
+ if (is_dir($folder)) {
+ // Find all system and user configuration files.
+ $options = [
+ 'levels' => $levels,
+ 'compare' => 'Filename',
+ 'pattern' => $pattern,
+ 'filters' => [
+ 'pre-key' => $this->base,
+ 'key' => $pattern,
+ 'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
+ return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()];
+ }
+ ],
+ 'key' => 'SubPathname'
+ ];
+
+ $list = Folder::all($folder, $options);
+
+ ksort($list);
+ } else {
+ $list = [];
+ }
+
+ return [$path => $list];
+ }
+
+ /**
+ * Detects all directories with the lookup file and returns them with last modification time.
+ *
+ * @param string $folder Location to look up from.
+ * @param string $lookup Filename to be located (defaults to directory name).
+ * @return array
+ * @internal
+ */
+ protected function detectInFolder($folder, $lookup = null)
+ {
+ $folder = rtrim($folder, '/');
+ $path = trim(Folder::getRelativePath($folder), '/');
+ $base = $path === $folder ? '' : ($path ? substr($folder, 0, -strlen($path)) : $folder . '/');
+
+ $list = [];
+
+ if (is_dir($folder)) {
+ $iterator = new \DirectoryIterator($folder);
+
+ /** @var \DirectoryIterator $directory */
+ foreach ($iterator as $directory) {
+ if (!$directory->isDir() || $directory->isDot()) {
+ continue;
+ }
+
+ $name = $directory->getFilename();
+ $find = ($lookup ?: $name) . '.yaml';
+ $filename = "{$path}/{$name}/{$find}";
+
+ if (file_exists($base . $filename)) {
+ $basename = $this->base . $name;
+ $list[$basename] = ['file' => $filename, 'modified' => filemtime($base . $filename)];
+ }
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * Detects all plugins with a configuration file and returns them with last modification time.
+ *
+ * @param string $folder Location to look up from.
+ * @param string $pattern Pattern to match the file. Pattern will also be removed from the key.
+ * @param int $levels Maximum number of recursive directories.
+ * @return array
+ * @internal
+ */
+ protected function detectAll($folder, $pattern, $levels)
+ {
+ $path = trim(Folder::getRelativePath($folder), '/');
+
+ if (is_dir($folder)) {
+ // Find all system and user configuration files.
+ $options = [
+ 'levels' => $levels,
+ 'compare' => 'Filename',
+ 'pattern' => $pattern,
+ 'filters' => [
+ 'pre-key' => $this->base,
+ 'key' => $pattern,
+ 'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
+ return ["{$path}/{$file->getSubPathname()}" => $file->getMTime()];
+ }
+ ],
+ 'key' => 'SubPathname'
+ ];
+
+ $list = Folder::all($folder, $options);
+
+ ksort($list);
+ } else {
+ $list = [];
+ }
+
+ return $list;
+ }
+}
diff --git a/system/src/Grav/Common/Config/Languages.php b/system/src/Grav/Common/Config/Languages.php
new file mode 100644
index 0000000..9c76dc5
--- /dev/null
+++ b/system/src/Grav/Common/Config/Languages.php
@@ -0,0 +1,83 @@
+checksum = $checksum;
+ }
+
+ return $this->checksum;
+ }
+
+ public function modified($modified = null)
+ {
+ if ($modified !== null) {
+ $this->modified = $modified;
+ }
+
+ return $this->modified;
+ }
+
+ public function timestamp($timestamp = null)
+ {
+ if ($timestamp !== null) {
+ $this->timestamp = $timestamp;
+ }
+
+ return $this->timestamp;
+ }
+
+ public function reformat()
+ {
+ if (isset($this->items['plugins'])) {
+ $this->items = array_merge_recursive($this->items, $this->items['plugins']);
+ unset($this->items['plugins']);
+ }
+ }
+
+ public function mergeRecursive(array $data)
+ {
+ $this->items = 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
new file mode 100644
index 0000000..6a4d336
--- /dev/null
+++ b/system/src/Grav/Common/Config/Setup.php
@@ -0,0 +1,321 @@
+ 'unknown',
+ '127.0.0.1' => 'localhost',
+ '::1' => 'localhost'
+ ];
+
+ /**
+ * @var string Current environment normalized to lower case.
+ */
+ public static $environment;
+
+ protected $streams = [
+ 'system' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['system'],
+ ]
+ ],
+ 'user' => [
+ 'type' => 'ReadOnlyStream',
+ 'force' => true,
+ 'prefixes' => [
+ '' => ['user'],
+ ]
+ ],
+ 'environment' => [
+ 'type' => 'ReadOnlyStream'
+ // If not defined, environment will be set up in the constructor.
+ ],
+ 'asset' => [
+ 'type' => 'Stream',
+ 'prefixes' => [
+ '' => ['assets'],
+ ]
+ ],
+ 'blueprints' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['environment://blueprints', 'user://blueprints', 'system/blueprints'],
+ ]
+ ],
+ 'config' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['environment://config', 'user://config', 'system/config'],
+ ]
+ ],
+ 'plugins' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['user://plugins'],
+ ]
+ ],
+ 'plugin' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['user://plugins'],
+ ]
+ ],
+ 'themes' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['user://themes'],
+ ]
+ ],
+ 'languages' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['environment://languages', 'user://languages', 'system/languages'],
+ ]
+ ],
+ 'cache' => [
+ 'type' => 'Stream',
+ 'force' => true,
+ 'prefixes' => [
+ '' => ['cache'],
+ 'images' => ['images']
+ ]
+ ],
+ 'log' => [
+ 'type' => 'Stream',
+ 'force' => true,
+ 'prefixes' => [
+ '' => ['logs']
+ ]
+ ],
+ 'backup' => [
+ 'type' => 'Stream',
+ 'force' => true,
+ 'prefixes' => [
+ '' => ['backup']
+ ]
+ ],
+ 'tmp' => [
+ 'type' => 'Stream',
+ 'force' => true,
+ 'prefixes' => [
+ '' => ['tmp']
+ ]
+ ],
+ 'image' => [
+ 'type' => 'Stream',
+ 'prefixes' => [
+ '' => ['user://images', 'system://images']
+ ]
+ ],
+ 'page' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['user://pages']
+ ]
+ ],
+ 'user-data' => [
+ 'type' => 'Stream',
+ 'force' => true,
+ 'prefixes' => [
+ '' => ['user://data']
+ ]
+ ],
+ 'account' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['user://accounts']
+ ]
+ ],
+ ];
+
+ /**
+ * @param Container|array $container
+ */
+ public function __construct($container)
+ {
+ // 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 "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.
+ if (!isset($setup['streams']['schemes'])) {
+ $setup['streams']['schemes'] = [];
+ }
+ $setup['streams']['schemes'] += $this->streams;
+
+ // Initialize class.
+ parent::__construct($setup);
+
+ // Set up environment.
+ $this->def('environment', $environment);
+ $this->def('streams.schemes.environment.prefixes', ['' => ["user://{$this->get('environment')}"]]);
+ }
+
+ /**
+ * @return $this
+ * @throws \RuntimeException
+ * @throws \InvalidArgumentException
+ */
+ public function init()
+ {
+ $locator = new UniformResourceLocator(GRAV_ROOT);
+ $files = [];
+
+ $guard = 5;
+ do {
+ $check = $files;
+ $this->initializeLocator($locator);
+ $files = $locator->findResources('config://streams.yaml');
+
+ if ($check === $files) {
+ break;
+ }
+
+ // Update streams.
+ foreach (array_reverse($files) as $path) {
+ $file = CompiledYamlFile::instance($path);
+ $content = (array)$file->content();
+ if (!empty($content['schemes'])) {
+ $this->items['streams']['schemes'] = $content['schemes'] + $this->items['streams']['schemes'];
+ }
+ }
+ } while (--$guard);
+
+ if (!$guard) {
+ throw new \RuntimeException('Setup: Configuration reload loop detected!');
+ }
+
+ // Make sure we have valid setup.
+ $this->check($locator);
+
+ return $this;
+ }
+
+ /**
+ * Initialize resource locator by using the configuration.
+ *
+ * @param UniformResourceLocator $locator
+ * @throws \BadMethodCallException
+ */
+ public function initializeLocator(UniformResourceLocator $locator)
+ {
+ $locator->reset();
+
+ $schemes = (array) $this->get('streams.schemes', []);
+
+ foreach ($schemes as $scheme => $config) {
+ if (isset($config['paths'])) {
+ $locator->addPath($scheme, '', $config['paths']);
+ }
+
+ $override = $config['override'] ?? false;
+ $force = $config['force'] ?? false;
+
+ if (isset($config['prefixes'])) {
+ foreach ((array)$config['prefixes'] as $prefix => $paths) {
+ $locator->addPath($scheme, $prefix, $paths, $override, $force);
+ }
+ }
+ }
+ }
+
+ /**
+ * Get available streams and their types from the configuration.
+ *
+ * @return array
+ */
+ public function getStreams()
+ {
+ $schemes = [];
+ foreach ((array) $this->get('streams.schemes') as $scheme => $config) {
+ $type = $config['type'] ?? 'ReadOnlyStream';
+ if ($type[0] !== '\\') {
+ $type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type;
+ }
+
+ $schemes[$scheme] = $type;
+ }
+
+ return $schemes;
+ }
+
+ /**
+ * @param UniformResourceLocator $locator
+ * @throws \InvalidArgumentException
+ * @throws \BadMethodCallException
+ * @throws \RuntimeException
+ */
+ protected function check(UniformResourceLocator $locator)
+ {
+ $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));
+ if ($diff) {
+ throw new \InvalidArgumentException(
+ sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff))
+ );
+ }
+
+ try {
+ if (!$locator->findResource('environment://config', true)) {
+ // If environment does not have its own directory, remove it from the lookup.
+ $this->set('streams.schemes.environment.prefixes', ['config' => []]);
+ $this->initializeLocator($locator);
+ }
+
+ // Create security.yaml if it doesn't exist.
+ $filename = $locator->findResource('config://security.yaml', true, true);
+ $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
new file mode 100644
index 0000000..e703777
--- /dev/null
+++ b/system/src/Grav/Common/Data/Blueprint.php
@@ -0,0 +1,428 @@
+blueprintSchema) {
+ $this->blueprintSchema = clone $this->blueprintSchema;
+ }
+ }
+
+ public function setScope($scope)
+ {
+ $this->scope = $scope;
+ }
+
+ /**
+ * Set default values for field types.
+ *
+ * @param array $types
+ * @return $this
+ */
+ public function setTypes(array $types)
+ {
+ $this->initInternals();
+
+ $this->blueprintSchema->setTypes($types);
+
+ 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()
+ {
+ $this->initInternals();
+
+ 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;
+ }
+
+ /**
+ * Merge two arrays by using blueprints.
+ *
+ * @param array $data1
+ * @param array $data2
+ * @param string $name Optional
+ * @param string $separator Optional
+ * @return array
+ */
+ public function mergeData(array $data1, array $data2, $name = null, $separator = '.')
+ {
+ $this->initInternals();
+
+ 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.
+ *
+ * @param array $data
+ * @param string $prefix
+ * @return array
+ */
+ public function extra(array $data, $prefix = '')
+ {
+ $this->initInternals();
+
+ return $this->blueprintSchema->extra($data, $prefix);
+ }
+
+ /**
+ * Validate data against blueprints.
+ *
+ * @param array $data
+ * @throws \RuntimeException
+ */
+ public function validate(array $data)
+ {
+ $this->initInternals();
+
+ $this->blueprintSchema->validate($data);
+ }
+
+ /**
+ * Filter data by using blueprints.
+ *
+ * @param array $data
+ * @param bool $missingValuesAsNull
+ * @param bool $keepEmptyValues
+ * @return array
+ */
+ public function filter(array $data, bool $missingValuesAsNull = false, bool $keepEmptyValues = false)
+ {
+ $this->initInternals();
+
+ 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.
+ *
+ * @return BlueprintSchema
+ */
+ public function schema()
+ {
+ $this->initInternals();
+
+ return $this->blueprintSchema;
+ }
+
+ public function addDynamicHandler(string $name, callable $callable): void
+ {
+ $this->handlers[$name] = $callable;
+ }
+
+ /**
+ * Initialize validator.
+ */
+ protected function initInternals()
+ {
+ 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;
+ }
+ }
+
+ /**
+ * @param string $filename
+ * @return string
+ */
+ protected function loadFile($filename)
+ {
+ $file = CompiledYamlFile::instance($filename);
+ $content = $file->content();
+ $file->free();
+
+ return $content;
+ }
+
+ /**
+ * @param string|array $path
+ * @param string $context
+ * @return array
+ */
+ protected function getFiles($path, $context = null)
+ {
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+
+ if (\is_string($path) && !$locator->isStream($path)) {
+ // Find path overrides.
+ $paths = (array) ($this->overrides[$path] ?? null);
+
+ // Add path pointing to default context.
+ if ($context === null) {
+ $context = $this->context;
+ }
+
+ if ($context && $context[\strlen($context)-1] !== '/') {
+ $context .= '/';
+ }
+
+ $path = $context . $path;
+
+ if (!preg_match('/\.yaml$/', $path)) {
+ $path .= '.yaml';
+ }
+
+ $paths[] = $path;
+ } else {
+ $paths = (array) $path;
+ }
+
+ $files = [];
+ foreach ($paths as $lookup) {
+ if (\is_string($lookup) && strpos($lookup, '://')) {
+ $files = array_merge($files, $locator->findResources($lookup));
+ } else {
+ $files[] = $lookup;
+ }
+ }
+
+ return array_values(array_unique($files));
+ }
+
+ /**
+ * @param array $field
+ * @param string $property
+ * @param array $call
+ */
+ protected function dynamicData(array &$field, $property, array &$call)
+ {
+ $params = $call['params'];
+
+ if (\is_array($params)) {
+ $function = array_shift($params);
+ } else {
+ $function = $params;
+ $params = [];
+ }
+
+ [$o, $f] = explode('::', $function, 2);
+
+ $data = null;
+ if (!$f) {
+ if (\function_exists($o)) {
+ $data = \call_user_func_array($o, $params);
+ }
+ } else {
+ if (method_exists($o, $f)) {
+ $data = \call_user_func_array([$o, $f], $params);
+ }
+ }
+
+ // If function returns a value,
+ if (null !== $data) {
+ if (\is_array($data) && isset($field[$property]) && \is_array($field[$property])) {
+ // Combine field and @data-field together.
+ $field[$property] += $data;
+ } else {
+ // Or create/replace field with @data-field.
+ $field[$property] = $data;
+ }
+ }
+ }
+
+ /**
+ * @param array $field
+ * @param string $property
+ * @param array $call
+ */
+ protected function dynamicConfig(array &$field, $property, array &$call)
+ {
+ $value = $call['params'];
+ $default = $field[$property] ?? null;
+ $config = Grav::instance()['config']->get($value, $default);
+
+ 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|null $user */
+ $user = $grav['user'] ?? null;
+ foreach ($actions as $action) {
+ if (!$user || !$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
new file mode 100644
index 0000000..beebaa6
--- /dev/null
+++ b/system/src/Grav/Common/Data/BlueprintSchema.php
@@ -0,0 +1,330 @@
+ true,
+ 'help' => true,
+ 'placeholder' => true,
+ 'placeholder_key' => true,
+ 'placeholder_value' => true,
+ 'fields' => 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.
+ *
+ * @param array $data
+ * @throws \RuntimeException
+ */
+ public function validate(array $data)
+ {
+ try {
+ $messages = $this->validateArray($data, $this->nested);
+
+ } catch (\RuntimeException $e) {
+ throw (new ValidationException($e->getMessage(), $e->getCode(), $e))->setMessages();
+ }
+
+ if (!empty($messages)) {
+ throw (new ValidationException())->setMessages($messages);
+ }
+ }
+
+ /**
+ * @param array $data
+ * @param array $toggles
+ * @return array
+ */
+ public function processForm(array $data, array $toggles = [])
+ {
+ 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
+ * @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
+ */
+ protected function validateArray(array $data, array $rules)
+ {
+ $messages = $this->checkRequired($data, $rules);
+
+ foreach ($data as $key => $child) {
+ $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($child, $rule);
+ } elseif (\is_array($child) && \is_array($val)) {
+ // Array has been defined in blueprints.
+ $messages += $this->validateArray($child, $val);
+ } elseif (isset($rules['validation']) && $rules['validation'] === 'strict') {
+ // Undefined/extra item.
+ throw new \RuntimeException(sprintf('%s is not defined in blueprints', $key));
+ }
+ }
+
+ return $messages;
+ }
+
+ /**
+ * @param array $data
+ * @param array $rules
+ * @param bool $missingValuesAsNull
+ * @param bool $keepEmptyValues
+ * @return array
+ */
+ protected function filterArray(array $data, array $rules, $missingValuesAsNull, $keepEmptyValues)
+ {
+ $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 = $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)) {
+ // Array has been defined in blueprints.
+ $field = $this->filterArray($field, $val, $missingValuesAsNull, $keepEmptyValues);
+
+ } elseif (isset($rules['validation']) && $rules['validation'] === 'strict') {
+ // Skip any extra data.
+ continue;
+ }
+
+ if ($keepEmptyValues || (null !== $field && (!\is_array($field) || !empty($field)))) {
+ $results[$key] = $field;
+ }
+ }
+
+ 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)) {
+ // Special toggle handling for all the nested data.
+ $toggle = $toggles[$key] ?? [];
+ if (!is_array($toggle)) {
+ if (!$toggle) {
+ $data[$key] = null;
+
+ continue;
+ }
+
+ $toggle = [];
+ }
+ // Recursively fetch the items.
+ $data[$key] = $this->processFormRecursive($data[$key] ?? null, $toggle, $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;
+ }
+
+ /**
+ * @param array $data
+ * @param array $fields
+ * @return array
+ */
+ protected function checkRequired(array $data, array $fields)
+ {
+ $messages = [];
+
+ foreach ($fields as $name => $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) {
+
+ if (isset($data[$name])) {
+ continue;
+ }
+ if ($field['type'] === 'file' && isset($data['data']['name'][$name])) { //handle case of file input fields required
+ continue;
+ }
+
+ $value = $field['label'] ?? $field['name'];
+ $language = Grav::instance()['language'];
+ $message = sprintf($language->translate('GRAV.FORM.MISSING_REQUIRED_FIELD', null, true) . ' %s', $language->translate($value));
+ $messages[$field['name']][] = $message;
+ }
+ }
+
+ return $messages;
+ }
+
+ /**
+ * @param array $field
+ * @param string $property
+ * @param array $call
+ */
+ protected function dynamicConfig(array &$field, $property, array &$call)
+ {
+ $value = $call['params'];
+
+ $default = $field[$property] ?? null;
+ $config = Grav::instance()['config']->get($value, $default);
+
+ if (null !== $config) {
+ $field[$property] = $config;
+ }
+ }
+}
diff --git a/system/src/Grav/Common/Data/Blueprints.php b/system/src/Grav/Common/Data/Blueprints.php
new file mode 100644
index 0000000..1d0fcc6
--- /dev/null
+++ b/system/src/Grav/Common/Data/Blueprints.php
@@ -0,0 +1,114 @@
+search = $search;
+ }
+
+ /**
+ * Get blueprint.
+ *
+ * @param string $type Blueprint type.
+ * @return Blueprint
+ * @throws \RuntimeException
+ */
+ public function get($type)
+ {
+ if (!isset($this->instances[$type])) {
+ $blueprint = $this->loadFile($type);
+ $this->instances[$type] = $blueprint;
+ }
+
+ return $this->instances[$type];
+ }
+
+ /**
+ * Get all available blueprint types.
+ *
+ * @return array List of type=>name
+ */
+ public function types()
+ {
+ if ($this->types === null) {
+ $this->types = [];
+
+ $grav = Grav::instance();
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $grav['locator'];
+
+ // Get stream / directory iterator.
+ if ($locator->isStream($this->search)) {
+ $iterator = $locator->getIterator($this->search);
+ } else {
+ $iterator = new \DirectoryIterator($this->search);
+ }
+
+ /** @var \DirectoryIterator $file */
+ foreach ($iterator as $file) {
+ if (!$file->isFile() || '.' . $file->getExtension() !== YAML_EXT) {
+ continue;
+ }
+ $name = $file->getBasename(YAML_EXT);
+ $this->types[$name] = ucfirst(str_replace('_', ' ', $name));
+ }
+ }
+
+ return $this->types;
+ }
+
+
+ /**
+ * Load blueprint file.
+ *
+ * @param string $name Name of the blueprint.
+ * @return Blueprint
+ */
+ protected function loadFile($name)
+ {
+ $blueprint = new Blueprint($name);
+
+ if (\is_array($this->search) || \is_object($this->search)) {
+ // Page types.
+ $blueprint->setOverrides($this->search);
+ $blueprint->setContext('blueprints://pages');
+ } else {
+ $blueprint->setContext($this->search);
+ }
+
+ try {
+ $blueprint->load()->init();
+ } catch (\RuntimeException $e) {
+ $log = Grav::instance()['log'];
+ $log->error(sprintf('Blueprint %s cannot be loaded: %s', $name, $e->getMessage()));
+
+ throw $e;
+ }
+
+ return $blueprint;
+ }
+}
diff --git a/system/src/Grav/Common/Data/Data.php b/system/src/Grav/Common/Data/Data.php
new file mode 100644
index 0000000..97b5530
--- /dev/null
+++ b/system/src/Grav/Common/Data/Data.php
@@ -0,0 +1,327 @@
+items = $items;
+ $this->blueprints = $blueprints;
+ }
+
+ /**
+ * @param bool $value
+ * @return $this
+ */
+ public function setKeepEmptyValues(bool $value)
+ {
+ $this->keepEmptyValues = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param bool $value
+ * @return $this
+ */
+ public function setMissingValuesAsNull(bool $value)
+ {
+ $this->missingValuesAsNull = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get value by using dot notation for nested arrays/objects.
+ *
+ * @example $value = $data->value('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 value($name, $default = null, $separator = '.')
+ {
+ return $this->get($name, $default, $separator);
+ }
+
+ /**
+ * 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 = '.')
+ {
+ $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->blueprints()->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->blueprints()->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 = '.')
+ {
+ if (\is_object($value)) {
+ $value = (array) $value;
+ }
+
+ $old = $this->get($name, null, $separator);
+ if ($old !== null) {
+ $value = $this->blueprints()->mergeData($value, $old, $name, $separator);
+ }
+
+ $this->set($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 = '.')
+ {
+ 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->blueprints()->mergeData($old, $value, $name, $separator);
+ }
+
+
+ /**
+ * Merge two configurations together.
+ *
+ * @param array $data
+ * @return $this
+ */
+ public function merge(array $data)
+ {
+ $this->items = $this->blueprints()->mergeData($this->items, $data);
+
+ return $this;
+ }
+
+ /**
+ * Set default values to the configuration if variables were not set.
+ *
+ * @param array $data
+ * @return $this
+ */
+ public function setDefaults(array $data)
+ {
+ $this->items = $this->blueprints()->mergeData($data, $this->items);
+
+ return $this;
+ }
+
+ /**
+ * Validate by blueprints.
+ *
+ * @return $this
+ * @throws \Exception
+ */
+ public function validate()
+ {
+ $this->blueprints()->validate($this->items);
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function filter()
+ {
+ $args = func_get_args();
+ $missingValuesAsNull = (bool)(array_shift($args) ?? $this->missingValuesAsNull);
+ $keepEmptyValues = (bool)(array_shift($args) ?? $this->keepEmptyValues);
+
+ $this->items = $this->blueprints()->filter($this->items, $missingValuesAsNull, $keepEmptyValues);
+
+ return $this;
+ }
+
+ /**
+ * Get extra items which haven't been defined in blueprints.
+ *
+ * @return array
+ */
+ public function extra()
+ {
+ return $this->blueprints()->extra($this->items);
+ }
+
+ /**
+ * Return blueprints.
+ *
+ * @return Blueprint
+ */
+ public function blueprints()
+ {
+ if (!$this->blueprints){
+ $this->blueprints = new Blueprint;
+ } elseif (\is_callable($this->blueprints)) {
+ // Lazy load blueprints.
+ $blueprints = $this->blueprints;
+ $this->blueprints = $blueprints();
+ }
+ return $this->blueprints;
+ }
+
+ /**
+ * Save data if storage has been defined.
+ * @throws \RuntimeException
+ */
+ public function save()
+ {
+ $file = $this->file();
+ if ($file) {
+ $file->save($this->items);
+ }
+ }
+
+ /**
+ * 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()
+ {
+ $file = $this->file();
+
+ return $file && $file->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()
+ {
+ $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 ($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
new file mode 100644
index 0000000..91279b2
--- /dev/null
+++ b/system/src/Grav/Common/Data/DataInterface.php
@@ -0,0 +1,70 @@
+value('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 value($name, $default = null, $separator = '.');
+
+ /**
+ * Merge external data.
+ *
+ * @param array $data
+ * @return mixed
+ */
+ public function merge(array $data);
+
+ /**
+ * Return blueprints.
+ */
+ public function blueprints();
+
+ /**
+ * Validate by blueprints.
+ *
+ * @throws \Exception
+ */
+ public function validate();
+
+ /**
+ * Filter all items by using blueprints.
+ */
+ public function filter();
+
+ /**
+ * Get extra items which haven't been defined in blueprints.
+ */
+ public function extra();
+
+ /**
+ * Save data into the file.
+ */
+ public function save();
+
+ /**
+ * Set or get the data storage.
+ *
+ * @param FileInterface $storage Optionally enter a new storage.
+ * @return FileInterface
+ */
+ public function file(FileInterface $storage = null);
+}
diff --git a/system/src/Grav/Common/Data/Validation.php b/system/src/Grav/Common/Data/Validation.php
new file mode 100644
index 0000000..a2bf0c5
--- /dev/null
+++ b/system/src/Grav/Common/Data/Validation.php
@@ -0,0 +1,813 @@
+translate($field['validate']['message'])
+ : $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 (isset($field['yaml']) && $field['yaml'] === true) {
+ $method = 'typeYaml';
+ }
+
+ $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(str_replace('-', '_', $rule));
+
+ if (method_exists(__CLASS__, $method)) {
+ $success = self::$method($value, $params);
+
+ if (!$success) {
+ $messages[$field['name']][] = $message;
+ }
+ }
+ }
+
+ return $messages;
+ }
+
+ /**
+ * Filter value against a blueprint field definition.
+ *
+ * @param mixed $value
+ * @param array $field
+ * @return mixed Filtered value.
+ */
+ public static function filter($value, array $field)
+ {
+ $validate = (array)($field['filter'] ?? $field['validate'] ?? null);
+
+ // If value isn't required, we will return null if empty value is given.
+ 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'];
+
+ $method = 'filter' . ucfirst(str_replace('-', '_', $type));
+
+ // If this is a YAML field validate/filter as such
+ if (isset($field['yaml']) && $field['yaml'] === true) {
+ $method = 'filterYaml';
+ }
+
+ if (!method_exists(__CLASS__, $method)) {
+ $method = isset($field['array']) && $field['array'] === true ? 'filterArray' : 'filterText';
+ }
+
+ return self::$method($value, $validate, $field);
+ }
+
+ /**
+ * HTML5 input: text
+ *
+ * @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 typeText($value, array $params, array $field)
+ {
+ if (!\is_string($value) && !is_numeric($value)) {
+ return false;
+ }
+
+ $value = (string)$value;
+
+ 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']) {
+ return false;
+ }
+
+ $min = $params['min'] ?? 0;
+ if (isset($params['step']) && (\strlen($value) - $min) % $params['step'] === 0) {
+ return false;
+ }
+
+ if ((!isset($params['multiline']) || !$params['multiline']) && preg_match('/\R/um', $value)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ 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;
+ }
+
+ /**
+ * @param mixed $value
+ * @param array $params
+ * @param array $field
+ * @return string|null
+ */
+ protected static function filterCheckbox($value, array $params, array $field)
+ {
+ $value = (string)$value;
+ $field_value = (string)($field['value'] ?? '1');
+
+ return $value === $field_value ? $value : null;
+ }
+
+ protected static function filterCommaList($value, array $params, array $field)
+ {
+ 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);
+ }
+
+ protected static function filterLower($value, array $params)
+ {
+ return strtolower($value);
+ }
+
+ protected static function filterUpper($value, array $params)
+ {
+ return strtoupper($value);
+ }
+
+
+ /**
+ * HTML5 input: textarea
+ *
+ * @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 typeTextarea($value, array $params, array $field)
+ {
+ if (!isset($params['multiline'])) {
+ $params['multiline'] = true;
+ }
+
+ return self::typeText($value, $params, $field);
+ }
+
+ /**
+ * HTML5 input: password
+ *
+ * @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 typePassword($value, array $params, array $field)
+ {
+ return self::typeText($value, $params, $field);
+ }
+
+ /**
+ * HTML5 input: hidden
+ *
+ * @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 typeHidden($value, array $params, array $field)
+ {
+ return self::typeText($value, $params, $field);
+ }
+
+ /**
+ * Custom input: checkbox list
+ *
+ * @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 typeCheckboxes($value, array $params, array $field)
+ {
+ // 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);
+ }
+
+ protected static function filterCheckboxes($value, array $params, array $field)
+ {
+ return self::filterArray($value, $params, $field);
+ }
+
+ /**
+ * HTML5 input: checkbox
+ *
+ * @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 typeCheckbox($value, array $params, array $field)
+ {
+ $value = (string)$value;
+ $field_value = (string)($field['value'] ?? '1');
+
+ return $value === $field_value;
+ }
+
+ /**
+ * HTML5 input: radio
+ *
+ * @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 typeRadio($value, array $params, array $field)
+ {
+ return self::typeArray((array) $value, $params, $field);
+ }
+
+ /**
+ * Custom input: toggle
+ *
+ * @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 typeToggle($value, array $params, array $field)
+ {
+ if (\is_bool($value)) {
+ $value = (int)$value;
+ }
+
+ return self::typeArray((array) $value, $params, $field);
+ }
+
+ /**
+ * Custom input: file
+ *
+ * @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 typeFile($value, array $params, array $field)
+ {
+ return self::typeArray((array)$value, $params, $field);
+ }
+
+ protected static function filterFile($value, array $params, array $field)
+ {
+ return (array)$value;
+ }
+
+ /**
+ * HTML5 input: select
+ *
+ * @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 typeSelect($value, array $params, array $field)
+ {
+ return self::typeArray((array) $value, $params, $field);
+ }
+
+ /**
+ * HTML5 input: number
+ *
+ * @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 typeNumber($value, array $params, array $field)
+ {
+ if (!is_numeric($value)) {
+ return false;
+ }
+
+ if (isset($params['min']) && $value < $params['min']) {
+ return false;
+ }
+
+ if (isset($params['max']) && $value > $params['max']) {
+ return false;
+ }
+
+ $min = $params['min'] ?? 0;
+
+ return !(isset($params['step']) && fmod($value - $min, $params['step']) === 0);
+ }
+
+ protected static function filterNumber($value, array $params, array $field)
+ {
+ return (string)(int)$value !== (string)(float)$value ? (float) $value : (int) $value;
+ }
+
+ protected static function filterDateTime($value, array $params, array $field)
+ {
+ $format = Grav::instance()['config']->get('system.pages.dateformat.default');
+ if ($format) {
+ $converted = new \DateTime($value);
+ return $converted->format($format);
+ }
+ return $value;
+ }
+
+
+ /**
+ * HTML5 input: range
+ *
+ * @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 typeRange($value, array $params, array $field)
+ {
+ return self::typeNumber($value, $params, $field);
+ }
+
+ protected static function filterRange($value, array $params, array $field)
+ {
+ return self::filterNumber($value, $params, $field);
+ }
+
+ /**
+ * HTML5 input: color
+ *
+ * @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 typeColor($value, array $params, array $field)
+ {
+ return preg_match('/^\#[0-9a-fA-F]{3}[0-9a-fA-F]{3}?$/u', $value);
+ }
+
+ /**
+ * HTML5 input: email
+ *
+ * @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 typeEmail($value, array $params, array $field)
+ {
+ $values = !\is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value;
+
+ foreach ($values as $val) {
+ if (!(self::typeText($val, $params, $field) && filter_var($val, FILTER_VALIDATE_EMAIL))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * HTML5 input: url
+ *
+ * @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 typeUrl($value, array $params, array $field)
+ {
+ return self::typeText($value, $params, $field) && filter_var($value, FILTER_VALIDATE_URL);
+ }
+
+ /**
+ * HTML5 input: datetime
+ *
+ * @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 typeDatetime($value, array $params, array $field)
+ {
+ if ($value instanceof \DateTime) {
+ return true;
+ }
+ if (!\is_string($value)) {
+ return false;
+ }
+ if (!isset($params['format'])) {
+ return false !== strtotime($value);
+ }
+
+ $dateFromFormat = \DateTime::createFromFormat($params['format'], $value);
+
+ return $dateFromFormat && $value === date($params['format'], $dateFromFormat->getTimestamp());
+ }
+
+ /**
+ * HTML5 input: datetime-local
+ *
+ * @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 typeDatetimeLocal($value, array $params, array $field)
+ {
+ return self::typeDatetime($value, $params, $field);
+ }
+
+ /**
+ * HTML5 input: date
+ *
+ * @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 typeDate($value, array $params, array $field)
+ {
+ if (!isset($params['format'])) {
+ $params['format'] = 'Y-m-d';
+ }
+
+ return self::typeDatetime($value, $params, $field);
+ }
+
+ /**
+ * HTML5 input: time
+ *
+ * @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 typeTime($value, array $params, array $field)
+ {
+ if (!isset($params['format'])) {
+ $params['format'] = 'H:i';
+ }
+
+ return self::typeDatetime($value, $params, $field);
+ }
+
+ /**
+ * HTML5 input: month
+ *
+ * @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 typeMonth($value, array $params, array $field)
+ {
+ if (!isset($params['format'])) {
+ $params['format'] = 'Y-m';
+ }
+
+ return self::typeDatetime($value, $params, $field);
+ }
+
+ /**
+ * HTML5 input: week
+ *
+ * @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 typeWeek($value, array $params, array $field)
+ {
+ if (!isset($params['format']) && !preg_match('/^\d{4}-W\d{2}$/u', $value)) {
+ return false;
+ }
+
+ return self::typeDatetime($value, $params, $field);
+ }
+
+ /**
+ * Custom input: array
+ *
+ * @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 typeArray($value, array $params, array $field)
+ {
+ if (!\is_array($value)) {
+ return false;
+ }
+
+ if (isset($field['multiple'])) {
+ if (isset($params['min']) && \count($value) < $params['min']) {
+ return false;
+ }
+
+ if (isset($params['max']) && \count($value) > $params['max']) {
+ return false;
+ }
+
+ $min = $params['min'] ?? 0;
+ if (isset($params['step']) && (\count($value) - $min) % $params['step'] === 0) {
+ return false;
+ }
+ }
+
+ // If creating new values is allowed, no further checks are needed.
+ if (!empty($field['selectize']['create'])) {
+ return true;
+ }
+
+ $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 !($options && array_diff($value, $options));
+ }
+
+ protected static function filterArray($value, $params, $field)
+ {
+ $values = (array) $value;
+ $options = isset($field['options']) ? array_keys($field['options']) : [];
+ $multi = $field['multiple'] ?? false;
+
+ if (\count($values) === 1 && isset($values[0]) && $values[0] === '') {
+ return null;
+ }
+
+
+ if ($options) {
+ $useKey = isset($field['use']) && $field['use'] === 'keys';
+ foreach ($values as $key => $val) {
+ $values[$key] = $useKey ? (bool) $val : $val;
+ }
+ }
+
+ if ($multi) {
+ foreach ($values as $key => $val) {
+ if (\is_array($val)) {
+ $val = implode(',', $val);
+ $values[$key] = array_map('trim', explode(',', $val));
+ } else {
+ $values[$key] = trim($val);
+ }
+ }
+ }
+
+ if (isset($field['ignore_empty']) && Utils::isPositive($field['ignore_empty'])) {
+ 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] = $val;
+ }
+ }
+
+ return $values;
+ }
+
+ public static function typeList($value, array $params, array $field)
+ {
+ if (!\is_array($value)) {
+ return false;
+ }
+
+ if (isset($field['fields'])) {
+ foreach ($value as $key => $item) {
+ foreach ($field['fields'] as $subKey => $subField) {
+ $subKey = trim($subKey, '.');
+ $subValue = $item[$subKey] ?? null;
+ self::validate($subValue, $subField);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ protected static function filterList($value, array $params, array $field)
+ {
+ return (array) $value;
+ }
+
+ public static function filterYaml($value, $params)
+ {
+ if (!\is_string($value)) {
+ return $value;
+ }
+
+ return (array) Yaml::parse($value);
+
+ }
+
+ /**
+ * Custom input: ignore (will not validate)
+ *
+ * @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 typeIgnore($value, array $params, array $field)
+ {
+ return true;
+ }
+
+ public static function filterIgnore($value, array $params, array $field)
+ {
+ 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)
+
+ public static function validateRequired($value, $params)
+ {
+ if (is_scalar($value)) {
+ return (bool) $params !== true || $value !== '';
+ }
+
+ return (bool) $params !== true || !empty($value);
+ }
+
+ public static function validatePattern($value, $params)
+ {
+ return (bool) preg_match("`^{$params}$`u", $value);
+ }
+
+
+ // Internal types
+
+ public static function validateAlpha($value, $params)
+ {
+ return ctype_alpha($value);
+ }
+
+ public static function validateAlnum($value, $params)
+ {
+ return ctype_alnum($value);
+ }
+
+ public static function typeBool($value, $params)
+ {
+ return \is_bool($value) || $value == 1 || $value == 0;
+ }
+
+ public static function validateBool($value, $params)
+ {
+ return \is_bool($value) || $value == 1 || $value == 0;
+ }
+
+ protected static function filterBool($value, $params)
+ {
+ return (bool) $value;
+ }
+
+ public static function validateDigit($value, $params)
+ {
+ return ctype_digit($value);
+ }
+
+ public static function validateFloat($value, $params)
+ {
+ return \is_float(filter_var($value, FILTER_VALIDATE_FLOAT));
+ }
+
+ protected static function filterFloat($value, $params)
+ {
+ return (float) $value;
+ }
+
+ public static function validateHex($value, $params)
+ {
+ return ctype_xdigit($value);
+ }
+
+ public static function validateInt($value, $params)
+ {
+ return is_numeric($value) && (int)$value == $value;
+ }
+
+ protected static function filterInt($value, $params)
+ {
+ return (int)$value;
+ }
+
+ public static function validateArray($value, $params)
+ {
+ return \is_array($value) || ($value instanceof \ArrayAccess && $value instanceof \Traversable && $value instanceof \Countable);
+ }
+
+ public static function filterItem_List($value, $params)
+ {
+ return array_values(array_filter($value, function($v) { return !empty($v); } ));
+ }
+
+ public static function validateJson($value, $params)
+ {
+ return (bool) (@json_decode($value));
+ }
+}
diff --git a/system/src/Grav/Common/Data/ValidationException.php b/system/src/Grav/Common/Data/ValidationException.php
new file mode 100644
index 0000000..8bdb057
--- /dev/null
+++ b/system/src/Grav/Common/Data/ValidationException.php
@@ -0,0 +1,38 @@
+messages = $messages;
+
+ $language = Grav::instance()['language'];
+ $this->message = $language->translate('GRAV.FORM.VALIDATION_FAIL', null, true) . ' ' . $this->message;
+
+ foreach ($messages as $variable => &$list) {
+ $list = array_unique($list);
+ foreach ($list as $message) {
+ $this->message .= " $message";
+ }
+ }
+
+ return $this;
+ }
+
+ public function getMessages()
+ {
+ return $this->messages;
+ }
+}
diff --git a/system/src/Grav/Common/Debugger.php b/system/src/Grav/Common/Debugger.php
new file mode 100644
index 0000000..0737209
--- /dev/null
+++ b/system/src/Grav/Common/Debugger.php
@@ -0,0 +1,606 @@
+init() gets called.
+ $this->enabled = 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();
+ }
+
+ /**
+ * Initialize the debugger
+ *
+ * @return $this
+ * @throws \DebugBar\DebugBarException
+ */
+ 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 = (bool)$this->config->get('system.debugger.enabled');
+
+ if ($this->enabled()) {
+ $this->initialized = true;
+
+ $plugins_config = (array)$this->config->get('plugins');
+
+ ksort($plugins_config);
+
+ $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);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set/get the enabled state of the debugger
+ *
+ * @param bool $state If null, the method returns the enabled value. If set, the method sets the enabled state
+ *
+ * @return bool
+ */
+ public function enabled($state = null)
+ {
+ if ($state !== null) {
+ $this->enabled = (bool)$state;
+ }
+
+ return $this->enabled;
+ }
+
+ /**
+ * Add the debugger assets to the Grav Assets
+ *
+ * @return $this
+ */
+ public function addAssets()
+ {
+ if ($this->enabled()) {
+
+ // Only add assets if Page is HTML
+ $page = $this->grav['page'];
+ if ($page->templateFormat() !== 'html') {
+ return $this;
+ }
+
+ /** @var Assets $assets */
+ $assets = $this->grav['assets'];
+
+ // Add jquery library
+ $assets->add('jquery', 101);
+
+ $this->renderer = $this->debugbar->getJavascriptRenderer();
+ $this->renderer->setIncludeVendors(false);
+
+ // Get the required CSS files
+ list($css_files, $js_files) = $this->renderer->getAssets(null, JavascriptRenderer::RELATIVE_URL);
+ foreach ((array)$css_files as $css) {
+ $assets->addCss($css);
+ }
+
+ $assets->addCss('/system/assets/debugger.css');
+
+ foreach ((array)$js_files as $js) {
+ $assets->addJs($js);
+ }
+ }
+
+ return $this;
+ }
+
+ public function getCaller($limit = 2)
+ {
+ $trace = debug_backtrace(false, $limit);
+
+ return array_pop($trace);
+ }
+
+ /**
+ * Adds a data collector
+ *
+ * @param DataCollectorInterface $collector
+ *
+ * @return $this
+ * @throws \DebugBar\DebugBarException
+ */
+ public function addCollector($collector)
+ {
+ $this->debugbar->addCollector($collector);
+
+ return $this;
+ }
+
+ /**
+ * Returns a data collector
+ *
+ * @param DataCollectorInterface $collector
+ *
+ * @return DataCollectorInterface
+ * @throws \DebugBar\DebugBarException
+ */
+ public function getCollector($collector)
+ {
+ return $this->debugbar->getCollector($collector);
+ }
+
+ /**
+ * Displays the debug bar
+ *
+ * @return $this
+ */
+ public function render()
+ {
+ if ($this->enabled()) {
+ // Only add assets if Page is HTML
+ $page = $this->grav['page'];
+ if (!$this->renderer || $page->templateFormat() !== 'html') {
+ return $this;
+ }
+
+ $this->addDeprecations();
+
+ echo $this->renderer->render();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sends the data through the HTTP headers
+ *
+ * @return $this
+ */
+ public function sendDataInHeaders()
+ {
+ if ($this->enabled()) {
+ $this->addDeprecations();
+ $this->debugbar->sendDataInHeaders();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns collected debugger data.
+ *
+ * @return array
+ */
+ public function getData()
+ {
+ if (!$this->enabled()) {
+ return null;
+ }
+
+ $this->addDeprecations();
+ $this->timers = [];
+
+ return $this->debugbar->getData();
+ }
+
+ /**
+ * Start a timer with an associated name and description
+ *
+ * @param string $name
+ * @param string|null $description
+ *
+ * @return $this
+ */
+ public function startTimer($name, $description = null)
+ {
+ if (strpos($name, '_') === 0 || $this->enabled()) {
+ $this->debugbar['time']->startMeasure($name, $description);
+ $this->timers[] = $name;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Stop the named timer
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function stopTimer($name)
+ {
+ if (\in_array($name, $this->timers, true) && (strpos($name, '_') === 0 || $this->enabled())) {
+ $this->debugbar['time']->stopMeasure($name);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Dump variables into the Messages tab of the Debug Bar
+ *
+ * @param mixed $message
+ * @param string $label
+ * @param bool $isString
+ *
+ * @return $this
+ */
+ public function addMessage($message, $label = 'info', $isString = true)
+ {
+ if ($this->enabled()) {
+ $this->debugbar['messages']->addMessage($message, $label, $isString);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Dump exception into the Messages tab of the Debug Bar
+ *
+ * @param \Exception $e
+ * @return Debugger
+ */
+ public function addException(\Exception $e)
+ {
+ if ($this->initialized && $this->enabled()) {
+ $this->debugbar['exceptions']->addException($e);
+ }
+
+ return $this;
+ }
+
+ public function setErrorHandler()
+ {
+ $this->errorHandler = set_error_handler(
+ [$this, 'deprecatedErrorHandler']
+ );
+ }
+
+ /**
+ * @param int $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param int $errline
+ * @return bool
+ */
+ public function deprecatedErrorHandler($errno, $errstr, $errfile, $errline)
+ {
+ if ($errno !== E_USER_DEPRECATED && $errno !== E_DEPRECATED) {
+ if ($this->errorHandler) {
+ return \call_user_func($this->errorHandler, $errno, $errstr, $errfile, $errline);
+ }
+
+ return true;
+ }
+
+ if (!$this->enabled()) {
+ return true;
+ }
+
+ // 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.
+ 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')) {
+ $cut = $i + 1;
+ continue;
+ }
+
+ break;
+ }
+
+ if ($cut) {
+ $backtrace = array_slice($backtrace, $cut);
+ }
+ $backtrace = array_values(array_filter($backtrace));
+
+ $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;
+ }
+
+ protected function addDeprecations()
+ {
+ if (!$this->deprecations) {
+ return;
+ }
+
+ $collector = new MessagesCollector('deprecated');
+ $this->addCollector($collector);
+ $collector->addMessage('Your site is using following deprecated features:');
+
+ /** @var array $deprecated */
+ foreach ($this->deprecations as $deprecated) {
+ list($message, $scope) = $this->getDepracatedMessage($deprecated);
+
+ $collector->addMessage($message, $scope);
+ }
+ }
+
+ protected function getDepracatedMessage($deprecated)
+ {
+ $scope = $deprecated['scope'];
+
+ $trace = [];
+ 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;
+ }
+ }
+ }
+
+ $array = [
+ 'message' => $deprecated['message'],
+ 'file' => $deprecated['file'],
+ 'line' => $deprecated['line'],
+ 'trace' => $trace
+ ];
+
+ return [
+ array_filter($array),
+ $scope
+ ];
+ }
+
+ protected function getFunction($trace)
+ {
+ if (!isset($trace['function'])) {
+ return '';
+ }
+
+ return $trace['function'] . '(' . implode(', ', $trace['args'] ?? []) . ')';
+ }
+}
diff --git a/system/src/Grav/Common/Errors/BareHandler.php b/system/src/Grav/Common/Errors/BareHandler.php
new file mode 100644
index 0000000..52effc6
--- /dev/null
+++ b/system/src/Grav/Common/Errors/BareHandler.php
@@ -0,0 +1,32 @@
+getInspector();
+ $code = $inspector->getException()->getCode();
+ if ( ($code >= 400) && ($code < 600) )
+ {
+ $this->getRun()->sendHttpCode($code);
+ }
+
+ return Handler::QUIT;
+ }
+
+}
diff --git a/system/src/Grav/Common/Errors/Errors.php b/system/src/Grav/Common/Errors/Errors.php
new file mode 100644
index 0000000..dbc955e
--- /dev/null
+++ b/system/src/Grav/Common/Errors/Errors.php
@@ -0,0 +1,73 @@
+get('system.errors');
+ $jsonRequest = $_SERVER && isset($_SERVER['HTTP_ACCEPT']) && $_SERVER['HTTP_ACCEPT'] === 'application/json';
+
+ // Setup Whoops-based error handler
+ $system = new SystemFacade;
+ $whoops = new Whoops\Run($system);
+
+ $verbosity = 1;
+
+ if (isset($config['display'])) {
+ if (is_int($config['display'])) {
+ $verbosity = $config['display'];
+ } else {
+ $verbosity = $config['display'] ? 1 : 0;
+ }
+ }
+
+ switch ($verbosity) {
+ case 1:
+ $error_page = new Whoops\Handler\PrettyPageHandler;
+ $error_page->setPageTitle('Crikey! There was an error...');
+ $error_page->addResourcePath(GRAV_ROOT . '/system/assets');
+ $error_page->addCustomCss('whoops.css');
+ $whoops->pushHandler($error_page);
+ break;
+ case -1:
+ $whoops->pushHandler(new BareHandler);
+ break;
+ default:
+ $whoops->pushHandler(new SimplePageHandler);
+ break;
+ }
+
+ if (Whoops\Util\Misc::isAjaxRequest() || $jsonRequest) {
+ $whoops->pushHandler(new Whoops\Handler\JsonResponseHandler);
+ }
+
+ if (isset($config['log']) && $config['log']) {
+ $logger = $grav['log'];
+ $whoops->pushHandler(function($exception, $inspector, $run) use ($logger) {
+ try {
+ $logger->addCritical($exception->getMessage() . ' - Trace: ' . $exception->getTraceAsString());
+ } catch (\Exception $e) {
+ echo $e;
+ }
+ });
+ }
+
+ $whoops->register();
+
+ // Re-register deprecation handler.
+ $grav['debugger']->setErrorHandler();
+ }
+}
diff --git a/system/src/Grav/Common/Errors/Resources/error.css b/system/src/Grav/Common/Errors/Resources/error.css
new file mode 100644
index 0000000..11ce3fd
--- /dev/null
+++ b/system/src/Grav/Common/Errors/Resources/error.css
@@ -0,0 +1,52 @@
+html, body {
+ height: 100%
+}
+body {
+ margin:0 3rem;
+ padding:0;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 1.5rem;
+ line-height: 1.4;
+ display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
+ display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */
+ display: -ms-flexbox; /* TWEENER - IE 10 */
+ display: -webkit-flex; /* NEW - Chrome */
+ display: flex;
+ -webkit-align-items: center;
+ align-items: center;
+ -webkit-justify-content: center;
+ justify-content: center;
+}
+.container {
+ margin: 0rem;
+ max-width: 600px;
+ padding-bottom:5rem;
+}
+
+header {
+ color: #000;
+ font-size: 4rem;
+ letter-spacing: 2px;
+ line-height: 1.1;
+ margin-bottom: 2rem;
+}
+p {
+ font-family: Optima, Segoe, "Segoe UI", Candara, Calibri, Arial, sans-serif;
+ color: #666;
+}
+
+h5 {
+ font-weight: normal;
+ color: #999;
+ font-size: 1rem;
+}
+
+h6 {
+ font-weight: normal;
+ color: #999;
+}
+
+code {
+ font-weight: bold;
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
+}
diff --git a/system/src/Grav/Common/Errors/Resources/layout.html.php b/system/src/Grav/Common/Errors/Resources/layout.html.php
new file mode 100644
index 0000000..6699959
--- /dev/null
+++ b/system/src/Grav/Common/Errors/Resources/layout.html.php
@@ -0,0 +1,30 @@
+
+
+
+
+
+ Whoops there was an error!
+
+
+
+
+
+
+
+
+
+
Sorry, something went terribly wrong!
+
+
-
+
+
For further details please review your logs/ folder, or enable displaying of errors in your system configuration.
+
+
+
+
diff --git a/system/src/Grav/Common/Errors/SimplePageHandler.php b/system/src/Grav/Common/Errors/SimplePageHandler.php
new file mode 100644
index 0000000..311d722
--- /dev/null
+++ b/system/src/Grav/Common/Errors/SimplePageHandler.php
@@ -0,0 +1,108 @@
+searchPaths[] = __DIR__ . '/Resources';
+ }
+
+ /**
+ * @return int|null
+ */
+ public function handle()
+ {
+ $inspector = $this->getInspector();
+
+ $helper = new TemplateHelper();
+ $templateFile = $this->getResource('layout.html.php');
+ $cssFile = $this->getResource('error.css');
+
+ $code = $inspector->getException()->getCode();
+ if ( ($code >= 400) && ($code < 600) )
+ {
+ $this->getRun()->sendHttpCode($code);
+ }
+ $message = $inspector->getException()->getMessage();
+
+ if ($inspector->getException() instanceof \ErrorException) {
+ $code = Misc::translateErrorCode($code);
+ }
+
+ $vars = array(
+ 'stylesheet' => file_get_contents($cssFile),
+ 'code' => $code,
+ 'message' => filter_var(rawurldecode($message), FILTER_SANITIZE_STRING),
+ );
+
+ $helper->setVariables($vars);
+ $helper->render($templateFile);
+
+ return Handler::QUIT;
+ }
+
+ /**
+ * @param string $resource
+ *
+ * @return string
+ * @throws \RuntimeException
+ */
+ protected function getResource($resource)
+ {
+ // If the resource was found before, we can speed things up
+ // by caching its absolute, resolved path:
+ if (isset($this->resourceCache[$resource])) {
+ return $this->resourceCache[$resource];
+ }
+
+ // Search through available search paths, until we find the
+ // resource we're after:
+ foreach ($this->searchPaths as $path) {
+ $fullPath = "{$path}/{$resource}";
+
+ if (is_file($fullPath)) {
+ // Cache the result:
+ $this->resourceCache[$resource] = $fullPath;
+ return $fullPath;
+ }
+ }
+
+ // If we got this far, nothing was found.
+ throw new \RuntimeException(
+ "Could not find resource '{$resource}' in any resource paths (searched: " . implode(', ', $this->searchPaths). ')'
+ );
+ }
+
+ public function addResourcePath($path)
+ {
+ if (!is_dir($path)) {
+ throw new \InvalidArgumentException(
+ "'{$path}' is not a valid directory"
+ );
+ }
+
+ array_unshift($this->searchPaths, $path);
+ }
+
+ public function getResourcePaths()
+ {
+ return $this->searchPaths;
+ }
+}
diff --git a/system/src/Grav/Common/Errors/SystemFacade.php b/system/src/Grav/Common/Errors/SystemFacade.php
new file mode 100644
index 0000000..02ef0cf
--- /dev/null
+++ b/system/src/Grav/Common/Errors/SystemFacade.php
@@ -0,0 +1,40 @@
+whoopsShutdownHandler = $function;
+ register_shutdown_function([$this, 'handleShutdown']);
+ }
+
+ /**
+ * Special case to deal with Fatal errors and the like.
+ */
+ public function handleShutdown()
+ {
+ $error = $this->getLastError();
+
+ // Ignore core warnings and errors.
+ if ($error && !($error['type'] & (E_CORE_WARNING | E_CORE_ERROR))) {
+ $handler = $this->whoopsShutdownHandler;
+ $handler();
+ }
+ }
+}
diff --git a/system/src/Grav/Common/File/CompiledFile.php b/system/src/Grav/Common/File/CompiledFile.php
new file mode 100644
index 0000000..f4b1e8d
--- /dev/null
+++ b/system/src/Grav/Common/File/CompiledFile.php
@@ -0,0 +1,110 @@
+raw === null && $this->content === null) {
+ $key = md5($this->filename);
+ $file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php");
+
+ $modified = $this->modified();
+
+ if (!$modified) {
+ return $this->decode($this->raw());
+ }
+
+ $class = get_class($this);
+
+ $cache = $file->exists() ? $file->content() : null;
+
+ // Load real file if cache isn't up to date (or is invalid).
+ if (
+ !isset($cache['@class'])
+ || $cache['@class'] !== $class
+ || $cache['modified'] !== $modified
+ || $cache['filename'] !== $this->filename
+ ) {
+ // Attempt to lock the file for writing.
+ try {
+ $file->lock(false);
+ } catch (\Exception $e) {
+ // Another process has locked the file; we will check this in a bit.
+ }
+
+ // Decode RAW file into compiled array.
+ $data = (array)$this->decode($this->raw());
+ $cache = [
+ '@class' => $class,
+ 'filename' => $this->filename,
+ 'modified' => $modified,
+ 'data' => $data
+ ];
+
+ // If compiled file wasn't already locked by another process, save it.
+ if ($file->locked() !== false) {
+ $file->save($cache);
+ $file->unlock();
+
+ // Compile cached file into bytecode cache
+ if (function_exists('opcache_invalidate')) {
+ // Silence error if function exists, but is restricted.
+ @opcache_invalidate($file->filename(), true);
+ }
+ }
+ }
+ $file->free();
+
+ $this->content = $cache['data'];
+ }
+
+ } catch (\Exception $e) {
+ throw new \RuntimeException(sprintf('Failed to read %s: %s', basename($this->filename), $e->getMessage()), 500, $e);
+ }
+
+ return parent::content($var);
+ }
+
+ /**
+ * Serialize file.
+ */
+ public function __sleep()
+ {
+ return [
+ 'filename',
+ 'extension',
+ 'raw',
+ 'content',
+ 'settings'
+ ];
+ }
+
+ /**
+ * Unserialize file.
+ */
+ public function __wakeup()
+ {
+ if (!isset(static::$instances[$this->filename])) {
+ static::$instances[$this->filename] = $this;
+ }
+ }
+}
diff --git a/system/src/Grav/Common/File/CompiledJsonFile.php b/system/src/Grav/Common/File/CompiledJsonFile.php
new file mode 100644
index 0000000..d73e817
--- /dev/null
+++ b/system/src/Grav/Common/File/CompiledJsonFile.php
@@ -0,0 +1,29 @@
+ ['.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
new file mode 100644
index 0000000..781c9d3
--- /dev/null
+++ b/system/src/Grav/Common/Filesystem/Folder.php
@@ -0,0 +1,526 @@
+isStream($path)) {
+ $directory = $locator->getRecursiveIterator($path, $flags);
+ } else {
+ $directory = new \RecursiveDirectoryIterator($path, $flags);
+ }
+ $filter = new RecursiveFolderFilterIterator($directory);
+ $iterator = new \RecursiveIteratorIterator($filter, \RecursiveIteratorIterator::SELF_FIRST);
+
+ /** @var \RecursiveDirectoryIterator $file */
+ foreach ($iterator as $dir) {
+ $dir_modified = $dir->getMTime();
+ if ($dir_modified > $last_modified) {
+ $last_modified = $dir_modified;
+ }
+ }
+
+ return $last_modified;
+ }
+
+ /**
+ * Recursively find the last modified time under given path by file.
+ *
+ * @param string $path
+ * @param string $extensions which files to search for specifically
+ *
+ * @return int
+ */
+ public static function lastModifiedFile($path, $extensions = 'md|yaml')
+ {
+ if (!file_exists($path)) {
+ return 0;
+ }
+
+ $last_modified = 0;
+
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+ $flags = \RecursiveDirectoryIterator::SKIP_DOTS;
+ if ($locator->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 {
+ $file_modified = $file->getMTime();
+ if ($file_modified > $last_modified) {
+ $last_modified = $file_modified;
+ }
+ } catch (\Exception $e) {
+ Grav::instance()['log']->error('Could not process file: ' . $e->getMessage());
+ }
+ }
+
+ return $last_modified;
+ }
+
+ /**
+ * Recursively md5 hash all files in a path
+ *
+ * @param string $path
+ * @return string
+ */
+ public static function hashAllFiles($path)
+ {
+ $files = [];
+
+ if (file_exists($path)) {
+ $flags = \RecursiveDirectoryIterator::SKIP_DOTS;
+
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+ if ($locator->isStream($path)) {
+ $directory = $locator->getRecursiveIterator($path, $flags);
+ } else {
+ $directory = new \RecursiveDirectoryIterator($path, $flags);
+ }
+
+ $iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
+
+ foreach ($iterator as $file) {
+ $files[] = $file->getPathname() . '?'. $file->getMTime();
+ }
+ }
+
+ return md5(serialize($files));
+ }
+
+ /**
+ * Get relative path between target and base path. If path isn't relative, return full path.
+ *
+ * @param string $path
+ * @param mixed|string $base
+ *
+ * @return string
+ */
+ public static function getRelativePath($path, $base = GRAV_ROOT)
+ {
+ if ($base) {
+ $base = preg_replace('![\\\/]+!', '/', $base);
+ $path = preg_replace('![\\\/]+!', '/', $path);
+ if (strpos($path, $base) === 0) {
+ $path = ltrim(substr($path, strlen($base)), '/');
+ }
+ }
+
+ return $path;
+ }
+
+ /**
+ * Get relative path between target and base path. If path isn't relative, return full path.
+ *
+ * @param string $path
+ * @param string $base
+ * @return string
+ */
+ public static function getRelativePathDotDot($path, $base)
+ {
+ // Normalize paths.
+ $base = preg_replace('![\\\/]+!', '/', $base);
+ $path = preg_replace('![\\\/]+!', '/', $path);
+
+ if ($path === $base) {
+ return '';
+ }
+
+ $baseParts = explode('/', ltrim($base, '/'));
+ $pathParts = explode('/', ltrim($path, '/'));
+
+ array_pop($baseParts);
+ $lastPart = array_pop($pathParts);
+ foreach ($baseParts as $i => $directory) {
+ if (isset($pathParts[$i]) && $pathParts[$i] === $directory) {
+ unset($baseParts[$i], $pathParts[$i]);
+ } else {
+ break;
+ }
+ }
+ $pathParts[] = $lastPart;
+ $path = str_repeat('../', count($baseParts)) . implode('/', $pathParts);
+
+ return '' === $path
+ || strpos($path, '/') === 0
+ || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos)
+ ? "./$path" : $path;
+ }
+
+ /**
+ * Shift first directory out of the path.
+ *
+ * @param string $path
+ * @return string
+ */
+ public static function shift(&$path)
+ {
+ $parts = explode('/', trim($path, '/'), 2);
+ $result = array_shift($parts);
+ $path = array_shift($parts);
+
+ return $result ?: null;
+ }
+
+ /**
+ * Return recursive list of all files and directories under given path.
+ *
+ * @param string $path
+ * @param array $params
+ * @return array
+ * @throws \RuntimeException
+ */
+ public static function all($path, array $params = [])
+ {
+ if ($path === false) {
+ throw new \RuntimeException("Path doesn't exist.");
+ }
+ if (!file_exists($path)) {
+ return [];
+ }
+
+ $compare = isset($params['compare']) ? 'get' . $params['compare'] : null;
+ $pattern = $params['pattern'] ?? null;
+ $filters = $params['filters'] ?? null;
+ $recursive = $params['recursive'] ?? true;
+ $levels = $params['levels'] ?? -1;
+ $key = isset($params['key']) ? 'get' . $params['key'] : null;
+ $value = 'get' . ($params['value'] ?? ($recursive ? 'SubPathname' : 'Filename'));
+ $folders = $params['folders'] ?? true;
+ $files = $params['files'] ?? true;
+
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+ if ($recursive) {
+ $flags = \RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS
+ + \FilesystemIterator::CURRENT_AS_SELF + \FilesystemIterator::FOLLOW_SYMLINKS;
+ if ($locator->isStream($path)) {
+ $directory = $locator->getRecursiveIterator($path, $flags);
+ } else {
+ $directory = new \RecursiveDirectoryIterator($path, $flags);
+ }
+ $iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
+ $iterator->setMaxDepth(max($levels, -1));
+ } else {
+ if ($locator->isStream($path)) {
+ $iterator = $locator->getIterator($path);
+ } else {
+ $iterator = new \FilesystemIterator($path);
+ }
+ }
+
+ $results = [];
+
+ /** @var \RecursiveDirectoryIterator $file */
+ foreach ($iterator as $file) {
+ // Ignore hidden files.
+ if (strpos($file->getFilename(), '.') === 0 && $file->isFile()) {
+ continue;
+ }
+ if (!$folders && $file->isDir()) {
+ continue;
+ }
+ if (!$files && $file->isFile()) {
+ continue;
+ }
+ if ($compare && $pattern && !preg_match($pattern, $file->{$compare}())) {
+ continue;
+ }
+ $fileKey = $key ? $file->{$key}() : null;
+ $filePath = $file->{$value}();
+ if ($filters) {
+ if (isset($filters['key'])) {
+ $pre = !empty($filters['pre-key']) ? $filters['pre-key'] : '';
+ $fileKey = $pre . preg_replace($filters['key'], '', $fileKey);
+ }
+ if (isset($filters['value'])) {
+ $filter = $filters['value'];
+ if (is_callable($filter)) {
+ $filePath = $filter($file);
+ } else {
+ $filePath = preg_replace($filter, '', $filePath);
+ }
+ }
+ }
+
+ if ($fileKey !== null) {
+ $results[$fileKey] = $filePath;
+ } else {
+ $results[] = $filePath;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Recursively copy directory in filesystem.
+ *
+ * @param string $source
+ * @param string $target
+ * @param string $ignore Ignore files matching pattern (regular expression).
+ * @throws \RuntimeException
+ */
+ public static function copy($source, $target, $ignore = null)
+ {
+ $source = rtrim($source, '\\/');
+ $target = rtrim($target, '\\/');
+
+ if (!is_dir($source)) {
+ throw new \RuntimeException('Cannot copy non-existing folder.');
+ }
+
+ // Make sure that path to the target exists before copying.
+ self::create($target);
+
+ $success = true;
+
+ // Go through all sub-directories and copy everything.
+ $files = self::all($source);
+ foreach ($files as $file) {
+ if ($ignore && preg_match($ignore, $file)) {
+ continue;
+ }
+ $src = $source .'/'. $file;
+ $dst = $target .'/'. $file;
+
+ if (is_dir($src)) {
+ // Create current directory (if it doesn't exist).
+ if (!is_dir($dst)) {
+ $success &= @mkdir($dst, 0777, true);
+ }
+ } else {
+ // Or copy current file.
+ $success &= @copy($src, $dst);
+ }
+ }
+
+ if (!$success) {
+ $error = error_get_last();
+ throw new \RuntimeException($error['message'] ?? 'Unknown error');
+ }
+
+ // Make sure that the change will be detected when caching.
+ @touch(dirname($target));
+ }
+
+ /**
+ * Move directory in filesystem.
+ *
+ * @param string $source
+ * @param string $target
+ * @throws \RuntimeException
+ */
+ public static function move($source, $target)
+ {
+ if (!file_exists($source) || !is_dir($source)) {
+ // Rename fails if source folder does not exist.
+ throw new \RuntimeException('Cannot move non-existing folder.');
+ }
+
+ // Don't do anything if the source is the same as the new target
+ if ($source === $target) {
+ return;
+ }
+
+ if (file_exists($target)) {
+ // Rename fails if target folder exists.
+ throw new \RuntimeException('Cannot move files to existing folder/file.');
+ }
+
+ // Make sure that path to the target exists before moving.
+ self::create(dirname($target));
+
+ // Silence warnings (chmod failed etc).
+ @rename($source, $target);
+
+ // Rename function can fail while still succeeding, so let's check if the folder exists.
+ if (!file_exists($target) || !is_dir($target)) {
+ // In some rare cases rename() creates file, not a folder. Get rid of it.
+ if (file_exists($target)) {
+ @unlink($target);
+ }
+ // Rename doesn't support moving folders across filesystems. Use copy instead.
+ self::copy($source, $target);
+ self::delete($source);
+ }
+
+ // Make sure that the change will be detected when caching.
+ @touch(dirname($source));
+ @touch(dirname($target));
+ @touch($target);
+ }
+
+ /**
+ * Recursively delete directory from filesystem.
+ *
+ * @param string $target
+ * @param bool $include_target
+ * @return bool
+ * @throws \RuntimeException
+ */
+ public static function delete($target, $include_target = true)
+ {
+ if (!is_dir($target)) {
+ return false;
+ }
+
+ $success = self::doDelete($target, $include_target);
+
+ if (!$success) {
+ $error = error_get_last();
+ throw new \RuntimeException($error['message']);
+ }
+
+ // Make sure that the change will be detected when caching.
+ if ($include_target) {
+ @touch(dirname($target));
+ } else {
+ @touch($target);
+ }
+
+ return $success;
+ }
+
+ /**
+ * @param string $folder
+ * @throws \RuntimeException
+ */
+ public static function mkdir($folder)
+ {
+ self::create($folder);
+ }
+
+ /**
+ * @param string $folder
+ * @throws \RuntimeException
+ */
+ public static function create($folder)
+ {
+ // Silence error for open_basedir; should fail in mkdir instead.
+ if (@is_dir($folder)) {
+ return;
+ }
+
+ $success = @mkdir($folder, 0777, true);
+
+ if (!$success) {
+ // 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 string $src
+ * @param string $dest
+ *
+ * @return bool
+ * @throws \RuntimeException
+ */
+ public static function rcopy($src, $dest)
+ {
+
+ // If the src is not a directory do a simple file copy
+ if (!is_dir($src)) {
+ copy($src, $dest);
+ return true;
+ }
+
+ // If the destination directory does not exist create it
+ if (!is_dir($dest)) {
+ static::create($dest);
+ }
+
+ // Open the source directory to read in files
+ $i = new \DirectoryIterator($src);
+ /** @var \DirectoryIterator $f */
+ foreach ($i as $f) {
+ if ($f->isFile()) {
+ copy($f->getRealPath(), "{$dest}/" . $f->getFilename());
+ } else {
+ if (!$f->isDot() && $f->isDir()) {
+ static::rcopy($f->getRealPath(), "{$dest}/{$f}");
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @param string $folder
+ * @param bool $include_target
+ * @return bool
+ * @internal
+ */
+ protected static function doDelete($folder, $include_target = true)
+ {
+ // Special case for symbolic links.
+ if ($include_target && is_link($folder)) {
+ return @unlink($folder);
+ }
+
+ // Go through all items in filesystem and recursively remove everything.
+ $files = array_diff(scandir($folder, SCANDIR_SORT_NONE), array('.', '..'));
+ foreach ($files as $file) {
+ $path = "{$folder}/{$file}";
+ is_dir($path) ? self::doDelete($path) : @unlink($path);
+ }
+
+ return $include_target ? @rmdir($folder) : true;
+ }
+
+ /**
+ * Does a directory contain children
+ *
+ * @param string $directory
+ * @return int|false
+ */
+ public static function countChildren($directory) {
+ if (!is_dir($directory)) {
+ return false;
+ }
+ $directories = glob($directory . '/*', GLOB_ONLYDIR);
+
+ return count($directories);
+ }
+}
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
new file mode 100644
index 0000000..eb493db
--- /dev/null
+++ b/system/src/Grav/Common/Filesystem/RecursiveFolderFilterIterator.php
@@ -0,0 +1,47 @@
+get('system.pages.ignore_folders');
+ }
+
+ $this::$ignore_folders = $ignore_folders;
+ }
+
+ /**
+ * Check whether the current element of the iterator is acceptable
+ *
+ * @return bool true if the current element is acceptable, otherwise false.
+ */
+ public function accept()
+ {
+ /** @var \SplFileInfo $current */
+ $current = $this->current();
+
+ 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..b67313b
--- /dev/null
+++ b/system/src/Grav/Common/Form/FormFlash.php
@@ -0,0 +1,101 @@
+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
+ {
+ if (!$this->uniqueId) {
+ return false;
+ }
+
+ $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
+ {
+ if (!$this->uniqueId) {
+ return false;
+ }
+
+ $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;
+ }
+}
diff --git a/system/src/Grav/Common/GPM/AbstractCollection.php b/system/src/Grav/Common/GPM/AbstractCollection.php
new file mode 100644
index 0000000..a9e1ac9
--- /dev/null
+++ b/system/src/Grav/Common/GPM/AbstractCollection.php
@@ -0,0 +1,31 @@
+toArray());
+ }
+
+ public function toArray()
+ {
+ $items = [];
+
+ foreach ($this->items as $name => $package) {
+ $items[$name] = $package->toArray();
+ }
+
+ return $items;
+ }
+}
diff --git a/system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php b/system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php
new file mode 100644
index 0000000..bc4ecab
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php
@@ -0,0 +1,39 @@
+items as $name => $package) {
+ $items[$name] = $package->toArray();
+ }
+
+ return json_encode($items);
+ }
+
+ public function toArray()
+ {
+ $items = [];
+
+ foreach ($this->items as $name => $package) {
+ $items[$name] = $package->toArray();
+ }
+
+ return $items;
+ }
+}
diff --git a/system/src/Grav/Common/GPM/Common/CachedCollection.php b/system/src/Grav/Common/GPM/Common/CachedCollection.php
new file mode 100644
index 0000000..a6fca4d
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Common/CachedCollection.php
@@ -0,0 +1,33 @@
+ $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
new file mode 100644
index 0000000..40d3759
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Common/Package.php
@@ -0,0 +1,70 @@
+data = $package;
+
+ if ($type) {
+ $this->data->set('package_type', $type);
+ }
+ }
+
+ /**
+ * @return Data
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ public function __get($key)
+ {
+ return $this->data->get($key);
+ }
+
+ public function __set($key, $value)
+ {
+ return $this->data->set($key, $value);
+ }
+
+ public function __isset($key)
+ {
+ return isset($this->data->{$key});
+ }
+
+ public function __toString()
+ {
+ return $this->toJson();
+ }
+
+ public function toJson()
+ {
+ return $this->data->toJson();
+ }
+
+ /**
+ * @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
new file mode 100644
index 0000000..6c9e7b0
--- /dev/null
+++ b/system/src/Grav/Common/GPM/GPM.php
@@ -0,0 +1,1160 @@
+ 'user/plugins/%name%',
+ 'themes' => 'user/themes/%name%',
+ 'skeletons' => 'user/'
+ ];
+
+ /**
+ * Creates a new GPM instance with Local and Remote packages available
+ * @param bool $refresh Applies to Remote Packages only and forces a refetch of data
+ * @param callable $callback Either a function or callback in array notation
+ */
+ public function __construct($refresh = false, $callback = null)
+ {
+ parent::__construct();
+ $this->cache = [];
+ $this->installed = new Local\Packages();
+ try {
+ $this->repository = new Remote\Packages($refresh, $callback);
+ $this->grav = new Remote\GravCore($refresh, $callback);
+ } catch (\Exception $e) {
+ }
+ }
+
+ /**
+ * Return the locally installed packages
+ *
+ * @return Local\Packages
+ */
+ public function getInstalled()
+ {
+ return $this->installed;
+ }
+
+ /**
+ * Returns the Locally installable packages
+ *
+ * @param array $list_type_installed
+ * @return array The installed packages
+ */
+ public function getInstallable($list_type_installed = ['plugins' => true, 'themes' => true])
+ {
+ $items = ['total' => 0];
+ foreach ($list_type_installed as $type => $type_installed) {
+ if ($type_installed === false) {
+ continue;
+ }
+ $methodInstallableType = 'getInstalled' . ucfirst($type);
+ $to_install = $this->$methodInstallableType();
+ $items[$type] = $to_install;
+ $items['total'] += count($to_install);
+ }
+ return $items;
+ }
+
+ /**
+ * Returns the amount of locally installed packages
+ * @return int Amount of installed packages
+ */
+ public function countInstalled()
+ {
+ $installed = $this->getInstalled();
+
+ return count($installed['plugins']) + count($installed['themes']);
+ }
+
+ /**
+ * Return the instance of a specific Package
+ *
+ * @param string $slug The slug of the Package
+ * @return Local\Package The instance of the Package
+ */
+ public function getInstalledPackage($slug)
+ {
+ if (isset($this->installed['plugins'][$slug])) {
+ return $this->installed['plugins'][$slug];
+ }
+
+ if (isset($this->installed['themes'][$slug])) {
+ return $this->installed['themes'][$slug];
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the instance of a specific Plugin
+ * @param string $slug The slug of the Plugin
+ * @return Local\Package The instance of the Plugin
+ */
+ public function getInstalledPlugin($slug)
+ {
+ return $this->installed['plugins'][$slug];
+ }
+
+ /**
+ * Returns the Locally installed plugins
+ * @return Iterator The installed plugins
+ */
+ public function getInstalledPlugins()
+ {
+ return $this->installed['plugins'];
+ }
+
+ /**
+ * Checks if a Plugin is installed
+ * @param string $slug The slug of the Plugin
+ * @return bool True if the Plugin has been installed. False otherwise
+ */
+ public function isPluginInstalled($slug)
+ {
+ return isset($this->installed['plugins'][$slug]);
+ }
+
+ public function isPluginInstalledAsSymlink($slug)
+ {
+ return $this->installed['plugins'][$slug]->symlink;
+ }
+
+ /**
+ * Return the instance of a specific Theme
+ * @param string $slug The slug of the Theme
+ * @return Local\Package The instance of the Theme
+ */
+ public function getInstalledTheme($slug)
+ {
+ return $this->installed['themes'][$slug];
+ }
+
+ /**
+ * Returns the Locally installed themes
+ * @return Iterator The installed themes
+ */
+ public function getInstalledThemes()
+ {
+ return $this->installed['themes'];
+ }
+
+ /**
+ * Checks if a Theme is installed
+ * @param string $slug The slug of the Theme
+ * @return bool True if the Theme has been installed. False otherwise
+ */
+ public function isThemeInstalled($slug)
+ {
+ return isset($this->installed['themes'][$slug]);
+ }
+
+ /**
+ * Returns the amount of updates available
+ * @return int Amount of available updates
+ */
+ public function countUpdates()
+ {
+ $count = 0;
+
+ $count += count($this->getUpdatablePlugins());
+ $count += count($this->getUpdatableThemes());
+
+ return $count;
+ }
+
+ /**
+ * Returns an array of Plugins and Themes that can be updated.
+ * Plugins and Themes are extended with the `available` property that relies to the remote version
+ * @param array $list_type_update specifies what type of package to update
+ * @return array Array of updatable Plugins and Themes.
+ * Format: ['total' => int, 'plugins' => array, 'themes' => array]
+ */
+ public function getUpdatable($list_type_update = ['plugins' => true, 'themes' => true])
+ {
+
+ $items = ['total' => 0];
+ foreach ($list_type_update as $type => $type_updatable) {
+ if ($type_updatable === false) {
+ continue;
+ }
+ $methodUpdatableType = 'getUpdatable' . ucfirst($type);
+ $to_update = $this->$methodUpdatableType();
+ $items[$type] = $to_update;
+ $items['total'] += count($to_update);
+ }
+ return $items;
+ }
+
+ /**
+ * Returns an array of Plugins that can be updated.
+ * The Plugins are extended with the `available` property that relies to the remote version
+ * @return array Array of updatable Plugins
+ */
+ public function getUpdatablePlugins()
+ {
+ $items = [];
+ $repository = $this->repository['plugins'];
+
+ // local cache to speed things up
+ if (isset($this->cache[__METHOD__])) {
+ return $this->cache[__METHOD__];
+ }
+
+ foreach ($this->installed['plugins'] as $slug => $plugin) {
+ if (!isset($repository[$slug]) || $plugin->symlink || !$plugin->version || $plugin->gpm === false) {
+ continue;
+ }
+
+ $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]->type = $repository[$slug]->release_type;
+ $items[$slug] = $repository[$slug];
+ }
+ }
+
+ $this->cache[__METHOD__] = $items;
+
+ return $items;
+ }
+
+ /**
+ * Get the latest release of a package from the GPM
+ *
+ * @param string $package_name
+ *
+ * @return string|null
+ */
+ public function getLatestVersionOfPackage($package_name)
+ {
+ $repository = $this->repository['plugins'];
+ if (isset($repository[$package_name])) {
+ return $repository[$package_name]->available ?: $repository[$package_name]->version;
+ }
+
+ //Not a plugin, it's a theme?
+ $repository = $this->repository['themes'];
+ if (isset($repository[$package_name])) {
+ return $repository[$package_name]->available ?: $repository[$package_name]->version;
+ }
+
+ return null;
+ }
+
+ /**
+ * Check if a Plugin or Theme is updatable
+ * @param string $slug The slug of the package
+ * @return bool True if updatable. False otherwise or if not found
+ */
+ public function isUpdatable($slug)
+ {
+ return $this->isPluginUpdatable($slug) || $this->isThemeUpdatable($slug);
+ }
+
+ /**
+ * Checks if a Plugin is updatable
+ * @param string $plugin The slug of the Plugin
+ * @return bool True if the Plugin is updatable. False otherwise
+ */
+ public function isPluginUpdatable($plugin)
+ {
+ return array_key_exists($plugin, (array)$this->getUpdatablePlugins());
+ }
+
+ /**
+ * Returns an array of Themes that can be updated.
+ * The Themes are extended with the `available` property that relies to the remote version
+ * @return array Array of updatable Themes
+ */
+ public function getUpdatableThemes()
+ {
+ $items = [];
+ $repository = $this->repository['themes'];
+
+ // local cache to speed things up
+ if (isset($this->cache[__METHOD__])) {
+ return $this->cache[__METHOD__];
+ }
+
+ foreach ($this->installed['themes'] as $slug => $plugin) {
+ if (!isset($repository[$slug]) || $plugin->symlink || !$plugin->version || $plugin->gpm === false) {
+ continue;
+ }
+
+ $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]->type = $repository[$slug]->release_type;
+ $items[$slug] = $repository[$slug];
+ }
+ }
+
+ $this->cache[__METHOD__] = $items;
+
+ return $items;
+ }
+
+ /**
+ * Checks if a Theme is Updatable
+ * @param string $theme The slug of the Theme
+ * @return bool True if the Theme is updatable. False otherwise
+ */
+ public function isThemeUpdatable($theme)
+ {
+ return array_key_exists($theme, (array)$this->getUpdatableThemes());
+ }
+
+ /**
+ * Get the release type of a package (stable / testing)
+ *
+ * @param string $package_name
+ *
+ * @return string|null
+ */
+ public function getReleaseType($package_name)
+ {
+ $repository = $this->repository['plugins'];
+ if (isset($repository[$package_name])) {
+ return $repository[$package_name]->release_type;
+ }
+
+ //Not a plugin, it's a theme?
+ $repository = $this->repository['themes'];
+ if (isset($repository[$package_name])) {
+ return $repository[$package_name]->release_type;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns true if the package latest release is stable
+ *
+ * @param string $package_name
+ *
+ * @return bool
+ */
+ public function isStableRelease($package_name)
+ {
+ return $this->getReleaseType($package_name) === 'stable';
+ }
+
+ /**
+ * Returns true if the package latest release is testing
+ *
+ * @param string $package_name
+ *
+ * @return bool
+ */
+ public function isTestingRelease($package_name)
+ {
+ $hasTesting = isset($this->getInstalledPackage($package_name)->testing);
+ $testing = $hasTesting ? $this->getInstalledPackage($package_name)->testing : false;
+
+ return $this->getReleaseType($package_name) === 'testing' || $testing;
+ }
+
+ /**
+ * Returns a Plugin from the repository
+ * @param string $slug The slug of the Plugin
+ * @return mixed Package if found, NULL if not
+ */
+ public function getRepositoryPlugin($slug)
+ {
+ return @$this->repository['plugins'][$slug];
+ }
+
+ /**
+ * Returns the list of Plugins available in the repository
+ * @return Iterator The Plugins remotely available
+ */
+ public function getRepositoryPlugins()
+ {
+ return $this->repository['plugins'];
+ }
+
+ /**
+ * Returns a Theme from the repository
+ * @param string $slug The slug of the Theme
+ * @return mixed Package if found, NULL if not
+ */
+ public function getRepositoryTheme($slug)
+ {
+ return @$this->repository['themes'][$slug];
+ }
+
+ /**
+ * Returns the list of Themes available in the repository
+ * @return Iterator The Themes remotely available
+ */
+ public function getRepositoryThemes()
+ {
+ return $this->repository['themes'];
+ }
+
+ /**
+ * Returns the list of Plugins and Themes available in the repository
+ * @return Remote\Packages Available Plugins and Themes
+ * Format: ['plugins' => array, 'themes' => array]
+ */
+ public function getRepository()
+ {
+ return $this->repository;
+ }
+
+ /**
+ * Searches for a Package in the repository
+ * @param string $search Can be either the slug or the name
+ * @param bool $ignore_exception True if should not fire an exception (for use in Twig)
+ * @return Remote\Package|bool Package if found, FALSE if not
+ */
+ public function findPackage($search, $ignore_exception = false)
+ {
+ $search = strtolower($search);
+
+ $found = $this->getRepositoryTheme($search);
+ if ($found) {
+ return $found;
+ }
+
+ $found = $this->getRepositoryPlugin($search);
+ if ($found) {
+ return $found;
+ }
+
+ $themes = $this->getRepositoryThemes();
+ $plugins = $this->getRepositoryPlugins();
+
+ if (!$themes && !$plugins) {
+ if (!is_writable(ROOT_DIR . '/cache/gpm')) {
+ throw new \RuntimeException("The cache/gpm folder is not writable. Please check the folder permissions.");
+ }
+
+ if ($ignore_exception) {
+ return false;
+ }
+
+ throw new \RuntimeException("GPM not reachable. Please check your internet connection or check the Grav site is reachable");
+ }
+
+ if ($themes) {
+ foreach ($themes as $slug => $theme) {
+ if ($search == $slug || $search == $theme->name) {
+ return $theme;
+ }
+ }
+ }
+
+ if ($plugins) {
+ foreach ($plugins as $slug => $plugin) {
+ if ($search == $slug || $search == $plugin->name) {
+ return $plugin;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Download the zip package via the URL
+ *
+ * @param string $package_file
+ * @param string $tmp
+ * @return null|string
+ */
+ public static function downloadPackage($package_file, $tmp)
+ {
+ $package = parse_url($package_file);
+ $filename = basename($package['path']);
+
+ if (Grav::instance()['config']->get('system.gpm.official_gpm_only') && $package['host'] !== 'getgrav.org') {
+ throw new \RuntimeException("Only official GPM URLs are allowed. You can modify this behavior in the System configuration.");
+ }
+
+ $output = Response::get($package_file, []);
+
+ if ($output) {
+ Folder::create($tmp);
+ file_put_contents($tmp . DS . $filename, $output);
+ return $tmp . DS . $filename;
+ }
+
+ return null;
+ }
+
+ /**
+ * Copy the local zip package to tmp
+ *
+ * @param string $package_file
+ * @param string $tmp
+ * @return null|string
+ */
+ public static function copyPackage($package_file, $tmp)
+ {
+ $package_file = realpath($package_file);
+
+ if (file_exists($package_file)) {
+ $filename = basename($package_file);
+ Folder::create($tmp);
+ copy(realpath($package_file), $tmp . DS . $filename);
+ return $tmp . DS . $filename;
+ }
+
+ return null;
+ }
+
+ /**
+ * Try to guess the package type from the source files
+ *
+ * @param string $source
+ * @return bool|string
+ */
+ public static function getPackageType($source)
+ {
+ $plugin_regex = '/^class\\s{1,}[a-zA-Z0-9]{1,}\\s{1,}extends.+Plugin/m';
+ $theme_regex = '/^class\\s{1,}[a-zA-Z0-9]{1,}\\s{1,}extends.+Theme/m';
+
+ if (
+ file_exists($source . 'system/defines.php') &&
+ file_exists($source . 'system/config/system.yaml')
+ ) {
+ return 'grav';
+ }
+
+ // 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';
+ }
+ 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 string $source
+ * @return bool|string
+ */
+ public static function getPackageName($source)
+ {
+ $ignore_yaml_files = ['blueprints', 'languages'];
+
+ foreach (glob($source . '*.yaml') as $filename) {
+ $name = strtolower(basename($filename, '.yaml'));
+ if (in_array($name, $ignore_yaml_files)) {
+ continue;
+ }
+ return $name;
+ }
+ return false;
+ }
+
+ /**
+ * Find/Parse the blueprint file
+ *
+ * @param string $source
+ * @return array|bool
+ */
+ public static function getBlueprints($source)
+ {
+ $blueprint_file = $source . 'blueprints.yaml';
+ if (!file_exists($blueprint_file)) {
+ return false;
+ }
+
+ $file = YamlFile::instance($blueprint_file);
+ $blueprint = (array)$file->content();
+ $file->free();
+
+ return $blueprint;
+ }
+
+ /**
+ * Get the install path for a name and a particular type of package
+ *
+ * @param string $type
+ * @param string $name
+ * @return string
+ */
+ public static function getInstallPath($type, $name)
+ {
+ $locator = Grav::instance()['locator'];
+
+ if ($type === 'theme') {
+ $install_path = $locator->findResource('themes://', false) . DS . $name;
+ } else {
+ $install_path = $locator->findResource('plugins://', false) . DS . $name;
+ }
+ return $install_path;
+ }
+
+ /**
+ * Searches for a list of Packages in the repository
+ * @param array $searches An array of either slugs or names
+ * @return array Array of found Packages
+ * Format: ['total' => int, 'not_found' => array, ]
+ */
+ public function findPackages($searches = [])
+ {
+ $packages = ['total' => 0, 'not_found' => []];
+ $inflector = new Inflector();
+
+ foreach ($searches as $search) {
+ $repository = '';
+ // if this is an object, get the search data from the key
+ if (is_object($search)) {
+ $search = (array)$search;
+ $key = key($search);
+ $repository = $search[$key];
+ $search = $key;
+ }
+
+ $found = $this->findPackage($search);
+ if ($found) {
+ // set override repository if provided
+ if ($repository) {
+ $found->override_repository = $repository;
+ }
+ if (!isset($packages[$found->package_type])) {
+ $packages[$found->package_type] = [];
+ }
+
+ $packages[$found->package_type][$found->slug] = $found;
+ $packages['total']++;
+ } else {
+ // make a best guess at the type based on the repo URL
+ if (Utils::contains($repository, '-theme')) {
+ $type = 'themes';
+ } else {
+ $type = 'plugins';
+ }
+
+ $not_found = new \stdClass();
+ $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]);
+ $not_found->override_repository = $repository;
+ $packages['not_found'][$search] = $not_found;
+ }
+ }
+
+ return $packages;
+ }
+
+ /**
+ * Return the list of packages that have the passed one as dependency
+ *
+ * @param string $slug The slug name of the package
+ *
+ * @return array
+ */
+ public function getPackagesThatDependOnPackage($slug)
+ {
+ $plugins = $this->getInstalledPlugins();
+ $themes = $this->getInstalledThemes();
+ $packages = array_merge($plugins->toArray(), $themes->toArray());
+
+ $dependent_packages = [];
+
+ foreach ($packages as $package_name => $package) {
+ if (isset($package['dependencies'])) {
+ foreach ($package['dependencies'] as $dependency) {
+ if (is_array($dependency) && isset($dependency['name'])) {
+ $dependency = $dependency['name'];
+ }
+
+ if ($dependency === $slug) {
+ $dependent_packages[] = $package_name;
+ }
+ }
+ }
+ }
+
+ return $dependent_packages;
+ }
+
+
+ /**
+ * Get the required version of a dependency of a package
+ *
+ * @param string $package_slug
+ * @param string $dependency_slug
+ *
+ * @return mixed
+ */
+ public function getVersionOfDependencyRequiredByPackage($package_slug, $dependency_slug)
+ {
+ $dependencies = $this->getInstalledPackage($package_slug)->dependencies;
+ foreach ($dependencies as $dependency) {
+ if (isset($dependency[$dependency_slug])) {
+ return $dependency[$dependency_slug];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Check the package identified by $slug can be updated to the version passed as argument.
+ * Thrown an exception if it cannot be updated because another package installed requires it to be at an older version.
+ *
+ * @param string $slug
+ * @param string $version_with_operator
+ * @param array $ignore_packages_list
+ *
+ * @return bool
+ * @throws \RuntimeException
+ */
+ public function checkNoOtherPackageNeedsThisDependencyInALowerVersion(
+ $slug,
+ $version_with_operator,
+ $ignore_packages_list
+ ) {
+
+ // check if any of the currently installed package need this in a lower version than the one we need. In case, abort and tell which package
+ $dependent_packages = $this->getPackagesThatDependOnPackage($slug);
+ $version = $this->calculateVersionNumberFromDependencyVersion($version_with_operator);
+
+ if (count($dependent_packages)) {
+ foreach ($dependent_packages as $dependent_package) {
+ $other_dependency_version_with_operator = $this->getVersionOfDependencyRequiredByPackage($dependent_package,
+ $slug);
+ $other_dependency_version = $this->calculateVersionNumberFromDependencyVersion($other_dependency_version_with_operator);
+
+ // check version is compatible with the one needed by the current package
+ if ($this->versionFormatIsNextSignificantRelease($other_dependency_version_with_operator)) {
+ $compatible = $this->checkNextSignificantReleasesAreCompatible($version,
+ $other_dependency_version);
+ if (!$compatible) {
+ if (!in_array($dependent_package, $ignore_packages_list, true)) {
+ throw new \RuntimeException("Package $slug is required in an older version by package $dependent_package . This package needs a newer version, and because of this it cannot be installed. The $dependent_package package must be updated to use a newer release of $slug .",
+ 2);
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check the passed packages list can be updated
+ *
+ * @param array $packages_names_list
+ *
+ * @throws \Exception
+ */
+ public function checkPackagesCanBeInstalled($packages_names_list)
+ {
+ foreach ($packages_names_list as $package_name) {
+ $this->checkNoOtherPackageNeedsThisDependencyInALowerVersion($package_name,
+ $this->getLatestVersionOfPackage($package_name), $packages_names_list);
+ }
+ }
+
+ /**
+ * Fetch the dependencies, check the installed packages and return an array with
+ * the list of packages with associated an information on what to do: install, update or ignore.
+ *
+ * `ignore` means the package is already installed and can be safely left as-is.
+ * `install` means the package is not installed and must be installed.
+ * `update` means the package is already installed and must be updated as a dependency needs a higher version.
+ *
+ * @param array $packages
+ *
+ * @return mixed
+ * @throws \Exception
+ */
+ public function getDependencies($packages)
+ {
+ $dependencies = $this->calculateMergedDependenciesOfPackages($packages);
+ foreach ($dependencies as $dependency_slug => $dependencyVersionWithOperator) {
+ if (\in_array($dependency_slug, $packages, true)) {
+ unset($dependencies[$dependency_slug]);
+ continue;
+ }
+
+ // Check PHP version
+ 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 \RuntimeException("One of the packages require PHP {$dependencies['php']}. Please update PHP to resolve this");
+ }
+
+ unset($dependencies[$dependency_slug]);
+ continue;
+ }
+
+ //First, check for Grav dependency. If a dependency requires Grav > the current version, abort and tell.
+ if ($dependency_slug === 'grav') {
+ if (version_compare($this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator),
+ GRAV_VERSION) === 1
+ ) {
+ //Needs a Grav update first
+ throw new \RuntimeException("One of the packages require Grav {$dependencies['grav']}. Please update Grav to the latest release.");
+ }
+
+ unset($dependencies[$dependency_slug]);
+ continue;
+ }
+
+ if ($this->isPluginInstalled($dependency_slug)) {
+ if ($this->isPluginInstalledAsSymlink($dependency_slug)) {
+ unset($dependencies[$dependency_slug]);
+ continue;
+ }
+
+ $dependencyVersion = $this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator);
+
+ // get currently installed version
+ $locator = Grav::instance()['locator'];
+ $blueprints_path = $locator->findResource('plugins://' . $dependency_slug . DS . 'blueprints.yaml');
+ $file = YamlFile::instance($blueprints_path);
+ $package_yaml = $file->content();
+ $file->free();
+ $currentlyInstalledVersion = $package_yaml['version'];
+
+ // if requirement is next significant release, check is compatible with currently installed version, might not be
+ if ($this->versionFormatIsNextSignificantRelease($dependencyVersionWithOperator)) {
+ if ($this->firstVersionIsLower($dependencyVersion, $currentlyInstalledVersion)) {
+ $compatible = $this->checkNextSignificantReleasesAreCompatible($dependencyVersion,
+ $currentlyInstalledVersion);
+
+ if (!$compatible) {
+ 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);
+ }
+ }
+ }
+
+ //if I already have the latest release, remove the dependency
+ $latestRelease = $this->getLatestVersionOfPackage($dependency_slug);
+
+ if ($this->firstVersionIsLower($latestRelease, $dependencyVersion)) {
+ //throw an exception if a required version cannot be found in the GPM yet
+ 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);
+ }
+
+ if ($this->firstVersionIsLower($currentlyInstalledVersion, $dependencyVersion)) {
+ $dependencies[$dependency_slug] = 'update';
+ } else {
+ if ($currentlyInstalledVersion == $latestRelease) {
+ unset($dependencies[$dependency_slug]);
+ } else {
+ // an update is not strictly required mark as 'ignore'
+ $dependencies[$dependency_slug] = 'ignore';
+ }
+ }
+ } else {
+ $dependencyVersion = $this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator);
+
+ // if requirement is next significant release, check is compatible with latest available version, might not be
+ if ($this->versionFormatIsNextSignificantRelease($dependencyVersionWithOperator)) {
+ $latestVersionOfPackage = $this->getLatestVersionOfPackage($dependency_slug);
+ if ($this->firstVersionIsLower($dependencyVersion, $latestVersionOfPackage)) {
+ $compatible = $this->checkNextSignificantReleasesAreCompatible($dependencyVersion,
+ $latestVersionOfPackage);
+
+ if (!$compatible) {
+ throw new \Exception('Dependency ' . $dependency_slug . ' is required in an older version than the latest release available, and it cannot be installed. This package must be updated. Please get in touch with its developer.',
+ 2);
+ }
+ }
+ }
+
+ $dependencies[$dependency_slug] = 'install';
+ }
+ }
+
+ $dependencies_slugs = array_keys($dependencies);
+ $this->checkNoOtherPackageNeedsTheseDependenciesInALowerVersion(array_merge($packages, $dependencies_slugs));
+
+ return $dependencies;
+ }
+
+ public function checkNoOtherPackageNeedsTheseDependenciesInALowerVersion($dependencies_slugs)
+ {
+ foreach ($dependencies_slugs as $dependency_slug) {
+ $this->checkNoOtherPackageNeedsThisDependencyInALowerVersion($dependency_slug,
+ $this->getLatestVersionOfPackage($dependency_slug), $dependencies_slugs);
+ }
+ }
+
+ private function firstVersionIsLower($firstVersion, $secondVersion)
+ {
+ return version_compare($firstVersion, $secondVersion) === -1;
+ }
+
+ /**
+ * Calculates and merges the dependencies of a package
+ *
+ * @param string $packageName The package information
+ *
+ * @param array $dependencies The dependencies array
+ *
+ * @return array
+ * @throws \Exception
+ */
+ private function calculateMergedDependenciesOfPackage($packageName, $dependencies)
+ {
+ $packageData = $this->findPackage($packageName);
+
+ //Check for dependencies
+ if (isset($packageData->dependencies)) {
+ foreach ($packageData->dependencies as $dependency) {
+ $current_package_name = $dependency['name'];
+ if (isset($dependency['version'])) {
+ $current_package_version_information = $dependency['version'];
+ }
+
+ if (!isset($dependencies[$current_package_name])) {
+ // Dependency added for the first time
+
+ if (!isset($current_package_version_information)) {
+ $dependencies[$current_package_name] = '*';
+ } else {
+ $dependencies[$current_package_name] = $current_package_version_information;
+ }
+
+ //Factor in the package dependencies too
+ $dependencies = $this->calculateMergedDependenciesOfPackage($current_package_name, $dependencies);
+ } else {
+ // Dependency already added by another package
+ //if this package requires a version higher than the currently stored one, store this requirement instead
+ if (isset($current_package_version_information) && $current_package_version_information !== '*') {
+
+ $currently_stored_version_information = $dependencies[$current_package_name];
+ $currently_stored_version_number = $this->calculateVersionNumberFromDependencyVersion($currently_stored_version_information);
+
+ $currently_stored_version_is_in_next_significant_release_format = false;
+ if ($this->versionFormatIsNextSignificantRelease($currently_stored_version_information)) {
+ $currently_stored_version_is_in_next_significant_release_format = true;
+ }
+
+ if (!$currently_stored_version_number) {
+ $currently_stored_version_number = '*';
+ }
+
+ $current_package_version_number = $this->calculateVersionNumberFromDependencyVersion($current_package_version_information);
+ if (!$current_package_version_number) {
+ throw new \RuntimeException('Bad format for version of dependency ' . $current_package_name . ' for package ' . $packageName,
+ 1);
+ }
+
+ $current_package_version_is_in_next_significant_release_format = false;
+ if ($this->versionFormatIsNextSignificantRelease($current_package_version_information)) {
+ $current_package_version_is_in_next_significant_release_format = true;
+ }
+
+ //If I had stored '*', change right away with the more specific version required
+ if ($currently_stored_version_number === '*') {
+ $dependencies[$current_package_name] = $current_package_version_information;
+ } else {
+ 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 is higher
+ $dependencies[$current_package_name] = $current_package_version_information;
+ }
+ } else {
+ $compatible = $this->checkNextSignificantReleasesAreCompatible($currently_stored_version_number,
+ $current_package_version_number);
+ if (!$compatible) {
+ throw new \RuntimeException('Dependency ' . $current_package_name . ' is required in two incompatible versions',
+ 2);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return $dependencies;
+ }
+
+ /**
+ * Calculates and merges the dependencies of the passed packages
+ *
+ * @param array $packages
+ *
+ * @return mixed
+ * @throws \Exception
+ */
+ public function calculateMergedDependenciesOfPackages($packages)
+ {
+ $dependencies = [];
+
+ foreach ($packages as $package) {
+ $dependencies = $this->calculateMergedDependenciesOfPackage($package, $dependencies);
+ }
+
+ return $dependencies;
+ }
+
+ /**
+ * Returns the actual version from a dependency version string.
+ * Examples:
+ * $versionInformation == '~2.0' => returns '2.0'
+ * $versionInformation == '>=2.0.2' => returns '2.0.2'
+ * $versionInformation == '2.0.2' => returns '2.0.2'
+ * $versionInformation == '*' => returns null
+ * $versionInformation == '' => returns null
+ *
+ * @param string $version
+ *
+ * @return null|string
+ */
+ public function calculateVersionNumberFromDependencyVersion($version)
+ {
+ if ($version === '*') {
+ return null;
+ }
+ if ($version === '') {
+ return null;
+ }
+ if ($this->versionFormatIsNextSignificantRelease($version)) {
+ return trim(substr($version, 1));
+ }
+ if ($this->versionFormatIsEqualOrHigher($version)) {
+ return trim(substr($version, 2));
+ }
+
+ return $version;
+ }
+
+ /**
+ * Check if the passed version information contains next significant release (tilde) operator
+ *
+ * Example: returns true for $version: '~2.0'
+ *
+ * @param string $version
+ *
+ * @return bool
+ */
+ public function versionFormatIsNextSignificantRelease($version): bool
+ {
+ return strpos($version, '~') === 0;
+ }
+
+ /**
+ * Check if the passed version information contains equal or higher operator
+ *
+ * Example: returns true for $version: '>=2.0'
+ *
+ * @param string $version
+ *
+ * @return bool
+ */
+ public function versionFormatIsEqualOrHigher($version): bool
+ {
+ return strpos($version, '>=') === 0;
+ }
+
+ /**
+ * Check if two releases are compatible by next significant release
+ *
+ * ~1.2 is equivalent to >=1.2 <2.0.0
+ * ~1.2.3 is equivalent to >=1.2.3 <1.3.0
+ *
+ * In short, allows the last digit specified to go up
+ *
+ * @param string $version1 the version string (e.g. '2.0.0' or '1.0')
+ * @param string $version2 the version string (e.g. '2.0.0' or '1.0')
+ *
+ * @return bool
+ */
+ public function checkNextSignificantReleasesAreCompatible($version1, $version2): bool
+ {
+ $version1array = explode('.', $version1);
+ $version2array = explode('.', $version2);
+
+ if (\count($version1array) > \count($version2array)) {
+ list($version1array, $version2array) = [$version2array, $version1array];
+ }
+
+ $i = 0;
+ while ($i < \count($version1array) - 1) {
+ if ($version1array[$i] != $version2array[$i]) {
+ return false;
+ }
+ $i++;
+ }
+
+ return true;
+ }
+
+}
diff --git a/system/src/Grav/Common/GPM/Installer.php b/system/src/Grav/Common/GPM/Installer.php
new file mode 100644
index 0000000..cd51163
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Installer.php
@@ -0,0 +1,543 @@
+ true,
+ 'ignore_symlinks' => true,
+ 'sophisticated' => false,
+ 'theme' => false,
+ 'install_path' => '',
+ 'ignores' => [],
+ 'exclude_checks' => [self::EXISTS, self::NOT_FOUND, self::IS_LINK]
+ ];
+
+ /**
+ * Installs a given package to a given destination.
+ *
+ * @param string $zip the local path to ZIP package
+ * @param string $destination The local path to the Grav Instance
+ * @param array $options Options to use for installing. ie, ['install_path' => '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, $keepExtracted = false)
+ {
+ $destination = rtrim($destination, DS);
+ $options = array_merge(self::$options, $options);
+ $install_path = rtrim($destination . DS . ltrim($options['install_path'], DS), DS);
+
+ if (!self::isGravInstance($destination) || !self::isValidDestination($install_path,
+ $options['exclude_checks'])
+ ) {
+ return false;
+ }
+
+ 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('', false);
+
+ if (!$extracted) {
+ $extracted = self::unZip($zip, $tmp);
+ if (!$extracted) {
+ Folder::delete($tmp);
+ return false;
+ }
+ }
+
+ if (!file_exists($extracted)) {
+ self::$error = self::INVALID_SOURCE;
+ return false;
+ }
+
+ $is_install = true;
+ $installer = self::loadInstaller($extracted, $is_install);
+
+ if (isset($options['is_update']) && $options['is_update'] === true) {
+ $method = 'preUpdate';
+ } else {
+ $method = 'preInstall';
+ }
+
+ if ($installer && method_exists($installer, $method)) {
+ $method_result = $installer::$method();
+ if ($method_result !== true) {
+ self::$error = 'An error occurred';
+ if (is_string($method_result)) {
+ self::$error = $method_result;
+ }
+
+ return false;
+ }
+ }
+
+ if (!$options['sophisticated']) {
+ if ($options['theme']) {
+ self::copyInstall($extracted, $install_path);
+ } else {
+ self::moveInstall($extracted, $install_path);
+ }
+ } else {
+ self::sophisticatedInstall($extracted, $install_path, $options['ignores'], $keepExtracted);
+ }
+
+ Folder::delete($tmp);
+
+ if (isset($options['is_update']) && $options['is_update'] === true) {
+ $method = 'postUpdate';
+ } else {
+ $method = 'postInstall';
+ }
+
+ self::$message = '';
+ if ($installer && method_exists($installer, $method)) {
+ self::$message = $installer::$method();
+ }
+
+ self::$error = self::OK;
+
+ return true;
+
+ }
+
+ /**
+ * Unzip a file to somewhere
+ *
+ * @param string $zip_file
+ * @param string $destination
+ * @return bool|string
+ */
+ public static function unZip($zip_file, $destination)
+ {
+ $zip = new \ZipArchive();
+ $archive = $zip->open($zip_file);
+
+ if ($archive === true) {
+ Folder::create($destination);
+
+ $unzip = $zip->extractTo($destination);
+
+
+ if (!$unzip) {
+ self::$error = self::ZIP_EXTRACT_ERROR;
+ Folder::delete($destination);
+ $zip->close();
+ return false;
+ }
+
+ $package_folder_name = preg_replace('#\./$#', '', $zip->getNameIndex(0));
+ $zip->close();
+
+ return $destination . '/' . $package_folder_name;
+ }
+
+ self::$error = self::ZIP_EXTRACT_ERROR;
+ self::$error_zip = $archive;
+ return false;
+ }
+
+ /**
+ * Instantiates and returns the package installer class
+ *
+ * @param string $installer_file_folder The folder path that contains install.php
+ * @param bool $is_install True if install, false if removal
+ *
+ * @return null|string
+ */
+ private static function loadInstaller($installer_file_folder, $is_install)
+ {
+ $installer = null;
+
+ $installer_file_folder = rtrim($installer_file_folder, DS);
+
+ $install_file = $installer_file_folder . DS . 'install.php';
+
+ if (file_exists($install_file)) {
+ require_once $install_file;
+ } else {
+ return null;
+ }
+
+ if ($is_install) {
+ $slug = '';
+ if (($pos = strpos($installer_file_folder, 'grav-plugin-')) !== false) {
+ $slug = substr($installer_file_folder, $pos + strlen('grav-plugin-'));
+ } elseif (($pos = strpos($installer_file_folder, 'grav-theme-')) !== false) {
+ $slug = substr($installer_file_folder, $pos + strlen('grav-theme-'));
+ }
+ } else {
+ $path_elements = explode('/', $installer_file_folder);
+ $slug = end($path_elements);
+ }
+
+ if (!$slug) {
+ return null;
+ }
+
+ $class_name = ucfirst($slug) . 'Install';
+
+ if (class_exists($class_name)) {
+ return $class_name;
+ }
+
+ $class_name_alphanumeric = preg_replace('/[^a-zA-Z0-9]+/', '', $class_name);
+
+ if (class_exists($class_name_alphanumeric)) {
+ return $class_name_alphanumeric;
+ }
+
+ return $installer;
+ }
+
+ /**
+ * @param string $source_path
+ * @param string $install_path
+ *
+ * @return bool
+ */
+ public static function moveInstall($source_path, $install_path)
+ {
+ if (file_exists($install_path)) {
+ Folder::delete($install_path);
+ }
+
+ Folder::move($source_path, $install_path);
+
+ return true;
+ }
+
+ /**
+ * @param string $source_path
+ * @param string $install_path
+ *
+ * @return bool
+ */
+ public static function copyInstall($source_path, $install_path)
+ {
+ if (empty($source_path)) {
+ throw new \RuntimeException("Directory $source_path is missing");
+ }
+
+ Folder::rcopy($source_path, $install_path);
+
+ return true;
+ }
+
+ /**
+ * @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 = [], $keep_source = false)
+ {
+ foreach (new \DirectoryIterator($source_path) as $file) {
+
+ if ($file->isLink() || $file->isDot() || \in_array($file->getFilename(), $ignores, true)) {
+ continue;
+ }
+
+ $path = $install_path . DS . $file->getFilename();
+
+ if ($file->isDir()) {
+ Folder::delete($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) {
+ @chmod($bin_file, 0755);
+ }
+ }
+ } else {
+ @unlink($path);
+ @copy($file->getPathname(), $path);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Uninstalls one or more given package
+ *
+ * @param string $path The slug of the package(s)
+ * @param array $options Options to use for uninstalling
+ *
+ * @return bool True if everything went fine, False otherwise.
+ */
+ public static function uninstall($path, $options = [])
+ {
+ $options = array_merge(self::$options, $options);
+ if (!self::isValidDestination($path, $options['exclude_checks'])
+ ) {
+ return false;
+ }
+
+ $installer_file_folder = $path;
+ $is_install = false;
+ $installer = self::loadInstaller($installer_file_folder, $is_install);
+
+ if ($installer && method_exists($installer, 'preUninstall')) {
+ $method_result = $installer::preUninstall();
+ if ($method_result !== true) {
+ self::$error = 'An error occurred';
+ if (is_string($method_result)) {
+ self::$error = $method_result;
+ }
+
+ return false;
+ }
+ }
+
+ $result = Folder::delete($path);
+
+ self::$message = '';
+ if ($result && $installer && method_exists($installer, 'postUninstall')) {
+ self::$message = $installer::postUninstall();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Runs a set of checks on the destination and sets the Error if any
+ *
+ * @param string $destination The directory to run validations at
+ * @param array $exclude An array of constants to exclude from the validation
+ *
+ * @return bool True if validation passed. False otherwise
+ */
+ public static function isValidDestination($destination, $exclude = [])
+ {
+ self::$error = 0;
+ self::$target = $destination;
+
+ if (is_link($destination)) {
+ self::$error = self::IS_LINK;
+ } elseif (file_exists($destination)) {
+ self::$error = self::EXISTS;
+ } elseif (!file_exists($destination)) {
+ self::$error = self::NOT_FOUND;
+ } elseif (!is_dir($destination)) {
+ self::$error = self::NOT_DIRECTORY;
+ }
+
+ if (\count($exclude) && \in_array(self::$error, $exclude, true)) {
+ return true;
+ }
+
+ return !self::$error;
+ }
+
+ /**
+ * Validates if the given path is a Grav Instance
+ *
+ * @param string $target The local path to the Grav Instance
+ *
+ * @return bool True if is a Grav Instance. False otherwise
+ */
+ public static function isGravInstance($target)
+ {
+ self::$error = 0;
+ self::$target = $target;
+
+ if (
+ !file_exists($target . DS . 'index.php') ||
+ !file_exists($target . DS . 'bin') ||
+ !file_exists($target . DS . 'user') ||
+ !file_exists($target . DS . 'system' . DS . 'config' . DS . 'system.yaml')
+ ) {
+ self::$error = self::NOT_GRAV_ROOT;
+ }
+
+ return !self::$error;
+ }
+
+ /**
+ * Returns the last message added by the installer
+ * @return string The message
+ */
+ public static function getMessage()
+ {
+ return self::$message;
+ }
+
+ /**
+ * Returns the last error occurred in a string message format
+ * @return string The message of the last error
+ */
+ public static function lastErrorMsg()
+ {
+ if (is_string(self::$error)) {
+ return self::$error;
+ }
+
+ switch (self::$error) {
+ case 0:
+ $msg = 'No Error';
+ break;
+
+ case self::EXISTS:
+ $msg = 'The target path "' . self::$target . '" already exists';
+ break;
+
+ case self::IS_LINK:
+ $msg = 'The target path "' . self::$target . '" is a symbolic link';
+ break;
+
+ case self::NOT_FOUND:
+ $msg = 'The target path "' . self::$target . '" does not appear to exist';
+ break;
+
+ case self::NOT_DIRECTORY:
+ $msg = 'The target path "' . self::$target . '" does not appear to be a folder';
+ break;
+
+ case self::NOT_GRAV_ROOT:
+ $msg = 'The target path "' . self::$target . '" does not appear to be a Grav instance';
+ break;
+
+ case self::ZIP_OPEN_ERROR:
+ $msg = 'Unable to open the package file';
+ break;
+
+ case self::ZIP_EXTRACT_ERROR:
+ $msg = 'Unable to extract the package. ';
+ if (self::$error_zip) {
+ switch(self::$error_zip) {
+ case \ZipArchive::ER_EXISTS:
+ $msg .= 'File already exists.';
+ break;
+
+ case \ZipArchive::ER_INCONS:
+ $msg .= 'Zip archive inconsistent.';
+ break;
+
+ case \ZipArchive::ER_MEMORY:
+ $msg .= 'Memory allocation failure.';
+ break;
+
+ case \ZipArchive::ER_NOENT:
+ $msg .= 'No such file.';
+ break;
+
+ case \ZipArchive::ER_NOZIP:
+ $msg .= 'Not a zip archive.';
+ break;
+
+ case \ZipArchive::ER_OPEN:
+ $msg .= "Can't open file.";
+ break;
+
+ case \ZipArchive::ER_READ:
+ $msg .= 'Read error.';
+ break;
+
+ case \ZipArchive::ER_SEEK:
+ $msg .= 'Seek error.';
+ break;
+ }
+ }
+ break;
+
+ case self::INVALID_SOURCE:
+ $msg = 'Invalid source file';
+ break;
+
+ default:
+ $msg = 'Unknown Error';
+ break;
+ }
+
+ return $msg;
+ }
+
+ /**
+ * Returns the last error code of the occurred error
+ * @return int|string The code of the last error
+ */
+ public static function lastErrorCode()
+ {
+ return self::$error;
+ }
+
+ /**
+ * Allows to manually set an error
+ *
+ * @param int|string $error the Error code
+ */
+
+ public static function setError($error)
+ {
+ self::$error = $error;
+ }
+}
diff --git a/system/src/Grav/Common/GPM/Licenses.php b/system/src/Grav/Common/GPM/Licenses.php
new file mode 100644
index 0000000..14c9258
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Licenses.php
@@ -0,0 +1,123 @@
+content();
+ $slug = strtolower($slug);
+
+ if ($license && !self::validate($license)) {
+ return false;
+ }
+
+ if (!\is_string($license)) {
+ if (isset($data['licenses'][$slug])) {
+ unset($data['licenses'][$slug]);
+ } else {
+ return false;
+ }
+ } else {
+ $data['licenses'][$slug] = $license;
+ }
+
+ $licenses->save($data);
+ $licenses->free();
+
+ return true;
+ }
+
+ /**
+ * Returns the license for a Premium package
+ *
+ * @param string $slug
+ *
+ * @return array|string
+ */
+ public static function get($slug = null)
+ {
+ $licenses = self::getLicenseFile();
+ $data = (array)$licenses->content();
+ $licenses->free();
+ $slug = strtolower($slug);
+
+ if (!$slug) {
+ return $data['licenses'] ?? [];
+ }
+
+ return $data['licenses'][$slug] ?? '';
+ }
+
+
+ /**
+ * Validates the License format
+ *
+ * @param string $license
+ *
+ * @return bool
+ */
+ public static function validate($license = null)
+ {
+ if (!is_string($license)) {
+ return false;
+ }
+
+ return preg_match('#' . self::$regex. '#', $license);
+ }
+
+ /**
+ * Get the License File object
+ *
+ * @return \RocketTheme\Toolbox\File\FileInterface
+ */
+ public static function getLicenseFile()
+
+ {
+ if (!isset(self::$file)) {
+ $path = Grav::instance()['locator']->findResource('user-data://') . '/licenses.yaml';
+ if (!file_exists($path)) {
+ touch($path);
+ }
+ self::$file = CompiledYamlFile::instance($path);
+ }
+
+ return self::$file;
+ }
+}
diff --git a/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php b/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php
new file mode 100644
index 0000000..be565c9
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php
@@ -0,0 +1,25 @@
+ $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
new file mode 100644
index 0000000..88241a4
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Local/Package.php
@@ -0,0 +1,41 @@
+blueprints()->toArray());
+ parent::__construct($data, $package_type);
+
+ $this->settings = $package->toArray();
+
+ $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->__get('slug')));
+ }
+
+ /**
+ * @return mixed
+ */
+ public function isEnabled()
+ {
+ return $this->settings['enabled'];
+ }
+}
diff --git a/system/src/Grav/Common/GPM/Local/Packages.php b/system/src/Grav/Common/GPM/Local/Packages.php
new file mode 100644
index 0000000..14a0b0b
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Local/Packages.php
@@ -0,0 +1,25 @@
+ new Plugins(),
+ 'themes' => new Themes()
+ ];
+
+ parent::__construct($items);
+ }
+}
diff --git a/system/src/Grav/Common/GPM/Local/Plugins.php b/system/src/Grav/Common/GPM/Local/Plugins.php
new file mode 100644
index 0000000..19adfb8
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Local/Plugins.php
@@ -0,0 +1,31 @@
+all());
+ }
+}
diff --git a/system/src/Grav/Common/GPM/Local/Themes.php b/system/src/Grav/Common/GPM/Local/Themes.php
new file mode 100644
index 0000000..8607e6b
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Local/Themes.php
@@ -0,0 +1,28 @@
+all());
+ }
+}
diff --git a/system/src/Grav/Common/GPM/Remote/AbstractPackageCollection.php b/system/src/Grav/Common/GPM/Remote/AbstractPackageCollection.php
new file mode 100644
index 0000000..429c5b1
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Remote/AbstractPackageCollection.php
@@ -0,0 +1,77 @@
+get('system.gpm.releases', 'stable');
+ $cache_dir = Grav::instance()['locator']->findResource('cache://gpm', true, true);
+ $this->cache = new FilesystemCache($cache_dir);
+
+ $this->repository = $repository . '?v=' . GRAV_VERSION . '&' . $channel . '=1';
+ $this->raw = $this->cache->fetch(md5($this->repository));
+
+ $this->fetch($refresh, $callback);
+ foreach (json_decode($this->raw, true) as $slug => $data) {
+ // Temporarily fix for using multisites
+ if (isset($data['install_path'])) {
+ $path = preg_replace('~^user/~i', 'user://', $data['install_path']);
+ $data['install_path'] = Grav::instance()['locator']->findResource($path, false, true);
+ }
+ $this->items[$slug] = new Package($data, $this->type);
+ }
+ }
+
+ public function fetch($refresh = false, $callback = null)
+ {
+ if (!$this->raw || $refresh) {
+ $response = Response::get($this->repository, [], $callback);
+ $this->raw = $response;
+ $this->cache->save(md5($this->repository), $this->raw, $this->lifetime);
+ }
+
+ return $this->raw;
+ }
+}
diff --git a/system/src/Grav/Common/GPM/Remote/GravCore.php b/system/src/Grav/Common/GPM/Remote/GravCore.php
new file mode 100644
index 0000000..1d30120
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Remote/GravCore.php
@@ -0,0 +1,141 @@
+get('system.gpm.releases', 'stable');
+ $cache_dir = Grav::instance()['locator']->findResource('cache://gpm', true, true);
+ $this->cache = new FilesystemCache($cache_dir);
+ $this->repository .= '?v=' . GRAV_VERSION . '&' . $channel . '=1';
+ $this->raw = $this->cache->fetch(md5($this->repository));
+
+ $this->fetch($refresh, $callback);
+
+ $this->data = json_decode($this->raw, true);
+ $this->version = $this->data['version'] ?? '-';
+ $this->date = $this->data['date'] ?? '-';
+ $this->min_php = $this->data['min_php'] ?? null;
+
+ if (isset($this->data['assets'])) {
+ foreach ((array)$this->data['assets'] as $slug => $data) {
+ $this->items[$slug] = new Package($data);
+ }
+ }
+ }
+
+ /**
+ * Returns the list of assets associated to the latest version of Grav
+ *
+ * @return array list of assets
+ */
+ public function getAssets()
+ {
+ return $this->data['assets'];
+ }
+
+ /**
+ * Returns the changelog list for each version of Grav
+ *
+ * @param string $diff the version number to start the diff from
+ *
+ * @return array changelog list for each version
+ */
+ public function getChangelog($diff = null)
+ {
+ if (!$diff) {
+ return $this->data['changelog'];
+ }
+
+ $diffLog = [];
+ foreach ((array)$this->data['changelog'] as $version => $changelog) {
+ preg_match("/[\w\-\.]+/", $version, $cleanVersion);
+
+ if (!$cleanVersion || version_compare($diff, $cleanVersion[0], '>=')) {
+ continue;
+ }
+
+ $diffLog[$version] = $changelog;
+ }
+
+ return $diffLog;
+ }
+
+ /**
+ * Return the release date of the latest Grav
+ *
+ * @return string
+ */
+ public function getDate()
+ {
+ return $this->date;
+ }
+
+ /**
+ * Determine if this version of Grav is eligible to be updated
+ *
+ * @return mixed
+ */
+ public function isUpdatable()
+ {
+ return version_compare(GRAV_VERSION, $this->getVersion(), '<');
+ }
+
+ /**
+ * Returns the latest version of Grav available remotely
+ *
+ * @return string
+ */
+ public function getVersion()
+ {
+ return $this->version;
+ }
+
+ /**
+ * Returns the minimum PHP version
+ *
+ * @return null|string
+ */
+ public function getMinPHPVersion()
+ {
+ // If non min set, assume current PHP version
+ if (null === $this->min_php) {
+ $this->min_php = phpversion();
+ }
+ return $this->min_php;
+ }
+
+ /**
+ * Is this installation symlinked?
+ *
+ * @return bool
+ */
+ public function isSymlink()
+ {
+ return is_link(GRAV_ROOT . DS . 'index.php');
+ }
+}
diff --git a/system/src/Grav/Common/GPM/Remote/Package.php b/system/src/Grav/Common/GPM/Remote/Package.php
new file mode 100644
index 0000000..196e92b
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Remote/Package.php
@@ -0,0 +1,27 @@
+data;
+ }
+}
diff --git a/system/src/Grav/Common/GPM/Remote/Packages.php b/system/src/Grav/Common/GPM/Remote/Packages.php
new file mode 100644
index 0000000..46bc31f
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Remote/Packages.php
@@ -0,0 +1,25 @@
+ new Plugins($refresh, $callback),
+ 'themes' => new Themes($refresh, $callback)
+ ];
+
+ parent::__construct($items);
+ }
+}
diff --git a/system/src/Grav/Common/GPM/Remote/Plugins.php b/system/src/Grav/Common/GPM/Remote/Plugins.php
new file mode 100644
index 0000000..1d905e3
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Remote/Plugins.php
@@ -0,0 +1,30 @@
+repository, $refresh, $callback);
+ }
+}
diff --git a/system/src/Grav/Common/GPM/Remote/Themes.php b/system/src/Grav/Common/GPM/Remote/Themes.php
new file mode 100644
index 0000000..8024b40
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Remote/Themes.php
@@ -0,0 +1,30 @@
+repository, $refresh, $callback);
+ }
+}
diff --git a/system/src/Grav/Common/GPM/Response.php b/system/src/Grav/Common/GPM/Response.php
new file mode 100644
index 0000000..c444ee8
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Response.php
@@ -0,0 +1,436 @@
+ [
+ CURLOPT_USERAGENT => 'Grav GPM',
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_FAILONERROR => true,
+ CURLOPT_TIMEOUT => 15,
+ CURLOPT_HEADER => false,
+ //CURLOPT_SSL_VERIFYPEER => true, // this is set in the constructor since it's a setting
+ /**
+ * Example of callback parameters from within your own class
+ */
+ //CURLOPT_NOPROGRESS => false,
+ //CURLOPT_PROGRESSFUNCTION => [$this, 'progress']
+ ],
+ 'fopen' => [
+ 'method' => 'GET',
+ 'user_agent' => 'Grav GPM',
+ 'max_redirects' => 5,
+ 'follow_location' => 1,
+ 'timeout' => 15,
+ /* // this is set in the constructor since it's a setting
+ 'ssl' => [
+ 'verify_peer' => true,
+ 'verify_peer_name' => true,
+ ],
+ */
+ /**
+ * Example of callback parameters from within your own class
+ */
+ //'notification' => [$this, 'progress']
+ ]
+ ];
+
+ /**
+ * Sets the preferred method to use for making HTTP calls.
+ *
+ * @param string $method Default is `auto`
+ *
+ * @return Response
+ */
+ public static function setMethod($method = 'auto')
+ {
+ if (!\in_array($method, ['auto', 'curl', 'fopen'], true)) {
+ $method = 'auto';
+ }
+
+ self::$method = $method;
+
+ return new self();
+ }
+
+ /**
+ * Makes a request to the URL by using the preferred method
+ *
+ * @param string $uri URL to call
+ * @param array $options An array of parameters for both `curl` and `fopen`
+ * @param callable $callback Either a function or callback in array notation
+ *
+ * @return string The response of the request
+ */
+ public static function get($uri = '', $options = [], $callback = null)
+ {
+ if (!self::isCurlAvailable() && !self::isFopenAvailable()) {
+ throw new \RuntimeException('Could not start an HTTP request. `allow_url_open` is disabled and `cURL` is not available');
+ }
+
+ // check if this function is available, if so use it to stop any timeouts
+ try {
+ if (function_exists('set_time_limit') && !Utils::isFunctionDisabled('set_time_limit')) {
+ set_time_limit(0);
+ }
+ } catch (\Exception $e) {
+ }
+
+ $config = Grav::instance()['config'];
+ $overrides = [];
+
+ // Override CA Bundle
+ $caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath();
+ if (is_dir($caPathOrFile) || (is_link($caPathOrFile) && is_dir(readlink($caPathOrFile)))) {
+ $overrides['curl'][CURLOPT_CAPATH] = $caPathOrFile;
+ $overrides['fopen']['ssl']['capath'] = $caPathOrFile;
+ } else {
+ $overrides['curl'][CURLOPT_CAINFO] = $caPathOrFile;
+ $overrides['fopen']['ssl']['cafile'] = $caPathOrFile;
+ }
+
+ // SSL Verify Peer and Proxy Setting
+ $settings = [
+ 'method' => $config->get('system.gpm.method', self::$method),
+ 'verify_peer' => $config->get('system.gpm.verify_peer', true),
+ // `system.proxy_url` is for fallback
+ // introduced with 1.1.0-beta.1 probably safe to remove at some point
+ 'proxy_url' => $config->get('system.gpm.proxy_url', $config->get('system.proxy_url', false)),
+ ];
+
+ if (!$settings['verify_peer']) {
+ $overrides = array_replace_recursive([], $overrides, [
+ 'curl' => [
+ CURLOPT_SSL_VERIFYPEER => $settings['verify_peer']
+ ],
+ 'fopen' => [
+ 'ssl' => [
+ 'verify_peer' => $settings['verify_peer'],
+ 'verify_peer_name' => $settings['verify_peer'],
+ ]
+ ]
+ ]);
+ }
+
+ // Proxy Setting
+ if ($settings['proxy_url']) {
+ $proxy = parse_url($settings['proxy_url']);
+ $fopen_proxy = ($proxy['scheme'] ?: 'http') . '://' . $proxy['host'] . (isset($proxy['port']) ? ':' . $proxy['port'] : '');
+
+ $overrides = array_replace_recursive([], $overrides, [
+ 'curl' => [
+ CURLOPT_PROXY => $proxy['host'],
+ CURLOPT_PROXYTYPE => 'HTTP'
+ ],
+ 'fopen' => [
+ 'proxy' => $fopen_proxy,
+ 'request_fulluri' => true
+ ]
+ ]);
+
+ if (isset($proxy['port'])) {
+ $overrides['curl'][CURLOPT_PROXYPORT] = $proxy['port'];
+ }
+
+ if (isset($proxy['user'], $proxy['pass'])) {
+ $fopen_auth = $auth = base64_encode($proxy['user'] . ':' . $proxy['pass']);
+ $overrides['curl'][CURLOPT_PROXYUSERPWD] = $proxy['user'] . ':' . $proxy['pass'];
+ $overrides['fopen']['header'] = "Proxy-Authorization: Basic $fopen_auth";
+ }
+ }
+
+ $options = array_replace_recursive(self::$defaults, $options, $overrides);
+ $method = 'get' . ucfirst(strtolower($settings['method']));
+
+ self::$callback = $callback;
+ return static::$method($uri, $options, $callback);
+ }
+
+ /**
+ * Checks if cURL is available
+ *
+ * @return bool
+ */
+ public static function isCurlAvailable()
+ {
+ return function_exists('curl_version');
+ }
+
+ /**
+ * Checks if the remote fopen request is enabled in PHP
+ *
+ * @return bool
+ */
+ public static function isFopenAvailable()
+ {
+ return preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
+ }
+
+ /**
+ * Is this a remote file or not
+ *
+ * @param string $file
+ * @return bool
+ */
+ public static function isRemote($file)
+ {
+ return (bool) filter_var($file, FILTER_VALIDATE_URL);
+ }
+
+ /**
+ * Progress normalized for cURL and Fopen
+ * Accepts a variable length of arguments passed in by stream method
+ */
+ public static function progress()
+ {
+ static $filesize = null;
+
+ $args = func_get_args();
+ $isCurlResource = is_resource($args[0]) && get_resource_type($args[0]) === 'curl';
+
+ $notification_code = !$isCurlResource ? $args[0] : false;
+ $bytes_transferred = $isCurlResource ? $args[2] : $args[4];
+
+ if ($isCurlResource) {
+ $filesize = $args[1];
+ } elseif ($notification_code == STREAM_NOTIFY_FILE_SIZE_IS) {
+ $filesize = $args[5];
+ }
+
+ if ($bytes_transferred > 0) {
+ if ($notification_code == STREAM_NOTIFY_PROGRESS | STREAM_NOTIFY_COMPLETED || $isCurlResource) {
+
+ $progress = [
+ 'code' => $notification_code,
+ 'filesize' => $filesize,
+ 'transferred' => $bytes_transferred,
+ 'percent' => $filesize <= 0 ? '-' : round(($bytes_transferred * 100) / $filesize, 1)
+ ];
+
+ if (self::$callback !== null) {
+ call_user_func(self::$callback, $progress);
+ }
+ }
+ }
+ }
+
+ /**
+ * Automatically picks the preferred method
+ *
+ * @return string The response of the request
+ */
+ private static function getAuto()
+ {
+ if (!ini_get('open_basedir') && self::isFopenAvailable()) {
+ return self::getFopen(func_get_args());
+ }
+
+ if (self::isCurlAvailable()) {
+ return self::getCurl(func_get_args());
+ }
+
+ return null;
+ }
+
+ /**
+ * Starts a HTTP request via fopen
+ *
+ * @return string The response of the request
+ */
+ private static function getFopen()
+ {
+ if (\count($args = func_get_args()) === 1) {
+ $args = $args[0];
+ }
+
+ $uri = $args[0];
+ $options = $args[1] ?? [];
+ $callback = $args[2] ?? null;
+
+ if ($callback) {
+ $options['fopen']['notification'] = ['self', 'progress'];
+ }
+
+ $options['fopen']['header'] = 'Referer: ' . Grav::instance()['uri']->rootUrl(true);
+ if (isset($options['fopen']['ssl'])) {
+ $ssl = $options['fopen']['ssl'];
+ unset($options['fopen']['ssl']);
+
+ $stream = stream_context_create([
+ 'http' => $options['fopen'],
+ 'ssl' => $ssl
+ ], $options['fopen']);
+ } else {
+ $stream = stream_context_create(['http' => $options['fopen']], $options['fopen']);
+ }
+
+
+ $content = @file_get_contents($uri, false, $stream);
+
+ if ($content === false) {
+ $code = null;
+ // Function file_get_contents() creates local variable $http_response_header, check it
+ if (isset($http_response_header)) {
+ $code = explode(' ', $http_response_header[0] ?? '')[1] ?? null;
+ }
+
+ switch ($code) {
+ case '404':
+ throw new \RuntimeException('Page not found');
+ case '401':
+ throw new \RuntimeException('Invalid LICENSE');
+ default:
+ throw new \RuntimeException("Error while trying to download (code: {$code}): {$uri}\n");
+ }
+ }
+
+ return $content;
+ }
+
+ /**
+ * Starts a HTTP request via cURL
+ *
+ * @return string The response of the request
+ */
+ private static function getCurl()
+ {
+ $args = func_get_args();
+ $args = count($args) > 1 ? $args : array_shift($args);
+
+ $uri = $args[0];
+ $options = $args[1] ?? [];
+ $callback = $args[2] ?? null;
+
+ $ch = curl_init($uri);
+
+ $response = static::curlExecFollow($ch, $options, $callback);
+ $errno = curl_errno($ch);
+
+ if ($errno) {
+ $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $error_message = curl_strerror($errno) . "\n" . curl_error($ch);
+
+ switch ($code) {
+ case '404':
+ throw new \RuntimeException('Page not found');
+ case '401':
+ throw new \RuntimeException('Invalid LICENSE');
+ default:
+ throw new \RuntimeException("Error while trying to download (code: $code): $uri \nMessage: $error_message");
+ }
+ }
+
+ curl_close($ch);
+
+ return $response;
+ }
+
+ /**
+ * @param resource $ch
+ * @param array $options
+ * @param bool $callback
+ *
+ * @return bool|mixed
+ */
+ private static function curlExecFollow($ch, $options, $callback)
+ {
+ curl_setopt_array($ch, [ CURLOPT_REFERER => Grav::instance()['uri']->rootUrl(true) ]);
+
+ if ($callback) {
+ curl_setopt_array(
+ $ch,
+ [
+ CURLOPT_NOPROGRESS => false,
+ CURLOPT_PROGRESSFUNCTION => ['self', 'progress']
+ ]
+ );
+ }
+
+ // no open_basedir set, we can proceed normally
+ if (!ini_get('open_basedir')) {
+ curl_setopt_array($ch, $options['curl']);
+ return curl_exec($ch);
+ }
+
+ $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
+ curl_setopt_array($ch, $options['curl']);
+ if ($max_redirects <= 0) {
+ return curl_exec($ch);
+ }
+
+ $uri = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
+ $rch = curl_copy_handle($ch);
+
+ curl_setopt($rch, CURLOPT_HEADER, true);
+ curl_setopt($rch, CURLOPT_NOBODY, true);
+ curl_setopt($rch, CURLOPT_FORBID_REUSE, false);
+ curl_setopt($rch, CURLOPT_RETURNTRANSFER, true);
+
+ do {
+ curl_setopt($rch, CURLOPT_URL, $uri);
+ $header = curl_exec($rch);
+
+ if (curl_errno($rch)) {
+ $code = 0;
+ } else {
+ $code = (int)curl_getinfo($rch, CURLINFO_HTTP_CODE);
+ if ($code === 301 || $code === 302 || $code === 303) {
+ preg_match('/(?:^|\n)Location:(.*?)\n/i', $header, $matches);
+ $uri = trim(array_pop($matches));
+ } else {
+ $code = 0;
+ }
+ }
+ } while ($code && --$max_redirects);
+
+ curl_close($rch);
+
+ if (!$max_redirects) {
+ if ($max_redirects === null) {
+ trigger_error('Too many redirects. When following redirects, libcurl hit the maximum amount.', E_USER_WARNING);
+ }
+
+ return false;
+ }
+
+ curl_setopt($ch, CURLOPT_URL, $uri);
+
+ return curl_exec($ch);
+ }
+}
diff --git a/system/src/Grav/Common/GPM/Upgrader.php b/system/src/Grav/Common/GPM/Upgrader.php
new file mode 100644
index 0000000..5d9406d
--- /dev/null
+++ b/system/src/Grav/Common/GPM/Upgrader.php
@@ -0,0 +1,142 @@
+remote = new Remote\GravCore($refresh, $callback);
+ }
+
+ /**
+ * Returns the release date of the latest version of Grav
+ *
+ * @return string
+ */
+ public function getReleaseDate()
+ {
+ return $this->remote->getDate();
+ }
+
+ /**
+ * Returns the version of the installed Grav
+ *
+ * @return string
+ */
+ public function getLocalVersion()
+ {
+ return GRAV_VERSION;
+ }
+
+ /**
+ * Returns the version of the remotely available Grav
+ *
+ * @return string
+ */
+ public function getRemoteVersion()
+ {
+ return $this->remote->getVersion();
+ }
+
+ /**
+ * Returns an array of assets available to download remotely
+ *
+ * @return array
+ */
+ public function getAssets()
+ {
+ return $this->remote->getAssets();
+ }
+
+ /**
+ * Returns the changelog list for each version of Grav
+ *
+ * @param string $diff the version number to start the diff from
+ *
+ * @return array return the changelog list for each version
+ */
+ public function getChangelog($diff = null)
+ {
+ return $this->remote->getChangelog($diff);
+ }
+
+ /**
+ * Make sure this meets minimum PHP requirements
+ *
+ * @return bool
+ */
+ public function meetsRequirements()
+ {
+ $current_php_version = phpversion();
+ if (version_compare($current_php_version, $this->minPHPVersion(), '<')) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get minimum PHP version from remote
+ *
+ * @return null
+ */
+ public function minPHPVersion()
+ {
+ if (null === $this->min_php) {
+ $this->min_php = $this->remote->getMinPHPVersion();
+ }
+ return $this->min_php;
+ }
+
+ /**
+ * Checks if the currently installed Grav is upgradable to a newer version
+ *
+ * @return boolean True if it's upgradable, False otherwise.
+ */
+ public function isUpgradable()
+ {
+ return version_compare($this->getLocalVersion(), $this->getRemoteVersion(), '<');
+ }
+
+ /**
+ * Checks if Grav is currently symbolically linked
+ *
+ * @return boolean True if Grav is symlinked, False otherwise.
+ */
+
+ public function isSymlink()
+ {
+ return $this->remote->isSymlink();
+ }
+}
diff --git a/system/src/Grav/Common/Getters.php b/system/src/Grav/Common/Getters.php
new file mode 100644
index 0000000..e69116e
--- /dev/null
+++ b/system/src/Grav/Common/Getters.php
@@ -0,0 +1,161 @@
+offsetSet($offset, $value);
+ }
+
+ /**
+ * Magic getter method
+ *
+ * @param mixed $offset Medium name value
+ *
+ * @return mixed Medium value
+ */
+ public function __get($offset)
+ {
+ return $this->offsetGet($offset);
+ }
+
+ /**
+ * Magic method to determine if the attribute is set
+ *
+ * @param mixed $offset Medium name value
+ *
+ * @return boolean True if the value is set
+ */
+ public function __isset($offset)
+ {
+ return $this->offsetExists($offset);
+ }
+
+ /**
+ * Magic method to unset the attribute
+ *
+ * @param mixed $offset The name value to unset
+ */
+ public function __unset($offset)
+ {
+ $this->offsetUnset($offset);
+ }
+
+ /**
+ * @param mixed $offset
+ *
+ * @return bool
+ */
+ public function offsetExists($offset)
+ {
+ if ($this->gettersVariable) {
+ $var = $this->gettersVariable;
+
+ return isset($this->{$var}[$offset]);
+ }
+
+ return isset($this->{$offset});
+ }
+
+ /**
+ * @param mixed $offset
+ *
+ * @return mixed
+ */
+ public function offsetGet($offset)
+ {
+ if ($this->gettersVariable) {
+ $var = $this->gettersVariable;
+
+ return $this->{$var}[$offset] ?? null;
+ }
+
+ return $this->{$offset} ?? null;
+ }
+
+ /**
+ * @param mixed $offset
+ * @param mixed $value
+ */
+ public function offsetSet($offset, $value)
+ {
+ if ($this->gettersVariable) {
+ $var = $this->gettersVariable;
+ $this->{$var}[$offset] = $value;
+ } else {
+ $this->{$offset} = $value;
+ }
+ }
+
+ /**
+ * @param mixed $offset
+ */
+ public function offsetUnset($offset)
+ {
+ if ($this->gettersVariable) {
+ $var = $this->gettersVariable;
+ unset($this->{$var}[$offset]);
+ } else {
+ unset($this->{$offset});
+ }
+ }
+
+ /**
+ * @return int
+ */
+ public function count()
+ {
+ if ($this->gettersVariable) {
+ $var = $this->gettersVariable;
+ return \count($this->{$var});
+ }
+
+ return \count($this->toArray());
+ }
+
+ /**
+ * Returns an associative array of object properties.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ if ($this->gettersVariable) {
+ $var = $this->gettersVariable;
+
+ return $this->{$var};
+ }
+
+ $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
new file mode 100644
index 0000000..458fe16
--- /dev/null
+++ b/system/src/Grav/Common/Grav.php
@@ -0,0 +1,618 @@
+ '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 middleware processors that are processed in $this->process()
+ */
+ protected $middleware = [
+ 'configurationProcessor',
+ 'loggerProcessor',
+ 'errorsProcessor',
+ 'debuggerProcessor',
+ 'initializeProcessor',
+ 'pluginsProcessor',
+ 'themesProcessor',
+ 'requestProcessor',
+ 'tasksProcessor',
+ 'backupsProcessor',
+ 'schedulerProcessor',
+ 'assetsProcessor',
+ 'twigProcessor',
+ 'pagesProcessor',
+ 'debuggerAssetsProcessor',
+ 'renderProcessor',
+ ];
+
+ protected $initialized = [];
+
+ /**
+ * Reset the Grav instance.
+ */
+ public static function resetInstance()
+ {
+ if (self::$instance) {
+ self::$instance = null;
+ }
+ }
+
+ /**
+ * Return the Grav instance. Create it if it's not already instanced
+ *
+ * @param array $values
+ *
+ * @return Grav
+ */
+ public static function instance(array $values = [])
+ {
+ if (!self::$instance) {
+ self::$instance = static::load($values);
+ } elseif ($values) {
+ $instance = self::$instance;
+ foreach ($values as $key => $value) {
+ $instance->offsetSet($key, $value);
+ }
+ }
+
+ 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;
+ }
+
+ /**
+ * Initialize CLI environment.
+ *
+ * Call after `$grav->setup($environment)`
+ *
+ * - Load configuration
+ * - Disable debugger
+ * - Set timezone, locale
+ * - Load plugins
+ * - Set Users type to be used in the site
+ *
+ * This method WILL NOT initialize assets, twig or pages.
+ *
+ * @param string|null $environment
+ * @return $this
+ */
+ public function initializeCli()
+ {
+ InitializeProcessor::initializeCli($this);
+
+ return $this;
+ }
+
+ /**
+ * Process a request
+ */
+ public function 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, ['Expires' => 0, 'Cache-Control' => 'no-cache, no-store, must-revalidate'], 'Not Found');
+ };
+
+ /** @var Debugger $debugger */
+ $debugger = $this['debugger'];
+
+ $collection = new RequestHandler($this->middleware, $default, $container);
+
+ $response = $collection->handle($this['request']);
+ $body = $response->getBody();
+
+ // Handle ETag and If-None-Match headers.
+ if ($response->getHeaderLine('ETag') === '1') {
+ $etag = md5($body);
+ $response = $response->withHeader('ETag', $etag);
+
+ if ($this['request']->getHeaderLine('If-None-Match') === $etag) {
+ $response = $response->withStatus(304);
+ $body = '';
+ }
+ }
+
+ $this->header($response);
+ echo $body;
+
+ $debugger->render();
+
+ register_shutdown_function([$this, 'shutdown']);
+ }
+
+ /**
+ * Set the system locale based on the language and configuration
+ */
+ public function setLocale()
+ {
+ // 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);
+ } elseif ($this['config']->get('system.default_locale')) {
+ setlocale(LC_ALL, $this['config']->get('system.default_locale'));
+ }
+ }
+
+ /**
+ * Redirect browser to another location.
+ *
+ * @param string $route Internal route.
+ * @param int $code Redirection code (30x)
+ */
+ public function redirect($route, $code = null)
+ {
+ /** @var Uri $uri */
+ $uri = $this['uri'];
+
+ // Clean route for redirect
+ $route = preg_replace("#^\/[\\\/]+\/#", '/', $route);
+
+ // Check for code in route
+ $regex = '/.*(\[(30[1-7])\])$/';
+ preg_match($regex, $route, $matches);
+ if ($matches) {
+ $route = str_replace($matches[1], '', $matches[0]);
+ $code = $matches[2];
+ }
+
+ if ($code === null) {
+ $code = $this['config']->get('system.pages.redirect_default_code', 302);
+ }
+
+ if (isset($this['session'])) {
+ $this['session']->close();
+ }
+
+ if ($uri->isExternal($route)) {
+ $url = $route;
+ } else {
+ $url = rtrim($uri->rootUrl(), '/') . '/';
+
+ if ($this['config']->get('system.pages.redirect_trailing_slash', true)) {
+ $url .= trim($route, '/'); // Remove trailing slash
+ } else {
+ $url .= ltrim($route, '/'); // Support trailing slash default routes
+ }
+ }
+
+ header("Location: {$url}", true, $code);
+ exit();
+ }
+
+ /**
+ * Redirect browser to another location taking language into account (preferred)
+ *
+ * @param string $route Internal route.
+ * @param int $code Redirection code (30x)
+ */
+ public function redirectLangSafe($route, $code = null)
+ {
+ if (!$this['uri']->isExternal($route)) {
+ $this->redirect($this['pages']->route($route), $code);
+ } else {
+ $this->redirect($route, $code);
+ }
+ }
+
+ /**
+ * Set response header.
+ *
+ * @param ResponseInterface|null $response
+ */
+ public function header(ResponseInterface $response = null)
+ {
+ if (null === $response) {
+ /** @var PageInterface $page */
+ $page = $this['page'];
+ $response = new Response($page->httpResponseCode(), $page->httpHeaders(), '');
+ }
+
+ header("HTTP/{$response->getProtocolVersion()} {$response->getStatusCode()} {$response->getReasonPhrase()}");
+ foreach ($response->getHeaders() as $key => $values) {
+ foreach ($values as $i => $value) {
+ header($key . ': ' . $value, $i === 0);
+ }
+ }
+ }
+
+ /**
+ * Fires an event with optional parameters.
+ *
+ * @param string $eventName
+ * @param Event $event
+ *
+ * @return Event
+ */
+ public function fireEvent($eventName, Event $event = null)
+ {
+ /** @var EventDispatcher $events */
+ $events = $this['events'];
+
+ return $events->dispatch($eventName, $event);
+ }
+
+ /**
+ * Set the final content length for the page and flush the buffer
+ *
+ */
+ public function shutdown()
+ {
+ // Prevent user abort allowing onShutdown event to run without interruptions.
+ if (\function_exists('ignore_user_abort')) {
+ @ignore_user_abort(true);
+ }
+
+ // Close the session allowing new requests to be handled.
+ if (isset($this['session'])) {
+ $this['session']->close();
+ }
+
+ if ($this['config']->get('system.debugger.shutdown.close_connection', true)) {
+ // Flush the response and close the connection to allow time consuming tasks to be performed without leaving
+ // 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;
+
+ if (!$success) {
+ // Unfortunately without FastCGI there is no way to force close the connection.
+ // We need to ask browser to close the connection for us.
+ if ($this['config']->get('system.cache.gzip')) {
+ // Flush gzhandler buffer if gzip setting was enabled.
+ ob_end_flush();
+
+ } else {
+ // Without gzip we have no other choice than to prevent server from compressing the output.
+ // This action turns off mod_deflate which would prevent us from closing the connection.
+ if ($this['config']->get('system.cache.allow_webserver_gzip')) {
+ header('Content-Encoding: identity');
+ } else {
+ header('Content-Encoding: none');
+ }
+
+ }
+
+
+ // Get length and close the connection.
+ header('Content-Length: ' . ob_get_length());
+ header('Connection: close');
+
+ ob_end_flush();
+ @ob_flush();
+ flush();
+ }
+ }
+
+ // Run any time consuming tasks.
+ $this->fireEvent('onShutdown');
+ }
+
+ /**
+ * Magic Catch All Function
+ *
+ * Used to call closures.
+ *
+ * Source: http://stackoverflow.com/questions/419804/closures-as-class-members
+ *
+ * @param string $method
+ * @param array $args
+ * @return
+ */
+ public function __call($method, $args)
+ {
+ $closure = $this->{$method} ?? null;
+
+ return is_callable($closure) ? $closure(...$args) : null;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Initialize and return a Grav instance
+ *
+ * @param array $values
+ *
+ * @return static
+ */
+ protected static function load(array $values)
+ {
+ $container = new static($values);
+
+ $container['debugger'] = new 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);
+
+ return $container;
+ };
+
+ $container->measureTime('_services', 'Services', function () use ($container) {
+ $container->registerServices();
+ });
+
+ return $container;
+ }
+
+ /**
+ * Register all services
+ * Services are defined in the diMap. They can either only the class
+ * of a Service Provider or a pair of serviceKey => serviceClass that
+ * gets directly mapped into the container.
+ *
+ * @return void
+ */
+ protected function registerServices()
+ {
+ foreach (self::$diMap as $serviceKey => $serviceClass) {
+ if (\is_int($serviceKey)) {
+ $this->register(new $serviceClass);
+ } else {
+ $this[$serviceKey] = function ($c) use ($serviceClass) {
+ return new $serviceClass($c);
+ };
+ }
+ }
+ }
+
+ /**
+ * This attempts to find media, other files, and download them
+ *
+ * @param string $path
+ */
+ public function fallbackUrl($path)
+ {
+ $this->fireEvent('onPageFallBackUrl');
+
+ /** @var Uri $uri */
+ $uri = $this['uri'];
+
+ /** @var Config $config */
+ $config = $this['config'];
+
+ $uri_extension = strtolower($uri->extension());
+ $fallback_types = $config->get('system.media.allowed_fallback_types', null);
+ $supported_types = $config->get('media.types');
+
+ // Check whitelist first, then ensure extension is a valid media type
+ if (!empty($fallback_types) && !\in_array($uri_extension, $fallback_types, true)) {
+ return false;
+ }
+ if (!array_key_exists($uri_extension, $supported_types)) {
+ return false;
+ }
+
+ $path_parts = pathinfo($path);
+
+ /** @var PageInterface $page */
+ $page = $this['pages']->dispatch($path_parts['dirname'], true);
+
+ if ($page) {
+ $media = $page->media()->all();
+ $parsed_url = parse_url(rawurldecode($uri->basename()));
+ $media_file = $parsed_url['path'];
+
+ // if this is a media object, try actions first
+ if (isset($media[$media_file])) {
+ /** @var Medium $medium */
+ $medium = $media[$media_file];
+ foreach ($uri->query(null, true) as $action => $params) {
+ if (\in_array($action, ImageMedium::$magic_actions, true)) {
+ \call_user_func_array([&$medium, $action], explode(',', $params));
+ }
+ }
+ Utils::download($medium->path(), false);
+ }
+
+ // unsupported media type, try to download it...
+ if ($uri_extension) {
+ $extension = $uri_extension;
+ } else {
+ if (isset($path_parts['extension'])) {
+ $extension = $path_parts['extension'];
+ } else {
+ $extension = null;
+ }
+ }
+
+ if ($extension) {
+ $download = true;
+ if (\in_array(ltrim($extension, '.'), $config->get('system.media.unsupported_inline_types', []), true)) {
+ $download = false;
+ }
+ Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), $download);
+ }
+
+ // Nothing found
+ return false;
+ }
+
+ return $page;
+ }
+}
diff --git a/system/src/Grav/Common/GravTrait.php b/system/src/Grav/Common/GravTrait.php
new file mode 100644
index 0000000..a82c23a
--- /dev/null
+++ b/system/src/Grav/Common/GravTrait.php
@@ -0,0 +1,33 @@
+', '?'
+ 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G'
+ 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', '[', '\', ']', '^', '_'
+ 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g'
+ 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 string $bytes
+ * @return string
+ */
+ public static function encode($bytes)
+ {
+ $i = 0; $index = 0;
+ $base32 = '';
+ $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) < $bytesLen) {
+ $nextByte = \ord($bytes[$i+1]);
+ } else {
+ $nextByte = 0;
+ }
+
+ $digit = $currByte & (0xFF >> $index);
+ $index = ($index + 5) % 8;
+ $digit <<= $index;
+ $digit |= $nextByte >> (8 - $index);
+ $i++;
+ } else {
+ $digit = ($currByte >> (8 - ($index + 5))) & 0x1F;
+ $index = ($index + 5) % 8;
+ if ($index === 0) {
+ $i++;
+ }
+ }
+
+ $base32 .= self::$base32Chars[$digit];
+ }
+ return $base32;
+ }
+
+ /**
+ * Decode in Base32
+ *
+ * @param string $base32
+ * @return string
+ */
+ 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 < $base32Len; $i++) {
+ $lookup = \ord($base32[$i]) - \ord('0');
+
+ /* Skip chars outside the lookup table */
+ 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) {
+ $index = ($index + 5) % 8;
+ if ($index === 0) {
+ $bytes[$offset] |= $digit;
+ $offset++;
+ if ($offset >= \count($bytes)) {
+ break;
+ }
+ } else {
+ $bytes[$offset] |= $digit << (8 - $index);
+ }
+ } else {
+ $index = ($index + 5) % 8;
+ $bytes[$offset] |= ($digit >> $index);
+ $offset++;
+ if ($offset >= \count($bytes)) {
+ break;
+ }
+ $bytes[$offset] |= $digit << (8 - $index);
+ }
+ }
+
+ $bites = '';
+ 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
new file mode 100644
index 0000000..95e2b14
--- /dev/null
+++ b/system/src/Grav/Common/Helpers/Excerpts.php
@@ -0,0 +1,183 @@
+`
+ * @param PageInterface|null $page Page, defaults to the current page object
+ * @return string Returns final HTML string
+ */
+ public static function processImageHtml($html, PageInterface $page = null)
+ {
+ $excerpt = static::getExcerptFromHtml($html, 'img');
+
+ $original_src = $excerpt['element']['attributes']['src'];
+ $excerpt['element']['attributes']['href'] = $original_src;
+
+ $excerpt = static::processLinkExcerpt($excerpt, $page, 'image');
+
+ $excerpt['element']['attributes']['src'] = $excerpt['element']['attributes']['href'];
+ unset($excerpt['element']['attributes']['href']);
+
+ $excerpt = static::processImageExcerpt($excerpt, $page);
+
+ $excerpt['element']['attributes']['data-src'] = $original_src;
+
+ $html = static::getHtmlFromExcerpt($excerpt);
+
+ return $html;
+ }
+
+ /**
+ * Process Grav page link URL from HTML tag
+ *
+ * @param string $html HTML tag e.g. `Page Link `
+ * @param PageInterface|null $page Page, defaults to the current page object
+ * @return string Returns final HTML string
+ */
+ public static function processLinkHtml($html, PageInterface $page = null)
+ {
+ $excerpt = static::getExcerptFromHtml($html, 'a');
+
+ $original_href = $excerpt['element']['attributes']['href'];
+ $excerpt = static::processLinkExcerpt($excerpt, $page, 'link');
+ $excerpt['element']['attributes']['data-href'] = $original_href;
+
+ $html = static::getHtmlFromExcerpt($excerpt);
+
+ return $html;
+ }
+
+ /**
+ * Get an Excerpt array from a chunk of HTML
+ *
+ * @param string $html Chunk of HTML
+ * @param string $tag A tag, for example `img`
+ * @return array|null returns nested array excerpt
+ */
+ public static function getExcerptFromHtml($html, $tag)
+ {
+ $doc = new \DOMDocument('1.0', 'UTF-8');
+ $internalErrors = libxml_use_internal_errors(true);
+ $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
+ libxml_use_internal_errors($internalErrors);
+
+ $elements = $doc->getElementsByTagName($tag);
+ $excerpt = null;
+ $inner = [];
+
+ /** @var \DOMElement $element */
+ foreach ($elements as $element) {
+ $attributes = [];
+ foreach ($element->attributes as $name => $value) {
+ $attributes[$name] = $value->value;
+ }
+ $excerpt = [
+ 'element' => [
+ 'name' => $element->tagName,
+ 'attributes' => $attributes
+ ]
+ ];
+
+ foreach ($element->childNodes as $node) {
+ $inner[] = $doc->saveHTML($node);
+ }
+
+ $excerpt = array_merge_recursive($excerpt, ['element' => ['text' => implode('', $inner)]]);
+
+
+ }
+
+ return $excerpt;
+ }
+
+ /**
+ * Rebuild HTML tag from an excerpt array
+ *
+ * @param array $excerpt
+ * @return string
+ */
+ public static function getHtmlFromExcerpt($excerpt)
+ {
+ $element = $excerpt['element'];
+ $html = '<'.$element['name'];
+
+ if (isset($element['attributes'])) {
+ foreach ($element['attributes'] as $name => $value) {
+ if ($value === null) {
+ continue;
+ }
+ $html .= ' '.$name.'="'.$value.'"';
+ }
+ }
+
+ if (isset($element['text'])) {
+ $html .= '>';
+ $html .= is_array($element['text']) ? static::getHtmlFromExcerpt(['element' => $element['text']]) : $element['text'];
+ $html .= ''.$element['name'].'>';
+ } else {
+ $html .= ' />';
+ }
+
+ return $html;
+ }
+
+ /**
+ * Process a Link excerpt
+ *
+ * @param array $excerpt
+ * @param PageInterface|null $page Page, defaults to the current page object
+ * @param string $type
+ * @return mixed
+ */
+ public static function processLinkExcerpt($excerpt, PageInterface $page = null, $type = 'link')
+ {
+ $excerpts = new ExcerptsObject($page);
+
+ return $excerpts->processLinkExcerpt($excerpt, $type);
+ }
+
+ /**
+ * Process an image excerpt
+ *
+ * @param array $excerpt
+ * @param PageInterface|null $page Page, defaults to the current page object
+ * @return array
+ */
+ public static function processImageExcerpt(array $excerpt, PageInterface $page = null)
+ {
+ $excerpts = new ExcerptsObject($page);
+
+ return $excerpts->processImageExcerpt($excerpt);
+ }
+
+ /**
+ * Process media actions
+ *
+ * @param Medium $medium
+ * @param string|array $url
+ * @param PageInterface|null $page Page, defaults to the current page object
+ * @return Medium
+ */
+ public static function processMediaActions($medium, $url, PageInterface $page = null)
+ {
+ $excerpts = new ExcerptsObject($page);
+
+ return $excerpts->processMediaActions($medium, $url);
+ }
+}
diff --git a/system/src/Grav/Common/Helpers/Exif.php b/system/src/Grav/Common/Helpers/Exif.php
new file mode 100644
index 0000000..15aff66
--- /dev/null
+++ b/system/src/Grav/Common/Helpers/Exif.php
@@ -0,0 +1,41 @@
+get('system.media.auto_metadata_exif')) {
+ if (function_exists('exif_read_data') && class_exists('\PHPExif\Reader\Reader')) {
+ $this->reader = \PHPExif\Reader\Reader::factory(\PHPExif\Reader\Reader::TYPE_NATIVE);
+ } else {
+ throw new \RuntimeException('Please enable the Exif extension for PHP or disable Exif support in Grav system configuration');
+ }
+ }
+ }
+
+ public function getReader()
+ {
+ if ($this->reader) {
+ return $this->reader;
+ }
+
+ return false;
+ }
+}
diff --git a/system/src/Grav/Common/Helpers/LogViewer.php b/system/src/Grav/Common/Helpers/LogViewer.php
new file mode 100644
index 0000000..397fb17
--- /dev/null
+++ b/system/src/Grav/Common/Helpers/LogViewer.php
@@ -0,0 +1,149 @@
+.*)\] (?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
new file mode 100644
index 0000000..d116bb3
--- /dev/null
+++ b/system/src/Grav/Common/Helpers/Truncator.php
@@ -0,0 +1,335 @@
+getElementsByTagName('div')->item(0);
+ $container = $container->parentNode->removeChild($container);
+
+ // Iterate over words.
+ $words = new DOMWordsIterator($container);
+ $truncated = false;
+ foreach ($words as $word) {
+
+ // If we have exceeded the limit, we delete the remainder of the content.
+ if ($words->key() >= $limit) {
+
+ // Grab current position.
+ $currentWordPosition = $words->currentWordPosition();
+ $curNode = $currentWordPosition[0];
+ $offset = $currentWordPosition[1];
+ $words = $currentWordPosition[2];
+
+ $curNode->nodeValue = substr(
+ $curNode->nodeValue,
+ 0,
+ $words[$offset][1] + strlen($words[$offset][0])
+ );
+
+ self::removeProceedingNodes($curNode, $container);
+
+ if (!empty($ellipsis)) {
+ self::insertEllipsis($curNode, $ellipsis);
+ }
+
+ $truncated = true;
+
+ break;
+ }
+
+ }
+
+ // Return original HTML if not truncated.
+ if ($truncated) {
+ $html = self::getCleanedHtml($doc, $container);
+ }
+
+ return $html;
+ }
+
+ /**
+ * Safely truncates HTML by a given number of letters.
+ * @param string $html Input HTML.
+ * @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 = '')
+ {
+ if ($limit <= 0) {
+ return $html;
+ }
+
+ $doc = self::htmlToDomDocument($html);
+ $container = $doc->getElementsByTagName('div')->item(0);
+ $container = $container->parentNode->removeChild($container);
+
+ // Iterate over letters.
+ $letters = new DOMLettersIterator($container);
+ $truncated = false;
+ foreach ($letters as $letter) {
+
+ // If we have exceeded the limit, we want to delete the remainder of this document.
+ if ($letters->key() >= $limit) {
+
+ $currentText = $letters->currentTextPosition();
+ $currentText[0]->nodeValue = mb_substr($currentText[0]->nodeValue, 0, $currentText[1] + 1);
+ self::removeProceedingNodes($currentText[0], $container);
+
+ if (!empty($ellipsis)) {
+ self::insertEllipsis($currentText[0], $ellipsis);
+ }
+
+ $truncated = true;
+
+ break;
+ }
+ }
+
+ // Return original HTML if not truncated.
+ if ($truncated) {
+ $html = self::getCleanedHtml($doc, $container);
+ }
+
+ return $html;
+ }
+
+ /**
+ * Builds a DOMDocument object from a string containing HTML.
+ * @param string $html HTML to load
+ * @returns DOMDocument Returns a DOMDocument object.
+ */
+ public static function htmlToDomDocument($html)
+ {
+ if (!$html) {
+ $html = '';
+ }
+
+ // Transform multibyte entities which otherwise display incorrectly.
+ $html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8');
+
+ // Internal errors enabled as HTML5 not fully supported.
+ libxml_use_internal_errors(true);
+
+ // Instantiate new DOMDocument object, and then load in UTF-8 HTML.
+ $dom = new DOMDocument();
+ $dom->encoding = 'UTF-8';
+ $dom->loadHTML("$html
");
+
+ return $dom;
+ }
+
+ /**
+ * Removes all nodes after the current node.
+ * @param DOMNode|DOMElement $domNode
+ * @param DOMNode|DOMElement $topNode
+ * @return void
+ */
+ private static function removeProceedingNodes($domNode, $topNode)
+ {
+ $nextNode = $domNode->nextSibling;
+
+ if ($nextNode !== null) {
+ self::removeProceedingNodes($nextNode, $topNode);
+ $domNode->parentNode->removeChild($nextNode);
+ } else {
+ //scan upwards till we find a sibling
+ $curNode = $domNode->parentNode;
+ while ($curNode !== $topNode) {
+ if ($curNode->nextSibling !== null) {
+ $curNode = $curNode->nextSibling;
+ self::removeProceedingNodes($curNode, $topNode);
+ $curNode->parentNode->removeChild($curNode);
+ break;
+ }
+ $curNode = $curNode->parentNode;
+ }
+ }
+ }
+
+ /**
+ * 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.
+ * @param string $ellipsis Text used to suffix our document.
+ * @return void
+ */
+ private static function insertEllipsis($domNode, $ellipsis)
+ {
+ $avoid = array('a', 'strong', 'em', 'h1', 'h2', 'h3', 'h4', 'h5'); //html tags to avoid appending the ellipsis to
+
+ if ($domNode->parentNode->parentNode !== null && in_array($domNode->parentNode->nodeName, $avoid, true)) {
+ // Append as text node to parent instead
+ $textNode = new DOMText($ellipsis);
+
+ if ($domNode->parentNode->parentNode->nextSibling) {
+ $domNode->parentNode->parentNode->insertBefore($textNode, $domNode->parentNode->parentNode->nextSibling);
+ } else {
+ $domNode->parentNode->parentNode->appendChild($textNode);
+ }
+
+ } else {
+ // Append to current node
+ $domNode->nodeValue = rtrim($domNode->nodeValue) . $ellipsis;
+ }
+ }
+
+ /**
+ *
+ */
+ 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));
+ }
+ }
+ // 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..b7e608d
--- /dev/null
+++ b/system/src/Grav/Common/Helpers/YamlLinter.php
@@ -0,0 +1,89 @@
+get('system.pages.theme');
+ $theme_path = 'themes://' . $current_theme . '/blueprints';
+
+ $locator->addPath('blueprints', '', [$theme_path]);
+ return static::recurseFolder('blueprints://');
+ }
+
+ public static function recurseFolder($path, $extensions = 'md|yaml')
+ {
+ $lint_errors = [];
+
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+ $flags = \RecursiveDirectoryIterator::SKIP_DOTS;
+ if ($locator->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
new file mode 100644
index 0000000..eca93c1
--- /dev/null
+++ b/system/src/Grav/Common/Inflector.php
@@ -0,0 +1,330 @@
+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) ?: [];
+ }
+ }
+
+ /**
+ * Pluralizes English nouns.
+ *
+ * @param string $word English noun to pluralize
+ * @param int $count The count
+ *
+ * @return string Plural noun
+ */
+ public static function pluralize($word, $count = 2)
+ {
+ static::init();
+
+ if ((int)$count === 1) {
+ return $word;
+ }
+
+ $lowercased_word = strtolower($word);
+
+ foreach (static::$uncountable as $_uncountable) {
+ if (substr($lowercased_word, -1 * strlen($_uncountable)) === $_uncountable) {
+ return $word;
+ }
+ }
+
+ 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 (static::$plural as $rule => $replacement) {
+ if (preg_match($rule, $word)) {
+ return preg_replace($rule, $replacement, $word);
+ }
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Singularizes English nouns.
+ *
+ * @param string $word English noun to singularize
+ * @param int $count
+ *
+ * @return string Singular noun.
+ */
+ public static function singularize($word, $count = 1)
+ {
+ static::init();
+
+ if ((int)$count !== 1) {
+ return $word;
+ }
+
+ $lowercased_word = strtolower($word);
+ foreach (static::$uncountable as $_uncountable) {
+ if (substr($lowercased_word, -1 * strlen($_uncountable)) === $_uncountable) {
+ return $word;
+ }
+ }
+
+ 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 (static::$singular as $rule => $replacement) {
+ if (preg_match($rule, $word)) {
+ return preg_replace($rule, $replacement, $word);
+ }
+ }
+
+ return $word;
+ }
+
+ /**
+ * Converts an underscored or CamelCase word into a English
+ * sentence.
+ *
+ * The titleize public function converts text like "WelcomePage",
+ * "welcome_page" or "welcome page" to this "Welcome
+ * Page".
+ * If second parameter is set to 'first' it will only
+ * capitalize the first character of the title.
+ *
+ * @param string $word Word to format as tile
+ * @param string $uppercase If set to 'first' it will only uppercase the
+ * first character. Otherwise it will uppercase all
+ * the words in the title.
+ *
+ * @return string Text formatted as title
+ */
+ public static function titleize($word, $uppercase = '')
+ {
+ $uppercase = $uppercase === 'first' ? 'ucfirst' : 'ucwords';
+
+ return $uppercase(static::humanize(static::underscorize($word)));
+ }
+
+ /**
+ * Returns given word as CamelCased
+ *
+ * Converts a word like "send_email" to "SendEmail". It
+ * will remove non alphanumeric character from the word, so
+ * "who's online" will be converted to "WhoSOnline"
+ *
+ * @see variablize
+ *
+ * @param string $word Word to convert to camel case
+ *
+ * @return string UpperCamelCasedWord
+ */
+ public static function camelize($word)
+ {
+ return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word)));
+ }
+
+ /**
+ * Converts a word "into_it_s_underscored_version"
+ *
+ * Convert any "CamelCased" or "ordinary Word" into an
+ * "underscored_word".
+ *
+ * This can be really useful for creating friendly URLs.
+ *
+ * @param string $word Word to underscore
+ *
+ * @return string Underscored 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);
+ $regex3 = preg_replace('/[^A-Z^a-z^0-9]+/', '_', $regex2);
+
+ return strtolower($regex3);
+ }
+
+ /**
+ * Converts a word "into-it-s-hyphenated-version"
+ *
+ * Convert any "CamelCased" or "ordinary Word" into an
+ * "hyphenated-word".
+ *
+ * This can be really useful for creating friendly URLs.
+ *
+ * @param string $word Word to hyphenate
+ *
+ * @return string hyphenized 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);
+ $regex3 = preg_replace('/([0-9])([A-Z])/', '\1-\2', $regex2);
+ $regex4 = preg_replace('/[^A-Z^a-z^0-9]+/', '-', $regex3);
+
+ $regex4 = trim($regex4, '-');
+
+ return strtolower($regex4);
+ }
+
+ /**
+ * Returns a human-readable string from $word
+ *
+ * Returns a human-readable string from $word, by replacing
+ * underscores with a space, and by upper-casing the initial
+ * character by default.
+ *
+ * If you need to uppercase all the words you just have to
+ * pass 'all' as a second parameter.
+ *
+ * @param string $word String to "humanize"
+ * @param string $uppercase If set to 'all' it will uppercase all the words
+ * instead of just the first one.
+ *
+ * @return string Human-readable word
+ */
+ public static function humanize($word, $uppercase = '')
+ {
+ $uppercase = $uppercase === 'all' ? 'ucwords' : 'ucfirst';
+
+ return $uppercase(str_replace('_', ' ', preg_replace('/_id$/', '', $word)));
+ }
+
+ /**
+ * Same as camelize but first char is underscored
+ *
+ * Converts a word like "send_email" to "sendEmail". It
+ * will remove non alphanumeric character from the word, so
+ * "who's online" will be converted to "whoSOnline"
+ *
+ * @see camelize
+ *
+ * @param string $word Word to lowerCamelCase
+ *
+ * @return string Returns a lowerCamelCasedWord
+ */
+ public static function variablize($word)
+ {
+ $word = static::camelize($word);
+
+ return strtolower($word[0]) . substr($word, 1);
+ }
+
+ /**
+ * Converts a class name to its table name according to rails
+ * naming conventions.
+ *
+ * Converts "Person" to "people"
+ *
+ * @see classify
+ *
+ * @param string $class_name Class name for getting related table_name.
+ *
+ * @return string plural_table_name
+ */
+ public static function tableize($class_name)
+ {
+ return static::pluralize(static::underscorize($class_name));
+ }
+
+ /**
+ * Converts a table name to its class name according to rails
+ * naming conventions.
+ *
+ * Converts "people" to "Person"
+ *
+ * @see tableize
+ *
+ * @param string $table_name Table name for getting related ClassName.
+ *
+ * @return string SingularClassName
+ */
+ public static function classify($table_name)
+ {
+ return static::camelize(static::singularize($table_name));
+ }
+
+ /**
+ * Converts number to its ordinal English form.
+ *
+ * This method converts 13 to 13th, 2 to 2nd ...
+ *
+ * @param int $number Number to get its ordinal value
+ *
+ * @return string Ordinal representation of given string.
+ */
+ public static function ordinalize($number)
+ {
+ static::init();
+
+ 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'];
+ }
+ }
+
+ /**
+ * Converts a number of days to a number of months
+ *
+ * @param int $days
+ *
+ * @return int
+ */
+ public static function monthize($days)
+ {
+ $now = new \DateTime();
+ $end = new \DateTime();
+
+ $duration = new \DateInterval("P{$days}D");
+
+ $diff = $end->add($duration)->diff($now);
+
+ // handle years
+ if ($diff->y > 0) {
+ $diff->m += 12 * $diff->y;
+ }
+
+ return $diff->m;
+ }
+}
diff --git a/system/src/Grav/Common/Iterator.php b/system/src/Grav/Common/Iterator.php
new file mode 100644
index 0000000..5f6dc7e
--- /dev/null
+++ b/system/src/Grav/Common/Iterator.php
@@ -0,0 +1,265 @@
+items[$key] ?? null;
+ }
+
+ /**
+ * Clone the iterator.
+ */
+ public function __clone()
+ {
+ foreach ($this as $key => $value) {
+ if (\is_object($value)) {
+ $this->{$key} = clone $this->{$key};
+ }
+ }
+ }
+
+ /**
+ * Convents iterator to a comma separated list.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return implode(',', $this->items);
+ }
+
+ /**
+ * Remove item from the list.
+ *
+ * @param string $key
+ */
+ public function remove($key)
+ {
+ $this->offsetUnset($key);
+ }
+
+ /**
+ * Return previous item.
+ *
+ * @return mixed
+ */
+ public function prev()
+ {
+ return prev($this->items);
+ }
+
+ /**
+ * Return nth item.
+ *
+ * @param int $key
+ *
+ * @return mixed|bool
+ */
+ public function nth($key)
+ {
+ $items = array_keys($this->items);
+
+ return isset($items[$key]) ? $this->offsetGet($items[$key]) : false;
+ }
+
+ /**
+ * Get the first item
+ *
+ * @return mixed
+ */
+ public function first()
+ {
+ $items = array_keys($this->items);
+
+ return $this->offsetGet(array_shift($items));
+ }
+
+ /**
+ * Get the last item
+ *
+ * @return mixed
+ */
+ public function last()
+ {
+ $items = array_keys($this->items);
+
+ return $this->offsetGet(array_pop($items));
+ }
+
+ /**
+ * Reverse the Iterator
+ *
+ * @return $this
+ */
+ public function reverse()
+ {
+ $this->items = array_reverse($this->items);
+
+ return $this;
+ }
+
+ /**
+ * @param mixed $needle Searched value.
+ *
+ * @return string|bool Key if found, otherwise false.
+ */
+ public function indexOf($needle)
+ {
+ foreach (array_values($this->items) as $key => $value) {
+ if ($value === $needle) {
+ return $key;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Shuffle items.
+ *
+ * @return $this
+ */
+ public function shuffle()
+ {
+ $keys = array_keys($this->items);
+ shuffle($keys);
+
+ $new = [];
+ foreach ($keys as $key) {
+ $new[$key] = $this->items[$key];
+ }
+
+ $this->items = $new;
+
+ return $this;
+ }
+
+ /**
+ * Slice the list.
+ *
+ * @param int $offset
+ * @param int $length
+ *
+ * @return $this
+ */
+ public function slice($offset, $length = null)
+ {
+ $this->items = \array_slice($this->items, $offset, $length);
+
+ return $this;
+ }
+
+ /**
+ * Pick one or more random entries.
+ *
+ * @param int $num Specifies how many entries should be picked.
+ *
+ * @return $this
+ */
+ public function random($num = 1)
+ {
+ $count = \count($this->items);
+ if ($num > $count) {
+ $num = $count;
+ }
+
+ $this->items = array_intersect_key($this->items, array_flip((array)array_rand($this->items, $num)));
+
+ return $this;
+ }
+
+ /**
+ * Append new elements to the list.
+ *
+ * @param array|Iterator $items Items to be appended. Existing keys will be overridden with the new values.
+ *
+ * @return $this
+ */
+ public function append($items)
+ {
+ if ($items instanceof static) {
+ $items = $items->toArray();
+ }
+ $this->items = array_merge($this->items, (array)$items);
+
+ return $this;
+ }
+
+ /**
+ * Filter elements from the list
+ *
+ * @param callable|null $callback A function the receives ($value, $key) and must return a boolean to indicate
+ * filter status
+ *
+ * @return $this
+ */
+ public function filter(callable $callback = null)
+ {
+ foreach ($this->items as $key => $value) {
+ if (
+ (!$callback && !(bool)$value) ||
+ ($callback && !$callback($value, $key))
+ ) {
+ unset($this->items[$key]);
+ }
+ }
+
+ return $this;
+ }
+
+
+ /**
+ * Sorts elements from the list and returns a copy of the list in the proper order
+ *
+ * @param callable|null $callback
+ *
+ * @param bool $desc
+ *
+ * @return $this|array
+ * @internal param bool $asc
+ *
+ */
+ public function sort(callable $callback = null, $desc = false)
+ {
+ if (!$callback || !\is_callable($callback)) {
+ return $this;
+ }
+
+ $items = $this->items;
+ uasort($items, $callback);
+
+ return !$desc ? $items : array_reverse($items, true);
+ }
+}
diff --git a/system/src/Grav/Common/Language/Language.php b/system/src/Grav/Common/Language/Language.php
new file mode 100644
index 0000000..a625146
--- /dev/null
+++ b/system/src/Grav/Common/Language/Language.php
@@ -0,0 +1,537 @@
+grav = $grav;
+ $this->config = $grav['config'];
+ $this->languages = $this->config->get('system.languages.supported', []);
+ $this->init();
+ }
+
+ /**
+ * Initialize the default and enabled languages
+ */
+ public function init()
+ {
+ $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;
+ }
+ }
+
+ /**
+ * Ensure that languages are enabled
+ *
+ * @return bool
+ */
+ public function enabled()
+ {
+ return $this->enabled;
+ }
+
+ /**
+ * Gets the array of supported languages
+ *
+ * @return array
+ */
+ public function getLanguages()
+ {
+ return $this->languages;
+ }
+
+ /**
+ * Sets the current supported languages manually
+ *
+ * @param array $langs
+ */
+ public function setLanguages($langs)
+ {
+ $this->languages = $langs;
+ $this->init();
+ }
+
+ /**
+ * Gets a pipe-separated string of available languages
+ *
+ * @return string
+ */
+ public function getAvailable()
+ {
+ $languagesArray = $this->languages; //Make local copy
+
+ $languagesArray = array_map(function($value) {
+ return preg_quote($value);
+ }, $languagesArray);
+
+ sort($languagesArray);
+
+ return implode('|', array_reverse($languagesArray));
+ }
+
+ /**
+ * Gets language, active if set, else default
+ *
+ * @return string
+ */
+ public function getLanguage()
+ {
+ return $this->active ?: $this->default;
+ }
+
+ /**
+ * Gets current default language
+ *
+ * @return mixed
+ */
+ public function getDefault()
+ {
+ return $this->default;
+ }
+
+ /**
+ * Sets default language manually
+ *
+ * @param string $lang
+ *
+ * @return bool
+ */
+ public function setDefault($lang)
+ {
+ if ($this->validate($lang)) {
+ $this->default = $lang;
+
+ return $lang;
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets current active language
+ *
+ * @return string
+ */
+ public function getActive()
+ {
+ return $this->active;
+ }
+
+ /**
+ * Sets active language manually
+ *
+ * @param string $lang
+ *
+ * @return string|bool
+ */
+ public function setActive($lang)
+ {
+ if ($this->validate($lang)) {
+ $this->active = $lang;
+
+ return $lang;
+ }
+
+ return false;
+ }
+
+ /**
+ * Sets the active language based on the first part of the URL
+ *
+ * @param string $uri
+ *
+ * @return string
+ */
+ public function setActiveFromUri($uri)
+ {
+ $regex = '/(^\/(' . $this->getAvailable() . '))(?:\/|\?|$)/i';
+
+ // if languages set
+ if ($this->enabled()) {
+ // Try setting language from prefix of URL (/en/blah/blah).
+ if (preg_match($regex, $uri, $matches)) {
+ $this->lang_in_url = true;
+ $this->active = $matches[2];
+ $uri = preg_replace("/\\" . $matches[1] . '/', '', $uri, 1);
+
+ // Store in session if language is different.
+ if (isset($this->grav['session']) && $this->grav['session']->isStarted()
+ && $this->config->get('system.languages.session_store_active', true)
+ && $this->grav['session']->active_language != $this->active
+ ) {
+ $this->grav['session']->active_language = $this->active;
+ }
+ } 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)) {
+ $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') &&
+ $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();
+ }
+
+ }
+ }
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Get a URL prefix based on configuration
+ *
+ * @param string|null $lang
+ * @return string
+ */
+ public function getLanguageURLPrefix($lang = null)
+ {
+ // if active lang is not passed in, use current active
+ if (!$lang) {
+ $lang = $this->getLanguage();
+ }
+
+ return $this->isIncludeDefaultLanguage($lang) ? '/' . $lang : '';
+ }
+
+ /**
+ * Test to see if language is default and language should be included in the URL
+ *
+ * @param string|null $lang
+ * @return bool
+ */
+ public function isIncludeDefaultLanguage($lang = null)
+ {
+ // if active lang is not passed in, use current active
+ if (!$lang) {
+ $lang = $this->getLanguage();
+ }
+
+ return !($this->default === $lang && $this->config->get('system.languages.include_default_lang') === false);
+ }
+
+ /**
+ * Simple getter to tell if a language was found in the URL
+ *
+ * @return bool
+ */
+ public function isLanguageInUrl()
+ {
+ return (bool) $this->lang_in_url;
+ }
+
+
+ /**
+ * Gets an array of valid extensions with active first, then fallback extensions
+ *
+ * @param string|null $file_ext
+ *
+ * @return array
+ */
+ public function getFallbackPageExtensions($file_ext = null)
+ {
+ if (empty($this->page_extensions)) {
+ if (!$file_ext) {
+ $file_ext = CONTENT_EXT;
+ }
+
+ if ($this->enabled()) {
+ $valid_lang_extensions = [];
+ foreach ($this->languages as $lang) {
+ $valid_lang_extensions[] = '.' . $lang . $file_ext;
+ }
+
+ if ($this->active) {
+ $active_extension = '.' . $this->active . $file_ext;
+ $key = \array_search($active_extension, $valid_lang_extensions, true);
+
+ // 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;
+ }
+ }
+
+ return $this->page_extensions;
+ }
+
+ /**
+ * Resets the page_extensions value.
+ *
+ * Useful to re-initialize the pages and change site language at runtime, example:
+ *
+ * ```
+ * $this->grav['language']->setActive('it');
+ * $this->grav['language']->resetFallbackPageExtensions();
+ * $this->grav['pages']->init();
+ * ```
+ */
+ public function resetFallbackPageExtensions()
+ {
+ $this->page_extensions = null;
+ }
+
+ /**
+ * Gets an array of languages with active first, then fallback languages
+ *
+ * @return array
+ */
+ public function getFallbackLanguages()
+ {
+ if (empty($this->fallback_languages)) {
+ if ($this->enabled()) {
+ $fallback_languages = $this->languages;
+
+ if ($this->active) {
+ $active_extension = $this->active;
+ $key = \array_search($active_extension, $fallback_languages, true);
+ unset($fallback_languages[$key]);
+ array_unshift($fallback_languages, $active_extension);
+ }
+ $this->fallback_languages = $fallback_languages;
+ }
+ // always add english in case a translation doesn't exist
+ $this->fallback_languages[] = 'en';
+ }
+
+ return $this->fallback_languages;
+ }
+
+ /**
+ * Ensures the language is valid and supported
+ *
+ * @param string $lang
+ *
+ * @return bool
+ */
+ public function validate($lang)
+ {
+ return \in_array($lang, $this->languages, true);
+ }
+
+ /**
+ * Translate a key and possibly arguments into a string using current lang and fallbacks
+ *
+ * @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
+ * @param bool $html_out
+ *
+ * @return string
+ */
+ public function translate($args, array $languages = null, $array_support = false, $html_out = false)
+ {
+ if (\is_array($args)) {
+ $lookup = array_shift($args);
+ } else {
+ $lookup = $args;
+ $args = [];
+ }
+
+ if ($this->config->get('system.languages.translations', true)) {
+ if ($this->enabled() && $lookup) {
+ if (empty($languages)) {
+ if ($this->config->get('system.languages.translations_fallback', true)) {
+ $languages = $this->getFallbackLanguages();
+ } else {
+ $languages = (array)$this->getLanguage();
+ }
+ }
+ } else {
+ $languages = ['en'];
+ }
+
+ foreach ((array)$languages as $lang) {
+ $translation = $this->getTranslation($lang, $lookup, $array_support);
+
+ if ($translation) {
+ if (\count($args) >= 1) {
+ return vsprintf($translation, $args);
+ }
+
+ return $translation;
+ }
+ }
+ }
+
+ if ($html_out) {
+ return '' . $lookup . ' ';
+ }
+
+ return $lookup;
+ }
+
+ /**
+ * Translate Array
+ *
+ * @param string $key
+ * @param string $index
+ * @param array|null $languages
+ * @param bool $html_out
+ *
+ * @return string
+ */
+ public function translateArray($key, $index, $languages = null, $html_out = false)
+ {
+ if ($this->config->get('system.languages.translations', true)) {
+ if ($this->enabled() && $key) {
+ if (empty($languages)) {
+ if ($this->config->get('system.languages.translations_fallback', true)) {
+ $languages = $this->getFallbackLanguages();
+ } else {
+ $languages = (array)$this->getDefault();
+ }
+ }
+ } else {
+ $languages = ['en'];
+ }
+
+ foreach ((array)$languages as $lang) {
+ $translation_array = (array)Grav::instance()['languages']->get($lang . '.' . $key, null);
+ if ($translation_array && array_key_exists($index, $translation_array)) {
+ return $translation_array[$index];
+ }
+ }
+ }
+
+ if ($html_out) {
+ return '' . $key . '[' . $index . '] ';
+ }
+
+ return $key . '[' . $index . ']';
+ }
+
+ /**
+ * Lookup the translation text for a given lang and key
+ *
+ * @param string $lang lang code
+ * @param string $key key to lookup with
+ * @param bool $array_support
+ *
+ * @return string
+ */
+ public function getTranslation($lang, $key, $array_support = false)
+ {
+ $translation = Grav::instance()['languages']->get($lang . '.' . $key, null);
+ if (!$array_support && is_array($translation)) {
+ return (string)array_shift($translation);
+ }
+
+ return $translation;
+ }
+
+ /**
+ * 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'];
+ } else {
+ return $accept_langs;
+ }
+
+ $langs = [];
+
+ foreach (explode(',', $accept_langs) as $k => $pref) {
+ // split $pref again by ';q='
+ // and decorate the language entries by inverted position
+ if (false !== ($i = strpos($pref, ';q='))) {
+ $langs[substr($pref, 0, $i)] = [(float)substr($pref, $i + 3), -$k];
+ } else {
+ $langs[$pref] = [1, -$k];
+ }
+ }
+ arsort($langs);
+
+ // no need to undecorate, because we're only interested in the keys
+ $this->http_accept_language = array_keys($langs);
+ }
+ 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
new file mode 100644
index 0000000..780df7c
--- /dev/null
+++ b/system/src/Grav/Common/Language/LanguageCodes.php
@@ -0,0 +1,205 @@
+ [ 'name' => 'Afrikaans', 'nativeName' => 'Afrikaans' ],
+ 'ak' => [ 'name' => 'Akan', 'nativeName' => 'Akan' ], // unverified native name
+ 'ast' => [ 'name' => 'Asturian', 'nativeName' => 'Asturianu' ],
+ 'ar' => [ 'name' => 'Arabic', 'nativeName' => 'عربي', 'orientation' => 'rtl'],
+ 'as' => [ 'name' => 'Assamese', 'nativeName' => 'অসমীয়া' ],
+ 'be' => [ 'name' => 'Belarusian', 'nativeName' => 'Беларуская' ],
+ 'bg' => [ 'name' => 'Bulgarian', 'nativeName' => 'Български' ],
+ 'bn' => [ 'name' => 'Bengali', 'nativeName' => 'বাংলা' ],
+ 'bn-BD' => [ 'name' => 'Bengali (Bangladesh)', 'nativeName' => 'বাংলা (বাংলাদেশ)' ],
+ 'bn-IN' => [ 'name' => 'Bengali (India)', 'nativeName' => 'বাংলা (ভারত)' ],
+ 'br' => [ 'name' => 'Breton', 'nativeName' => 'Brezhoneg' ],
+ 'bs' => [ 'name' => 'Bosnian', 'nativeName' => 'Bosanski' ],
+ 'ca' => [ 'name' => 'Catalan', 'nativeName' => 'Català' ],
+ 'ca-valencia'=> [ 'name' => 'Catalan (Valencian)', 'nativeName' => 'Català (valencià)' ], // not iso-639-1. a=l10n-drivers
+ 'cs' => [ 'name' => 'Czech', 'nativeName' => 'Čeština' ],
+ 'cy' => [ 'name' => 'Welsh', 'nativeName' => 'Cymraeg' ],
+ 'da' => [ 'name' => 'Danish', 'nativeName' => 'Dansk' ],
+ 'de' => [ 'name' => 'German', 'nativeName' => 'Deutsch' ],
+ 'de-AT' => [ 'name' => 'German (Austria)', 'nativeName' => 'Deutsch (Österreich)' ],
+ 'de-CH' => [ 'name' => 'German (Switzerland)', 'nativeName' => 'Deutsch (Schweiz)' ],
+ 'de-DE' => [ 'name' => 'German (Germany)', 'nativeName' => 'Deutsch (Deutschland)' ],
+ 'dsb' => [ 'name' => 'Lower Sorbian', 'nativeName' => 'Dolnoserbšćina' ], // iso-639-2
+ 'el' => [ 'name' => 'Greek', 'nativeName' => 'Ελληνικά' ],
+ 'en' => [ 'name' => 'English', 'nativeName' => 'English' ],
+ 'en-AU' => [ 'name' => 'English (Australian)', 'nativeName' => 'English (Australian)' ],
+ 'en-CA' => [ 'name' => 'English (Canadian)', 'nativeName' => 'English (Canadian)' ],
+ 'en-GB' => [ 'name' => 'English (British)', 'nativeName' => 'English (British)' ],
+ 'en-NZ' => [ 'name' => 'English (New Zealand)', 'nativeName' => 'English (New Zealand)' ],
+ 'en-US' => [ 'name' => 'English (US)', 'nativeName' => 'English (US)' ],
+ 'en-ZA' => [ 'name' => 'English (South African)', 'nativeName' => 'English (South African)' ],
+ 'eo' => [ 'name' => 'Esperanto', 'nativeName' => 'Esperanto' ],
+ 'es' => [ 'name' => 'Spanish', 'nativeName' => 'Español' ],
+ 'es-AR' => [ 'name' => 'Spanish (Argentina)', 'nativeName' => 'Español (de Argentina)' ],
+ 'es-CL' => [ 'name' => 'Spanish (Chile)', 'nativeName' => 'Español (de Chile)' ],
+ 'es-ES' => [ 'name' => 'Spanish (Spain)', 'nativeName' => 'Español (de España)' ],
+ 'es-MX' => [ 'name' => 'Spanish (Mexico)', 'nativeName' => 'Español (de México)' ],
+ 'et' => [ 'name' => 'Estonian', 'nativeName' => 'Eesti keel' ],
+ 'eu' => [ 'name' => 'Basque', 'nativeName' => 'Euskara' ],
+ 'fa' => [ 'name' => 'Persian', 'nativeName' => 'فارسی' , 'orientation' => 'rtl' ],
+ 'fi' => [ 'name' => 'Finnish', 'nativeName' => 'Suomi' ],
+ 'fj-FJ' => [ 'name' => 'Fijian', 'nativeName' => 'Vosa vaka-Viti' ],
+ 'fr' => [ 'name' => 'French', 'nativeName' => 'Français' ],
+ 'fr-CA' => [ 'name' => 'French (Canada)', 'nativeName' => 'Français (Canada)' ],
+ 'fr-FR' => [ 'name' => 'French (France)', 'nativeName' => 'Français (France)' ],
+ 'fur' => [ 'name' => 'Friulian', 'nativeName' => 'Furlan' ],
+ 'fur-IT' => [ 'name' => 'Friulian', 'nativeName' => 'Furlan' ],
+ 'fy' => [ 'name' => 'Frisian', 'nativeName' => 'Frysk' ],
+ 'fy-NL' => [ 'name' => 'Frisian', 'nativeName' => 'Frysk' ],
+ 'ga' => [ 'name' => 'Irish', 'nativeName' => 'Gaeilge' ],
+ 'ga-IE' => [ 'name' => 'Irish (Ireland)', 'nativeName' => 'Gaeilge (Éire)' ],
+ 'gd' => [ 'name' => 'Gaelic (Scotland)', 'nativeName' => 'Gàidhlig' ],
+ 'gl' => [ 'name' => 'Galician', 'nativeName' => 'Galego' ],
+ 'gu' => [ 'name' => 'Gujarati', 'nativeName' => 'ગુજરાતી' ],
+ 'gu-IN' => [ 'name' => 'Gujarati', 'nativeName' => 'ગુજરાતી' ],
+ 'he' => [ 'name' => 'Hebrew', 'nativeName' => 'עברית', 'orientation' => 'rtl' ],
+ 'hi' => [ 'name' => 'Hindi', 'nativeName' => 'हिन्दी' ],
+ 'hi-IN' => [ 'name' => 'Hindi (India)', 'nativeName' => 'हिन्दी (भारत)' ],
+ 'hr' => [ 'name' => 'Croatian', 'nativeName' => 'Hrvatski' ],
+ 'hsb' => [ 'name' => 'Upper Sorbian', 'nativeName' => 'Hornjoserbsce' ],
+ 'hu' => [ 'name' => 'Hungarian', 'nativeName' => 'Magyar' ],
+ 'hy' => [ 'name' => 'Armenian', 'nativeName' => 'Հայերեն' ],
+ 'hy-AM' => [ 'name' => 'Armenian', 'nativeName' => 'Հայերեն' ],
+ 'id' => [ 'name' => 'Indonesian', 'nativeName' => 'Bahasa Indonesia' ],
+ 'is' => [ 'name' => 'Icelandic', 'nativeName' => 'íslenska' ],
+ 'it' => [ 'name' => 'Italian', 'nativeName' => 'Italiano' ],
+ 'ja' => [ 'name' => 'Japanese', 'nativeName' => '日本語' ],
+ 'ja-JP' => [ 'name' => 'Japanese', 'nativeName' => '日本語' ], // not iso-639-1
+ 'ka' => [ 'name' => 'Georgian', 'nativeName' => 'ქართული' ],
+ 'kk' => [ 'name' => 'Kazakh', 'nativeName' => 'Қазақ' ],
+ 'kn' => [ 'name' => 'Kannada', 'nativeName' => 'ಕನ್ನಡ' ],
+ 'ko' => [ 'name' => 'Korean', 'nativeName' => '한국어' ],
+ 'ku' => [ 'name' => 'Kurdish', 'nativeName' => 'Kurdî' ],
+ 'la' => [ 'name' => 'Latin', 'nativeName' => 'Latina' ],
+ 'lb' => [ 'name' => 'Luxembourgish', 'nativeName' => 'Lëtzebuergesch' ],
+ 'lg' => [ 'name' => 'Luganda', 'nativeName' => 'Luganda' ],
+ 'lt' => [ 'name' => 'Lithuanian', 'nativeName' => 'Lietuvių kalba' ],
+ 'lv' => [ 'name' => 'Latvian', 'nativeName' => 'Latviešu' ],
+ 'mai' => [ 'name' => 'Maithili', 'nativeName' => 'मैथिली মৈথিলী' ],
+ 'mg' => [ 'name' => 'Malagasy', 'nativeName' => 'Malagasy' ],
+ 'mi' => [ 'name' => 'Maori (Aotearoa)', 'nativeName' => 'Māori (Aotearoa)' ],
+ 'mk' => [ 'name' => 'Macedonian', 'nativeName' => 'Македонски' ],
+ 'ml' => [ 'name' => 'Malayalam', 'nativeName' => 'മലയാളം' ],
+ 'mn' => [ 'name' => 'Mongolian', 'nativeName' => 'Монгол' ],
+ 'mr' => [ 'name' => 'Marathi', 'nativeName' => 'मराठी' ],
+ 'no' => [ 'name' => 'Norwegian', 'nativeName' => 'Norsk' ],
+ 'nb' => [ 'name' => 'Norwegian', 'nativeName' => 'Norsk' ],
+ 'nb-NO' => [ 'name' => 'Norwegian (Bokmål)', 'nativeName' => 'Norsk bokmål' ],
+ 'ne-NP' => [ 'name' => 'Nepali', 'nativeName' => 'नेपाली' ],
+ 'nn-NO' => [ 'name' => 'Norwegian (Nynorsk)', 'nativeName' => 'Norsk nynorsk' ],
+ 'nl' => [ 'name' => 'Dutch', 'nativeName' => 'Nederlands' ],
+ 'nr' => [ 'name' => 'Ndebele, South', 'nativeName' => 'IsiNdebele' ],
+ 'nso' => [ 'name' => 'Northern Sotho', 'nativeName' => 'Sepedi' ],
+ 'oc' => [ 'name' => 'Occitan (Lengadocian)', 'nativeName' => 'Occitan (lengadocian)' ],
+ 'or' => [ 'name' => 'Oriya', 'nativeName' => 'ଓଡ଼ିଆ' ],
+ 'pa' => [ 'name' => 'Punjabi', 'nativeName' => 'ਪੰਜਾਬੀ' ],
+ 'pa-IN' => [ 'name' => 'Punjabi', 'nativeName' => 'ਪੰਜਾਬੀ' ],
+ 'pl' => [ 'name' => 'Polish', 'nativeName' => 'Polski' ],
+ 'pt' => [ 'name' => 'Portuguese', 'nativeName' => 'Português' ],
+ 'pt-BR' => [ 'name' => 'Portuguese (Brazilian)', 'nativeName' => 'Português (do Brasil)' ],
+ 'pt-PT' => [ 'name' => 'Portuguese (Portugal)', 'nativeName' => 'Português (Europeu)' ],
+ 'ro' => [ 'name' => 'Romanian', 'nativeName' => 'Română' ],
+ 'rm' => [ 'name' => 'Romansh', 'nativeName' => 'Rumantsch' ],
+ 'ru' => [ 'name' => 'Russian', 'nativeName' => 'Русский' ],
+ 'rw' => [ 'name' => 'Kinyarwanda', 'nativeName' => 'Ikinyarwanda' ],
+ 'si' => [ 'name' => 'Sinhala', 'nativeName' => 'සිංහල' ],
+ 'sk' => [ 'name' => 'Slovak', 'nativeName' => 'Slovenčina' ],
+ 'sl' => [ 'name' => 'Slovenian', 'nativeName' => 'Slovensko' ],
+ 'son' => [ 'name' => 'Songhai', 'nativeName' => 'Soŋay' ],
+ 'sq' => [ 'name' => 'Albanian', 'nativeName' => 'Shqip' ],
+ 'sr' => [ 'name' => 'Serbian', 'nativeName' => 'Српски' ],
+ 'sr-Latn' => [ 'name' => 'Serbian', 'nativeName' => 'Srpski' ], // follows RFC 4646
+ 'ss' => [ 'name' => 'Siswati', 'nativeName' => 'siSwati' ],
+ 'st' => [ 'name' => 'Southern Sotho', 'nativeName' => 'Sesotho' ],
+ 'sv' => [ 'name' => 'Swedish', 'nativeName' => 'Svenska' ],
+ 'sv-SE' => [ 'name' => 'Swedish', 'nativeName' => 'Svenska' ],
+ 'ta' => [ 'name' => 'Tamil', 'nativeName' => 'தமிழ்' ],
+ 'ta-IN' => [ 'name' => 'Tamil (India)', 'nativeName' => 'தமிழ் (இந்தியா)' ],
+ 'ta-LK' => [ 'name' => 'Tamil (Sri Lanka)', 'nativeName' => 'தமிழ் (இலங்கை)' ],
+ 'te' => [ 'name' => 'Telugu', 'nativeName' => 'తెలుగు' ],
+ 'th' => [ 'name' => 'Thai', 'nativeName' => 'ไทย' ],
+ 'tlh' => [ 'name' => 'Klingon', 'nativeName' => 'Klingon' ],
+ 'tn' => [ 'name' => 'Tswana', 'nativeName' => 'Setswana' ],
+ 'tr' => [ 'name' => 'Turkish', 'nativeName' => 'Türkçe' ],
+ 'ts' => [ 'name' => 'Tsonga', 'nativeName' => 'Xitsonga' ],
+ 'tt' => [ 'name' => 'Tatar', 'nativeName' => 'Tatarça' ],
+ 'tt-RU' => [ 'name' => 'Tatar', 'nativeName' => 'Tatarça' ],
+ 'uk' => [ 'name' => 'Ukrainian', 'nativeName' => 'Українська' ],
+ 'ur' => [ 'name' => 'Urdu', 'nativeName' => 'اُردو', 'orientation' => 'rtl' ],
+ 've' => [ 'name' => 'Venda', 'nativeName' => 'Tshivenḓa' ],
+ 'vi' => [ 'name' => 'Vietnamese', 'nativeName' => 'Tiếng Việt' ],
+ 'wo' => [ 'name' => 'Wolof', 'nativeName' => 'Wolof' ],
+ 'xh' => [ 'name' => 'Xhosa', 'nativeName' => 'isiXhosa' ],
+ 'zh' => [ 'name' => 'Chinese (Simplified)', 'nativeName' => '中文 (简体)' ],
+ 'zh-CN' => [ 'name' => 'Chinese (Simplified)', 'nativeName' => '中文 (简体)' ],
+ 'zh-TW' => [ 'name' => 'Chinese (Traditional)', 'nativeName' => '正體中文 (繁體)' ],
+ 'zu' => [ 'name' => 'Zulu', 'nativeName' => 'isiZulu' ]
+ ];
+
+ public static function getName($code)
+ {
+ return static::get($code, 'name');
+ }
+
+ public static function getNativeName($code)
+ {
+ if (isset(static::$codes[$code])) {
+ return static::get($code, 'nativeName');
+ }
+
+ if (preg_match('/[a-zA-Z]{2}-[a-zA-Z]{2}/', $code)) {
+ return static::get(substr($code, 0, 2), 'nativeName') . ' (' . substr($code, -2) . ')';
+ }
+
+ return $code;
+ }
+
+ public static function getOrientation($code)
+ {
+ if (isset(static::$codes[$code])) {
+ if (isset(static::$codes[$code]['orientation'])) {
+ return static::get($code, 'orientation');
+ }
+ }
+ return 'ltr';
+ }
+
+ public static function isRtl($code)
+ {
+ return static::getOrientation($code) === 'rtl';
+ }
+
+ public static function getNames(array $keys)
+ {
+ $results = [];
+ foreach ($keys as $key) {
+ if (isset(static::$codes[$key])) {
+ $results[$key] = static::$codes[$key];
+ }
+ }
+ return $results;
+ }
+
+ public static function get($code, $type)
+ {
+ if (isset(static::$codes[$code][$type])) {
+ return static::$codes[$code][$type];
+ }
+
+ return false;
+ }
+}
diff --git a/system/src/Grav/Common/Markdown/Parsedown.php b/system/src/Grav/Common/Markdown/Parsedown.php
new file mode 100644
index 0000000..8b8565f
--- /dev/null
+++ b/system/src/Grav/Common/Markdown/Parsedown.php
@@ -0,0 +1,40 @@
+ $defaults];
+ }
+ $excerpts = new Excerpts($excerpts, $defaults);
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '($page, $defaults) is deprecated since Grav 1.6.10, use new ' . __CLASS__ . '(new ' . Excerpts::class . '($page, [\'markdown\' => $defaults])) instead.', E_USER_DEPRECATED);
+ }
+
+ $this->init($excerpts, $defaults);
+ }
+
+}
diff --git a/system/src/Grav/Common/Markdown/ParsedownExtra.php b/system/src/Grav/Common/Markdown/ParsedownExtra.php
new file mode 100644
index 0000000..70c8596
--- /dev/null
+++ b/system/src/Grav/Common/Markdown/ParsedownExtra.php
@@ -0,0 +1,42 @@
+ $defaults];
+ }
+ $excerpts = new Excerpts($excerpts, $defaults);
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '($page, $defaults) is deprecated since Grav 1.6.10, use new ' . __CLASS__ . '(new ' . Excerpts::class . '($page, [\'markdown\' => $defaults])) instead.', E_USER_DEPRECATED);
+ }
+
+ parent::__construct();
+
+ $this->init($excerpts, $defaults);
+ }
+}
diff --git a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php
new file mode 100644
index 0000000..aa76cdf
--- /dev/null
+++ b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php
@@ -0,0 +1,277 @@
+ $defaults];
+ }
+ $this->excerpts = new Excerpts($excerpts, $defaults);
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '($page, $defaults) is deprecated since Grav 1.6.10, use ->init(new ' . Excerpts::class . '($page, [\'markdown\' => $defaults])) instead.', E_USER_DEPRECATED);
+ } else {
+ $this->excerpts = $excerpts;
+ }
+
+ $this->BlockTypes['{'][] = 'TwigTag';
+ $this->special_chars = ['>' => 'gt', '<' => 'lt', '"' => 'quot'];
+
+ $defaults = $this->excerpts->getConfig();
+
+ if (isset($defaults['markdown']['auto_line_breaks'])) {
+ $this->setBreaksEnabled($defaults['markdown']['auto_line_breaks']);
+ }
+ if (isset($defaults['markdown']['auto_url_links'])) {
+ $this->setUrlsLinked($defaults['markdown']['auto_url_links']);
+ }
+ if (isset($defaults['markdown']['escape_markup'])) {
+ $this->setMarkupEscaped($defaults['markdown']['escape_markup']);
+ }
+ if (isset($defaults['markdown']['special_chars'])) {
+ $this->setSpecialChars($defaults['markdown']['special_chars']);
+ }
+
+ $this->excerpts->fireInitializedEvent($this);
+ }
+
+ /**
+ * @return Excerpts
+ */
+ public function getExcerpts()
+ {
+ return $this->excerpts;
+ }
+
+ /**
+ * Be able to define a new Block type or override an existing one
+ *
+ * @param string $type
+ * @param string $tag
+ * @param bool $continuable
+ * @param bool $completable
+ * @param int|null $index
+ */
+ public function addBlockType($type, $tag, $continuable = false, $completable = false, $index = null)
+ {
+ $block = &$this->unmarkedBlockTypes;
+ if ($type) {
+ if (!isset($this->BlockTypes[$type])) {
+ $this->BlockTypes[$type] = [];
+ }
+ $block = &$this->BlockTypes[$type];
+ }
+
+ if (null === $index) {
+ $block[] = $tag;
+ } else {
+ array_splice($block, $index, 0, [$tag]);
+ }
+
+ if ($continuable) {
+ $this->continuable_blocks[] = $tag;
+ }
+ if ($completable) {
+ $this->completable_blocks[] = $tag;
+ }
+ }
+
+ /**
+ * Be able to define a new Inline type or override an existing one
+ *
+ * @param string $type
+ * @param string $tag
+ * @param int|null $index
+ */
+ public function addInlineType($type, $tag, $index = null)
+ {
+ if (null === $index || !isset($this->InlineTypes[$type])) {
+ $this->InlineTypes[$type] [] = $tag;
+ } else {
+ array_splice($this->InlineTypes[$type], $index, 0, [$tag]);
+ }
+
+ if (strpos($this->inlineMarkerList, $type) === false) {
+ $this->inlineMarkerList .= $type;
+ }
+ }
+
+ /**
+ * Overrides the default behavior to allow for plugin-provided blocks to be continuable
+ *
+ * @param string $Type
+ *
+ * @return bool
+ */
+ protected function isBlockContinuable($Type)
+ {
+ $continuable = \in_array($Type, $this->continuable_blocks, true)
+ || method_exists($this, 'block' . $Type . 'Continue');
+
+ return $continuable;
+ }
+
+ /**
+ * Overrides the default behavior to allow for plugin-provided blocks to be completable
+ *
+ * @param string $Type
+ *
+ * @return bool
+ */
+ protected function isBlockCompletable($Type)
+ {
+ $completable = \in_array($Type, $this->completable_blocks, true)
+ || method_exists($this, 'block' . $Type . 'Complete');
+
+ return $completable;
+ }
+
+
+ /**
+ * Make the element function publicly accessible, Medium uses this to render from Twig
+ *
+ * @param array $Element
+ *
+ * @return string markup
+ */
+ public function elementToHtml(array $Element)
+ {
+ return $this->element($Element);
+ }
+
+ /**
+ * Setter for special chars
+ *
+ * @param array $special_chars
+ *
+ * @return $this
+ */
+ public function setSpecialChars($special_chars)
+ {
+ $this->special_chars = $special_chars;
+
+ return $this;
+ }
+
+ /**
+ * Ensure Twig tags are treated as block level items with no
tags
+ *
+ * @param array $line
+ * @return array|null
+ */
+ protected function blockTwigTag($line)
+ {
+ if (preg_match('/(?:{{|{%|{#)(.*)(?:}}|%}|#})/', $line['body'], $matches)) {
+ return ['markup' => $line['body']];
+ }
+
+ return null;
+ }
+
+ protected function inlineSpecialCharacter($excerpt)
+ {
+ if ($excerpt['text'][0] === '&' && !preg_match('/^?\w+;/', $excerpt['text'])) {
+ return [
+ 'markup' => '&',
+ 'extent' => 1,
+ ];
+ }
+
+ if (isset($this->special_chars[$excerpt['text'][0]])) {
+ return [
+ 'markup' => '&' . $this->special_chars[$excerpt['text'][0]] . ';',
+ 'extent' => 1,
+ ];
+ }
+
+ return null;
+ }
+
+ protected function inlineImage($excerpt)
+ {
+ if (preg_match($this->twig_link_regex, $excerpt['text'], $matches)) {
+ $excerpt['text'] = str_replace($matches[1], '/', $excerpt['text']);
+ $excerpt = parent::inlineImage($excerpt);
+ $excerpt['element']['attributes']['src'] = $matches[1];
+ $excerpt['extent'] = $excerpt['extent'] + strlen($matches[1]) - 1;
+
+ return $excerpt;
+ }
+
+ $excerpt['type'] = 'image';
+ $excerpt = parent::inlineImage($excerpt);
+
+ // if this is an image process it
+ if (isset($excerpt['element']['attributes']['src'])) {
+ $excerpt = $this->excerpts->processImageExcerpt($excerpt);
+ }
+
+ return $excerpt;
+ }
+
+ protected function inlineLink($excerpt)
+ {
+ $type = $excerpt['type'] ?? 'link';
+
+ // do some trickery to get around Parsedown requirement for valid URL if its Twig in there
+ if (preg_match($this->twig_link_regex, $excerpt['text'], $matches)) {
+ $excerpt['text'] = str_replace($matches[1], '/', $excerpt['text']);
+ $excerpt = parent::inlineLink($excerpt);
+ $excerpt['element']['attributes']['href'] = $matches[1];
+ $excerpt['extent'] = $excerpt['extent'] + strlen($matches[1]) - 1;
+
+ return $excerpt;
+ }
+
+ $excerpt = parent::inlineLink($excerpt);
+
+ // if this is a link
+ if (isset($excerpt['element']['attributes']['href'])) {
+ $excerpt = $this->excerpts->processLinkExcerpt($excerpt, $type);
+ }
+
+ return $excerpt;
+ }
+
+ /**
+ * For extending this class via plugins
+ */
+ public function __call($method, $args)
+ {
+ if (isset($this->{$method}) === true) {
+ $func = $this->{$method};
+
+ return \call_user_func_array($func, $args);
+ }
+
+ return null;
+ }
+}
diff --git a/system/src/Grav/Common/Media/Interfaces/MediaCollectionInterface.php b/system/src/Grav/Common/Media/Interfaces/MediaCollectionInterface.php
new file mode 100644
index 0000000..9f91663
--- /dev/null
+++ b/system/src/Grav/Common/Media/Interfaces/MediaCollectionInterface.php
@@ -0,0 +1,23 @@
+getMediaFolder();
+
+ if (strpos($folder, '://')) {
+ return $folder;
+ }
+
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+ $user = $locator->findResource('user://');
+ if (strpos($folder, $user) === 0) {
+ return 'user://' . substr($folder, \strlen($user)+1);
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the associated media collection.
+ *
+ * @return MediaCollectionInterface Representation of associated media.
+ */
+ public function getMedia()
+ {
+ if ($this->media === null) {
+ $cache = $this->getMediaCache();
+
+ // Use cached media if possible.
+ $cacheKey = md5('media' . $this->getCacheKey());
+ if (!$media = $cache->get($cacheKey)) {
+ $media = new Media($this->getMediaFolder(), $this->getMediaOrder());
+ $cache->set($cacheKey, $media);
+ }
+ $this->media = $media;
+ }
+
+ return $this->media;
+ }
+
+ /**
+ * Sets the associated media collection.
+ *
+ * @param MediaCollectionInterface $media Representation of associated media.
+ * @return $this
+ */
+ protected function setMedia(MediaCollectionInterface $media)
+ {
+ $cache = $this->getMediaCache();
+ $cacheKey = md5('media' . $this->getCacheKey());
+ $cache->set($cacheKey, $media);
+
+ $this->media = $media;
+
+ return $this;
+ }
+
+ /**
+ * Clear media cache.
+ */
+ protected function clearMediaCache()
+ {
+ $cache = $this->getMediaCache();
+ $cacheKey = md5('media' . $this->getCacheKey());
+ $cache->delete($cacheKey);
+
+ $this->media = null;
+ }
+
+ /**
+ * @return CacheInterface
+ */
+ protected function getMediaCache()
+ {
+ /** @var Cache $cache */
+ $cache = Grav::instance()['cache'];
+
+ return $cache->getSimpleCache();
+ }
+
+ /**
+ * @return string
+ */
+ abstract protected function getCacheKey(): string;
+}
diff --git a/system/src/Grav/Common/Page/Collection.php b/system/src/Grav/Common/Page/Collection.php
new file mode 100644
index 0000000..51de16b
--- /dev/null
+++ b/system/src/Grav/Common/Page/Collection.php
@@ -0,0 +1,630 @@
+params = $params;
+ $this->pages = $pages ? $pages : Grav::instance()->offsetGet('pages');
+ }
+
+ /**
+ * Get the collection params
+ *
+ * @return array
+ */
+ public function params()
+ {
+ return $this->params;
+ }
+
+ /**
+ * Add a single page to a collection
+ *
+ * @param PageInterface $page
+ *
+ * @return $this
+ */
+ public function addPage(PageInterface $page)
+ {
+ $this->items[$page->path()] = ['slug' => $page->slug()];
+
+ return $this;
+ }
+
+ /**
+ * Add a page with path and slug
+ *
+ * @param string $path
+ * @param string $slug
+ * @return $this
+ */
+ public function add($path, $slug)
+ {
+ $this->items[$path] = ['slug' => $slug];
+
+ return $this;
+ }
+
+ /**
+ *
+ * Create a copy of this collection
+ *
+ * @return static
+ */
+ public function copy()
+ {
+ return new static($this->items, $this->params, $this->pages);
+ }
+
+ /**
+ *
+ * Merge another collection with the current collection
+ *
+ * @param Collection $collection
+ * @return $this
+ */
+ public function merge(Collection $collection)
+ {
+ foreach($collection as $page) {
+ $this->addPage($page);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Intersect another collection with the current collection
+ *
+ * @param Collection $collection
+ * @return $this
+ */
+ public function intersect(Collection $collection)
+ {
+ $array1 = $this->items;
+ $array2 = $collection->toArray();
+
+ $this->items = array_uintersect($array1, $array2, function($val1, $val2) {
+ return strcmp($val1['slug'], $val2['slug']);
+ });
+
+ return $this;
+ }
+
+ /**
+ * Set parameters to the Collection
+ *
+ * @param array $params
+ *
+ * @return $this
+ */
+ public function setParams(array $params)
+ {
+ $this->params = array_merge($this->params, $params);
+
+ return $this;
+ }
+
+ /**
+ * Returns current page.
+ *
+ * @return PageInterface
+ */
+ public function current()
+ {
+ $current = parent::key();
+
+ return $this->pages->get($current);
+ }
+
+ /**
+ * Returns current slug.
+ *
+ * @return mixed
+ */
+ public function key()
+ {
+ $current = parent::current();
+
+ return $current['slug'];
+ }
+
+ /**
+ * Returns the value at specified offset.
+ *
+ * @param mixed $offset The offset to retrieve.
+ *
+ * @return mixed Can return all value types.
+ */
+ public function offsetGet($offset)
+ {
+ return $this->pages->get($offset) ?: null;
+ }
+
+ /**
+ * Split collection into array of smaller collections.
+ *
+ * @param int $size
+ * @return array|Collection[]
+ */
+ public function batch($size)
+ {
+ $chunks = array_chunk($this->items, $size, true);
+
+ $list = [];
+ foreach ($chunks as $chunk) {
+ $list[] = new static($chunk, $this->params, $this->pages);
+ }
+
+ return $list;
+ }
+
+ /**
+ * Remove item from the list.
+ *
+ * @param PageInterface|string|null $key
+ *
+ * @return $this
+ * @throws \InvalidArgumentException
+ */
+ public function remove($key = null)
+ {
+ if ($key instanceof PageInterface) {
+ $key = $key->path();
+ } elseif (null === $key) {
+ $key = (string)key($this->items);
+ }
+ if (!\is_string($key)) {
+ throw new \InvalidArgumentException('Invalid argument $key.');
+ }
+
+ parent::remove($key);
+
+ return $this;
+ }
+
+ /**
+ * Reorder collection.
+ *
+ * @param string $by
+ * @param string $dir
+ * @param array $manual
+ * @param string $sort_flags
+ *
+ * @return $this
+ */
+ public function order($by, $dir = 'asc', $manual = null, $sort_flags = null)
+ {
+ $this->items = $this->pages->sortCollection($this, $by, $dir, $manual, $sort_flags);
+
+ return $this;
+ }
+
+ /**
+ * Check to see if this item is the first in the collection.
+ *
+ * @param string $path
+ *
+ * @return bool True if item is first.
+ */
+ public function isFirst($path)
+ {
+ return $this->items && $path === array_keys($this->items)[0];
+ }
+
+ /**
+ * Check to see if this item is the last in the collection.
+ *
+ * @param string $path
+ *
+ * @return bool True if item is last.
+ */
+ public function isLast($path)
+ {
+ return $this->items && $path === array_keys($this->items)[\count($this->items) - 1];
+ }
+
+ /**
+ * Gets the previous sibling based on current position.
+ *
+ * @param string $path
+ *
+ * @return PageInterface The previous item.
+ */
+ public function prevSibling($path)
+ {
+ return $this->adjacentSibling($path, -1);
+ }
+
+ /**
+ * Gets the next sibling based on current position.
+ *
+ * @param string $path
+ *
+ * @return PageInterface The next item.
+ */
+ public function nextSibling($path)
+ {
+ return $this->adjacentSibling($path, 1);
+ }
+
+ /**
+ * Returns the adjacent sibling based on a direction.
+ *
+ * @param string $path
+ * @param int $direction either -1 or +1
+ *
+ * @return PageInterface|Collection The sibling item.
+ */
+ public function adjacentSibling($path, $direction = 1)
+ {
+ $values = array_keys($this->items);
+ $keys = array_flip($values);
+
+ if (array_key_exists($path, $keys)) {
+ $index = $keys[$path] - $direction;
+
+ return isset($values[$index]) ? $this->offsetGet($values[$index]) : $this;
+ }
+
+ return $this;
+
+ }
+
+ /**
+ * Returns the item in the current position.
+ *
+ * @param string $path the path the item
+ *
+ * @return int the index of the current page.
+ */
+ public function currentPosition($path)
+ {
+ return \array_search($path, \array_keys($this->items), true);
+ }
+
+ /**
+ * Returns the items between a set of date ranges of either the page date field (default) or
+ * an arbitrary datetime page field where end date is optional
+ * Dates can be passed in as text that strtotime() can process
+ * http://php.net/manual/en/function.strtotime.php
+ *
+ * @param string $startDate
+ * @param bool $endDate
+ * @param string|null $field
+ *
+ * @return $this
+ * @throws \Exception
+ */
+ public function dateRange($startDate, $endDate = false, $field = null)
+ {
+ $start = Utils::date2timestamp($startDate);
+ $end = $endDate ? Utils::date2timestamp($endDate) : false;
+
+ $date_range = [];
+ foreach ($this->items as $path => $slug) {
+ $page = $this->pages->get($path);
+ if ($page !== null) {
+ $date = $field ? strtotime($page->value($field)) : $page->date();
+
+ if ($date >= $start && (!$end || $date <= $end)) {
+ $date_range[$path] = $slug;
+ }
+ }
+ }
+
+ $this->items = $date_range;
+
+ return $this;
+ }
+
+ /**
+ * Creates new collection with only visible pages
+ *
+ * @return Collection The collection with only visible pages
+ */
+ public function visible()
+ {
+ $visible = [];
+
+ foreach ($this->items as $path => $slug) {
+ $page = $this->pages->get($path);
+ if ($page !== null && $page->visible()) {
+ $visible[$path] = $slug;
+ }
+ }
+ $this->items = $visible;
+
+ return $this;
+ }
+
+ /**
+ * Creates new collection with only non-visible pages
+ *
+ * @return Collection The collection with only non-visible pages
+ */
+ public function nonVisible()
+ {
+ $visible = [];
+
+ foreach ($this->items as $path => $slug) {
+ $page = $this->pages->get($path);
+ if ($page !== null && !$page->visible()) {
+ $visible[$path] = $slug;
+ }
+ }
+ $this->items = $visible;
+
+ return $this;
+ }
+
+ /**
+ * Creates new collection with only modular pages
+ *
+ * @return Collection The collection with only modular pages
+ */
+ public function modular()
+ {
+ $modular = [];
+
+ foreach ($this->items as $path => $slug) {
+ $page = $this->pages->get($path);
+ if ($page !== null && $page->modular()) {
+ $modular[$path] = $slug;
+ }
+ }
+ $this->items = $modular;
+
+ return $this;
+ }
+
+ /**
+ * Creates new collection with only non-modular pages
+ *
+ * @return Collection The collection with only non-modular pages
+ */
+ public function nonModular()
+ {
+ $modular = [];
+
+ foreach ($this->items as $path => $slug) {
+ $page = $this->pages->get($path);
+ if ($page !== null && !$page->modular()) {
+ $modular[$path] = $slug;
+ }
+ }
+ $this->items = $modular;
+
+ return $this;
+ }
+
+ /**
+ * Creates new collection with only published pages
+ *
+ * @return Collection The collection with only published pages
+ */
+ public function published()
+ {
+ $published = [];
+
+ foreach ($this->items as $path => $slug) {
+ $page = $this->pages->get($path);
+ if ($page !== null && $page->published()) {
+ $published[$path] = $slug;
+ }
+ }
+ $this->items = $published;
+
+ return $this;
+ }
+
+ /**
+ * Creates new collection with only non-published pages
+ *
+ * @return Collection The collection with only non-published pages
+ */
+ public function nonPublished()
+ {
+ $published = [];
+
+ foreach ($this->items as $path => $slug) {
+ $page = $this->pages->get($path);
+ if ($page !== null && !$page->published()) {
+ $published[$path] = $slug;
+ }
+ }
+ $this->items = $published;
+
+ return $this;
+ }
+
+ /**
+ * Creates new collection with only routable pages
+ *
+ * @return Collection The collection with only routable pages
+ */
+ public function routable()
+ {
+ $routable = [];
+
+ foreach ($this->items as $path => $slug) {
+ $page = $this->pages->get($path);
+
+ if ($page !== null && $page->routable()) {
+ $routable[$path] = $slug;
+ }
+ }
+
+ $this->items = $routable;
+
+ return $this;
+ }
+
+ /**
+ * Creates new collection with only non-routable pages
+ *
+ * @return Collection The collection with only non-routable pages
+ */
+ public function nonRoutable()
+ {
+ $routable = [];
+
+ foreach ($this->items as $path => $slug) {
+ $page = $this->pages->get($path);
+ if ($page !== null && !$page->routable()) {
+ $routable[$path] = $slug;
+ }
+ }
+ $this->items = $routable;
+
+ return $this;
+ }
+
+ /**
+ * Creates new collection with only pages of the specified type
+ *
+ * @param string $type
+ *
+ * @return Collection The collection
+ */
+ public function ofType($type)
+ {
+ $items = [];
+
+ foreach ($this->items as $path => $slug) {
+ $page = $this->pages->get($path);
+ if ($page !== null && $page->template() === $type) {
+ $items[$path] = $slug;
+ }
+ }
+
+ $this->items = $items;
+
+ return $this;
+ }
+
+ /**
+ * Creates new collection with only pages of one of the specified types
+ *
+ * @param string[] $types
+ *
+ * @return Collection The collection
+ */
+ public function ofOneOfTheseTypes($types)
+ {
+ $items = [];
+
+ foreach ($this->items as $path => $slug) {
+ $page = $this->pages->get($path);
+ if ($page !== null && \in_array($page->template(), $types, true)) {
+ $items[$path] = $slug;
+ }
+ }
+
+ $this->items = $items;
+
+ return $this;
+ }
+
+ /**
+ * Creates new collection with only pages of one of the specified access levels
+ *
+ * @param array $accessLevels
+ *
+ * @return Collection The collection
+ */
+ public function ofOneOfTheseAccessLevels($accessLevels)
+ {
+ $items = [];
+
+ foreach ($this->items as $path => $slug) {
+ $page = $this->pages->get($path);
+
+ if ($page !== null && isset($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)) {
+ foreach ($accessLevel as $innerIndex => $innerAccessLevel) {
+ if (\in_array($innerAccessLevel, $accessLevels)) {
+ $valid = true;
+ }
+ }
+ } else {
+ if (\in_array($index, $accessLevels)) {
+ $valid = true;
+ }
+ }
+ }
+ if ($valid) {
+ $items[$path] = $slug;
+ }
+ } else {
+ //Single value for access
+ if (\in_array($page->header()->access, $accessLevels)) {
+ $items[$path] = $slug;
+ }
+ }
+
+ }
+ }
+
+ $this->items = $items;
+
+ return $this;
+ }
+
+ /**
+ * Get the extended version of this Collection with each page keyed by route
+ *
+ * @return array
+ * @throws \Exception
+ */
+ public function toExtendedArray()
+ {
+ $items = [];
+ foreach ($this->items as $path => $slug) {
+ $page = $this->pages->get($path);
+
+ if ($page !== null) {
+ $items[$page->route()] = $page->toArray();
+ }
+ }
+ return $items;
+ }
+}
diff --git a/system/src/Grav/Common/Page/Header.php b/system/src/Grav/Common/Page/Header.php
new file mode 100644
index 0000000..e3854ae
--- /dev/null
+++ b/system/src/Grav/Common/Page/Header.php
@@ -0,0 +1,18 @@
+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
new file mode 100644
index 0000000..24bf50c
--- /dev/null
+++ b/system/src/Grav/Common/Page/Interfaces/PageInterface.php
@@ -0,0 +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 @@
+page = $page ?? Grav::instance()['page'] ?? null;
+
+ // Add defaults to the configuration.
+ if (null === $config || !isset($config['markdown'], $config['images'])) {
+ $c = Grav::instance()['config'];
+ $config = $config ?? [];
+ $config += [
+ 'markdown' => $c->get('system.pages.markdown', []),
+ 'images' => $c->get('system.images', [])
+ ];
+ }
+
+ $this->config = $config;
+ }
+
+ public function getPage(): PageInterface
+ {
+ return $this->page;
+ }
+
+ public function getConfig(): array
+ {
+ return $this->config;
+ }
+
+ public function fireInitializedEvent($markdown): void
+ {
+ $grav = Grav::instance();
+
+ $grav->fireEvent('onMarkdownInitialized', new Event(['markdown' => $markdown, 'page' => $this->page]));
+ }
+
+ /**
+ * Process a Link excerpt
+ *
+ * @param array $excerpt
+ * @param string $type
+ * @return array
+ */
+ public function processLinkExcerpt(array $excerpt, string $type = 'link'): array
+ {
+ $url = htmlspecialchars_decode(rawurldecode($excerpt['element']['attributes']['href']));
+
+ $url_parts = $this->parseUrl($url);
+
+ // If there is a query, then parse it and build action calls.
+ if (isset($url_parts['query'])) {
+ $actions = array_reduce(
+ explode('&', $url_parts['query']),
+ static function ($carry, $item) {
+ $parts = explode('=', $item, 2);
+ $value = isset($parts[1]) ? rawurldecode($parts[1]) : true;
+ $carry[$parts[0]] = $value;
+
+ return $carry;
+ },
+ []
+ );
+
+ // Valid attributes supported.
+ $valid_attributes = Grav::instance()['config']->get('system.pages.markdown.valid_link_attributes');
+
+ // Unless told to not process, go through actions.
+ if (array_key_exists('noprocess', $actions)) {
+ unset($actions['noprocess']);
+ } else {
+ // Loop through actions for the image and call them.
+ foreach ($actions as $attrib => $value) {
+ $key = $attrib;
+
+ if (in_array($attrib, $valid_attributes, true)) {
+ // support both class and classes.
+ if ($attrib === 'classes') {
+ $attrib = 'class';
+ }
+ $excerpt['element']['attributes'][$attrib] = str_replace(',', ' ', $value);
+ unset($actions[$key]);
+ }
+ }
+ }
+
+ $url_parts['query'] = http_build_query($actions, null, '&', PHP_QUERY_RFC3986);
+ }
+
+ // If no query elements left, unset query.
+ if (empty($url_parts['query'])) {
+ unset ($url_parts['query']);
+ }
+
+ // Set path to / if not set.
+ if (empty($url_parts['path'])) {
+ $url_parts['path'] = '';
+ }
+
+ // If scheme isn't http(s)..
+ if (!empty($url_parts['scheme']) && !in_array($url_parts['scheme'], ['http', 'https'])) {
+ // Handle custom streams.
+ if ($type !== 'image' && !empty($url_parts['stream']) && !empty($url_parts['path'])) {
+ $grav = Grav::instance();
+ $url_parts['path'] = $grav['base_url_relative'] . '/' . $this->resolveStream("{$url_parts['scheme']}://{$url_parts['path']}");
+ unset($url_parts['stream'], $url_parts['scheme']);
+ }
+
+ $excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
+
+ return $excerpt;
+ }
+
+ // Handle paths and such.
+ $url_parts = Uri::convertUrl($this->page, $url_parts, $type);
+
+ // Build the URL from the component parts and set it on the element.
+ $excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
+
+ return $excerpt;
+ }
+
+ /**
+ * Process an image excerpt
+ *
+ * @param array $excerpt
+ * @return array
+ */
+ public function processImageExcerpt(array $excerpt): array
+ {
+ $url = htmlspecialchars_decode(urldecode($excerpt['element']['attributes']['src']));
+ $url_parts = $this->parseUrl($url);
+
+ $media = null;
+ $filename = null;
+
+ if (!empty($url_parts['stream'])) {
+ $filename = $url_parts['scheme'] . '://' . ($url_parts['path'] ?? '');
+
+ $media = $this->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['uri']->host());
+
+ if ($local_file) {
+ $filename = basename($url_parts['path']);
+ $folder = dirname($url_parts['path']);
+
+ // Get the local path to page media if possible.
+ if ($this->page && $folder === $this->page->url(false, false, false)) {
+ // Get the media objects for this page.
+ $media = $this->page->getMedia();
+ } else {
+ // see if this is an external page to this one
+ $base_url = rtrim($grav['base_url_relative'] . $grav['pages']->base(), '/');
+ $page_route = '/' . ltrim(str_replace($base_url, '', $folder), '/');
+
+ /** @var PageInterface $ext_page */
+ $ext_page = $grav['pages']->dispatch($page_route, true);
+ if ($ext_page) {
+ $media = $ext_page->getMedia();
+ } else {
+ $grav->fireEvent('onMediaLocate', new Event(['route' => $page_route, 'media' => &$media]));
+ }
+ }
+ }
+ }
+
+ // If there is a media file that matches the path referenced..
+ if ($media && $filename && isset($media[$filename])) {
+ // Get the medium object.
+ /** @var Medium $medium */
+ $medium = $media[$filename];
+
+ // Process operations
+ $medium = $this->processMediaActions($medium, $url_parts);
+ $element_excerpt = $excerpt['element']['attributes'];
+
+ $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);
+
+ } else {
+ // Not a current page media file, see if it needs converting to relative.
+ $excerpt['element']['attributes']['src'] = Uri::buildUrl($url_parts);
+ }
+
+ return $excerpt;
+ }
+
+ /**
+ * Process media actions
+ *
+ * @param Medium $medium
+ * @param string|array $url
+ * @return Medium|Link
+ */
+ public function processMediaActions($medium, $url)
+ {
+ $url_parts = is_string($url) ? $this->parseUrl($url) : $url;
+ $actions = [];
+
+
+ // if there is a query, then parse it and build action calls
+ if (isset($url_parts['query'])) {
+ $actions = array_reduce(
+ explode('&', $url_parts['query']),
+ static function ($carry, $item) {
+ $parts = explode('=', $item, 2);
+ $value = $parts[1] ?? null;
+ $carry[] = ['method' => $parts[0], 'params' => $value];
+
+ return $carry;
+ },
+ []
+ );
+ }
+
+ $config = $this->getConfig();
+ if (!empty($config['images']['auto_fix_orientation'])) {
+ $actions[] = ['method' => 'fixOrientation', 'params' => ''];
+ }
+
+ $defaults = $config['images']['defaults'] ?? [];
+ if (count($defaults)) {
+ foreach ($defaults as $method => $params) {
+ $actions[] = [
+ 'method' => $method,
+ 'params' => $params,
+ ];
+ }
+ }
+
+ // loop through actions for the image and call them
+ foreach ($actions as $action) {
+ $matches = [];
+
+ if (preg_match('/\[(.*)\]/', $action['params'], $matches)) {
+ $args = [explode(',', $matches[1])];
+ } else {
+ $args = explode(',', $action['params']);
+ }
+
+ $medium = call_user_func_array([$medium, $action['method']], $args);
+ }
+
+ if (isset($url_parts['fragment'])) {
+ $medium->urlHash($url_parts['fragment']);
+ }
+
+ return $medium;
+ }
+
+ /**
+ * Variation of parse_url() which works also with local streams.
+ *
+ * @param string $url
+ * @return array|bool
+ */
+ protected function parseUrl(string $url)
+ {
+ $url_parts = Utils::multibyteParseUrl($url);
+
+ if (isset($url_parts['scheme'])) {
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+
+ // Special handling for the streams.
+ if ($locator->schemeExists($url_parts['scheme'])) {
+ if (isset($url_parts['host'])) {
+ // Merge host and path into a path.
+ $url_parts['path'] = $url_parts['host'] . (isset($url_parts['path']) ? '/' . $url_parts['path'] : '');
+ unset($url_parts['host']);
+ }
+
+ $url_parts['stream'] = true;
+ }
+ }
+
+ return $url_parts;
+ }
+
+ /**
+ * @param string $url
+ * @return bool|string
+ */
+ protected function resolveStream(string $url)
+ {
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+
+ if ($locator->isStream($url)) {
+ return $locator->findResource($url, false) ?: $locator->findResource($url, false, true);
+ }
+
+ return $url;
+ }
+}
diff --git a/system/src/Grav/Common/Page/Media.php b/system/src/Grav/Common/Page/Media.php
new file mode 100644
index 0000000..88b2ced
--- /dev/null
+++ b/system/src/Grav/Common/Page/Media.php
@@ -0,0 +1,222 @@
+setPath($path);
+ $this->media_order = $media_order;
+
+ $this->__wakeup();
+ if ($load) {
+ $this->init();
+ }
+ }
+
+ /**
+ * Initialize static variables on unserialize.
+ */
+ public function __wakeup()
+ {
+ if (!isset(static::$global)) {
+ // Add fallback to global media.
+ static::$global = new GlobalMedia();
+ }
+ }
+
+ /**
+ * @param mixed $offset
+ *
+ * @return bool
+ */
+ public function offsetExists($offset)
+ {
+ return parent::offsetExists($offset) ?: isset(static::$global[$offset]);
+ }
+
+ /**
+ * @param mixed $offset
+ *
+ * @return mixed
+ */
+ public function offsetGet($offset)
+ {
+ return parent::offsetGet($offset) ?: static::$global[$offset];
+ }
+
+ /**
+ * Initialize class.
+ */
+ 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->getPath())) {
+ return;
+ }
+
+ $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' || 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, true)) {
+ continue;
+ }
+
+ if ($type === 'alternative') {
+ $media["{$basename}.{$ext}"][$type][$extra] = ['file' => $path, 'size' => $info->getSize()];
+ } else {
+ $media["{$basename}.{$ext}"][$type] = ['file' => $path, 'size' => $info->getSize()];
+ }
+ }
+
+ foreach ($media as $name => $types) {
+ // First prepare the alternatives in case there is no base medium
+ if (!empty($types['alternative'])) {
+ foreach ($types['alternative'] as $ratio => &$alt) {
+ $alt['file'] = MediumFactory::fromFile($alt['file']);
+
+ if (!$alt['file']) {
+ unset($types['alternative'][$ratio]);
+ } else {
+ $alt['file']->set('size', $alt['size']);
+ }
+ }
+ }
+
+ $file_path = null;
+
+ // Create the base medium
+ if (empty($types['base'])) {
+ if (!isset($types['alternative'])) {
+ continue;
+ }
+
+ $max = max(array_keys($types['alternative']));
+ $medium = $types['alternative'][$max]['file'];
+ $file_path = $medium->path();
+ $medium = MediumFactory::scaledFromMedium($medium, $max, 1)['file'];
+ } else {
+ $medium = MediumFactory::fromFile($types['base']['file']);
+ $medium && $medium->set('size', $types['base']['size']);
+ $file_path = $medium->path();
+ }
+
+ if (empty($medium)) {
+ continue;
+ }
+
+ // metadata file
+ $meta_path = $file_path . '.meta.yaml';
+
+ if (file_exists($meta_path)) {
+ $types['meta']['file'] = $meta_path;
+ } 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);
+
+ if ($meta) {
+ $meta_data = $meta->getData();
+ $meta_trimmed = array_diff_key($meta_data, array_flip($this->standard_exif));
+ if ($meta_trimmed) {
+ 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;
+ }
+ }
+ }
+
+ if (!empty($types['meta'])) {
+ $medium->addMetaFile($types['meta']['file']);
+ }
+
+ if (!empty($types['thumb'])) {
+ // We will not turn it into medium yet because user might never request the thumbnail
+ // not wasting any resources on that, maybe we should do this for medium in general?
+ $medium->set('thumbnails.page', $types['thumb']['file']);
+ }
+
+ // Build missing alternatives
+ if (!empty($types['alternative'])) {
+ $alternatives = $types['alternative'];
+ $max = max(array_keys($alternatives));
+
+ for ($i=$max; $i > 1; $i--) {
+ if (isset($alternatives[$i])) {
+ continue;
+ }
+
+ $types['alternative'][$i] = MediumFactory::scaledFromMedium($alternatives[$max]['file'], $max, $i);
+ }
+
+ foreach ($types['alternative'] as $altMedium) {
+ if ($altMedium['file'] != $medium) {
+ $altWidth = $altMedium['file']->get('width');
+ $medWidth = $medium->get('width');
+ if ($altWidth && $medWidth) {
+ $ratio = $altWidth / $medWidth;
+ $medium->addAlternative($ratio, $altMedium['file']);
+ }
+ }
+ }
+ }
+
+ $this->add($name, $medium);
+ }
+ }
+
+ /**
+ * @return string
+ * @deprecated 1.6 Use $this->getPath() instead.
+ */
+ public function path()
+ {
+ return $this->getPath();
+ }
+}
diff --git a/system/src/Grav/Common/Page/Medium/AbstractMedia.php b/system/src/Grav/Common/Page/Medium/AbstractMedia.php
new file mode 100644
index 0000000..f67cd65
--- /dev/null
+++ b/system/src/Grav/Common/Page/Medium/AbstractMedia.php
@@ -0,0 +1,244 @@
+path;
+ }
+
+ public function setPath(?string $path)
+ {
+ $this->path = $path;
+ }
+
+ /**
+ * Get medium by filename.
+ *
+ * @param string $filename
+ * @return Medium|null
+ */
+ public function get($filename)
+ {
+ return $this->offsetGet($filename);
+ }
+
+ /**
+ * Call object as function to get medium by filename.
+ *
+ * @param string $filename
+ * @return mixed
+ */
+ public function __invoke($filename)
+ {
+ return $this->offsetGet($filename);
+ }
+
+ /**
+ * Set file modification timestamps (query params) for all the media files.
+ *
+ * @param string|int|null $timestamp
+ * @return $this
+ */
+ public function setTimestamps($timestamp = null)
+ {
+ /** @var Medium $instance */
+ foreach ($this->items as $instance) {
+ $instance->setTimestamp($timestamp);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get a list of all media.
+ *
+ * @return MediaObjectInterface[]
+ */
+ public function all()
+ {
+ $this->items = $this->orderMedia($this->items);
+
+ return $this->items;
+ }
+
+ /**
+ * Get a list of all image media.
+ *
+ * @return MediaObjectInterface[]
+ */
+ public function images()
+ {
+ $this->images = $this->orderMedia($this->images);
+
+ return $this->images;
+ }
+
+ /**
+ * Get a list of all video media.
+ *
+ * @return MediaObjectInterface[]
+ */
+ public function videos()
+ {
+ $this->videos = $this->orderMedia($this->videos);
+
+ return $this->videos;
+ }
+
+ /**
+ * Get a list of all audio media.
+ *
+ * @return MediaObjectInterface[]
+ */
+ public function audios()
+ {
+ $this->audios = $this->orderMedia($this->audios);
+
+ return $this->audios;
+ }
+
+ /**
+ * Get a list of all file media.
+ *
+ * @return MediaObjectInterface[]
+ */
+ public function files()
+ {
+ $this->files = $this->orderMedia($this->files);
+
+ return $this->files;
+ }
+
+ /**
+ * @param string $name
+ * @param MediaObjectInterface $file
+ */
+ public function add($name, $file)
+ {
+ if (!$file) {
+ return;
+ }
+ $this->offsetSet($name, $file);
+ switch ($file->type) {
+ case 'image':
+ $this->images[$name] = $file;
+ break;
+ case 'video':
+ $this->videos[$name] = $file;
+ break;
+ case 'audio':
+ $this->audios[$name] = $file;
+ break;
+ default:
+ $this->files[$name] = $file;
+ }
+ }
+
+ /**
+ * Order the media based on the page's media_order
+ *
+ * @param array $media
+ * @return array
+ */
+ protected function orderMedia($media)
+ {
+ if (null === $this->media_order) {
+ /** @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));
+ }
+ }
+
+ if (!empty($this->media_order) && is_array($this->media_order)) {
+ $media = Utils::sortArrayByArray($media, $this->media_order);
+ } else {
+ ksort($media, SORT_NATURAL | SORT_FLAG_CASE);
+ }
+
+ return $media;
+ }
+
+ /**
+ * Get filename, extension and meta part.
+ *
+ * @param string $filename
+ * @return array
+ */
+ protected function getFileParts($filename)
+ {
+ if (preg_match('/(.*)@(\d+)x\.(.*)$/', $filename, $matches)) {
+ $name = $matches[1];
+ $extension = $matches[3];
+ $extra = (int) $matches[2];
+ $type = 'alternative';
+
+ if ($extra === 1) {
+ $type = 'base';
+ $extra = null;
+ }
+ } else {
+ $fileParts = explode('.', $filename);
+
+ $name = array_shift($fileParts);
+ $extension = null;
+ $extra = null;
+ $type = 'base';
+
+ while (($part = array_shift($fileParts)) !== null) {
+ if ($part !== 'meta' && $part !== 'thumb') {
+ if (null !== $extension) {
+ $name .= '.' . $extension;
+ }
+ $extension = $part;
+ } else {
+ $type = $part;
+ $extra = '.' . $part . '.' . implode('.', $fileParts);
+ break;
+ }
+ }
+ }
+
+ return array($name, $extension, $type, $extra);
+ }
+}
diff --git a/system/src/Grav/Common/Page/Medium/AudioMedium.php b/system/src/Grav/Common/Page/Medium/AudioMedium.php
new file mode 100644
index 0000000..74ef746
--- /dev/null
+++ b/system/src/Grav/Common/Page/Medium/AudioMedium.php
@@ -0,0 +1,148 @@
+url($reset);
+
+ return [
+ 'name' => 'audio',
+ 'text' => 'Your browser does not support the audio tag.',
+ 'attributes' => $attributes
+ ];
+ }
+
+ /**
+ * Allows to set or remove the HTML5 default controls
+ *
+ * @param bool $display
+ * @return $this
+ */
+ public function controls($display = true)
+ {
+ if($display) {
+ $this->attributes['controls'] = true;
+ } else {
+ unset($this->attributes['controls']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Allows to set the preload behaviour
+ *
+ * @param string $preload
+ * @return $this
+ */
+ public function preload($preload)
+ {
+ $validPreloadAttrs = ['auto', 'metadata', 'none'];
+
+ if (\in_array($preload, $validPreloadAttrs, true)) {
+ $this->attributes['preload'] = $preload;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Allows to set the controlsList behaviour
+ * Separate multiple values with a hyphen
+ *
+ * @param string $controlsList
+ * @return $this
+ */
+ public function controlsList($controlsList)
+ {
+ $controlsList = str_replace('-', ' ', $controlsList);
+ $this->attributes['controlsList'] = $controlsList;
+
+ return $this;
+ }
+
+ /**
+ * Allows to set the muted attribute
+ *
+ * @param bool $status
+ * @return $this
+ */
+ public function muted($status = false)
+ {
+ if($status) {
+ $this->attributes['muted'] = true;
+ } else {
+ unset($this->attributes['muted']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Allows to set the loop attribute
+ *
+ * @param bool $status
+ * @return $this
+ */
+ public function loop($status = false)
+ {
+ if($status) {
+ $this->attributes['loop'] = true;
+ } else {
+ unset($this->attributes['loop']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Allows to set the autoplay attribute
+ *
+ * @param bool $status
+ * @return $this
+ */
+ public function autoplay($status = false)
+ {
+ if($status) {
+ $this->attributes['autoplay'] = true;
+ } else {
+ unset($this->attributes['autoplay']);
+ }
+
+ return $this;
+ }
+
+
+ /**
+ * Reset medium.
+ *
+ * @return $this
+ */
+ public function reset()
+ {
+ 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
new file mode 100644
index 0000000..9fb30e9
--- /dev/null
+++ b/system/src/Grav/Common/Page/Medium/GlobalMedia.php
@@ -0,0 +1,128 @@
+resolveStream($offset));
+ }
+
+ /**
+ * @param mixed $offset
+ *
+ * @return mixed
+ */
+ public function offsetGet($offset)
+ {
+ return parent::offsetGet($offset) ?: $this->addMedium($offset);
+ }
+
+ /**
+ * @param string $filename
+ * @return string|null
+ */
+ protected function resolveStream($filename)
+ {
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+
+ return $locator->isStream($filename) ? ($locator->findResource($filename) ?: null) : null;
+ }
+
+ /**
+ * @param string $stream
+ * @return Medium|null
+ */
+ protected function addMedium($stream)
+ {
+ $filename = $this->resolveStream($stream);
+ if (!$filename) {
+ return null;
+ }
+
+ $path = dirname($filename);
+ list($basename, $ext,, $extra) = $this->getFileParts(basename($filename));
+ $medium = MediumFactory::fromFile($filename);
+
+ if (empty($medium)) {
+ return null;
+ }
+
+ $medium->set('size', filesize($filename));
+ $scale = (int) ($extra ?: 1);
+
+ if ($scale !== 1) {
+ $altMedium = $medium;
+
+ // Create scaled down regular sized image.
+ $medium = MediumFactory::scaledFromMedium($altMedium, $scale, 1)['file'];
+
+ if (empty($medium)) {
+ return null;
+ }
+
+ // Add original sized image as alternative.
+ $medium->addAlternative($scale, $altMedium['file']);
+
+ // Locate or generate smaller retina images.
+ for ($i = $scale-1; $i > 1; $i--) {
+ $altFilename = "{$path}/{$basename}@{$i}x.{$ext}";
+
+ if (file_exists($altFilename)) {
+ $scaled = MediumFactory::fromFile($altFilename);
+ } else {
+ $scaled = MediumFactory::scaledFromMedium($altMedium, $scale, $i)['file'];
+ }
+
+ if ($scaled) {
+ $medium->addAlternative($i, $scaled);
+ }
+ }
+ }
+
+ $meta = "{$path}/{$basename}.{$ext}.yaml";
+ if (file_exists($meta)) {
+ $medium->addMetaFile($meta);
+ }
+ $meta = "{$path}/{$basename}.{$ext}.meta.yaml";
+ if (file_exists($meta)) {
+ $medium->addMetaFile($meta);
+ }
+
+ $thumb = "{$path}/{$basename}.thumb.{$ext}";
+ if (file_exists($thumb)) {
+ $medium->set('thumbnails.page', $thumb);
+ }
+
+ $this->add($stream, $medium);
+
+ return $medium;
+ }
+}
diff --git a/system/src/Grav/Common/Page/Medium/ImageFile.php b/system/src/Grav/Common/Page/Medium/ImageFile.php
new file mode 100644
index 0000000..d56c342
--- /dev/null
+++ b/system/src/Grav/Common/Page/Medium/ImageFile.php
@@ -0,0 +1,145 @@
+getAdapter()->deinit();
+ }
+
+ /**
+ * Clear previously applied operations
+ */
+ public function clearOperations()
+ {
+ $this->operations = [];
+ }
+
+ /**
+ * This is the same as the Gregwar Image class except this one fires a Grav Event on creation of new cached file
+ *
+ * @param string $type the image type
+ * @param int $quality the quality (for JPEG)
+ * @param bool $actual
+ * @param array $extras
+ *
+ * @return string
+ */
+ public function cacheFile($type = 'jpg', $quality = 80, $actual = false, $extras = [])
+ {
+ if ($type === 'guess') {
+ $type = $this->guessType();
+ }
+
+ if (!$this->forceCache && !count($this->operations) && $type === $this->guessType()) {
+ return $this->getFilename($this->getFilePath());
+ }
+
+ // Computes the hash
+ $this->hash = $this->getHash($type, $quality, $extras);
+
+ // Seo friendly image names
+ $seofriendly = Grav::instance()['config']->get('system.images.seofriendly', false);
+
+ 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;
+
+ // If the files does not exists, save it
+ $image = $this;
+
+ // Target file should be younger than all the current image
+ // dependencies
+ $conditions = array(
+ 'younger-than' => $this->getDependencies()
+ );
+
+ // The generating function
+ $generate = function ($target) use ($image, $type, $quality) {
+ $result = $image->save($target, $type, $quality);
+
+ if ($result !== $target) {
+ throw new GenerationError($result);
+ }
+
+ Grav::instance()->fireEvent('onImageMediumSaved', new Event(['image' => $target]));
+ };
+
+ // Asking the cache for the cacheFile
+ try {
+ $perms = Grav::instance()['config']->get('system.images.cache_perms', '0755');
+ $perms = octdec($perms);
+ $file = $this->getCacheSystem()->setDirectoryMode($perms)->getOrCreateFile($cacheFile, $conditions, $generate, $actual);
+ } catch (GenerationError $e) {
+ $file = $e->getNewFile();
+ }
+
+ // Nulling the resource
+ $this->getAdapter()->setSource(new Source\File($file));
+ $this->getAdapter()->deinit();
+
+ if ($actual) {
+ return $file;
+ }
+
+ 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
new file mode 100644
index 0000000..13c142a
--- /dev/null
+++ b/system/src/Grav/Common/Page/Medium/ImageMedium.php
@@ -0,0 +1,668 @@
+ [0, 1],
+ 'forceResize' => [0, 1],
+ 'cropResize' => [0, 1],
+ 'crop' => [0, 1, 2, 3],
+ 'zoomCrop' => [0, 1]
+ ];
+
+ /**
+ * @var string
+ */
+ protected $sizes = '100vw';
+
+ /**
+ * Construct.
+ *
+ * @param array $items
+ * @param Blueprint $blueprint
+ */
+ public function __construct($items = [], Blueprint $blueprint = null)
+ {
+ parent::__construct($items, $blueprint);
+
+ $config = Grav::instance()['config'];
+
+ $path = $this->get('filepath');
+ if (!$path || !file_exists($path) || !filesize($path)) {
+ return;
+ }
+
+ $image_info = getimagesize($path);
+
+ $this->def('width', $image_info[0]);
+ $this->def('height', $image_info[1]);
+ $this->def('mime', $image_info['mime']);
+ $this->def('debug', $config->get('system.images.debug'));
+
+ $this->set('thumbnails.media', $this->get('filepath'));
+
+ $this->default_quality = $config->get('system.images.default_image_quality', 85);
+
+ $this->reset();
+
+ if ($config->get('system.images.cache_all', false)) {
+ $this->cache();
+ }
+ }
+
+ public function __destruct()
+ {
+ unset($this->image);
+ }
+
+ public function __clone()
+ {
+ $this->image = $this->image ? clone $this->image : null;
+
+ parent::__clone();
+ }
+
+ /**
+ * Add meta file for the medium.
+ *
+ * @param string $filepath
+ * @return $this
+ */
+ public function addMetaFile($filepath)
+ {
+ parent::addMetaFile($filepath);
+
+ // Apply filters in meta file
+ $this->reset();
+
+ return $this;
+ }
+
+ /**
+ * Clear out the alternatives
+ */
+ public function clearAlternatives()
+ {
+ $this->alternatives = [];
+ }
+
+ /**
+ * Return PATH to image.
+ *
+ * @param bool $reset
+ * @return string path to image
+ */
+ public function path($reset = true)
+ {
+ $output = $this->saveImage();
+
+ if ($reset) {
+ $this->reset();
+ }
+
+ return $output;
+ }
+
+ /**
+ * Return URL to image.
+ *
+ * @param bool $reset
+ * @return string
+ */
+ public function url($reset = true)
+ {
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+ $image_path = $locator->findResource('cache://images', true) ?: $locator->findResource('cache://images', true, true);
+ $saved_image_path = $this->saveImage();
+
+ $output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $saved_image_path);
+
+ if ($locator->isStream($output)) {
+ $output = $locator->findResource($output, false);
+ }
+
+ if (Utils::startsWith($output, $image_path)) {
+ $image_dir = $locator->findResource('cache://images', false);
+ $output = '/' . $image_dir . preg_replace('|^' . preg_quote($image_path, '|') . '|', '', $output);
+ }
+
+ if ($reset) {
+ $this->reset();
+ }
+
+ return trim(Grav::instance()['base_url'] . '/' . $this->urlQuerystring($output), '\\');
+ }
+
+ /**
+ * Simply processes with no extra methods. Useful for triggering events.
+ *
+ * @return $this
+ */
+ public function cache()
+ {
+ if (!$this->image) {
+ $this->image();
+ }
+
+ return $this;
+ }
+
+
+ /**
+ * Return srcset string for this Medium and its alternatives.
+ *
+ * @param bool $reset
+ * @return string
+ */
+ public function srcset($reset = true)
+ {
+ if (empty($this->alternatives)) {
+ if ($reset) {
+ $this->reset();
+ }
+
+ return '';
+ }
+
+ $srcset = [];
+ foreach ($this->alternatives as $ratio => $medium) {
+ $srcset[] = $medium->url($reset) . ' ' . $medium->get('width') . 'w';
+ }
+ $srcset[] = str_replace(' ', '%20', $this->url($reset)) . ' ' . $this->get('width') . 'w';
+
+ return implode(', ', $srcset);
+ }
+
+ /**
+ * Allows the ability to override the image's pretty name stored in cache
+ *
+ * @param string $name
+ */
+ public function setImagePrettyName($name)
+ {
+ $this->set('prettyname', $name);
+ if ($this->image) {
+ $this->image->setPrettyName($name);
+ }
+ }
+
+ public function getImagePrettyName()
+ {
+ if ($this->get('prettyname')) {
+ return $this->get('prettyname');
+ }
+
+ $basename = $this->get('basename');
+ if (preg_match('/[a-z0-9]{40}-(.*)/', $basename, $matches)) {
+ $basename = $matches[1];
+ }
+ return $basename;
+ }
+
+ /**
+ * Generate alternative image widths, using either an array of integers, or
+ * a min width, a max width, and a step parameter to fill out the necessary
+ * widths. Existing image alternatives won't be overwritten.
+ *
+ * @param int|int[] $min_width
+ * @param int $max_width
+ * @param int $step
+ * @return $this
+ */
+ public function derivatives($min_width, $max_width = 2500, $step = 200)
+ {
+ if (!empty($this->alternatives)) {
+ $max = max(array_keys($this->alternatives));
+ $base = $this->alternatives[$max];
+ } else {
+ $base = $this;
+ }
+
+ $widths = [];
+
+ if (func_num_args() === 1) {
+ foreach ((array) func_get_arg(0) as $width) {
+ if ($width < $base->get('width')) {
+ $widths[] = $width;
+ }
+ }
+ } else {
+ $max_width = min($max_width, $base->get('width'));
+
+ for ($width = $min_width; $width < $max_width; $width = $width + $step) {
+ $widths[] = $width;
+ }
+ }
+
+ foreach ($widths as $width) {
+ // Only generate image alternatives that don't already exist
+ if (array_key_exists((int) $width, $this->alternatives)) {
+ continue;
+ }
+
+ $derivative = MediumFactory::fromFile($base->get('filepath'));
+
+ // It's possible that MediumFactory::fromFile returns null if the
+ // original image file no longer exists and this class instance was
+ // retrieved from the page cache
+ if (null !== $derivative) {
+ $index = 2;
+ $alt_widths = array_keys($this->alternatives);
+ sort($alt_widths);
+
+ foreach ($alt_widths as $i => $key) {
+ if ($width > $key) {
+ $index += max($i, 1);
+ }
+ }
+
+ $basename = preg_replace('/(@\d+x){0,1}$/', "@{$width}w", $base->get('basename'), 1);
+ $derivative->setImagePrettyName($basename);
+
+ $ratio = $base->get('width') / $width;
+ $height = $derivative->get('height') / $ratio;
+
+ $derivative->resize($width, $height);
+ $derivative->set('width', $width);
+ $derivative->set('height', $height);
+
+ $this->addAlternative($ratio, $derivative);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Parsedown element for source display mode
+ *
+ * @param array $attributes
+ * @param bool $reset
+ * @return array
+ */
+ public function sourceParsedownElement(array $attributes, $reset = true)
+ {
+ empty($attributes['src']) && $attributes['src'] = $this->url(false);
+
+ $srcset = $this->srcset($reset);
+ if ($srcset) {
+ empty($attributes['srcset']) && $attributes['srcset'] = $srcset;
+ $attributes['sizes'] = $this->sizes();
+ }
+
+ return ['name' => 'img', 'attributes' => $attributes];
+ }
+
+ /**
+ * Reset image.
+ *
+ * @return $this
+ */
+ public function reset()
+ {
+ parent::reset();
+
+ if ($this->image) {
+ $this->image();
+ $this->medium_querystring = [];
+ $this->filter();
+ $this->clearAlternatives();
+ }
+
+ $this->format = 'guess';
+ $this->quality = $this->default_quality;
+
+ $this->debug_watermarked = false;
+
+ return $this;
+ }
+
+ /**
+ * Turn the current Medium into a Link
+ *
+ * @param bool $reset
+ * @param array $attributes
+ * @return Link
+ */
+ public function link($reset = true, array $attributes = [])
+ {
+ $attributes['href'] = $this->url(false);
+ $srcset = $this->srcset(false);
+ if ($srcset) {
+ $attributes['data-srcset'] = $srcset;
+ }
+
+ return parent::link($reset, $attributes);
+ }
+
+ /**
+ * Turn the current Medium into a Link with lightbox enabled
+ *
+ * @param int $width
+ * @param int $height
+ * @param bool $reset
+ * @return Link
+ */
+ public function lightbox($width = null, $height = null, $reset = true)
+ {
+ if ($this->mode !== 'source') {
+ $this->display('source');
+ }
+
+ if ($width && $height) {
+ $this->__call('cropResize', [$width, $height]);
+ }
+
+ return parent::lightbox($width, $height, $reset);
+ }
+
+ /**
+ * Sets or gets the quality of the image
+ *
+ * @param int $quality 0-100 quality
+ * @return int|$this
+ */
+ public function quality($quality = null)
+ {
+ if ($quality) {
+ if (!$this->image) {
+ $this->image();
+ }
+
+ $this->quality = $quality;
+
+ return $this;
+ }
+
+ return $this->quality;
+ }
+
+ /**
+ * Sets image output format.
+ *
+ * @param string $format
+ * @return $this
+ */
+ public function format($format)
+ {
+ if (!$this->image) {
+ $this->image();
+ }
+
+ $this->format = $format;
+
+ return $this;
+ }
+
+ /**
+ * Set or get sizes parameter for srcset media action
+ *
+ * @param string $sizes
+ * @return string
+ */
+ public function sizes($sizes = null)
+ {
+
+ if ($sizes) {
+ $this->sizes = $sizes;
+
+ return $this;
+ }
+
+ return empty($this->sizes) ? '100vw' : $this->sizes;
+ }
+
+ /**
+ * Allows to set the width attribute from Markdown or Twig
+ * Examples: 
+ * 
+ * 
+ * 
+ * {{ page.media['myimg.png'].width().height().html }}
+ * {{ page.media['myimg.png'].resize(100,200).width(100).height(200).html }}
+ *
+ * @param mixed $value A value or 'auto' or empty to use the width of the image
+ * @return $this
+ */
+ public function width($value = 'auto')
+ {
+ if (!$value || $value === 'auto') {
+ $this->attributes['width'] = $this->get('width');
+ } else {
+ $this->attributes['width'] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Allows to set the height attribute from Markdown or Twig
+ * Examples: 
+ * 
+ * 
+ * 
+ * {{ page.media['myimg.png'].width().height().html }}
+ * {{ page.media['myimg.png'].resize(100,200).width(100).height(200).html }}
+ *
+ * @param mixed $value A value or 'auto' or empty to use the height of the image
+ * @return $this
+ */
+ public function height($value = 'auto')
+ {
+ if (!$value || $value === 'auto') {
+ $this->attributes['height'] = $this->get('height');
+ } else {
+ $this->attributes['height'] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Handle this commonly used variant
+ */
+ public function cropZoom()
+ {
+ $this->__call('zoomCrop', func_get_args());
+ return $this;
+ }
+
+ /**
+ * Forward the call to the image processing method.
+ *
+ * @param string $method
+ * @param mixed $args
+ * @return $this|mixed
+ */
+ public function __call($method, $args)
+ {
+ if (!\in_array($method, self::$magic_actions, true)) {
+ return parent::__call($method, $args);
+ }
+
+ // Always initialize image.
+ if (!$this->image) {
+ $this->image();
+ }
+
+ try {
+ call_user_func_array([$this->image, $method], $args);
+
+ foreach ($this->alternatives as $medium) {
+ if (!$medium->image) {
+ $medium->image();
+ }
+
+ $args_copy = $args;
+
+ // regular image: resize 400x400 -> 200x200
+ // --> @2x: resize 800x800->400x400
+ if (isset(self::$magic_resize_actions[$method])) {
+ foreach (self::$magic_resize_actions[$method] as $param) {
+ if (isset($args_copy[$param])) {
+ $args_copy[$param] *= $medium->get('ratio');
+ }
+ }
+ }
+
+ call_user_func_array([$medium, $method], $args_copy);
+ }
+ } catch (\BadFunctionCallException $e) {
+ }
+
+ return $this;
+ }
+
+ /**
+ * Gets medium image, resets image manipulation operations.
+ *
+ * @return $this
+ */
+ protected function image()
+ {
+ $locator = Grav::instance()['locator'];
+
+ $file = $this->get('filepath');
+
+ // Use existing cache folder or if it doesn't exist, create it.
+ $cacheDir = $locator->findResource('cache://images', true) ?: $locator->findResource('cache://images', true, true);
+
+ // Make sure we free previous image.
+ unset($this->image);
+
+ $this->image = ImageFile::open($file)
+ ->setCacheDir($cacheDir)
+ ->setActualCacheDir($cacheDir)
+ ->setPrettyName($this->getImagePrettyName());
+
+ return $this;
+ }
+
+ /**
+ * Save the image with cache.
+ *
+ * @return string
+ */
+ protected function saveImage()
+ {
+ if (!$this->image) {
+ return parent::path(false);
+ }
+
+ $this->filter();
+
+ if (isset($this->result)) {
+ return $this->result;
+ }
+
+ if (!$this->debug_watermarked && $this->get('debug')) {
+ $ratio = $this->get('ratio');
+ if (!$ratio) {
+ $ratio = 1;
+ }
+
+ $locator = Grav::instance()['locator'];
+ $overlay = $locator->findResource("system://assets/responsive-overlays/{$ratio}x.png") ?: $locator->findResource('system://assets/responsive-overlays/unknown.png');
+ $this->image->merge(ImageFile::open($overlay));
+ }
+
+ return $this->image->cacheFile($this->format, $this->quality, false, [$this->get('width'), $this->get('height'), $this->get('modified')]);
+ }
+
+ /**
+ * Filter image by using user defined filter parameters.
+ *
+ * @param string $filter Filter to be used.
+ */
+ public function filter($filter = 'image.filters.default')
+ {
+ $filters = (array) $this->get($filter, []);
+ foreach ($filters as $params) {
+ $params = (array) $params;
+ $method = array_shift($params);
+ $this->__call($method, $params);
+ }
+ }
+
+ /**
+ * Return the image higher quality version
+ *
+ * @return ImageMedium the alternative version with higher quality
+ */
+ public function higherQualityAlternative()
+ {
+ if ($this->alternatives) {
+ $max = reset($this->alternatives);
+ foreach($this->alternatives as $alternative)
+ {
+ if($alternative->quality() > $max->quality())
+ {
+ $max = $alternative;
+ }
+ }
+
+ return $max;
+ }
+
+ return $this;
+ }
+
+}
diff --git a/system/src/Grav/Common/Page/Medium/Link.php b/system/src/Grav/Common/Page/Medium/Link.php
new file mode 100644
index 0000000..6978635
--- /dev/null
+++ b/system/src/Grav/Common/Page/Medium/Link.php
@@ -0,0 +1,71 @@
+attributes = $attributes;
+ $this->source = $medium->reset()->thumbnail('auto')->display('thumbnail');
+ $this->source->linked = true;
+ }
+
+ /**
+ * Get an element (is array) that can be rendered by the Parsedown engine
+ *
+ * @param string $title
+ * @param string $alt
+ * @param string $class
+ * @param string $id
+ * @param bool $reset
+ * @return array
+ */
+ public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true)
+ {
+ $innerElement = $this->source->parsedownElement($title, $alt, $class, $id, $reset);
+
+ return [
+ 'name' => 'a',
+ 'attributes' => $this->attributes,
+ 'handler' => is_string($innerElement) ? 'line' : 'element',
+ 'text' => $innerElement
+ ];
+ }
+
+ /**
+ * Forward the call to the source element
+ *
+ * @param string $method
+ * @param mixed $args
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ $this->source = call_user_func_array(array($this->source, $method), $args);
+
+ // Don't start nesting links, if user has multiple link calls in his
+ // actions, we will drop the previous links.
+ return $this->source instanceof Link ? $this->source : $this;
+ }
+}
diff --git a/system/src/Grav/Common/Page/Medium/Medium.php b/system/src/Grav/Common/Page/Medium/Medium.php
new file mode 100644
index 0000000..bf0bb1f
--- /dev/null
+++ b/system/src/Grav/Common/Page/Medium/Medium.php
@@ -0,0 +1,679 @@
+get('system.media.enable_media_timestamp', true)) {
+ $this->timestamp = Grav::instance()['cache']->getKey();
+ }
+
+ $this->def('mime', 'application/octet-stream');
+ $this->reset();
+ }
+
+ public function __clone()
+ {
+ // Allows future compatibility as parent::__clone() works.
+ }
+
+ /**
+ * Create a copy of this media object
+ *
+ * @return Medium
+ */
+ public function copy()
+ {
+ return clone $this;
+ }
+
+ /**
+ * Return just metadata from the Medium object
+ *
+ * @return Data
+ */
+ public function meta()
+ {
+ return new Data($this->items);
+ }
+
+ /**
+ * Check if this medium exists or not
+ *
+ * @return bool
+ */
+ public function exists()
+ {
+ $path = $this->get('filepath');
+ if (file_exists($path)) {
+ return true;
+ }
+ 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
+ *
+ * @return array
+ */
+ public function metadata()
+ {
+ return $this->metadata;
+ }
+
+ /**
+ * Add meta file for the medium.
+ *
+ * @param string $filepath
+ */
+ public function addMetaFile($filepath)
+ {
+ $this->metadata = (array)CompiledYamlFile::instance($filepath)->content();
+ $this->merge($this->metadata);
+ }
+
+ /**
+ * Add alternative Medium to this Medium.
+ *
+ * @param int|float $ratio
+ * @param Medium $alternative
+ */
+ public function addAlternative($ratio, Medium $alternative)
+ {
+ if (!is_numeric($ratio) || $ratio === 0) {
+ return;
+ }
+
+ $alternative->set('ratio', $ratio);
+ $width = $alternative->get('width');
+
+ $this->alternatives[$width] = $alternative;
+ }
+
+ /**
+ * Return string representation of the object (html).
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->html();
+ }
+
+ /**
+ * Return PATH to file.
+ *
+ * @param bool $reset
+ * @return string path to file
+ */
+ public function path($reset = true)
+ {
+ if ($reset) {
+ $this->reset();
+ }
+
+ return $this->get('filepath');
+ }
+
+ /**
+ * Return the relative path to file
+ *
+ * @param bool $reset
+ * @return mixed
+ */
+ 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, '', $output);
+ }
+
+ /**
+ * Return URL to file.
+ *
+ * @param bool $reset
+ * @return string
+ */
+ public function url($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 trim(Grav::instance()['base_url'] . '/' . $this->urlQuerystring($output), '\\');
+ }
+
+ /**
+ * Get/set querystring for the file's url
+ *
+ * @param string $querystring
+ * @param bool $withQuestionmark
+ * @return string
+ */
+ public function querystring($querystring = null, $withQuestionmark = true)
+ {
+ if (null !== $querystring) {
+ $this->medium_querystring[] = ltrim($querystring, '?&');
+ foreach ($this->alternatives as $alt) {
+ $alt->querystring($querystring, $withQuestionmark);
+ }
+ }
+
+ 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 bool $withHash
+ * @return string
+ */
+ public function urlHash($hash = null, $withHash = true)
+ {
+ if ($hash) {
+ $this->set('urlHash', ltrim($hash, '#'));
+ }
+
+ $hash = $this->get('urlHash', '');
+
+ return $withHash && !empty($hash) ? '#' . $hash : $hash;
+ }
+
+ /**
+ * Get an element (is array) that can be rendered by the Parsedown engine
+ *
+ * @param string $title
+ * @param string $alt
+ * @param string $class
+ * @param string $id
+ * @param bool $reset
+ * @return array
+ */
+ public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true)
+ {
+ $attributes = $this->attributes;
+
+ $style = '';
+ foreach ($this->styleAttributes as $key => $value) {
+ if (is_numeric($key)) // Special case for inline style attributes, refer to style() method
+ $style .= $value;
+ else
+ $style .= $key . ': ' . $value . ';';
+ }
+ if ($style) {
+ $attributes['style'] = $style;
+ }
+
+ if (empty($attributes['title'])) {
+ if (!empty($title)) {
+ $attributes['title'] = $title;
+ } elseif (!empty($this->items['title'])) {
+ $attributes['title'] = $this->items['title'];
+ }
+ }
+
+ if (empty($attributes['alt'])) {
+ if (!empty($alt)) {
+ $attributes['alt'] = $alt;
+ } elseif (!empty($this->items['alt'])) {
+ $attributes['alt'] = $this->items['alt'];
+ } elseif (!empty($this->items['alt_text'])) {
+ $attributes['alt'] = $this->items['alt_text'];
+ } else {
+ $attributes['alt'] = '';
+ }
+ }
+
+ if (empty($attributes['class'])) {
+ if (!empty($class)) {
+ $attributes['class'] = $class;
+ } elseif (!empty($this->items['class'])) {
+ $attributes['class'] = $this->items['class'];
+ }
+ }
+
+ if (empty($attributes['id'])) {
+ if (!empty($id)) {
+ $attributes['id'] = $id;
+ } elseif (!empty($this->items['id'])) {
+ $attributes['id'] = $this->items['id'];
+ }
+ }
+
+ switch ($this->mode) {
+ case 'text':
+ $element = $this->textParsedownElement($attributes, false);
+ break;
+ case 'thumbnail':
+ $element = $this->getThumbnail()->sourceParsedownElement($attributes, false);
+ break;
+ case 'source':
+ $element = $this->sourceParsedownElement($attributes, false);
+ break;
+ default:
+ $element = [];
+ }
+
+ if ($reset) {
+ $this->reset();
+ }
+
+ $this->display('source');
+
+ return $element;
+ }
+
+ /**
+ * Parsedown element for source display mode
+ *
+ * @param array $attributes
+ * @param bool $reset
+ * @return array
+ */
+ protected function sourceParsedownElement(array $attributes, $reset = true)
+ {
+ return $this->textParsedownElement($attributes, $reset);
+ }
+
+ /**
+ * Parsedown element for text display mode
+ *
+ * @param array $attributes
+ * @param bool $reset
+ * @return array
+ */
+ protected function textParsedownElement(array $attributes, $reset = true)
+ {
+ $text = empty($attributes['title']) ? empty($attributes['alt']) ? $this->get('filename') : $attributes['alt'] : $attributes['title'];
+
+ $element = [
+ 'name' => 'p',
+ 'attributes' => $attributes,
+ 'text' => $text
+ ];
+
+ if ($reset) {
+ $this->reset();
+ }
+
+ return $element;
+ }
+
+ /**
+ * Reset medium.
+ *
+ * @return $this
+ */
+ public function reset()
+ {
+ $this->attributes = [];
+ return $this;
+ }
+
+ /**
+ * Switch display mode.
+ *
+ * @param string $mode
+ *
+ * @return $this
+ */
+ public function display($mode = 'source')
+ {
+ if ($this->mode === $mode) {
+ return $this;
+ }
+
+
+ $this->mode = $mode;
+
+ return $mode === 'thumbnail' ? ($this->getThumbnail() ? $this->getThumbnail()->reset() : null) : $this->reset();
+ }
+
+ /**
+ * Helper method to determine if this media item has a thumbnail or not
+ *
+ * @param string $type;
+ *
+ * @return bool
+ */
+ public function thumbnailExists($type = 'page')
+ {
+ $thumbs = $this->get('thumbnails');
+ if (isset($thumbs[$type])) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Switch thumbnail.
+ *
+ * @param string $type
+ *
+ * @return $this
+ */
+ public function thumbnail($type = 'auto')
+ {
+ if ($type !== 'auto' && !\in_array($type, $this->thumbnailTypes, true)) {
+ return $this;
+ }
+
+ if ($this->thumbnailType !== $type) {
+ $this->_thumbnail = null;
+ }
+
+ $this->thumbnailType = $type;
+
+ return $this;
+ }
+
+
+ /**
+ * Turn the current Medium into a Link
+ *
+ * @param bool $reset
+ * @param array $attributes
+ * @return Link
+ */
+ public function link($reset = true, array $attributes = [])
+ {
+ if ($this->mode !== 'source') {
+ $this->display('source');
+ }
+
+ foreach ($this->attributes as $key => $value) {
+ empty($attributes['data-' . $key]) && $attributes['data-' . $key] = $value;
+ }
+
+ empty($attributes['href']) && $attributes['href'] = $this->url();
+
+ return new Link($attributes, $this);
+ }
+
+ /**
+ * Turn the current Medium into a Link with lightbox enabled
+ *
+ * @param int $width
+ * @param int $height
+ * @param bool $reset
+ * @return Link
+ */
+ public function lightbox($width = null, $height = null, $reset = true)
+ {
+ $attributes = ['rel' => 'lightbox'];
+
+ if ($width && $height) {
+ $attributes['data-width'] = $width;
+ $attributes['data-height'] = $height;
+ }
+
+ return $this->link($reset, $attributes);
+ }
+
+ /**
+ * Add a class to the element from Markdown or Twig
+ * Example:  or 
+ *
+ * @return $this
+ */
+ public function classes()
+ {
+ $classes = func_get_args();
+ if (!empty($classes)) {
+ $this->attributes['class'] = implode(',', $classes);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add an id to the element from Markdown or Twig
+ * Example: 
+ *
+ * @param string $id
+ * @return $this
+ */
+ public function id($id)
+ {
+ if (is_string($id)) {
+ $this->attributes['id'] = trim($id);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Allows to add an inline style attribute from Markdown or Twig
+ * Example: 
+ *
+ * @param string $style
+ * @return $this
+ */
+ public function style($style)
+ {
+ $this->styleAttributes[] = rtrim($style, ';') . ';';
+ return $this;
+ }
+
+ /**
+ * Allow any action to be called on this medium from twig or markdown
+ *
+ * @param string $method
+ * @param mixed $args
+ * @return $this
+ */
+ public function __call($method, $args)
+ {
+ $qs = $method;
+ if (\count($args) > 1 || (\count($args) === 1 && !empty($args[0]))) {
+ $qs .= '=' . implode(',', array_map(function ($a) {
+ if (is_array($a)) {
+ $a = '[' . implode(',', $a) . ']';
+ }
+ return rawurlencode($a);
+ }, $args));
+ }
+
+ if (!empty($qs)) {
+ $this->querystring($this->querystring(null, false) . '&' . $qs);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the thumbnail Medium object
+ *
+ * @return ThumbnailImageMedium
+ */
+ protected function getThumbnail()
+ {
+ if (!$this->_thumbnail) {
+ $types = $this->thumbnailTypes;
+
+ if ($this->thumbnailType !== 'auto') {
+ array_unshift($types, $this->thumbnailType);
+ }
+
+ foreach ($types as $type) {
+ $thumb = $this->get('thumbnails.' . $type, false);
+
+ if ($thumb) {
+ $thumb = $thumb instanceof ThumbnailImageMedium ? $thumb : MediumFactory::fromFile($thumb, ['type' => 'thumbnail']);
+ $thumb->parent = $this;
+ }
+
+ if ($thumb) {
+ $this->_thumbnail = $thumb;
+ break;
+ }
+ }
+ }
+
+ return $this->_thumbnail;
+ }
+
+}
diff --git a/system/src/Grav/Common/Page/Medium/MediumFactory.php b/system/src/Grav/Common/Page/Medium/MediumFactory.php
new file mode 100644
index 0000000..dc372d8
--- /dev/null
+++ b/system/src/Grav/Common/Page/Medium/MediumFactory.php
@@ -0,0 +1,192 @@
+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',
+ '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',
+ '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 array of parameters
+ *
+ * @param array $items
+ * @param Blueprint|null $blueprint
+ * @return Medium
+ */
+ public static function fromArray(array $items = [], Blueprint $blueprint = null)
+ {
+ $type = $items['type'] ?? null;
+
+ switch ($type) {
+ case 'image':
+ return new ImageMedium($items, $blueprint);
+ case 'thumbnail':
+ return new ThumbnailImageMedium($items, $blueprint);
+ case 'animated':
+ case 'vector':
+ return new StaticImageMedium($items, $blueprint);
+ case 'video':
+ return new VideoMedium($items, $blueprint);
+ case 'audio':
+ return new AudioMedium($items, $blueprint);
+ default:
+ return new Medium($items, $blueprint);
+ }
+ }
+
+ /**
+ * Create a new ImageMedium by scaling another ImageMedium object.
+ *
+ * @param ImageMedium $medium
+ * @param int $from
+ * @param int $to
+ * @return Medium|array
+ */
+ public static function scaledFromMedium($medium, $from, $to)
+ {
+ if (! $medium instanceof ImageMedium) {
+ return $medium;
+ }
+
+ if ($to > $from) {
+ return $medium;
+ }
+
+ $ratio = $to / $from;
+ $width = $medium->get('width') * $ratio;
+ $height = $medium->get('height') * $ratio;
+
+ $prev_basename = $medium->get('basename');
+ $basename = str_replace('@'.$from.'x', '@'.$to.'x', $prev_basename);
+
+ $debug = $medium->get('debug');
+ $medium->set('debug', false);
+ $medium->setImagePrettyName($basename);
+
+ $file = $medium->resize($width, $height)->path();
+
+ $medium->set('debug', $debug);
+ $medium->setImagePrettyName($prev_basename);
+
+ $size = filesize($file);
+
+ $medium = self::fromFile($file);
+ if ($medium) {
+ $medium->set('size', $size);
+ }
+
+ return ['file' => $medium, 'size' => $size];
+ }
+}
diff --git a/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php b/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php
new file mode 100644
index 0000000..c6d75bc
--- /dev/null
+++ b/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php
@@ -0,0 +1,42 @@
+parsedownElement($title, $alt, $class, $id, $reset);
+
+ if (!$this->parsedown) {
+ $this->parsedown = new Parsedown(new Excerpts());
+ }
+
+ return $this->parsedown->elementToHtml($element);
+ }
+}
diff --git a/system/src/Grav/Common/Page/Medium/RenderableInterface.php b/system/src/Grav/Common/Page/Medium/RenderableInterface.php
new file mode 100644
index 0000000..50a6e67
--- /dev/null
+++ b/system/src/Grav/Common/Page/Medium/RenderableInterface.php
@@ -0,0 +1,36 @@
+url($reset);
+
+ 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
new file mode 100644
index 0000000..8d6d43f
--- /dev/null
+++ b/system/src/Grav/Common/Page/Medium/StaticResizeTrait.php
@@ -0,0 +1,28 @@
+styleAttributes['width'] = $width . 'px';
+ $this->styleAttributes['height'] = $height . 'px';
+
+ return $this;
+ }
+}
diff --git a/system/src/Grav/Common/Page/Medium/ThumbnailImageMedium.php b/system/src/Grav/Common/Page/Medium/ThumbnailImageMedium.php
new file mode 100644
index 0000000..380baee
--- /dev/null
+++ b/system/src/Grav/Common/Page/Medium/ThumbnailImageMedium.php
@@ -0,0 +1,132 @@
+bubble('parsedownElement', [$title, $alt, $class, $id, $reset]);
+ }
+
+ /**
+ * Return HTML markup from the medium.
+ *
+ * @param string $title
+ * @param string $alt
+ * @param string $class
+ * @param string $id
+ * @param bool $reset
+ * @return string
+ */
+ public function html($title = null, $alt = null, $class = null, $id = null, $reset = true)
+ {
+ return $this->bubble('html', [$title, $alt, $class, $id, $reset]);
+ }
+
+ /**
+ * Switch display mode.
+ *
+ * @param string $mode
+ *
+ * @return $this
+ */
+ public function display($mode = 'source')
+ {
+ return $this->bubble('display', [$mode], false);
+ }
+
+ /**
+ * Switch thumbnail.
+ *
+ * @param string $type
+ *
+ * @return $this
+ */
+ public function thumbnail($type = 'auto')
+ {
+ $this->bubble('thumbnail', [$type], false);
+
+ return $this->bubble('getThumbnail', [], false);
+ }
+
+ /**
+ * Turn the current Medium into a Link
+ *
+ * @param bool $reset
+ * @param array $attributes
+ * @return Link
+ */
+ public function link($reset = true, array $attributes = [])
+ {
+ return $this->bubble('link', [$reset, $attributes], false);
+ }
+
+ /**
+ * Turn the current Medium into a Link with lightbox enabled
+ *
+ * @param int $width
+ * @param int $height
+ * @param bool $reset
+ * @return Link
+ */
+ public function lightbox($width = null, $height = null, $reset = true)
+ {
+ return $this->bubble('lightbox', [$width, $height, $reset], false);
+ }
+
+ /**
+ * Bubble a function call up to either the superclass function or the parent Medium instance
+ *
+ * @param string $method
+ * @param array $arguments
+ * @param bool $testLinked
+ * @return Medium
+ */
+ protected function bubble($method, array $arguments = [], $testLinked = true)
+ {
+ if (!$testLinked || $this->linked) {
+ return $this->parent ? call_user_func_array(array($this->parent, $method), $arguments) : $this;
+ }
+
+ return call_user_func_array(array($this, 'parent::' . $method), $arguments);
+ }
+}
diff --git a/system/src/Grav/Common/Page/Medium/VideoMedium.php b/system/src/Grav/Common/Page/Medium/VideoMedium.php
new file mode 100644
index 0000000..c0b488a
--- /dev/null
+++ b/system/src/Grav/Common/Page/Medium/VideoMedium.php
@@ -0,0 +1,162 @@
+url($reset);
+
+ return [
+ 'name' => 'video',
+ 'text' => 'Your browser does not support the video tag.',
+ 'attributes' => $attributes
+ ];
+ }
+
+ /**
+ * Allows to set or remove the HTML5 default controls
+ *
+ * @param bool $display
+ * @return $this
+ */
+ public function controls($display = true)
+ {
+ if($display) {
+ $this->attributes['controls'] = true;
+ } else {
+ unset($this->attributes['controls']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Allows to set the video's poster image
+ *
+ * @param string $urlImage
+ * @return $this
+ */
+ public function poster($urlImage)
+ {
+ $this->attributes['poster'] = $urlImage;
+
+ return $this;
+ }
+
+ /**
+ * Allows to set the loop attribute
+ *
+ * @param bool $status
+ * @return $this
+ */
+ public function loop($status = false)
+ {
+ if($status) {
+ $this->attributes['loop'] = true;
+ } else {
+ unset($this->attributes['loop']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Allows to set the autoplay attribute
+ *
+ * @param bool $status
+ * @return $this
+ */
+ public function autoplay($status = false)
+ {
+ if ($status) {
+ $this->attributes['autoplay'] = '';
+ } else {
+ unset($this->attributes['autoplay']);
+ }
+
+ 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
+ *
+ * @param bool $status
+ * @return $this
+ */
+ public function playsinline($status = false)
+ {
+ if($status) {
+ $this->attributes['playsinline'] = true;
+ } else {
+ unset($this->attributes['playsinline']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Allows to set the muted attribute
+ *
+ * @param bool $status
+ * @return $this
+ */
+ public function muted($status = false)
+ {
+ if($status) {
+ $this->attributes['muted'] = true;
+ } else {
+ unset($this->attributes['muted']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Reset medium.
+ *
+ * @return $this
+ */
+ public function reset()
+ {
+ parent::reset();
+
+ $this->attributes['controls'] = true;
+
+ return $this;
+ }
+}
diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php
new file mode 100644
index 0000000..b33d514
--- /dev/null
+++ b/system/src/Grav/Common/Page/Page.php
@@ -0,0 +1,3183 @@
+taxonomy = [];
+ $this->process = $config->get('system.pages.process');
+ $this->published = true;
+ }
+
+ /**
+ * Initializes the page instance variables based on a file
+ *
+ * @param \SplFileInfo $file The file information for the .md file that the page represents
+ * @param string $extension
+ *
+ * @return $this
+ */
+ public function init(\SplFileInfo $file, $extension = null)
+ {
+ $config = Grav::instance()['config'];
+
+ // 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);
+
+ $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->modified($file->getMTime());
+ $this->id($this->modified() . md5($this->filePath()));
+ $this->routable(true);
+ $this->header();
+ $this->date();
+ $this->metadata();
+ $this->url();
+ $this->visible();
+ $this->modularTwig(strpos($this->slug(), '_') === 0);
+ $this->setPublishState();
+ $this->published();
+ $this->urlExtension();
+
+
+ return $this;
+ }
+
+ protected function processFrontmatter()
+ {
+ // Quick check for twig output tags in frontmatter if enabled
+ $process_fields = (array)$this->header();
+ if (Utils::contains(json_encode(array_values($process_fields)), '{{')) {
+ $ignored_fields = [];
+ foreach ((array)Grav::instance()['config']->get('system.pages.frontmatter.ignore_fields') as $field) {
+ if (isset($process_fields[$field])) {
+ $ignored_fields[$field] = $process_fields[$field];
+ unset($process_fields[$field]);
+ }
+ }
+ $text_header = Grav::instance()['twig']->processString(json_encode($process_fields, JSON_UNESCAPED_UNICODE), ['page' => $this]);
+ $this->header((object)(json_decode($text_header, true) + $ignored_fields));
+ }
+ }
+
+ /**
+ * Return an array with the routes of other translated languages
+ *
+ * @param bool $onlyPublished only return published translations
+ *
+ * @return array the page translated languages
+ */
+ public function translatedLanguages($onlyPublished = false)
+ {
+ $filename = substr($this->name, 0, -(strlen($this->extension())));
+ $config = Grav::instance()['config'];
+ $languages = $config->get('system.languages.supported', []);
+ $translatedLanguages = [];
+
+ foreach ($languages as $language) {
+ $path = $this->path . DS . $this->folder . DS . $filename . '.' . $language . '.md';
+ if (file_exists($path)) {
+ $aPage = new Page();
+ $aPage->init(new \SplFileInfo($path), $language . '.md');
+
+ $route = $aPage->header()->routes['default'] ?? $aPage->rawRoute();
+ if (!$route) {
+ $route = $aPage->route();
+ }
+
+ if ($onlyPublished && !$aPage->published()) {
+ continue;
+ }
+
+ $translatedLanguages[$language] = $route;
+ }
+ }
+
+ return $translatedLanguages;
+ }
+
+ /**
+ * Return an array listing untranslated languages available
+ *
+ * @param bool $includeUnpublished also list unpublished translations
+ *
+ * @return array the page untranslated languages
+ */
+ public function untranslatedLanguages($includeUnpublished = false)
+ {
+ $filename = substr($this->name, 0, -strlen($this->extension()));
+ $config = Grav::instance()['config'];
+ $languages = $config->get('system.languages.supported', []);
+ $untranslatedLanguages = [];
+
+ foreach ($languages as $language) {
+ $path = $this->path . DS . $this->folder . DS . $filename . '.' . $language . '.md';
+ if (file_exists($path)) {
+ $aPage = new Page();
+ $aPage->init(new \SplFileInfo($path), $language . '.md');
+ if ($includeUnpublished && !$aPage->published()) {
+ $untranslatedLanguages[] = $language;
+ }
+ } else {
+ $untranslatedLanguages[] = $language;
+ }
+ }
+
+ return $untranslatedLanguages;
+ }
+
+ /**
+ * Gets and Sets the raw data
+ *
+ * @param string $var Raw content string
+ *
+ * @return string Raw content string
+ */
+ public function raw($var = null)
+ {
+ $file = $this->file();
+
+ if ($var) {
+ // First update file object.
+ if ($file) {
+ $file->raw($var);
+ }
+
+ // Reset header and content.
+ $this->modified = time();
+ $this->id($this->modified() . md5($this->filePath()));
+ $this->header = null;
+ $this->content = null;
+ $this->summary = null;
+ }
+
+ return $file ? $file->raw() : '';
+ }
+
+ /**
+ * Gets and Sets the page frontmatter
+ *
+ * @param string|null $var
+ *
+ * @return string
+ */
+ public function frontmatter($var = null)
+ {
+
+ if ($var) {
+ $this->frontmatter = (string)$var;
+
+ // Update also file object.
+ $file = $this->file();
+ if ($file) {
+ $file->frontmatter((string)$var);
+ }
+
+ // Force content re-processing.
+ $this->id(time() . md5($this->filePath()));
+ }
+ if (!$this->frontmatter) {
+ $this->header();
+ }
+
+ return $this->frontmatter;
+ }
+
+ /**
+ * Gets and Sets the header based on the YAML configuration at the top of the .md file
+ *
+ * @param object|array $var a YAML object representing the configuration for the file
+ *
+ * @return object the current YAML configuration
+ */
+ public function header($var = null)
+ {
+ if ($var) {
+ $this->header = (object)$var;
+
+ // Update also file object.
+ $file = $this->file();
+ if ($file) {
+ $file->header((array)$var);
+ }
+
+ // Force content re-processing.
+ $this->id(time() . md5($this->filePath()));
+ }
+ if (!$this->header) {
+ $file = $this->file();
+ if ($file) {
+ try {
+ $this->raw_content = $file->markdown();
+ $this->frontmatter = $file->frontmatter();
+ $this->header = (object)$file->header();
+
+ if (!Utils::isAdminPlugin()) {
+ // If there's a `frontmatter.yaml` file merge that in with the page header
+ // note page's own frontmatter has precedence and will overwrite any defaults
+ $frontmatterFile = CompiledYamlFile::instance($this->path . '/' . $this->folder . '/frontmatter.yaml');
+ if ($frontmatterFile->exists()) {
+ $frontmatter_data = (array)$frontmatterFile->content();
+ $this->header = (object)array_replace_recursive($frontmatter_data,
+ (array)$this->header);
+ $frontmatterFile->free();
+ }
+ // Process frontmatter with Twig if enabled
+ if (Grav::instance()['config']->get('system.pages.frontmatter.process_twig') === true) {
+ $this->processFrontmatter();
+ }
+ }
+ } catch (\Exception $e) {
+ $file->raw(Grav::instance()['language']->translate([
+ 'GRAV.FRONTMATTER_ERROR_PAGE',
+ $this->slug(),
+ $file->filename(),
+ $e->getMessage(),
+ $file->raw()
+ ]));
+ $this->raw_content = $file->markdown();
+ $this->frontmatter = $file->frontmatter();
+ $this->header = (object)$file->header();
+ }
+ $var = true;
+ }
+
+
+ }
+
+ if ($var) {
+ if (isset($this->header->slug)) {
+ $this->slug($this->header->slug);
+ }
+ if (isset($this->header->routes)) {
+ $this->routes = (array)$this->header->routes;
+ }
+ if (isset($this->header->title)) {
+ $this->title = trim($this->header->title);
+ }
+ if (isset($this->header->language)) {
+ $this->language = trim($this->header->language);
+ }
+ if (isset($this->header->template)) {
+ $this->template = trim($this->header->template);
+ }
+ if (isset($this->header->menu)) {
+ $this->menu = trim($this->header->menu);
+ }
+ if (isset($this->header->routable)) {
+ $this->routable = (bool)$this->header->routable;
+ }
+ if (isset($this->header->visible)) {
+ $this->visible = (bool)$this->header->visible;
+ }
+ if (isset($this->header->redirect)) {
+ $this->redirect = trim($this->header->redirect);
+ }
+ if (isset($this->header->external_url)) {
+ $this->external_url = trim($this->header->external_url);
+ }
+ if (isset($this->header->order_dir)) {
+ $this->order_dir = trim($this->header->order_dir);
+ }
+ if (isset($this->header->order_by)) {
+ $this->order_by = trim($this->header->order_by);
+ }
+ if (isset($this->header->order_manual)) {
+ $this->order_manual = (array)$this->header->order_manual;
+ }
+ if (isset($this->header->dateformat)) {
+ $this->dateformat($this->header->dateformat);
+ }
+ if (isset($this->header->date)) {
+ $this->date($this->header->date);
+ }
+ if (isset($this->header->markdown_extra)) {
+ $this->markdown_extra = (bool)$this->header->markdown_extra;
+ }
+ if (isset($this->header->taxonomy)) {
+ $this->taxonomy($this->header->taxonomy);
+ }
+ if (isset($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) {
+ $this->process[$process] = (bool)$status;
+ }
+ }
+ if (isset($this->header->published)) {
+ $this->published = (bool)$this->header->published;
+ }
+ if (isset($this->header->publish_date)) {
+ $this->publishDate($this->header->publish_date);
+ }
+ if (isset($this->header->unpublish_date)) {
+ $this->unpublishDate($this->header->unpublish_date);
+ }
+ if (isset($this->header->expires)) {
+ $this->expires = (int)$this->header->expires;
+ }
+ if (isset($this->header->cache_control)) {
+ $this->cache_control = $this->header->cache_control;
+ }
+ if (isset($this->header->etag)) {
+ $this->etag = (bool)$this->header->etag;
+ }
+ if (isset($this->header->last_modified)) {
+ $this->last_modified = (bool)$this->header->last_modified;
+ }
+ if (isset($this->header->ssl)) {
+ $this->ssl = (bool)$this->header->ssl;
+ }
+ if (isset($this->header->template_format)) {
+ $this->template_format = $this->header->template_format;
+ }
+ 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;
+ }
+
+ /**
+ * Get page language
+ *
+ * @param string $var
+ *
+ * @return mixed
+ */
+ public function language($var = null)
+ {
+ if ($var !== null) {
+ $this->language = $var;
+ }
+
+ return $this->language;
+ }
+
+ /**
+ * Modify a header value directly
+ *
+ * @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;
+ }
+
+ // Ask Grav to calculate ETag from the final content.
+ if ($this->eTag()) {
+ $headers['ETag'] = '1';
+ }
+
+ // 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 bool $textOnly Only count text size.
+ *
+ * @return string
+ */
+ public function summary($size = null, $textOnly = false)
+ {
+ $config = (array)Grav::instance()['config']->get('site.summary');
+ if (isset($this->header->summary)) {
+ $config = array_merge($config, $this->header->summary);
+ }
+
+ // Return summary based on settings in site config file
+ if (!$config['enabled']) {
+ return $this->content();
+ }
+
+ // Set up variables to process summary from page or from custom summary
+ if ($this->summary === null) {
+ $content = $textOnly ? strip_tags($this->content()) : $this->content();
+ $summary_size = $this->summary_size;
+ } else {
+ $content = strip_tags($this->summary);
+ // Use mb_strwidth to deal with the 2 character widths characters
+ $summary_size = mb_strwidth($content, 'utf-8');
+ }
+
+ // Return calculated summary based on summary divider's position
+ $format = $config['format'];
+ // Return entire page content on wrong/ unknown format
+ if (!in_array($format, ['short', 'long'])) {
+ return $content;
+ }
+ if (($format === 'short') && isset($summary_size)) {
+ // Use mb_strimwidth to slice the string
+ if (mb_strwidth($content, 'utf8') > $summary_size) {
+ return mb_substr($content, 0, $summary_size);
+ }
+
+ return $content;
+ }
+
+ // Get summary size from site config's file
+ if ($size === null) {
+ $size = $config['size'];
+ }
+
+ // If the size is zero, return the entire page content
+ if ($size === 0) {
+ return $content;
+ // Return calculated summary based on defaults
+ }
+ if (!is_numeric($size) || ($size < 0)) {
+ $size = 300;
+ }
+
+ // Only return string but not html, wrap whatever html tag you want when using
+ if ($textOnly) {
+ if (mb_strwidth($content, 'utf-8') <= $size) {
+ return $content;
+ }
+
+ return mb_strimwidth($content, 0, $size, '…', 'UTF-8');
+ }
+
+ $summary = Utils::truncateHtml($content, $size);
+
+ return html_entity_decode($summary, ENT_COMPAT | ENT_HTML401, 'UTF-8');
+ }
+
+ /**
+ * Sets the summary of the page
+ *
+ * @param string $summary Summary
+ */
+ public function setSummary($summary)
+ {
+ $this->summary = $summary;
+ }
+
+ /**
+ * Gets and Sets the content based on content portion of the .md file
+ *
+ * @param string $var Content
+ *
+ * @return string Content
+ */
+ public function content($var = null)
+ {
+ if ($var !== null) {
+ $this->raw_content = $var;
+
+ // Update file object.
+ $file = $this->file();
+ if ($file) {
+ $file->markdown($var);
+ }
+
+ // Force re-processing.
+ $this->id(time() . md5($this->filePath()));
+ $this->content = null;
+ }
+ // If no content, process it
+ if ($this->content === null) {
+ // Get media
+ $this->media();
+
+ /** @var Config $config */
+ $config = Grav::instance()['config'];
+
+ // Load cached content
+ /** @var Cache $cache */
+ $cache = Grav::instance()['cache'];
+ $cache_id = md5('page' . $this->getCacheKey());
+ $content_obj = $cache->fetch($cache_id);
+
+ if (is_array($content_obj)) {
+ $this->content = $content_obj['content'];
+ $this->content_meta = $content_obj['content_meta'];
+ } else {
+ $this->content = $content_obj;
+ }
+
+
+ $process_markdown = $this->shouldProcess('markdown');
+ $process_twig = $this->shouldProcess('twig') || $this->modularTwig();
+
+ $cache_enable = $this->header->cache_enable ?? $config->get('system.cache.enabled',
+ true);
+ $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 = $this->header->never_cache_twig ?? $config->get('system.pages.never_cache_twig',
+ false);
+
+ // if no cached-content run everything
+ if ($never_cache_twig) {
+ if ($this->content === false || $cache_enable === false) {
+ $this->content = $this->raw_content;
+ Grav::instance()->fireEvent('onPageContentRaw', new Event(['page' => $this]));
+
+ if ($process_markdown) {
+ $this->processMarkdown();
+ }
+
+ // Content Processed but not cached yet
+ Grav::instance()->fireEvent('onPageContentProcessed', new Event(['page' => $this]));
+
+ if ($cache_enable) {
+ $this->cachePageContent();
+ }
+ }
+
+ if ($process_twig) {
+ $this->processTwig();
+ }
+
+ } else {
+ if ($this->content === false || $cache_enable === false) {
+ $this->content = $this->raw_content;
+ Grav::instance()->fireEvent('onPageContentRaw', new Event(['page' => $this]));
+
+ if ($twig_first) {
+ if ($process_twig) {
+ $this->processTwig();
+ }
+ if ($process_markdown) {
+ $this->processMarkdown();
+ }
+
+ // Content Processed but not cached yet
+ Grav::instance()->fireEvent('onPageContentProcessed', new Event(['page' => $this]));
+
+ } else {
+ if ($process_markdown) {
+ $this->processMarkdown();
+ }
+
+ // Content Processed but not cached yet
+ Grav::instance()->fireEvent('onPageContentProcessed', new Event(['page' => $this]));
+
+ if ($process_twig) {
+ $this->processTwig();
+ }
+ }
+
+ if ($cache_enable) {
+ $this->cachePageContent();
+ }
+ }
+ }
+
+ // Handle summary divider
+ $delimiter = $config->get('site.summary.delimiter', '===');
+ $divider_pos = mb_strpos($this->content, "{$delimiter}
");
+ if ($divider_pos !== false) {
+ $this->summary_size = $divider_pos;
+ $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;
+ }
+
+ /**
+ * Get the contentMeta array and initialize content first if it's not already
+ *
+ * @return mixed
+ */
+ public function contentMeta()
+ {
+ if ($this->content === null) {
+ $this->content();
+ }
+
+ return $this->getContentMeta();
+ }
+
+ /**
+ * Add an entry to the page's contentMeta array
+ *
+ * @param string $name
+ * @param string $value
+ */
+ public function addContentMeta($name, $value)
+ {
+ $this->content_meta[$name] = $value;
+ }
+
+ /**
+ * Return the whole contentMeta array as it currently stands
+ *
+ * @param string|null $name
+ *
+ * @return string
+ */
+ public function getContentMeta($name = null)
+ {
+ if ($name) {
+ if (isset($this->content_meta[$name])) {
+ return $this->content_meta[$name];
+ }
+
+ return null;
+ }
+
+ return $this->content_meta;
+ }
+
+ /**
+ * Sets the whole content meta array in one shot
+ *
+ * @param array $content_meta
+ *
+ * @return array
+ */
+ public function setContentMeta($content_meta)
+ {
+ return $this->content_meta = $content_meta;
+ }
+
+ /**
+ * Process the Markdown content. Uses Parsedown or Parsedown Extra depending on configuration
+ */
+ protected function processMarkdown()
+ {
+ /** @var Config $config */
+ $config = Grav::instance()['config'];
+
+ $markdownDefaults = (array)$config->get('system.pages.markdown');
+ if (isset($this->header()->markdown)) {
+ $markdownDefaults = array_merge($markdownDefaults, $this->header()->markdown);
+ }
+
+ // pages.markdown_extra is deprecated, but still check it...
+ if (!isset($markdownDefaults['extra']) && (isset($this->markdown_extra) || $config->get('system.pages.markdown_extra') !== null)) {
+ user_error('Configuration option \'system.pages.markdown_extra\' is deprecated since Grav 1.5, use \'system.pages.markdown.extra\' instead', E_USER_DEPRECATED);
+
+ $markdownDefaults['extra'] = $this->markdown_extra ?: $config->get('system.pages.markdown_extra');
+ }
+
+ $extra = $markdownDefaults['extra'] ?? false;
+ $defaults = [
+ 'markdown' => $markdownDefaults,
+ 'images' => $config->get('system.images', [])
+ ];
+
+ $excerpts = new Excerpts($this, $defaults);
+
+ // Initialize the preferred variant of Parsedown
+ if ($extra) {
+ $parsedown = new ParsedownExtra($excerpts);
+ } else {
+ $parsedown = new Parsedown($excerpts);
+ }
+
+ $this->content = $parsedown->text($this->content);
+ }
+
+
+ /**
+ * Process the Twig page content.
+ */
+ private function processTwig()
+ {
+ $twig = Grav::instance()['twig'];
+ $this->content = $twig->processPage($this, $this->content);
+ }
+
+ /**
+ * Fires the onPageContentProcessed event, and caches the page content using a unique ID for the page
+ */
+ public function cachePageContent()
+ {
+ $cache = Grav::instance()['cache'];
+ $cache_id = md5('page' . $this->getCacheKey());
+ $cache->save($cache_id, ['content' => $this->content, 'content_meta' => $this->content_meta]);
+ }
+
+ /**
+ * Needed by the onPageContentProcessed event to get the raw page content
+ *
+ * @return string the current page content
+ */
+ public function getRawContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * Needed by the onPageContentProcessed event to set the raw page content
+ *
+ * @param string $content
+ */
+ public function setRawContent($content)
+ {
+ $this->content = $content ?? '';
+ }
+
+ /**
+ * Get value from a page variable (used mostly for creating edit forms).
+ *
+ * @param string $name Variable name.
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function value($name, $default = null)
+ {
+ if ($name === 'content') {
+ return $this->raw_content;
+ }
+ if ($name === 'route') {
+ $parent = $this->parent();
+
+ return $parent ? $parent->rawRoute() : '';
+ }
+ if ($name === 'order') {
+ $order = $this->order();
+
+ return $order ? (int)$this->order() : '';
+ }
+ if ($name === 'ordering') {
+ return (bool)$this->order();
+ }
+ if ($name === 'folder') {
+ return preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder);
+ }
+ if ($name === 'slug') {
+ return $this->slug();
+ }
+ if ($name === 'name') {
+ $language = $this->language() ? '.' . $this->language() : '';
+ $name_val = str_replace($language . '.md', '', $this->name());
+ if ($this->modular()) {
+ return 'modular/' . $name_val;
+ }
+
+ return $name_val;
+ }
+ if ($name === 'media') {
+ return $this->media()->all();
+ }
+ if ($name === 'media.file') {
+ return $this->media()->files();
+ }
+ if ($name === 'media.video') {
+ return $this->media()->videos();
+ }
+ if ($name === 'media.image') {
+ return $this->media()->images();
+ }
+ if ($name === 'media.audio') {
+ return $this->media()->audios();
+ }
+
+ $path = explode('.', $name);
+ $scope = array_shift($path);
+
+ if ($name === 'frontmatter') {
+ return $this->frontmatter;
+ }
+
+ if ($scope === 'header') {
+ $current = $this->header();
+ foreach ($path as $field) {
+ if (is_object($current) && isset($current->{$field})) {
+ $current = $current->{$field};
+ } elseif (is_array($current) && isset($current[$field])) {
+ $current = $current[$field];
+ } else {
+ return $default;
+ }
+ }
+
+ return $current;
+ }
+
+ return $default;
+ }
+
+ /**
+ * Gets and Sets the Page raw content
+ *
+ * @param string|null $var
+ *
+ * @return string
+ */
+ public function rawMarkdown($var = null)
+ {
+ if ($var !== null) {
+ $this->raw_content = $var;
+ }
+
+ return $this->raw_content;
+ }
+
+ /**
+ * Get file object to the page.
+ *
+ * @return MarkdownFile|null
+ */
+ public function file()
+ {
+ if ($this->name) {
+ return MarkdownFile::instance($this->filePath());
+ }
+
+ return null;
+ }
+
+ /**
+ * Save page if there's a file assigned to it.
+ *
+ * @param bool|mixed $reorder Internal use.
+ */
+ public function save($reorder = true)
+ {
+ // Perform move, copy [or reordering] if needed.
+ $this->doRelocation();
+
+ $file = $this->file();
+ if ($file) {
+ $file->filename($this->filePath());
+ $file->header((array)$this->header());
+ $file->markdown($this->raw_content);
+ $file->save();
+ }
+
+ // Perform reorder if required
+ if ($reorder && is_array($reorder)) {
+ $this->doReorder($reorder);
+ }
+
+ $this->_original = null;
+ }
+
+ /**
+ * Prepare move page to new location. Moves also everything that's under the current page.
+ *
+ * You need to call $this->save() in order to perform the move.
+ *
+ * @param PageInterface $parent New parent page.
+ *
+ * @return $this
+ */
+ public function move(PageInterface $parent)
+ {
+ if (!$this->_original) {
+ $clone = clone $this;
+ $this->_original = $clone;
+ }
+
+ $this->_action = 'move';
+
+ if ($this->route() === $parent->route()) {
+ throw new \RuntimeException('Failed: Cannot set page parent to self');
+ }
+ if (Utils::startsWith($parent->rawRoute(), $this->rawRoute())) {
+ throw new \RuntimeException('Failed: Cannot set page parent to a child of current page');
+ }
+
+ $this->parent($parent);
+ $this->id(time() . md5($this->filePath()));
+
+ if ($parent->path()) {
+ $this->path($parent->path() . '/' . $this->folder());
+ }
+
+ if ($parent->route()) {
+ $this->route($parent->route() . '/' . $this->slug());
+ } else {
+ $this->route(Grav::instance()['pages']->root()->route() . '/' . $this->slug());
+ }
+
+ $this->raw_route = null;
+
+ return $this;
+ }
+
+ /**
+ * 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)
+ {
+ $this->move($parent);
+ $this->_action = 'copy';
+
+ return $this;
+ }
+
+ /**
+ * Get blueprints for the page.
+ *
+ * @return Blueprint
+ */
+ public function blueprints()
+ {
+ $grav = Grav::instance();
+
+ /** @var Pages $pages */
+ $pages = $grav['pages'];
+
+ $blueprint = $pages->blueprints($this->blueprintName());
+ $fields = $blueprint->fields();
+ $edit_mode = isset($grav['admin']) ? $grav['config']->get('plugins.admin.edit_mode') : null;
+
+ // override if you only want 'normal' mode
+ if (empty($fields) && ($edit_mode === 'auto' || $edit_mode === 'normal')) {
+ $blueprint = $pages->blueprints('default');
+ }
+
+ // override if you only want 'expert' mode
+ if (!empty($fields) && $edit_mode === 'expert') {
+ $blueprint = $pages->blueprints('');
+ }
+
+ return $blueprint;
+ }
+
+ /**
+ * Get the blueprint name for this page. Use the blueprint form field if set
+ *
+ * @return string
+ */
+ public function blueprintName()
+ {
+ $blueprint_name = filter_input(INPUT_POST, 'blueprint', FILTER_SANITIZE_STRING) ?: $this->template();
+
+ return $blueprint_name;
+ }
+
+ /**
+ * Validate page header.
+ *
+ * @throws \Exception
+ */
+ public function validate()
+ {
+ $blueprints = $this->blueprints();
+ $blueprints->validate($this->toArray());
+ }
+
+ /**
+ * Filter page header from illegal contents.
+ */
+ public function filter()
+ {
+ $blueprints = $this->blueprints();
+ $values = $blueprints->filter($this->toArray());
+ if ($values && isset($values['header'])) {
+ $this->header($values['header']);
+ }
+ }
+
+ /**
+ * Get unknown header variables.
+ *
+ * @return array
+ */
+ public function extra()
+ {
+ $blueprints = $this->blueprints();
+
+ return $blueprints->extra($this->toArray()['header'], 'header.');
+ }
+
+ /**
+ * Convert page to an array.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return [
+ 'header' => (array)$this->header(),
+ 'content' => (string)$this->value('content')
+ ];
+ }
+
+ /**
+ * Convert page to YAML encoded string.
+ *
+ * @return string
+ */
+ public function toYaml()
+ {
+ return Yaml::dump($this->toArray(), 20);
+ }
+
+ /**
+ * Convert page to JSON encoded string.
+ *
+ * @return string
+ */
+ public function toJson()
+ {
+ return json_encode($this->toArray());
+ }
+
+ /**
+ * @return string
+ */
+ public function getCacheKey(): string
+ {
+ 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.
+ *
+ * @param Media $var Representation of associated media.
+ *
+ * @return Media Representation of associated media.
+ */
+ public function media($var = null)
+ {
+ if ($var) {
+ $this->setMedia($var);
+ }
+
+ return $this->getMedia();
+ }
+
+ /**
+ * Get filesystem path to the associated media.
+ *
+ * @return string|null
+ */
+ public function getMediaFolder()
+ {
+ return $this->path();
+ }
+
+ /**
+ * Get display order for the associated media.
+ *
+ * @return array Empty array means default ordering.
+ */
+ public function getMediaOrder()
+ {
+ $header = $this->header();
+
+ return isset($header->media_order) ? array_map('trim', explode(',', (string)$header->media_order)) : [];
+ }
+
+ /**
+ * 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)
+ {
+ if ($var !== null) {
+ $this->name = $var;
+ }
+
+ return $this->name ?: 'default.md';
+ }
+
+ /**
+ * Returns child page type.
+ *
+ * @return string
+ */
+ public function childType()
+ {
+ return isset($this->header->child_type) ? (string)$this->header->child_type : '';
+ }
+
+ /**
+ * 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)
+ {
+ if ($var !== null) {
+ $this->template = $var;
+ }
+ if (empty($this->template)) {
+ $this->template = ($this->modular() ? 'modular/' : '') . str_replace($this->extension(), '', $this->name());
+ }
+
+ return $this->template;
+ }
+
+ /**
+ * 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)
+ {
+ if ($var !== null) {
+ $this->template_format = $var;
+ return $this->template_format;
+ }
+
+ 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 negotiation via the `accept:` header
+ $http_accept = $_SERVER['HTTP_ACCEPT'] ?? null;
+ if (is_string($http_accept)) {
+ $negotiator = new Negotiator();
+
+ $supported_types = Utils::getSupportPageTypes(['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;
+ }
+
+ /**
+ * Gets and sets the extension field.
+ *
+ * @param null $var
+ *
+ * @return null|string
+ */
+ public function extension($var = null)
+ {
+ if ($var !== null) {
+ $this->extension = $var;
+ }
+ if (empty($this->extension)) {
+ $this->extension = '.' . pathinfo($this->name(), PATHINFO_EXTENSION);
+ }
+
+ return $this->extension;
+ }
+
+ /**
+ * Returns the page extension, got from the page `url_extension` config and falls back to the
+ * system config `system.pages.append_url_extension`.
+ *
+ * @return string The extension of this page. For example `.html`
+ */
+ public function urlExtension()
+ {
+ if ($this->home()) {
+ return '';
+ }
+
+ // if not set in the page get the value from system config
+ if (null === $this->url_extension) {
+ $this->url_extension = Grav::instance()['config']->get('system.pages.append_url_extension', '');
+ }
+
+ return $this->url_extension;
+ }
+
+ /**
+ * 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)
+ {
+ if ($var !== null) {
+ $this->expires = $var;
+ }
+
+ return $this->expires ?? Grav::instance()['config']->get('system.pages.expires');
+ }
+
+ /**
+ * 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)
+ {
+ if ($var !== null) {
+ $this->cache_control = $var;
+ }
+
+ return $this->cache_control ?? Grav::instance()['config']->get('system.pages.cache_control');
+ }
+
+ /**
+ * Gets and sets the title for this Page. If no title is set, it will use the slug() to get a name
+ *
+ * @param string $var the title of the Page
+ *
+ * @return string the title of the Page
+ */
+ public function title($var = null)
+ {
+ if ($var !== null) {
+ $this->title = $var;
+ }
+ if (empty($this->title)) {
+ $this->title = ucfirst($this->slug());
+ }
+
+ return $this->title;
+ }
+
+ /**
+ * Gets and sets the menu name for this Page. This is the text that can be used specifically for navigation.
+ * If no menu field is set, it will use the title()
+ *
+ * @param string $var the menu field for the page
+ *
+ * @return string the menu field for the page
+ */
+ public function menu($var = null)
+ {
+ if ($var !== null) {
+ $this->menu = $var;
+ }
+ if (empty($this->menu)) {
+ $this->menu = $this->title();
+ }
+
+ return $this->menu;
+ }
+
+ /**
+ * Gets and Sets whether or not this Page is visible for navigation
+ *
+ * @param bool $var true if the page is visible
+ *
+ * @return bool true if the page is visible
+ */
+ public function visible($var = null)
+ {
+ if ($var !== null) {
+ $this->visible = (bool)$var;
+ }
+
+ if ($this->visible === null) {
+ // Set item visibility in menu if folder is different from slug
+ // eg folder = 01.Home and slug = Home
+ if (preg_match(PAGE_ORDER_PREFIX_REGEX, $this->folder)) {
+ $this->visible = true;
+ } else {
+ $this->visible = false;
+ }
+ }
+
+ return $this->visible;
+ }
+
+ /**
+ * Gets and Sets whether or not this Page is considered published
+ *
+ * @param bool $var true if the page is published
+ *
+ * @return bool true if the page is published
+ */
+ public function published($var = null)
+ {
+ if ($var !== null) {
+ $this->published = (bool)$var;
+ }
+
+ // If not published, should not be visible in menus either
+ if ($this->published === false) {
+ $this->visible = false;
+ }
+
+ return $this->published;
+ }
+
+ /**
+ * Gets and Sets the Page publish date
+ *
+ * @param string $var string representation of a date
+ *
+ * @return int unix timestamp representation of the date
+ */
+ public function publishDate($var = null)
+ {
+ if ($var !== null) {
+ $this->publish_date = Utils::date2timestamp($var, $this->dateformat);
+ }
+
+ return $this->publish_date;
+ }
+
+ /**
+ * Gets and Sets the Page unpublish date
+ *
+ * @param string $var string representation of a date
+ *
+ * @return int|null unix timestamp representation of the date
+ */
+ public function unpublishDate($var = null)
+ {
+ if ($var !== null) {
+ $this->unpublish_date = Utils::date2timestamp($var, $this->dateformat);
+ }
+
+ return $this->unpublish_date;
+ }
+
+ /**
+ * Gets and Sets whether or not this Page is routable, ie you can reach it
+ * via a URL.
+ * The page must be *routable* and *published*
+ *
+ * @param bool $var true if the page is routable
+ *
+ * @return bool true if the page is routable
+ */
+ public function routable($var = null)
+ {
+ if ($var !== null) {
+ $this->routable = (bool)$var;
+ }
+
+ return $this->routable && $this->published();
+ }
+
+ public function ssl($var = null)
+ {
+ if ($var !== null) {
+ $this->ssl = (bool)$var;
+ }
+
+ return $this->ssl;
+ }
+
+ /**
+ * Gets and Sets the process setup for this Page. This is multi-dimensional array that consists of
+ * a simple array of arrays with the form array("markdown"=>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)
+ {
+ if ($var !== null) {
+ $this->process = (array)$var;
+ }
+
+ return $this->process;
+ }
+
+ /**
+ * Returns the state of the debugger override etting for this page
+ *
+ * @return mixed
+ */
+ public function debugger()
+ {
+ return !(isset($this->debugger) && $this->debugger === false);
+ }
+
+ /**
+ * 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)
+ {
+ if ($var !== null) {
+ $this->metadata = (array)$var;
+ }
+
+ // if not metadata yet, process it.
+ if (null === $this->metadata) {
+ $header_tag_http_equivs = ['content-type', 'default-style', 'refresh', 'x-ua-compatible'];
+
+ $this->metadata = [];
+
+ $metadata = [];
+ // Set the Generator tag
+ $metadata['generator'] = 'GravCMS';
+
+ // Get initial metadata for the page
+ $metadata = array_merge($metadata, Grav::instance()['config']->get('site.metadata', []));
+
+ if (isset($this->header->metadata) && is_array($this->header->metadata)) {
+ // Merge any site.metadata settings in with page metadata
+ $metadata = array_merge($metadata, $this->header->metadata);
+ }
+
+ // Build an array of meta objects..
+ foreach ((array)$metadata as $key => $value) {
+ // Lowercase the key
+ $key = strtolower($key);
+ // If this is a property type metadata: "og", "twitter", "facebook" etc
+ // Backward compatibility for nested arrays in metas
+ if (is_array($value)) {
+ foreach ($value as $property => $prop_value) {
+ $prop_key = $key . ':' . $property;
+ $this->metadata[$prop_key] = [
+ 'name' => $prop_key,
+ 'property' => $prop_key,
+ 'content' => htmlspecialchars($prop_value, ENT_QUOTES, 'UTF-8')
+ ];
+ }
+ } else {
+ // If it this is a standard meta data type
+ if ($value) {
+ if (\in_array($key, $header_tag_http_equivs, true)) {
+ $this->metadata[$key] = [
+ 'http_equiv' => $key,
+ 'content' => htmlspecialchars($value, ENT_QUOTES, 'UTF-8')
+ ];
+ } elseif ($key === 'charset') {
+ $this->metadata[$key] = ['charset' => htmlspecialchars($value, ENT_QUOTES, 'UTF-8')];
+ } else {
+ // if it's a social metadata with separator, render as property
+ $separator = strpos($key, ':');
+ $hasSeparator = $separator && $separator < strlen($key) - 1;
+ $entry = [
+ 'content' => htmlspecialchars($value, ENT_QUOTES, 'UTF-8')
+ ];
+
+ if ($hasSeparator && !Utils::startsWith($key, 'twitter')) {
+ $entry['property'] = $key;
+ } else {
+ $entry['name'] = $key;
+ }
+
+ $this->metadata[$key] = $entry;
+ }
+ }
+ }
+ }
+ }
+
+ 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
+ *
+ * @param string $var the slug, e.g. 'my-blog'
+ *
+ * @return string the slug
+ */
+ public function slug($var = null)
+ {
+ if ($var !== null && $var !== '') {
+ $this->slug = $var;
+ }
+
+ if (empty($this->slug)) {
+ $this->slug = $this->adjustRouteCase(preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder)) ?: null;
+ }
+
+ return $this->slug;
+ }
+
+ /**
+ * Get/set order number of this page.
+ *
+ * @param int $var
+ *
+ * @return int|bool
+ */
+ public function order($var = null)
+ {
+ if ($var !== null) {
+ $order = $var ? sprintf('%02d.', (int)$var) : '';
+ $this->folder($order . preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder));
+
+ return $order;
+ }
+
+ preg_match(PAGE_ORDER_PREFIX_REGEX, $this->folder, $order);
+
+ return $order[0] ?? false;
+ }
+
+ /**
+ * Gets the URL for a page - alias of url().
+ *
+ * @param bool $include_host
+ *
+ * @return string the permalink
+ */
+ public function link($include_host = false)
+ {
+ return $this->url($include_host);
+ }
+
+ /**
+ * Gets the URL with host information, aka Permalink.
+ * @return string The permalink.
+ */
+ public function permalink()
+ {
+ return $this->url(true, false, true, true);
+ }
+
+ /**
+ * Returns the canonical URL for a page
+ *
+ * @param bool $include_lang
+ *
+ * @return string
+ */
+ public function canonical($include_lang = true)
+ {
+ return $this->url(true, true, $include_lang);
+ }
+
+ /**
+ * Gets the url for the Page.
+ *
+ * @param bool $include_host Defaults false, but true would include http://yourhost.com
+ * @param bool $canonical True to return the canonical URL
+ * @param bool $include_base Include base url on multisite as well as language code
+ * @param bool $raw_route
+ *
+ * @return string The url.
+ */
+ public function url($include_host = false, $canonical = false, $include_base = true, $raw_route = false)
+ {
+ // Override any URL when external_url is set
+ if (isset($this->external_url)) {
+ return $this->external_url;
+ }
+
+ $grav = Grav::instance();
+
+ /** @var Pages $pages */
+ $pages = $grav['pages'];
+
+ /** @var Config $config */
+ $config = $grav['config'];
+
+ // get base route (multi-site base and language)
+ $route = $include_base ? $pages->baseRoute() : '';
+
+ // add full route if configured to do so
+ if (!$include_host && $config->get('system.absolute_urls', false)) {
+ $include_host = true;
+ }
+
+ if ($canonical) {
+ $route .= $this->routeCanonical();
+ } elseif ($raw_route) {
+ $route .= $this->rawRoute();
+ } else {
+ $route .= $this->route();
+ }
+
+ /** @var Uri $uri */
+ $uri = $grav['uri'];
+ $url = $uri->rootUrl($include_host) . '/' . trim($route, '/') . $this->urlExtension();
+
+ // trim trailing / if not root
+ if ($url !== '/') {
+ $url = rtrim($url, '/');
+ }
+
+ return Uri::filterPath($url);
+ }
+
+ /**
+ * Gets the route for the page based on the route headers if available, else from
+ * the parents route and the current Page's slug.
+ *
+ * @param string $var Set new default route.
+ *
+ * @return string The route for the Page.
+ */
+ public function route($var = null)
+ {
+ if ($var !== null) {
+ $this->route = $var;
+ }
+
+ if (empty($this->route)) {
+ $baseRoute = null;
+
+ // calculate route based on parent slugs
+ $parent = $this->parent();
+ if (isset($parent)) {
+ if ($this->hide_home_route && $parent->route() === $this->home_route) {
+ $baseRoute = '';
+ } else {
+ $baseRoute = (string)$parent->route();
+ }
+ }
+
+ $this->route = isset($baseRoute) ? $baseRoute . '/' . $this->slug() : null;
+
+ if (!empty($this->routes) && isset($this->routes['default'])) {
+ $this->routes['aliases'][] = $this->route;
+ $this->route = $this->routes['default'];
+
+ return $this->route;
+ }
+ }
+
+ return $this->route;
+ }
+
+ /**
+ * Helper method to clear the route out so it regenerates next time you use it
+ */
+ public function unsetRouteSlug()
+ {
+ unset($this->route, $this->slug);
+ }
+
+ /**
+ * Gets and Sets the page raw route
+ *
+ * @param null $var
+ *
+ * @return null|string
+ */
+ public function rawRoute($var = null)
+ {
+ if ($var !== null) {
+ $this->raw_route = $var;
+ }
+
+ if (empty($this->raw_route)) {
+ $parent = $this->parent();
+ $baseRoute = $parent ? (string)$parent->rawRoute() : null;
+
+ $slug = $this->adjustRouteCase(preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder));
+
+ $this->raw_route = isset($baseRoute) ? $baseRoute . '/' . $slug : null;
+ }
+
+ return $this->raw_route;
+ }
+
+ /**
+ * Gets the route aliases for the page based on page headers.
+ *
+ * @param array $var list of route aliases
+ *
+ * @return array The route aliases for the Page.
+ */
+ public function routeAliases($var = null)
+ {
+ if ($var !== null) {
+ $this->routes['aliases'] = (array)$var;
+ }
+
+ if (!empty($this->routes) && isset($this->routes['aliases'])) {
+ return $this->routes['aliases'];
+ }
+
+ return [];
+ }
+
+ /**
+ * Gets the canonical route for this page if its set. If provided it will use
+ * that value, else if it's `true` it will use the default route.
+ *
+ * @param null $var
+ *
+ * @return bool|string
+ */
+ public function routeCanonical($var = null)
+ {
+ if ($var !== null) {
+ $this->routes['canonical'] = $var;
+ }
+
+ if (!empty($this->routes) && isset($this->routes['canonical'])) {
+ return $this->routes['canonical'];
+ }
+
+ return $this->route();
+ }
+
+ /**
+ * Gets and sets the identifier for this Page object.
+ *
+ * @param string $var the identifier
+ *
+ * @return string the identifier
+ */
+ public function id($var = null)
+ {
+ if (null === $this->id) {
+ // We need to set unique id to avoid potential cache conflicts between pages.
+ $var = time() . md5($this->filePath());
+ }
+ if ($var !== null) {
+ // store unique per language
+ $active_lang = Grav::instance()['language']->getLanguage() ?: '';
+ $id = $active_lang . $var;
+ $this->id = $id;
+ }
+
+ return $this->id;
+ }
+
+ /**
+ * Gets and sets the modified timestamp.
+ *
+ * @param int $var modified unix timestamp
+ *
+ * @return int modified unix timestamp
+ */
+ public function modified($var = null)
+ {
+ if ($var !== null) {
+ $this->modified = $var;
+ }
+
+ return $this->modified;
+ }
+
+ /**
+ * Gets the redirect set in the header.
+ *
+ * @param string $var redirect url
+ *
+ * @return string
+ */
+ public function redirect($var = null)
+ {
+ if ($var !== null) {
+ $this->redirect = $var;
+ }
+
+ return $this->redirect;
+ }
+
+ /**
+ * 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)
+ {
+ if ($var !== null) {
+ $this->etag = $var;
+ }
+ if (!isset($this->etag)) {
+ $this->etag = (bool)Grav::instance()['config']->get('system.pages.etag');
+ }
+
+ return $this->etag;
+ }
+
+ /**
+ * Gets and sets the option to show the last_modified header for the page.
+ *
+ * @param bool $var show last_modified header
+ *
+ * @return bool show last_modified header
+ */
+ public function lastModified($var = null)
+ {
+ if ($var !== null) {
+ $this->last_modified = $var;
+ }
+ if (!isset($this->last_modified)) {
+ $this->last_modified = (bool)Grav::instance()['config']->get('system.pages.last_modified');
+ }
+
+ return $this->last_modified;
+ }
+
+ /**
+ * 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)
+ {
+ if ($var !== null) {
+ // Filename of the page.
+ $this->name = basename($var);
+ // Folder of the page.
+ $this->folder = basename(dirname($var));
+ // Path to the page.
+ $this->path = dirname($var, 2);
+ }
+
+ return $this->path . '/' . $this->folder . '/' . ($this->name ?: '');
+ }
+
+ /**
+ * Gets the relative path to the .md file
+ *
+ * @return string The relative file path
+ */
+ public function filePathClean()
+ {
+ return str_replace(ROOT_DIR, '', $this->filePath());
+ }
+
+ /**
+ * Returns the clean path to the page file
+ */
+ public function relativePagePath()
+ {
+ return str_replace('/' . $this->name(), '', $this->filePathClean());
+ }
+
+ /**
+ * Gets and sets the path to the folder where the .md for this Page object resides.
+ * This is equivalent to the filePath but without the filename.
+ *
+ * @param string $var the path
+ *
+ * @return string|null the path
+ */
+ public function path($var = null)
+ {
+ if ($var !== null) {
+ // Folder of the page.
+ $this->folder = basename($var);
+ // Path to the page.
+ $this->path = dirname($var);
+ }
+
+ return $this->path ? $this->path . '/' . $this->folder : null;
+ }
+
+ /**
+ * Get/set the folder.
+ *
+ * @param string $var Optional path
+ *
+ * @return string|null
+ */
+ public function folder($var = null)
+ {
+ if ($var !== null) {
+ $this->folder = $var;
+ }
+
+ return $this->folder;
+ }
+
+ /**
+ * 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)
+ {
+ if ($var !== null) {
+ $this->date = Utils::date2timestamp($var, $this->dateformat);
+ }
+
+ if (!$this->date) {
+ $this->date = $this->modified;
+ }
+
+ return $this->date;
+ }
+
+ /**
+ * 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)
+ {
+ if ($var !== null) {
+ $this->dateformat = $var;
+ }
+
+ return $this->dateformat;
+ }
+
+ /**
+ * 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)
+ {
+ //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';
+ }
+
+ return $this->order_dir;
+ }
+
+ /**
+ * 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)
+ {
+ //user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED);
+
+ if ($var !== null) {
+ $this->order_by = $var;
+ }
+
+ return $this->order_by;
+ }
+
+ /**
+ * 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)
+ {
+ //user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED);
+
+ if ($var !== null) {
+ $this->order_manual = $var;
+ }
+
+ return (array)$this->order_manual;
+ }
+
+ /**
+ * 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)
+ {
+ //user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED);
+
+ if ($var !== null) {
+ $this->max_count = (int)$var;
+ }
+ if (empty($this->max_count)) {
+ /** @var Config $config */
+ $config = Grav::instance()['config'];
+ $this->max_count = (int)$config->get('system.pages.list.count');
+ }
+
+ return $this->max_count;
+ }
+
+ /**
+ * 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)
+ {
+ 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;
+ }
+
+ return $this->taxonomy;
+ }
+
+ /**
+ * 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)
+ {
+ return $this->modularTwig($var);
+ }
+
+ /**
+ * 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)
+ {
+ if ($var !== null) {
+ $this->modular_twig = (bool)$var;
+ if ($var) {
+ $this->visible(false);
+ // some routable logic
+ if (empty($this->header->routable)) {
+ $this->routable = false;
+ }
+ }
+ }
+
+ return $this->modular_twig;
+ }
+
+ /**
+ * 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)
+ {
+ return (bool)($this->process[$process] ?? false);
+ }
+
+ /**
+ * Gets and Sets the parent object for this page
+ *
+ * @param PageInterface $var the parent page object
+ *
+ * @return PageInterface|null the parent page object if it exists.
+ */
+ public function parent(PageInterface $var = null)
+ {
+ if ($var) {
+ $this->parent = $var->path();
+
+ return $var;
+ }
+
+ /** @var Pages $pages */
+ $pages = Grav::instance()['pages'];
+
+ return $pages->get($this->parent);
+ }
+
+ /**
+ * Gets the top parent object for this page
+ *
+ * @return PageInterface|null the top parent page object if it exists.
+ */
+ public function topParent()
+ {
+ $topParent = $this->parent();
+
+ if (!$topParent) {
+ return null;
+ }
+
+ while (true) {
+ $theParent = $topParent->parent();
+ if ($theParent !== null && $theParent->parent() !== null) {
+ $topParent = $theParent;
+ } else {
+ break;
+ }
+ }
+
+ return $topParent;
+ }
+
+ /**
+ * Returns children of this page.
+ *
+ * @return Collection
+ */
+ public function children()
+ {
+ /** @var Pages $pages */
+ $pages = Grav::instance()['pages'];
+
+ return $pages->children($this->path());
+ }
+
+
+ /**
+ * 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()
+ {
+ $parent = $this->parent();
+ $collection = $parent ? $parent->collection('content', false) : null;
+ if ($collection instanceof Collection) {
+ return $collection->isFirst($this->path());
+ }
+
+ return true;
+ }
+
+ /**
+ * 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()
+ {
+ $parent = $this->parent();
+ $collection = $parent ? $parent->collection('content', false) : null;
+ if ($collection instanceof Collection) {
+ return $collection->isLast($this->path());
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets the previous sibling based on current position.
+ *
+ * @return PageInterface the previous Page item
+ */
+ public function prevSibling()
+ {
+ return $this->adjacentSibling(-1);
+ }
+
+ /**
+ * Gets the next sibling based on current position.
+ *
+ * @return PageInterface the next Page item
+ */
+ public function nextSibling()
+ {
+ return $this->adjacentSibling(1);
+ }
+
+ /**
+ * 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)
+ {
+ $parent = $this->parent();
+ $collection = $parent ? $parent->collection('content', false) : null;
+ if ($collection instanceof Collection) {
+ return $collection->adjacentSibling($this->path(), $direction);
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the item in the current position.
+ *
+ * @return int the index of the current page.
+ */
+ public function currentPosition()
+ {
+ $parent = $this->parent();
+ $collection = $parent ? $parent->collection('content', false) : null;
+ if ($collection instanceof Collection) {
+ return $collection->currentPosition($this->path());
+ }
+
+ return 1;
+ }
+
+ /**
+ * Returns whether or not this page is the currently active page requested via the URL.
+ *
+ * @return bool True if it is active
+ */
+ public function active()
+ {
+ $uri_path = rtrim(urldecode(Grav::instance()['uri']->path()), '/') ?: '/';
+ $routes = Grav::instance()['pages']->routes();
+
+ return isset($routes[$uri_path]) && $routes[$uri_path] === $this->path();
+ }
+
+ /**
+ * Returns whether or not this URI's URL contains the URL of the active page.
+ * Or in other words, is this page's URL in the current URL
+ *
+ * @return bool True if active child exists
+ */
+ public function activeChild()
+ {
+ $uri = Grav::instance()['uri'];
+ $pages = Grav::instance()['pages'];
+ $uri_path = rtrim(urldecode($uri->path()), '/');
+ $routes = Grav::instance()['pages']->routes();
+
+ if (isset($routes[$uri_path])) {
+ /** @var PageInterface $child_page */
+ $child_page = $pages->dispatch($uri->route())->parent();
+ if ($child_page) {
+ while (!$child_page->root()) {
+ if ($this->path() === $child_page->path()) {
+ return true;
+ }
+ $child_page = $child_page->parent();
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether or not this page is the currently configured home page.
+ *
+ * @return bool True if it is the homepage
+ */
+ public function home()
+ {
+ $home = Grav::instance()['config']->get('system.home.alias');
+
+ return $this->route() === $home || $this->rawRoute() === $home;
+ }
+
+ /**
+ * Returns whether or not this page is the root node of the pages tree.
+ *
+ * @return bool True if it is the root
+ */
+ public function root()
+ {
+ return !$this->parent && !$this->name && !$this->visible;
+ }
+
+ /**
+ * 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)
+ {
+ /** @var Pages $pages */
+ $pages = Grav::instance()['pages'];
+
+ return $pages->ancestor($this->route, $lookup);
+ }
+
+ /**
+ * 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)
+ {
+ list($inherited, $currentParams) = $this->getInheritedParams($field);
+
+ $this->modifyHeader($field, $currentParams);
+
+ return $inherited;
+ }
+
+ /**
+ * Helper method to return an ancestor field only to inherit from. The
+ * first occurrence of an ancestor field will be returned if at all.
+ *
+ * @param string $field Name of the parent folder
+ *
+ * @return array
+ */
+ public function inheritedField($field)
+ {
+ list($inherited, $currentParams) = $this->getInheritedParams($field);
+
+ return $currentParams;
+ }
+
+ /**
+ * Method that contains shared logic for inherited() and inheritedField()
+ *
+ * @param string $field Name of the parent folder
+ *
+ * @return array
+ */
+ protected function getInheritedParams($field)
+ {
+ $pages = Grav::instance()['pages'];
+
+ /** @var Pages $pages */
+ $inherited = $pages->inherited($this->route, $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);
+ }
+
+ return [$inherited, $currentParams];
+ }
+
+ /**
+ * 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)
+ {
+ /** @var Pages $pages */
+ $pages = Grav::instance()['pages'];
+
+ return $pages->find($url, $all);
+ }
+
+ /**
+ * 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)
+ {
+ if (is_string($params)) {
+ $params = (array)$this->value('header.' . $params);
+ } elseif (!is_array($params)) {
+ throw new \InvalidArgumentException('Argument should be either header variable name or array of parameters');
+ }
+
+ if (!isset($params['items'])) {
+ return new Collection();
+ }
+
+ // See if require published filter is set and use that, if assume published=true
+ $only_published = true;
+ if (isset($params['filter']['published']) && $params['filter']['published']) {
+ $only_published = false;
+ } elseif (isset($params['filter']['non-published']) && $params['filter']['non-published']) {
+ $only_published = false;
+ }
+
+ $collection = $this->evaluate($params['items'], $only_published);
+ if (!$collection instanceof Collection) {
+ $collection = new Collection();
+ }
+ $collection->setParams($params);
+
+ /** @var Uri $uri */
+ $uri = Grav::instance()['uri'];
+ /** @var Config $config */
+ $config = Grav::instance()['config'];
+
+ $process_taxonomy = $params['url_taxonomy_filters'] ?? $config->get('system.pages.url_taxonomy_filters');
+
+ if ($process_taxonomy) {
+ foreach ((array)$config->get('site.taxonomies') as $taxonomy) {
+ if ($uri->param(rawurlencode($taxonomy))) {
+ $items = explode(',', $uri->param($taxonomy));
+ $collection->setParams(['taxonomies' => [$taxonomy => $items]]);
+
+ foreach ($collection as $page) {
+ // Don't filter modular pages
+ if ($page->modular()) {
+ continue;
+ }
+ foreach ($items as $item) {
+ $item = rawurldecode($item);
+ if (empty($page->taxonomy[$taxonomy]) || !\in_array(htmlspecialchars_decode($item, ENT_QUOTES), $page->taxonomy[$taxonomy], true)
+ ) {
+ $collection->remove($page->path());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // If a filter or filters are set, filter the collection...
+ if (isset($params['filter'])) {
+
+ // remove any inclusive sets from filer:
+ $sets = ['published', 'visible', 'modular', 'routable'];
+ foreach ($sets as $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]);
+ }
+ }
+
+ foreach ((array)$params['filter'] as $type => $filter) {
+ switch ($type) {
+ case 'published':
+ if ((bool) $filter) {
+ $collection->published();
+ }
+ break;
+ case 'non-published':
+ if ((bool) $filter) {
+ $collection->nonPublished();
+ }
+ break;
+ case 'visible':
+ if ((bool) $filter) {
+ $collection->visible();
+ }
+ break;
+ case 'non-visible':
+ if ((bool) $filter) {
+ $collection->nonVisible();
+ }
+ break;
+ case 'modular':
+ if ((bool) $filter) {
+ $collection->modular();
+ }
+ break;
+ case 'non-modular':
+ if ((bool) $filter) {
+ $collection->nonModular();
+ }
+ break;
+ case 'routable':
+ if ((bool) $filter) {
+ $collection->routable();
+ }
+ break;
+ case 'non-routable':
+ if ((bool) $filter) {
+ $collection->nonRoutable();
+ }
+ break;
+ case 'type':
+ $collection->ofType($filter);
+ break;
+ case 'types':
+ $collection->ofOneOfTheseTypes($filter);
+ break;
+ case 'access':
+ $collection->ofOneOfTheseAccessLevels($filter);
+ break;
+ }
+ }
+ }
+
+ if (isset($params['dateRange'])) {
+ $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 = $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
+ $sort_flags = array_reduce($sort_flags, function ($a, $b) {
+ return $a | $b;
+ }, 0); //merge constant values using bit or
+ }
+
+ $collection->order($by, $dir, $custom, $sort_flags);
+ }
+
+ /** @var Grav $grav */
+ $grav = Grav::instance();
+
+ // New Custom event to handle things like pagination.
+ $grav->fireEvent('onCollectionProcessed', new Event(['collection' => $collection]));
+
+ // Slice and dice the collection if pagination is required
+ if ($pagination) {
+ $params = $collection->params();
+
+ $limit = (int)($params['limit'] ?? 0);
+ $start = !empty($params['pagination']) ? ($uri->currentPage() - 1) * $limit : 0;
+
+ if ($limit && $collection->count() > $limit) {
+ $collection->slice($start, $limit);
+ }
+ }
+
+ return $collection;
+ }
+
+ /**
+ * @param string|array $value
+ * @param bool $only_published
+ * @return mixed
+ */
+ public function evaluate($value, $only_published = true)
+ {
+ // Parse command.
+ if (is_string($value)) {
+ // Format: @command.param
+ $cmd = $value;
+ $params = [];
+ } elseif (is_array($value) && count($value) == 1 && !is_int(key($value))) {
+ // Format: @command.param: { attr1: value1, attr2: value2 }
+ $cmd = (string)key($value);
+ $params = (array)current($value);
+ } else {
+ $result = [];
+ foreach ((array)$value as $key => $val) {
+ if (is_int($key)) {
+ $result = $result + $this->evaluate($val, $only_published)->toArray();
+ } else {
+ $result = $result + $this->evaluate([$key => $val], $only_published)->toArray();
+ }
+
+ }
+
+ return new Collection($result);
+ }
+
+ /** @var Pages $pages */
+ $pages = Grav::instance()['pages'];
+
+ $parts = explode('.', $cmd);
+ $current = array_shift($parts);
+
+ /** @var Collection $results */
+ $results = new Collection();
+
+ switch ($current) {
+ case 'self@':
+ case '@self':
+ if (!empty($parts)) {
+ switch ($parts[0]) {
+ case 'modular':
+ // @self.modular: false (alternative to @self.children)
+ if (!empty($params) && $params[0] === false) {
+ $results = $this->children()->nonModular();
+ break;
+ }
+ $results = $this->children()->modular();
+ break;
+ case 'children':
+ $results = $this->children()->nonModular();
+ break;
+ case 'all':
+ $results = $this->children();
+ break;
+ case 'parent':
+ $collection = new Collection();
+ $results = $collection->addPage($this->parent());
+ break;
+ case 'siblings':
+ if (!$this->parent()) {
+ return new Collection();
+ }
+ $results = $this->parent()->children()->remove($this->path());
+ break;
+ case 'descendants':
+ $results = $pages->all($this)->remove($this->path())->nonModular();
+ break;
+ }
+ }
+
+
+ break;
+
+ case 'page@':
+ case '@page':
+ $page = null;
+
+ if (!empty($params)) {
+ $page = $this->find($params[0]);
+ }
+
+ // safety check in case page is not found
+ if (!isset($page)) {
+ return $results;
+ }
+
+ // Handle a @page.descendants
+ if (!empty($parts)) {
+ switch ($parts[0]) {
+ case 'modular':
+ $results = new Collection();
+ foreach ($page->children() as $child) {
+ $results = $results->addPage($child);
+ }
+ $results->modular();
+ break;
+ case 'page':
+ case 'self':
+ $results = new Collection();
+ $results = $results->addPage($page);
+ break;
+
+ case 'descendants':
+ $results = $pages->all($page)->remove($page->path())->nonModular();
+ break;
+
+ case 'children':
+ $results = $page->children()->nonModular();
+ break;
+ }
+ } else {
+ $results = $page->children()->nonModular();
+ }
+
+ break;
+
+ case 'root@':
+ case '@root':
+ if (!empty($parts) && $parts[0] === 'descendants') {
+ $results = $pages->all($pages->root())->nonModular();
+ } else {
+ $results = $pages->root()->children()->nonModular();
+ }
+ break;
+
+ case 'taxonomy@':
+ case '@taxonomy':
+ // Gets a collection of pages by using one of the following formats:
+ // @taxonomy.category: blog
+ // @taxonomy.category: [ blog, featured ]
+ // @taxonomy: { category: [ blog, featured ], level: 1 }
+
+ /** @var Taxonomy $taxonomy_map */
+ $taxonomy_map = Grav::instance()['taxonomy'];
+
+ if (!empty($parts)) {
+ $params = [implode('.', $parts) => $params];
+ }
+ $results = $taxonomy_map->findTaxonomy($params);
+ break;
+ }
+
+ if ($only_published) {
+ $results = $results->published();
+ }
+
+ return $results;
+ }
+
+ /**
+ * 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()
+ {
+ if ($this->name) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether or not this Page object is a directory or a page.
+ *
+ * @return bool True if its a directory
+ */
+ public function isDir()
+ {
+ return !$this->isPage();
+ }
+
+ /**
+ * Returns whether the page exists in the filesystem.
+ *
+ * @return bool
+ */
+ public function exists()
+ {
+ $file = $this->file();
+
+ return $file && $file->exists();
+ }
+
+ /**
+ * Returns whether or not the current folder exists
+ *
+ * @return bool
+ */
+ public function folderExists()
+ {
+ return file_exists($this->path());
+ }
+
+ /**
+ * Cleans the path.
+ *
+ * @param string $path the path
+ *
+ * @return string the path
+ */
+ protected function cleanPath($path)
+ {
+ $lastchunk = strrchr($path, DS);
+ if (strpos($lastchunk, ':') !== false) {
+ $path = str_replace($lastchunk, '', $path);
+ }
+
+ return $path;
+ }
+
+ /**
+ * Reorders all siblings according to a defined order
+ *
+ * @param array|null $new_order
+ */
+ protected function doReorder($new_order)
+ {
+ if (!$this->_original) {
+ return;
+ }
+
+ $pages = Grav::instance()['pages'];
+ $pages->init();
+
+ $this->_original->path($this->path());
+
+ $parent = $this->parent();
+ $siblings = $parent ? $parent->children() : null;
+
+ if ($siblings) {
+ $siblings->order('slug', 'asc', $new_order);
+
+ $counter = 0;
+
+ // 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);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Moves or copies the page in filesystem.
+ *
+ * @internal
+ *
+ * @throws \Exception
+ */
+ protected function doRelocation()
+ {
+ if (!$this->_original) {
+ return;
+ }
+
+ if (is_dir($this->_original->path())) {
+ if ($this->_action === 'move') {
+ Folder::move($this->_original->path(), $this->path());
+ } elseif ($this->_action === 'copy') {
+ Folder::copy($this->_original->path(), $this->path());
+ }
+ }
+
+ if ($this->name() !== $this->_original->name()) {
+ $path = $this->path();
+ if (is_file($path . '/' . $this->_original->name())) {
+ rename($path . '/' . $this->_original->name(), $path . '/' . $this->name());
+ }
+ }
+
+ }
+
+ protected function setPublishState()
+ {
+ // Handle publishing dates if no explicit published option set
+ if (Grav::instance()['config']->get('system.pages.publish_dates') && !isset($this->header->published)) {
+ // unpublish if required, if not clear cache right before page should be unpublished
+ if ($this->unpublishDate()) {
+ if ($this->unpublishDate() < time()) {
+ $this->published(false);
+ } else {
+ $this->published();
+ Grav::instance()['cache']->setLifeTime($this->unpublishDate());
+ }
+ }
+ // publish if required, if not clear cache right before page is published
+ if ($this->publishDate() && $this->publishDate() > time()) {
+ $this->published(false);
+ Grav::instance()['cache']->setLifeTime($this->publishDate());
+ }
+ }
+ }
+
+ protected function adjustRouteCase($route)
+ {
+ $case_insensitive = Grav::instance()['config']->get('system.force_lowercase_urls');
+
+ return $case_insensitive ? mb_strtolower($route) : $route;
+ }
+
+ /**
+ * Gets the Page Unmodified (original) version of the page.
+ *
+ * @return PageInterface The original version of the page.
+ */
+ public function getOriginal()
+ {
+ return $this->_original;
+ }
+
+ /**
+ * Gets the action.
+ *
+ * @return string The Action string.
+ */
+ public function getAction()
+ {
+ return $this->_action;
+ }
+}
diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php
new file mode 100644
index 0000000..513a7ba
--- /dev/null
+++ b/system/src/Grav/Common/Page/Pages.php
@@ -0,0 +1,1443 @@
+grav = $c;
+ }
+
+ /**
+ * Get or set base path for the pages.
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ public function base($path = null)
+ {
+ if ($path !== null) {
+ $path = trim($path, '/');
+ $this->base = $path ? '/' . $path : null;
+ $this->baseRoute = [];
+ }
+
+ return $this->base;
+ }
+
+ /**
+ *
+ * Get base route for Grav pages.
+ *
+ * @param string $lang Optional language code for multilingual routes.
+ *
+ * @return string
+ */
+ public function baseRoute($lang = null)
+ {
+ $key = $lang ?: $this->active_lang ?: 'default';
+
+ if (!isset($this->baseRoute[$key])) {
+ /** @var Language $language */
+ $language = $this->grav['language'];
+
+ $path_base = rtrim($this->base(), '/');
+ $path_lang = $language->enabled() ? $language->getLanguageURLPrefix($lang) : '';
+
+ $this->baseRoute[$key] = $path_base . $path_lang;
+ }
+
+ return $this->baseRoute[$key];
+ }
+
+ /**
+ *
+ * Get route for Grav site.
+ *
+ * @param string $route Optional route to the page.
+ * @param string $lang Optional language code for multilingual links.
+ *
+ * @return string
+ */
+ public function route($route = '/', $lang = null)
+ {
+ if (!$route || $route === '/') {
+ return $this->baseRoute($lang) ?: '/';
+ }
+
+ return $this->baseRoute($lang) . $route;
+ }
+
+ /**
+ *
+ * Get base URL for Grav pages.
+ *
+ * @param string $lang Optional language code for multilingual links.
+ * @param bool|null $absolute If true, return absolute url, if false, return relative url. Otherwise return default.
+ *
+ * @return string
+ */
+ public function baseUrl($lang = null, $absolute = null)
+ {
+ if ($absolute === null) {
+ $type = 'base_url';
+ } elseif ($absolute) {
+ $type = 'base_url_absolute';
+ } else {
+ $type = 'base_url_relative';
+ }
+
+ return $this->grav[$type] . $this->baseRoute($lang);
+ }
+
+ /**
+ *
+ * Get home URL for Grav site.
+ *
+ * @param string $lang Optional language code for multilingual links.
+ * @param bool $absolute If true, return absolute url, if false, return relative url. Otherwise return default.
+ *
+ * @return string
+ */
+ public function homeUrl($lang = null, $absolute = null)
+ {
+ return $this->baseUrl($lang, $absolute) ?: '/';
+ }
+
+ /**
+ *
+ * Get URL for Grav site.
+ *
+ * @param string $route Optional route to the page.
+ * @param string $lang Optional language code for multilingual links.
+ * @param bool $absolute If true, return absolute url, if false, return relative url. Otherwise return default.
+ *
+ * @return string
+ */
+ public function url($route = '/', $lang = null, $absolute = null)
+ {
+ if (!$route || $route === '/') {
+ return $this->homeUrl($lang, $absolute);
+ }
+
+ return $this->baseUrl($lang, $absolute) . Uri::filterPath($route);
+ }
+
+ public function setCheckMethod($method)
+ {
+ $this->check_method = strtolower($method);
+ }
+
+ /**
+ * Reset pages (used in search indexing etc).
+ */
+ public function reset()
+ {
+ $this->initialized = false;
+
+ $this->init();
+ }
+
+ /**
+ * Class initialization. Must be called before using this class.
+ */
+ public function init()
+ {
+ if ($this->initialized) {
+ return;
+ }
+
+ $config = $this->grav['config'];
+ $this->ignore_files = $config->get('system.pages.ignore_files');
+ $this->ignore_folders = $config->get('system.pages.ignore_folders');
+ $this->ignore_hidden = $config->get('system.pages.ignore_hidden');
+
+ $this->instances = [];
+ $this->children = [];
+ $this->routes = [];
+
+ if (!$this->check_method) {
+ $this->setCheckMethod($config->get('system.cache.check.method', 'file'));
+ }
+
+ $this->buildPages();
+ }
+
+ /**
+ * Get or set last modification time.
+ *
+ * @param int $modified
+ *
+ * @return int|null
+ */
+ public function lastModified($modified = null)
+ {
+ if ($modified && $modified > $this->last_modified) {
+ $this->last_modified = $modified;
+ }
+
+ return $this->last_modified;
+ }
+
+ /**
+ * Returns a list of all pages.
+ *
+ * @return array|PageInterface[]
+ */
+ public function instances()
+ {
+ return $this->instances;
+ }
+
+ /**
+ * Returns a list of all routes.
+ *
+ * @return array
+ */
+ public function routes()
+ {
+ return $this->routes;
+ }
+
+ /**
+ * Adds a page and assigns a route to it.
+ *
+ * @param PageInterface $page Page to be added.
+ * @param string $route Optional route (uses route from the object if not set).
+ */
+ public function addPage(PageInterface $page, $route = null)
+ {
+ if (!isset($this->instances[$page->path()])) {
+ $this->instances[$page->path()] = $page;
+ }
+ $route = $page->route($route);
+ if ($page->parent()) {
+ $this->children[$page->parent()->path()][$page->path()] = ['slug' => $page->slug()];
+ }
+ $this->routes[$route] = $page->path();
+
+ $this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
+ }
+
+ /**
+ * Sort sub-pages in a page.
+ *
+ * @param PageInterface $page
+ * @param string $order_by
+ * @param string $order_dir
+ *
+ * @return array
+ */
+ public function sort(PageInterface $page, $order_by = null, $order_dir = null, $sort_flags = null)
+ {
+ if ($order_by === null) {
+ $order_by = $page->orderBy();
+ }
+ if ($order_dir === null) {
+ $order_dir = $page->orderDir();
+ }
+
+ $path = $page->path();
+ $children = $this->children[$path] ?? [];
+
+ if (!$children) {
+ return $children;
+ }
+
+ if (!isset($this->sort[$path][$order_by])) {
+ $this->buildSort($path, $children, $order_by, $page->orderManual(), $sort_flags);
+ }
+
+ $sort = $this->sort[$path][$order_by];
+
+ if ($order_dir !== 'asc') {
+ $sort = array_reverse($sort);
+ }
+
+ return $sort;
+ }
+
+ /**
+ * @param Collection $collection
+ * @param string|int $orderBy
+ * @param string $orderDir
+ * @param array|null $orderManual
+ * @param int|null $sort_flags
+ *
+ * @return array
+ * @internal
+ */
+ public function sortCollection(Collection $collection, $orderBy, $orderDir = 'asc', $orderManual = null, $sort_flags = null)
+ {
+ $items = $collection->toArray();
+ if (!$items) {
+ return [];
+ }
+
+ $lookup = md5(json_encode($items) . json_encode($orderManual) . $orderBy . $orderDir);
+ if (!isset($this->sort[$lookup][$orderBy])) {
+ $this->buildSort($lookup, $items, $orderBy, $orderManual, $sort_flags);
+ }
+
+ $sort = $this->sort[$lookup][$orderBy];
+
+ if ($orderDir !== 'asc') {
+ $sort = array_reverse($sort);
+ }
+
+ return $sort;
+
+ }
+
+ /**
+ * Get a page instance.
+ *
+ * @param string $path The filesystem full path of the page
+ *
+ * @return PageInterface
+ * @throws \Exception
+ */
+ public function get($path)
+ {
+ return $this->instances[(string)$path] ?? null;
+ }
+
+ /**
+ * Get children of the path.
+ *
+ * @param string $path
+ *
+ * @return Collection
+ */
+ public function children($path)
+ {
+ $children = $this->children[(string)$path] ?? [];
+
+ return new Collection($children, [], $this);
+ }
+
+ /**
+ * Get a page ancestor.
+ *
+ * @param string $route The relative URL of the page
+ * @param string $path The relative path of the ancestor folder
+ *
+ * @return PageInterface|null
+ */
+ public function ancestor($route, $path = null)
+ {
+ if ($path !== null) {
+ $page = $this->dispatch($route, true);
+
+ if ($page && $page->path() === $path) {
+ return $page;
+ }
+
+ $parent = $page ? $page->parent() : null;
+ if ($parent && !$parent->root()) {
+ return $this->ancestor($parent->route(), $path);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get a page ancestor trait.
+ *
+ * @param string $route The relative route of the page
+ * @param string $field The field name of the ancestor to query for
+ *
+ * @return PageInterface|null
+ */
+ public function inherited($route, $field = null)
+ {
+ if ($field !== null) {
+
+ $page = $this->dispatch($route, true);
+
+ $parent = $page ? $page->parent() : null;
+ if ($parent && $parent->value('header.' . $field) !== null) {
+ return $parent;
+ }
+ if ($parent && !$parent->root()) {
+ return $this->inherited($parent->route(), $field);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * alias method to return find a page.
+ *
+ * @param string $route The relative URL of the page
+ * @param bool $all
+ *
+ * @return PageInterface|null
+ */
+ public function find($route, $all = false)
+ {
+ return $this->dispatch($route, $all, false);
+ }
+
+ /**
+ * Dispatch URI to a page.
+ *
+ * @param string $route The relative URL of the page
+ * @param bool $all
+ *
+ * @param bool $redirect
+ * @return PageInterface|null
+ * @throws \Exception
+ */
+ public function dispatch($route, $all = false, $redirect = true)
+ {
+ $route = urldecode($route);
+
+ // Fetch page if there's a defined route to it.
+ $page = isset($this->routes[$route]) ? $this->get($this->routes[$route]) : null;
+ // Try without trailing slash
+ if (!$page && Utils::endsWith($route, '/')) {
+ $page = isset($this->routes[rtrim($route, '/')]) ? $this->get($this->routes[rtrim($route, '/')]) : null;
+ }
+
+ // Are we in the admin? this is important!
+ $not_admin = !isset($this->grav['admin']);
+
+ // If the page cannot be reached, look into site wide redirects, routes + wildcards
+ if (!$all && $not_admin) {
+
+ // If the page is a simple redirect, just do it.
+ if ($redirect && $page && $page->redirect()) {
+ $this->grav->redirectLangSafe($page->redirect());
+ }
+
+ // fall back and check site based redirects
+ if (!$page || ($page && !$page->routable())) {
+ /** @var Config $config */
+ $config = $this->grav['config'];
+
+ // See if route matches one in the site configuration
+ $site_route = $config->get("site.routes.{$route}");
+ if ($site_route) {
+ $page = $this->dispatch($site_route, $all);
+ } else {
+
+ /** @var Uri $uri */
+ $uri = $this->grav['uri'];
+ /** @var \Grav\Framework\Uri\Uri $source_url */
+ $source_url = $uri->uri(false);
+
+ // Try Regex style 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) {
+ $this->grav->redirectLangSafe($found);
+ }
+ } catch (ErrorException $e) {
+ $this->grav['log']->error('site.redirects: ' . $pattern . '-> ' . $e->getMessage());
+ }
+ }
+ }
+
+ // Try Regex style routes
+ $site_routes = $config->get('site.routes');
+ if (is_array($site_routes)) {
+ foreach ((array)$site_routes as $pattern => $replace) {
+ $pattern = '#^' . str_replace('/', '\/', ltrim($pattern, '^')) . '#';
+ try {
+ $found = preg_replace($pattern, $replace, $source_url);
+ if ($found !== $source_url) {
+ $page = $this->dispatch($found, $all);
+ }
+ } catch (ErrorException $e) {
+ $this->grav['log']->error('site.routes: ' . $pattern . '-> ' . $e->getMessage());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return $page;
+ }
+
+ /**
+ * Get root page.
+ *
+ * @return PageInterface
+ */
+ public function root()
+ {
+ /** @var UniformResourceLocator $locator */
+ $locator = $this->grav['locator'];
+
+ return $this->instances[rtrim($locator->findResource('page://'), DS)];
+ }
+
+ /**
+ * Get a blueprint for a page type.
+ *
+ * @param string $type
+ *
+ * @return Blueprint
+ */
+ public function blueprints($type)
+ {
+ if ($this->blueprints === null) {
+ $this->blueprints = new Blueprints(self::getTypes());
+ }
+
+ try {
+ $blueprint = $this->blueprints->get($type);
+ } catch (\RuntimeException $e) {
+ $blueprint = $this->blueprints->get('default');
+ }
+
+ if (empty($blueprint->initialized)) {
+ $blueprint->initialized = true;
+ $this->grav->fireEvent('onBlueprintCreated', new Event(['blueprint' => $blueprint, 'type' => $type]));
+ }
+
+ return $blueprint;
+ }
+
+ /**
+ * Get all pages
+ *
+ * @param PageInterface $current
+ *
+ * @return \Grav\Common\Page\Collection
+ */
+ public function all(PageInterface $current = null)
+ {
+ $all = new Collection();
+
+ /** @var PageInterface $current */
+ $current = $current ?: $this->root();
+
+ if (!$current->root()) {
+ $all[$current->path()] = ['slug' => $current->slug()];
+ }
+
+ foreach ($current->children() as $next) {
+ $all->append($this->all($next));
+ }
+
+ return $all;
+ }
+
+ /**
+ * Get available parents raw routes.
+ *
+ * @return array
+ */
+ public static function parentsRawRoutes()
+ {
+ $rawRoutes = true;
+
+ return self::getParents($rawRoutes);
+ }
+
+ /**
+ * Get available parents routes
+ *
+ * @param bool $rawRoutes get the raw route or the normal route
+ *
+ * @return array
+ */
+ private static function getParents($rawRoutes)
+ {
+ $grav = Grav::instance();
+
+ /** @var Pages $pages */
+ $pages = $grav['pages'];
+
+ $parents = $pages->getList(null, 0, $rawRoutes);
+
+ if (isset($grav['admin'])) {
+ // Remove current route from parents
+
+ /** @var Admin $admin */
+ $admin = $grav['admin'];
+
+ $page = $admin->getPage($admin->route);
+ $page_route = $page->route();
+ if (isset($parents[$page_route])) {
+ unset($parents[$page_route]);
+ }
+
+ }
+
+ return $parents;
+ }
+
+ /**
+ * Get list of route/title of all pages.
+ *
+ * @param PageInterface $current
+ * @param int $level
+ * @param bool $rawRoutes
+ *
+ * @param bool $showAll
+ * @param bool $showFullpath
+ * @param bool $showSlug
+ * @param bool $showModular
+ * @param bool $limitLevels
+ * @return array
+ */
+ public function getList(PageInterface $current = null, $level = 0, $rawRoutes = false, $showAll = true, $showFullpath = false, $showSlug = false, $showModular = false, $limitLevels = false)
+ {
+ if (!$current) {
+ if ($level) {
+ throw new \RuntimeException('Internal error');
+ }
+
+ $current = $this->root();
+ }
+
+ $list = [];
+
+ if (!$current->root()) {
+ if ($rawRoutes) {
+ $route = $current->rawRoute();
+ } else {
+ $route = $current->route();
+ }
+
+ if ($showFullpath) {
+ $option = $current->route();
+ } else {
+ $extra = $showSlug ? '(' . $current->slug() . ') ' : '';
+ $option = str_repeat('—-', $level). '▸ ' . $extra . $current->title();
+
+
+ }
+
+ $list[$route] = $option;
+
+
+ }
+
+ if ($limitLevels === false || ($level+1 < $limitLevels)) {
+ foreach ($current->children() as $next) {
+ if ($showAll || $next->routable() || ($next->modular() && $showModular)) {
+ $list = array_merge($list, $this->getList($next, $level + 1, $rawRoutes, $showAll, $showFullpath, $showSlug, $showModular, $limitLevels));
+ }
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * Get available page types.
+ *
+ * @return Types
+ */
+ public static function getTypes()
+ {
+ if (!self::$types) {
+ $grav = Grav::instance();
+
+ $scanBlueprintsAndTemplates = function () use ($grav) {
+ // Scan blueprints
+ $event = new Event();
+ $event->types = self::$types;
+ $grav->fireEvent('onGetPageBlueprints', $event);
+
+ self::$types->scanBlueprints('theme://blueprints/');
+
+ // Scan templates
+ $event = new Event();
+ $event->types = self::$types;
+ $grav->fireEvent('onGetPageTemplates', $event);
+
+ self::$types->scanTemplates('theme://templates/');
+ };
+
+ if ($grav['config']->get('system.cache.enabled')) {
+ /** @var Cache $cache */
+ $cache = $grav['cache'];
+
+ // Use cached types if possible.
+ $types_cache_id = md5('types');
+ self::$types = $cache->fetch($types_cache_id);
+
+ if (!self::$types) {
+ self::$types = new Types();
+ $scanBlueprintsAndTemplates();
+ $cache->save($types_cache_id, self::$types);
+ }
+
+ } else {
+ self::$types = new Types();
+ $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;
+ }
+
+ /**
+ * Get available page types.
+ *
+ * @return array
+ */
+ public static function types()
+ {
+ $types = self::getTypes();
+
+ return $types->pageSelect();
+ }
+
+ /**
+ * Get available page types.
+ *
+ * @return array
+ */
+ public static function modularTypes()
+ {
+ $types = self::getTypes();
+
+ return $types->modularSelect();
+ }
+
+ /**
+ * Get template types based on page type (standard or modular)
+ *
+ * @return array
+ */
+ public static function pageTypes()
+ {
+ if (isset(Grav::instance()['admin'])) {
+ /** @var Admin $admin */
+ $admin = Grav::instance()['admin'];
+
+ /** @var PageInterface $page */
+ $page = $admin->getPage($admin->route);
+
+ if ($page && $page->modular()) {
+ return static::modularTypes();
+ }
+
+ return static::types();
+ }
+
+ return [];
+ }
+
+ /**
+ * Get access levels of the site pages
+ *
+ * @return array
+ */
+ public function accessLevels()
+ {
+ $accessLevels = [];
+ foreach ($this->all() as $page) {
+ if (isset($page->header()->access)) {
+ if (\is_array($page->header()->access)) {
+ foreach ($page->header()->access as $index => $accessLevel) {
+ if (\is_array($accessLevel)) {
+ foreach ($accessLevel as $innerIndex => $innerAccessLevel) {
+ $accessLevels[] = $innerIndex;
+ }
+ } else {
+ $accessLevels[] = $index;
+ }
+ }
+ } else {
+ $accessLevels[] = $page->header()->access;
+ }
+ }
+ }
+
+ return array_unique($accessLevels);
+ }
+
+ /**
+ * Get available parents routes
+ *
+ * @return array
+ */
+ public static function parents()
+ {
+ $rawRoutes = false;
+
+ return self::getParents($rawRoutes);
+ }
+
+
+
+ /**
+ * Gets the home route
+ *
+ * @return string
+ */
+ public static function getHomeRoute()
+ {
+ if (empty(self::$home_route)) {
+ $grav = Grav::instance();
+
+ /** @var Config $config */
+ $config = $grav['config'];
+
+ /** @var Language $language */
+ $language = $grav['language'];
+
+ $home = $config->get('system.home.alias');
+
+ if ($language->enabled()) {
+ $home_aliases = $config->get('system.home.aliases');
+ if ($home_aliases) {
+ $active = $language->getActive();
+ $default = $language->getDefault();
+
+ try {
+ if ($active) {
+ $home = $home_aliases[$active];
+ } else {
+ $home = $home_aliases[$default];
+ }
+ } catch (ErrorException $e) {
+ $home = $home_aliases[$default];
+ }
+
+ }
+ }
+
+ self::$home_route = trim($home, '/');
+ }
+
+ return self::$home_route;
+ }
+
+ /**
+ * Needed for testing where we change the home route via config
+ */
+ public static function resetHomeRoute()
+ {
+ self::$home_route = null;
+ return self::getHomeRoute();
+ }
+
+ /**
+ * Builds pages.
+ *
+ * @internal
+ */
+ protected function buildPages()
+ {
+ $this->sort = [];
+
+ /** @var Config $config */
+ $config = $this->grav['config'];
+
+ /** @var Language $language */
+ $language = $this->grav['language'];
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $this->grav['locator'];
+
+ $pages_dir = $locator->findResource('page://');
+
+ // Set active language
+ $this->active_lang = $language->getActive();
+
+ if ($config->get('system.cache.enabled')) {
+ /** @var Cache $cache */
+ $cache = $this->grav['cache'];
+ /** @var Taxonomy $taxonomy */
+ $taxonomy = $this->grav['taxonomy'];
+
+ // how should we check for last modified? Default is by file
+ switch ($this->check_method) {
+ case 'none':
+ case 'off':
+ $hash = 0;
+ break;
+ case 'folder':
+ $hash = Folder::lastModifiedFolder($pages_dir);
+ break;
+ case 'hash':
+ $hash = Folder::hashAllFiles($pages_dir);
+ break;
+ default:
+ $hash = Folder::lastModifiedFile($pages_dir);
+ }
+
+ $this->pages_cache_id = md5($pages_dir . $hash . $language->getActive() . $config->checksum());
+
+ $cached = $cache->fetch($this->pages_cache_id);
+ if ($cached) {
+ $this->grav['debugger']->addMessage('Page cache hit.');
+
+ list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cached;
+
+ // If pages was found in cache, set the taxonomy
+ $taxonomy->taxonomy($taxonomy_map);
+ } else {
+ $this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..');
+
+ // recurse pages and cache result
+ $this->resetPages($pages_dir);
+ }
+ } else {
+ $this->recurse($pages_dir);
+ $this->buildRoutes();
+ }
+ }
+
+ /**
+ * Accessible method to manually reset the pages cache
+ *
+ * @param string $pages_dir
+ */
+ public function resetPages($pages_dir)
+ {
+ $this->recurse($pages_dir);
+ $this->buildRoutes();
+
+ // cache if needed
+ if ($this->grav['config']->get('system.cache.enabled')) {
+ /** @var Cache $cache */
+ $cache = $this->grav['cache'];
+ /** @var Taxonomy $taxonomy */
+ $taxonomy = $this->grav['taxonomy'];
+
+ // save pages, routes, taxonomy, and sort to cache
+ $cache->save($this->pages_cache_id, [$this->instances, $this->routes, $this->children, $taxonomy->taxonomy(), $this->sort]);
+ }
+ }
+
+ /**
+ * Recursive function to load & build page relationships.
+ *
+ * @param string $directory
+ * @param PageInterface|null $parent
+ *
+ * @return PageInterface
+ * @throws \RuntimeException
+ * @internal
+ */
+ protected function recurse($directory, PageInterface $parent = null)
+ {
+ $directory = rtrim($directory, DS);
+ $page = new Page;
+
+ /** @var Config $config */
+ $config = $this->grav['config'];
+
+ /** @var Language $language */
+ $language = $this->grav['language'];
+
+ // 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);
+ if ($parent) {
+ $page->parent($parent);
+ }
+
+ $page->orderDir($config->get('system.pages.order.dir'));
+ $page->orderBy($config->get('system.pages.order.by'));
+
+ // Add into instances
+ if (!isset($this->instances[$page->path()])) {
+ $this->instances[$page->path()] = $page;
+ if ($parent && $page->path()) {
+ $this->children[$parent->path()][$page->path()] = ['slug' => $page->slug()];
+ }
+ } else {
+ throw new \RuntimeException('Fatal error when creating page instances.');
+ }
+
+ // Build regular expression for all the allowed page extensions.
+ $page_extensions = $language->getFallbackPageExtensions();
+ $regex = '/^[^\.]*(' . implode('|', array_map(
+ function ($str) {
+ return preg_quote($str, '/');
+ },
+ $page_extensions
+ )) . ')$/';
+
+ $folders = [];
+ $page_found = null;
+ $page_extension = '.md';
+ $last_modified = 0;
+
+ $iterator = new \FilesystemIterator($directory);
+ /** @var \FilesystemIterator $file */
+ foreach ($iterator as $file) {
+ $filename = $file->getFilename();
+
+ // Ignore all hidden files if set.
+ if ($this->ignore_hidden && $filename && strpos($filename, '.') === 0) {
+ continue;
+ }
+
+ // Handle folders later.
+ if ($file->isDir()) {
+ // But ignore all folders in ignore list.
+ if (!\in_array($filename, $this->ignore_folders, true)) {
+ $folders[] = $file;
+ }
+ continue;
+ }
+
+ // Ignore all files in ignore list.
+ if (\in_array($filename, $this->ignore_files, true)) {
+ continue;
+ }
+
+ // Update last modified date to match the last updated file in the folder.
+ $modified = $file->getMTime();
+ if ($modified > $last_modified) {
+ $last_modified = $modified;
+ }
+
+ // Page is the one that matches to $page_extensions list with the lowest index number.
+ if (preg_match($regex, $filename, $matches, PREG_OFFSET_CAPTURE)) {
+ $ext = $matches[1][0];
+
+ if ($page_found === null || array_search($ext, $page_extensions, true) < array_search($page_extension, $page_extensions, true)) {
+ $page_found = $file;
+ $page_extension = $ext;
+ }
+ }
+ }
+
+ $content_exists = false;
+ if ($parent && $page_found) {
+ $page->init($page_found, $page_extension);
+
+ $content_exists = true;
+
+ if ($config->get('system.pages.events.page')) {
+ $this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
+ }
+ }
+
+ // Now handle all the folders under the page.
+ /** @var \FilesystemIterator $file */
+ foreach ($folders as $file) {
+ $filename = $file->getFilename();
+
+ // if folder contains separator, continue
+ if (Utils::contains($file->getFilename(), $config->get('system.param_sep', ':'))) {
+ continue;
+ }
+
+ if (!$page->path()) {
+ $page->path($file->getPath());
+ }
+
+ $path = $directory . DS . $filename;
+ $child = $this->recurse($path, $page);
+
+ if (Utils::startsWith($filename, '_')) {
+ $child->routable(false);
+ }
+
+ $this->children[$page->path()][$child->path()] = ['slug' => $child->slug()];
+
+ if ($config->get('system.pages.events.page')) {
+ $this->grav->fireEvent('onFolderProcessed', new Event(['page' => $page]));
+ }
+ }
+
+
+ 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
+ if ($page->template() === 'modular') {
+ foreach ($page->collection() as $child) {
+ $modified = $child->modified();
+
+ if ($modified > $last_modified) {
+ $last_modified = $modified;
+ }
+ }
+ }
+
+ // Override the modified and ID so that it takes the latest change into account
+ $page->modified($last_modified);
+ $page->id($last_modified . md5($page->filePath()));
+
+ // Sort based on Defaults or Page Overridden sort order
+ $this->children[$page->path()] = $this->sort($page);
+
+ return $page;
+ }
+
+ /**
+ * @internal
+ */
+ protected function buildRoutes()
+ {
+ /** @var Taxonomy $taxonomy */
+ $taxonomy = $this->grav['taxonomy'];
+
+ // Get the home route
+ $home = self::resetHomeRoute();
+
+ // Build routes and taxonomy map.
+ /** @var PageInterface $page */
+ foreach ($this->instances as $page) {
+ if (!$page->root()) {
+ // process taxonomy
+ $taxonomy->addTaxonomy($page);
+
+ $route = $page->route();
+ $raw_route = $page->rawRoute();
+ $page_path = $page->path();
+
+ // add regular route
+ $this->routes[$route] = $page_path;
+
+ // add raw route
+ if ($raw_route !== $route) {
+ $this->routes[$raw_route] = $page_path;
+ }
+
+ // add canonical route
+ $route_canonical = $page->routeCanonical();
+ if ($route_canonical && ($route !== $route_canonical)) {
+ $this->routes[$route_canonical] = $page_path;
+ }
+
+ // add aliases to routes list if they are provided
+ $route_aliases = $page->routeAliases();
+ if ($route_aliases) {
+ foreach ($route_aliases as $alias) {
+ $this->routes[$alias] = $page_path;
+ }
+ }
+ }
+ }
+
+ // Alias and set default route to home page.
+ $homeRoute = '/' . $home;
+ if ($home && isset($this->routes[$homeRoute])) {
+ $this->routes['/'] = $this->routes[$homeRoute];
+ $this->get($this->routes[$homeRoute])->route('/');
+ }
+ }
+
+ /**
+ * @param string $path
+ * @param array $pages
+ * @param string $order_by
+ * @param array|null $manual
+ * @param int|null $sort_flags
+ *
+ * @throws \RuntimeException
+ * @internal
+ */
+ protected function buildSort($path, array $pages, $order_by = 'default', $manual = null, $sort_flags = null)
+ {
+ $list = [];
+ $header_default = null;
+ $header_query = [];
+
+ // do this header query work only once
+ if (strpos($order_by, 'header.') === 0) {
+ $query = explode('|', str_replace('header.', '', $order_by), 2);
+ $header_query = array_shift($query) ?? '';
+ $header_default = array_shift($query);
+ }
+
+ foreach ($pages as $key => $info) {
+ $child = $this->instances[$key] ?? null;
+ if (!$child) {
+ throw new \RuntimeException("Page does not exist: {$key}");
+ }
+
+ switch ($order_by) {
+ case 'title':
+ $list[$key] = $child->title();
+ break;
+ case 'date':
+ $list[$key] = $child->date();
+ $sort_flags = SORT_REGULAR;
+ break;
+ case 'modified':
+ $list[$key] = $child->modified();
+ $sort_flags = SORT_REGULAR;
+ break;
+ case 'publish_date':
+ $list[$key] = $child->publishDate();
+ $sort_flags = SORT_REGULAR;
+ break;
+ case 'unpublish_date':
+ $list[$key] = $child->unpublishDate();
+ $sort_flags = SORT_REGULAR;
+ break;
+ case 'slug':
+ $list[$key] = $child->slug();
+ break;
+ case 'basename':
+ $list[$key] = basename($key);
+ break;
+ case 'folder':
+ $list[$key] = $child->folder();
+ break;
+ case 'manual':
+ case 'default':
+ default:
+ if (is_string($header_query)) {
+ $child_header = $child->header();
+ if (!$child_header instanceof Header) {
+ $child_header = new Header((array)$child_header);
+ }
+ $header_value = $child_header->get($header_query);
+ if (is_array($header_value)) {
+ $list[$key] = implode(',', $header_value);
+ } elseif ($header_value) {
+ $list[$key] = $header_value;
+ } else {
+ $list[$key] = $header_default ?: $key;
+ }
+ $sort_flags = $sort_flags ?: SORT_REGULAR;
+ break;
+ }
+ $list[$key] = $key;
+ $sort_flags = $sort_flags ?: SORT_REGULAR;
+ }
+ }
+
+ if (!$sort_flags) {
+ $sort_flags = SORT_NATURAL | SORT_FLAG_CASE;
+ }
+
+ // handle special case when order_by is random
+ if ($order_by === 'random') {
+ $list = $this->arrayShuffle($list);
+ } else {
+ // else just sort the list according to specified key
+ if (extension_loaded('intl') && $this->grav['config']->get('system.intl_enabled')) {
+ $locale = setlocale(LC_COLLATE, 0); //`setlocale` with a 0 param returns the current locale set
+ $col = Collator::create($locale);
+ if ($col) {
+ if (($sort_flags & SORT_NATURAL) === SORT_NATURAL) {
+ $list = preg_replace_callback('~([0-9]+)\.~', function($number) {
+ return sprintf('%032d.', $number[0]);
+ }, $list);
+
+ $list_vals = array_values($list);
+ if (is_numeric(array_shift($list_vals))) {
+ $sort_flags = Collator::SORT_REGULAR;
+ } else {
+ $sort_flags = Collator::SORT_STRING;
+ }
+ }
+
+ $col->asort($list, $sort_flags);
+ } else {
+ asort($list, $sort_flags);
+ }
+ } else {
+ asort($list, $sort_flags);
+ }
+ }
+
+
+ // Move manually ordered items into the beginning of the list. Order of the unlisted items does not change.
+ if (is_array($manual) && !empty($manual)) {
+ $new_list = [];
+ $i = count($manual);
+
+ foreach ($list as $key => $dummy) {
+ $info = $pages[$key];
+ $order = \array_search($info['slug'], $manual, true);
+ if ($order === false) {
+ $order = $i++;
+ }
+ $new_list[$key] = (int)$order;
+ }
+
+ $list = $new_list;
+
+ // Apply manual ordering to the list.
+ asort($list);
+ }
+
+ foreach ($list as $key => $sort) {
+ $info = $pages[$key];
+ $this->sort[$path][$order_by][$key] = $info;
+ }
+ }
+
+ /**
+ * Shuffles an associative array
+ *
+ * @param array $list
+ *
+ * @return array
+ */
+ protected function arrayShuffle($list)
+ {
+ $keys = array_keys($list);
+ shuffle($keys);
+
+ $new = [];
+ foreach ($keys as $key) {
+ $new[$key] = $list[$key];
+ }
+
+ return $new;
+ }
+
+ /**
+ * Get the Pages cache ID
+ *
+ * this is particularly useful to know if pages have changed and you want
+ * to sync another cache with pages cache - works best in `onPagesInitialized()`
+ *
+ * @return mixed
+ */
+ public function getPagesCacheId()
+ {
+ return $this->pages_cache_id;
+ }
+}
diff --git a/system/src/Grav/Common/Page/Types.php b/system/src/Grav/Common/Page/Types.php
new file mode 100644
index 0000000..03e3f6e
--- /dev/null
+++ b/system/src/Grav/Common/Page/Types.php
@@ -0,0 +1,141 @@
+items[$type])) {
+ $this->items[$type] = [];
+ } elseif (!$blueprint) {
+ return;
+ }
+
+ if (!$blueprint && $this->systemBlueprints) {
+ $blueprint = $this->systemBlueprints[$type] ?? $this->systemBlueprints['default'];
+ }
+
+ if ($blueprint) {
+ array_unshift($this->items[$type], $blueprint);
+ }
+ }
+
+ public function scanBlueprints($uri)
+ {
+ if (!\is_string($uri)) {
+ throw new \InvalidArgumentException('First parameter must be URI');
+ }
+
+ if (!$this->systemBlueprints) {
+ $this->systemBlueprints = $this->findBlueprints('blueprints://pages');
+
+ // Register default by default.
+ $this->register('default');
+
+ $this->register('external');
+ }
+
+ foreach ($this->findBlueprints($uri) as $type => $blueprint) {
+ $this->register($type, $blueprint);
+ }
+ }
+
+ public function scanTemplates($uri)
+ {
+ if (!\is_string($uri)) {
+ throw new \InvalidArgumentException('First parameter must be URI');
+ }
+
+ $options = [
+ 'compare' => 'Filename',
+ 'pattern' => '|\.html\.twig$|',
+ 'filters' => [
+ 'value' => '|\.html\.twig$|'
+ ],
+ 'value' => 'Filename',
+ 'recursive' => false
+ ];
+
+ foreach (Folder::all($uri, $options) as $type) {
+ $this->register($type);
+ }
+
+ $modular_uri = rtrim($uri, '/') . '/modular';
+ if (is_dir($modular_uri)) {
+ foreach (Folder::all($modular_uri, $options) as $type) {
+ $this->register('modular/' . $type);
+ }
+ }
+ }
+
+ public function pageSelect()
+ {
+ $list = [];
+ foreach ($this->items as $name => $file) {
+ if (strpos($name, '/')) {
+ continue;
+ }
+ $list[$name] = ucfirst(str_replace('_', ' ', $name));
+ }
+ ksort($list);
+
+ return $list;
+ }
+
+ public function modularSelect()
+ {
+ $list = [];
+ foreach ($this->items as $name => $file) {
+ if (strpos($name, 'modular/') !== 0) {
+ continue;
+ }
+ $list[$name] = ucfirst(trim(str_replace('_', ' ', basename($name))));
+ }
+ ksort($list);
+
+ return $list;
+ }
+
+ private function findBlueprints($uri)
+ {
+ $options = [
+ 'compare' => 'Filename',
+ 'pattern' => '|\.yaml$|',
+ 'filters' => [
+ 'key' => '|\.yaml$|'
+ ],
+ 'key' => 'SubPathName',
+ 'value' => 'PathName',
+ ];
+
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+ if ($locator->isStream($uri)) {
+ $options['value'] = 'Url';
+ }
+
+ return Folder::all($uri, $options);
+ }
+}
diff --git a/system/src/Grav/Common/Plugin.php b/system/src/Grav/Common/Plugin.php
new file mode 100644
index 0000000..727fd95
--- /dev/null
+++ b/system/src/Grav/Common/Plugin.php
@@ -0,0 +1,390 @@
+name = $name;
+ $this->grav = $grav;
+ if ($config) {
+ $this->setConfig($config);
+ }
+ }
+
+ /**
+ * @param Config $config
+ * @return $this
+ */
+ public function setConfig(Config $config)
+ {
+ $this->config = $config;
+
+ return $this;
+ }
+
+ /**
+ * Get configuration of the plugin.
+ *
+ * @return array
+ */
+ public function config()
+ {
+ return $this->config["plugins.{$this->name}"];
+ }
+
+ /**
+ * Determine if plugin is running under the admin
+ *
+ * @return bool
+ */
+ public function isAdmin()
+ {
+ return Utils::isAdminPlugin();
+ }
+
+ /**
+ * Determine if plugin is running under the CLI
+ *
+ * @return bool
+ */
+ public function isCli()
+ {
+ return \defined('GRAV_CLI');
+ }
+
+ /**
+ * Determine if this route is in Admin and active for the plugin
+ *
+ * @param string $plugin_route
+ * @return bool
+ */
+ protected function isPluginActiveAdmin($plugin_route)
+ {
+ $should_run = false;
+
+ $uri = $this->grav['uri'];
+
+ if (strpos($uri->path(), $this->config->get('plugins.admin.route') . '/' . $plugin_route) === false) {
+ $should_run = false;
+ } elseif (isset($uri->paths()[1]) && $uri->paths()[1] === $plugin_route) {
+ $should_run = true;
+ }
+
+ return $should_run;
+ }
+
+ /**
+ * @param array $events
+ */
+ protected function enable(array $events)
+ {
+ /** @var EventDispatcher $dispatcher */
+ $dispatcher = $this->grav['events'];
+
+ foreach ($events as $eventName => $params) {
+ if (\is_string($params)) {
+ $dispatcher->addListener($eventName, [$this, $params]);
+ } elseif (\is_string($params[0])) {
+ $dispatcher->addListener($eventName, [$this, $params[0]], $this->getPriority($params, $eventName));
+ } else {
+ foreach ($params as $listener) {
+ $dispatcher->addListener($eventName, [$this, $listener[0]], $this->getPriority($listener, $eventName));
+ }
+ }
+ }
+ }
+
+ /**
+ * @param array $params
+ * @param string $eventName
+ */
+ private function getPriority($params, $eventName)
+ {
+ $grav = Grav::instance();
+ $override = implode('.', ["priorities", $this->name, $eventName, $params[0]]);
+ if ($grav['config']->get($override) !== null)
+ {
+ return $grav['config']->get($override);
+ } elseif (isset($params[1])) {
+ return $params[1];
+ }
+ return 0;
+ }
+
+ /**
+ * @param array $events
+ */
+ protected function disable(array $events)
+ {
+ /** @var EventDispatcher $dispatcher */
+ $dispatcher = $this->grav['events'];
+
+ foreach ($events as $eventName => $params) {
+ if (\is_string($params)) {
+ $dispatcher->removeListener($eventName, [$this, $params]);
+ } elseif (\is_string($params[0])) {
+ $dispatcher->removeListener($eventName, [$this, $params[0]]);
+ } else {
+ foreach ($params as $listener) {
+ $dispatcher->removeListener($eventName, [$this, $listener[0]]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Whether or not an offset exists.
+ *
+ * @param string $offset An offset to check for.
+ * @return bool Returns TRUE on success or FALSE on failure.
+ */
+ public function offsetExists($offset)
+ {
+ $this->loadBlueprint();
+
+ if ($offset === 'title') {
+ $offset = 'name';
+ }
+ return isset($this->blueprint[$offset]);
+ }
+
+ /**
+ * Returns the value at specified offset.
+ *
+ * @param string $offset The offset to retrieve.
+ * @return mixed Can return all value types.
+ */
+ public function offsetGet($offset)
+ {
+ $this->loadBlueprint();
+
+ if ($offset === 'title') {
+ $offset = 'name';
+ }
+ return $this->blueprint[$offset] ?? null;
+ }
+
+ /**
+ * Assigns a value to the specified offset.
+ *
+ * @param string $offset The offset to assign the value to.
+ * @param mixed $value The value to set.
+ * @throws \LogicException
+ */
+ public function offsetSet($offset, $value)
+ {
+ throw new \LogicException(__CLASS__ . ' blueprints cannot be modified.');
+ }
+
+ /**
+ * Unsets an offset.
+ *
+ * @param string $offset The offset to unset.
+ * @throws \LogicException
+ */
+ public function offsetUnset($offset)
+ {
+ throw new \LogicException(__CLASS__ . ' blueprints cannot be modified.');
+ }
+
+ /**
+ * This function will search a string for markdown links in a specific format. The link value can be
+ * optionally compared against via the $internal_regex and operated on by the callback $function
+ * provided.
+ *
+ * format: [plugin:myplugin_name](function_data)
+ *
+ * @param string $content The string to perform operations upon
+ * @param callable $function The anonymous callback function
+ * @param string $internal_regex Optional internal regex to extra data from
+ *
+ * @return string
+ */
+ protected function parseLinks($content, $function, $internal_regex = '(.*)')
+ {
+ $regex = '/\[plugin:(?:' . $this->name . ')\]\(' . $internal_regex . '\)/i';
+
+ return preg_replace_callback($regex, $function, $content);
+ }
+
+ /**
+ * Merge global and page configurations.
+ *
+ * @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
+ * merge with the plugin settings.
+ * @param string $type Is this 'plugins' or 'themes'
+ *
+ * @return Data
+ */
+ protected function mergeConfig(PageInterface $page, $deep = false, $params = [], $type = 'plugins')
+ {
+ $class_name = $this->name;
+ $class_name_merged = $class_name . '.merged';
+ $defaults = $this->config->get($type . '.' . $class_name, []);
+ $page_header = $page->header();
+ $header = [];
+
+ 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)) {
+ // Overwrite enabled option with boolean value in page header
+ $config = ['enabled' => $config];
+ }
+ // Merge page header settings using deep or shallow merging technique
+ $header = $this->mergeArrays($deep, $defaults, $config);
+
+ // 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));
+ } elseif (isset($page_header->{$class_name_merged})) {
+ $merged = $page_header->{$class_name_merged};
+ $header = $merged->toArray();
+ }
+ if (empty($header)) {
+ $header = $defaults;
+ }
+ // Merge additional parameter with configuration options
+ $header = $this->mergeArrays($deep, $header, $params);
+
+ // Return configurations as a new data config class
+ return new Data($header);
+ }
+
+ /**
+ * Merge arrays based on deepness
+ *
+ * @param string|bool $deep
+ * @param array $array1
+ * @param array $array2
+ * @return array
+ */
+ private function mergeArrays($deep, $array1, $array2)
+ {
+ if ($deep === 'merge') {
+ return Utils::arrayMergeRecursiveUnique($array1, $array2);
+ }
+ if ($deep === true) {
+ return array_replace_recursive($array1, $array2);
+ }
+
+ return array_merge($array1, $array2);
+ }
+
+ /**
+ * Persists to disk the plugin parameters currently stored in the Grav Config object
+ *
+ * @param string $plugin_name The name of the plugin whose config it should store.
+ *
+ * @return bool
+ */
+ public static function saveConfig($plugin_name)
+ {
+ if (!$plugin_name) {
+ return false;
+ }
+
+ $grav = Grav::instance();
+ $locator = $grav['locator'];
+ $filename = 'config://plugins/' . $plugin_name . '.yaml';
+ $file = YamlFile::instance($locator->findResource($filename, true, true));
+ $content = $grav['config']->get('plugins.' . $plugin_name);
+ $file->save($content);
+ $file->free();
+
+ return true;
+ }
+
+ /**
+ * Simpler getter for the plugin blueprint
+ *
+ * @return Blueprint
+ */
+ public function getBlueprint()
+ {
+ if (!$this->blueprint) {
+ $this->loadBlueprint();
+ }
+ return $this->blueprint;
+ }
+
+ /**
+ * Load blueprints.
+ */
+ protected function loadBlueprint()
+ {
+ if (!$this->blueprint) {
+ $grav = Grav::instance();
+ $plugins = $grav['plugins'];
+ $this->blueprint = $plugins->get($this->name)->blueprints();
+ }
+ }
+}
diff --git a/system/src/Grav/Common/Plugins.php b/system/src/Grav/Common/Plugins.php
new file mode 100644
index 0000000..358727f
--- /dev/null
+++ b/system/src/Grav/Common/Plugins.php
@@ -0,0 +1,228 @@
+getIterator('plugins://');
+
+ $plugins = [];
+ foreach($iterator as $directory) {
+ if (!$directory->isDir()) {
+ continue;
+ }
+ $plugins[] = $directory->getFilename();
+ }
+
+ natsort($plugins);
+
+ foreach ($plugins as $plugin) {
+ $this->add($this->loadPlugin($plugin));
+ }
+ }
+
+ /**
+ * @return $this
+ */
+ public function setup()
+ {
+ $blueprints = [];
+ $formFields = [];
+
+ /** @var Plugin $plugin */
+ foreach ($this->items as $plugin) {
+ if (isset($plugin->features['blueprints'])) {
+ $blueprints["plugin://{$plugin->name}/blueprints"] = $plugin->features['blueprints'];
+ }
+ if (method_exists($plugin, 'getFormFieldTypes')) {
+ $formFields[get_class($plugin)] = isset($plugin->features['formfields']) ? $plugin->features['formfields'] : 0;
+ }
+ }
+
+ if ($blueprints) {
+ // Order by priority.
+ arsort($blueprints);
+
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+ $locator->addPath('blueprints', '', array_keys($blueprints), 'system/blueprints');
+ }
+
+ if ($formFields) {
+ // Order by priority.
+ arsort($formFields);
+
+ $list = [];
+ foreach ($formFields as $className => $priority) {
+ $plugin = $this->items[$className];
+ $list += $plugin->getFormFieldTypes();
+ }
+
+ $this->formFieldTypes = $list;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Registers all plugins.
+ *
+ * @return Plugin[] array of Plugin objects
+ * @throws \RuntimeException
+ */
+ public function init()
+ {
+ $grav = Grav::instance();
+
+ /** @var Config $config */
+ $config = $grav['config'];
+
+ /** @var EventDispatcher $events */
+ $events = $grav['events'];
+
+ foreach ($this->items as $instance) {
+ // Register only enabled plugins.
+ if ($config["plugins.{$instance->name}.enabled"] && $instance instanceof Plugin) {
+ $instance->setConfig($config);
+ $events->addSubscriber($instance);
+ }
+ }
+
+ return $this->items;
+ }
+
+ /**
+ * Add a plugin
+ *
+ * @param Plugin $plugin
+ */
+ public function add($plugin)
+ {
+ if (is_object($plugin)) {
+ $this->items[get_class($plugin)] = $plugin;
+ }
+ }
+
+ /**
+ * Return list of all plugin data with their blueprints.
+ *
+ * @return array
+ */
+ public static function all()
+ {
+ $grav = Grav::instance();
+ $plugins = $grav['plugins'];
+ $list = [];
+
+ foreach ($plugins as $instance) {
+ $name = $instance->name;
+
+ try {
+ $result = self::get($name);
+ } catch (\Exception $e) {
+ $exception = new \RuntimeException(sprintf('Plugin %s: %s', $name, $e->getMessage()), $e->getCode(), $e);
+
+ /** @var Debugger $debugger */
+ $debugger = $grav['debugger'];
+ $debugger->addMessage("Plugin {$name} cannot be loaded, please check Exceptions tab", 'error');
+ $debugger->addException($exception);
+
+ continue;
+ }
+
+ if ($result) {
+ $list[$name] = $result;
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * Get a plugin by name
+ *
+ * @param string $name
+ *
+ * @return Data|null
+ */
+ public static function get($name)
+ {
+ $blueprints = new Blueprints('plugins://');
+ $blueprint = $blueprints->get("{$name}/blueprints");
+
+ // Load default configuration.
+ $file = CompiledYamlFile::instance("plugins://{$name}/{$name}" . YAML_EXT);
+
+ // ensure this is a valid plugin
+ if (!$file->exists()) {
+ return null;
+ }
+
+ $obj = new Data((array)$file->content(), $blueprint);
+
+ // Override with user configuration.
+ $obj->merge(Grav::instance()['config']->get('plugins.' . $name) ?: []);
+
+ // Save configuration always to user/config.
+ $file = CompiledYamlFile::instance("config://plugins/{$name}.yaml");
+ $obj->file($file);
+
+ return $obj;
+ }
+
+ protected function loadPlugin($name)
+ {
+ $grav = Grav::instance();
+ $locator = $grav['locator'];
+
+ $file = $locator->findResource('plugins://' . $name . DS . $name . PLUGIN_EXT);
+
+ if (is_file($file)) {
+ // Local variables available in the file: $grav, $config, $name, $file
+ $class = include_once $file;
+
+ $pluginClassFormat = [
+ 'Grav\\Plugin\\' . ucfirst($name). 'Plugin',
+ 'Grav\\Plugin\\' . Inflector::camelize($name) . 'Plugin'
+ ];
+
+ foreach ($pluginClassFormat as $pluginClass) {
+ if (class_exists($pluginClass)) {
+ $class = new $pluginClass($name, $grav);
+ break;
+ }
+ }
+ } else {
+ $grav['log']->addWarning(
+ sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $name)
+ );
+ return null;
+ }
+
+ return $class;
+ }
+
+}
diff --git a/system/src/Grav/Common/Processors/AssetsProcessor.php b/system/src/Grav/Common/Processors/AssetsProcessor.php
new file mode 100644
index 0000000..8b46401
--- /dev/null
+++ b/system/src/Grav/Common/Processors/AssetsProcessor.php
@@ -0,0 +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
new file mode 100644
index 0000000..83ceb84
--- /dev/null
+++ b/system/src/Grav/Common/Processors/ConfigurationProcessor.php
@@ -0,0 +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
new file mode 100644
index 0000000..11a39e8
--- /dev/null
+++ b/system/src/Grav/Common/Processors/DebuggerAssetsProcessor.php
@@ -0,0 +1,31 @@
+startTimer();
+ $this->container['debugger']->addAssets();
+ $this->stopTimer();
+
+ return $handler->handle($request);
+
+ }
+}
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
new file mode 100644
index 0000000..b0ef6ca
--- /dev/null
+++ b/system/src/Grav/Common/Processors/ErrorsProcessor.php
@@ -0,0 +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
new file mode 100644
index 0000000..eca7c54
--- /dev/null
+++ b/system/src/Grav/Common/Processors/InitializeProcessor.php
@@ -0,0 +1,128 @@
+processCli();
+ }
+ }
+
+ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
+ {
+ $this->startTimer();
+
+ /** @var Config $config */
+ $config = $this->container['config'];
+ $config->debug();
+
+ // Use output buffering to prevent headers from being sent too early.
+ ob_start();
+ if ($config->get('system.cache.gzip') && !@ob_start('ob_gzhandler')) {
+ // Enable zip/deflate with a fallback in case of if browser does not support compressing.
+ ob_start();
+ }
+
+ // Initialize the 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)) {
+ // 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 */
+ $uri = $this->container['uri'];
+ $uri->init();
+
+ // 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, '/')) {
+
+ $redirect = (string) $uri::getCurrentRoute()->toString();
+ $this->container->redirect($redirect);
+ }
+
+ $this->container->setLocale();
+ $this->stopTimer();
+
+ return $handler->handle($request);
+ }
+
+ public function processCli(): void
+ {
+ // Load configuration.
+ $this->container['config']->init();
+ $this->container['plugins']->setup();
+
+ // Disable debugger.
+ $this->container['debugger']->enabled(false);
+
+ // Set timezone, locale.
+ /** @var Config $config */
+ $config = $this->container['config'];
+ $timezone = $config->get('system.timezone');
+ if ($timezone) {
+ date_default_timezone_set($timezone);
+ }
+ $this->container->setLocale();
+
+ // Load plugins.
+ $this->container['plugins']->init();
+
+ // Initialize URI.
+ /** @var Uri $uri */
+ $uri = $this->container['uri'];
+ $uri->init();
+
+ // Load accounts.
+ // TODO: remove in 2.0.
+ $this->container['accounts'];
+ }
+}
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
new file mode 100644
index 0000000..ede22e5
--- /dev/null
+++ b/system/src/Grav/Common/Processors/PagesProcessor.php
@@ -0,0 +1,71 @@
+startTimer();
+
+ // Dump Cache state
+ $this->container['debugger']->addMessage($this->container['cache']->getCacheStatus());
+
+ $this->container['pages']->init();
+ $this->container->fireEvent('onPagesInitialized', new Event(['pages' => $this->container['pages']]));
+ $this->container->fireEvent('onPageInitialized', new Event(['page' => $this->container['page']]));
+
+ /** @var PageInterface $page */
+ $page = $this->container['page'];
+
+ if (!$page->routable()) {
+ // If no page found, fire event
+ $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'] = $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
new file mode 100644
index 0000000..9d2943b
--- /dev/null
+++ b/system/src/Grav/Common/Processors/PluginsProcessor.php
@@ -0,0 +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
new file mode 100644
index 0000000..5ce7bfd
--- /dev/null
+++ b/system/src/Grav/Common/Processors/ProcessorBase.php
@@ -0,0 +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
new file mode 100644
index 0000000..a8e4aaa
--- /dev/null
+++ b/system/src/Grav/Common/Processors/ProcessorInterface.php
@@ -0,0 +1,16 @@
+startTimer();
+
+ $container = $this->container;
+ $output = $container['output'];
+
+ 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..97564e6
--- /dev/null
+++ b/system/src/Grav/Common/Processors/RequestProcessor.php
@@ -0,0 +1,54 @@
+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));
+ }
+
+ $uri = $request->getUri();
+ $ext = mb_strtolower(pathinfo($uri->getPath(), PATHINFO_EXTENSION));
+
+ $request = $request
+ ->withAttribute('grav', $this->container)
+ ->withAttribute('time', $_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME)
+ ->withAttribute('route', Uri::getCurrentRoute()->withExtension($ext))
+ ->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/TasksProcessor.php b/system/src/Grav/Common/Processors/TasksProcessor.php
new file mode 100644
index 0000000..aaf5cdf
--- /dev/null
+++ b/system/src/Grav/Common/Processors/TasksProcessor.php
@@ -0,0 +1,61 @@
+startTimer();
+
+ $task = $this->container['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
new file mode 100644
index 0000000..d1c6ae5
--- /dev/null
+++ b/system/src/Grav/Common/Processors/ThemesProcessor.php
@@ -0,0 +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
new file mode 100644
index 0000000..4ed247d
--- /dev/null
+++ b/system/src/Grav/Common/Processors/TwigProcessor.php
@@ -0,0 +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..1838b07
--- /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' => 'à %02s:%02s',
+ '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 %02s:%02s',
+ '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
new file mode 100644
index 0000000..0f61097
--- /dev/null
+++ b/system/src/Grav/Common/Security.php
@@ -0,0 +1,165 @@
+routes();
+
+ // Remove duplicate for homepage
+ unset($routes['/']);
+
+ $list = [];
+
+ // This needs Symfony 4.1 to work
+ $status && $status([
+ 'type' => 'count',
+ 'steps' => count($routes),
+ ]);
+
+ foreach ($routes as $path) {
+
+ $status && $status([
+ 'type' => 'progress',
+ ]);
+
+ try {
+ $page = $pages->get($path);
+
+ // call the content to load/cache it
+ $header = (array) $page->header();
+ $content = $page->value('content');
+
+ $data = ['header' => $header, 'content' => $content];
+ $results = Security::detectXssFromArray($data);
+
+ if (!empty($results)) {
+ if ($route) {
+ $list[$page->route()] = $results;
+ } else {
+ $list[$page->filePathClean()] = $results;
+ }
+
+ }
+
+ } catch (\Exception $e) {
+ continue;
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * @param array $array Array such as $_POST or $_GET
+ * @param string $prefix Prefix for returned values.
+ * @return array Returns flatten list of potentially dangerous input values, such as 'data.content'.
+ */
+ public static function detectXssFromArray(array $array, $prefix = '')
+ {
+ $list = [];
+
+ foreach ($array as $key => $value) {
+ if (\is_array($value)) {
+ $list[] = static::detectXssFromArray($value, $prefix . $key . '.');
+ }
+ if ($result = static::detectXss($value)) {
+ $list[] = [$prefix . $key => $result];
+ }
+ }
+
+ if (!empty($list)) {
+ return array_merge(...$list);
+ }
+
+ return $list;
+ }
+
+ /**
+ * Determine if string potentially has a XSS attack. This simple function does not catch all XSS and it is likely to
+ * return false positives because of it tags all potentially dangerous HTML tags and attributes without looking into
+ * their content.
+ *
+ * @param string $string The string to run XSS detection logic on
+ * @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
+ */
+ public static function detectXss($string)
+ {
+ // Skip any null or non string values
+ if (null === $string || !\is_string($string) || empty($string)) {
+ return false;
+ }
+
+ // Keep a copy of the original string before cleaning up
+ $orig = $string;
+
+ // URL decode
+ $string = urldecode($string);
+
+ // Convert Hexadecimals
+ $string = (string)preg_replace_callback('!(|\\\)[xX]([0-9a-fA-F]+);?!u', function($m) {
+ return \chr(hexdec($m[2]));
+ }, $string);
+
+ // Clean up entities
+ $string = preg_replace('!(+[0-9]+)!u','$1;', $string);
+
+ // Decode entities
+ $string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8');
+
+ // Strip whitespace characters
+ $string = preg_replace('!\s!u','', $string);
+
+ $config = Grav::instance()['config'];
+
+ $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
+ $patterns = [
+ // Match any attribute starting with "on" or xmlns
+ 'on_events' => '#(<[^>]+[[a-z\x00-\x20\"\'\/])(\son|\sxmlns)[a-z].*=>?#iUu',
+
+ // Match javascript:, livescript:, vbscript:, mocha:, feed: and data: protocols
+ 'invalid_protocols' => '#(' . implode('|', $invalid_protocols) . '):.*?#iUu',
+
+ // Match -moz-bindings
+ 'moz_binding' => '#-moz-binding[a-z\x00-\x20]*:#u',
+
+ // Match style attributes
+ 'html_inline_styles' => '#(<[^>]+[a-z\x00-\x20\"\'\/])(style=[^>]*(url\:|x\:expression).*)>?#iUu',
+
+ // Match potentially dangerous tags
+ 'dangerous_tags' => '#*(' . implode('|', $dangerous_tags) . ')[^>]*>?#ui'
+ ];
+
+
+ // Iterate over rules and return label if fail
+ foreach ((array) $patterns as $name => $regex) {
+ if ($enabled_rules[$name] === true) {
+
+ if (preg_match($regex, $string) || preg_match($regex, $orig)) {
+ return $name;
+ }
+
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/system/src/Grav/Common/Service/AccountsServiceProvider.php b/system/src/Grav/Common/Service/AccountsServiceProvider.php
new file mode 100644
index 0000000..e0778be
--- /dev/null
+++ b/system/src/Grav/Common/Service/AccountsServiceProvider.php
@@ -0,0 +1,129 @@
+get('system.accounts.type', 'data'));
+ if ($type === 'flex') {
+ /** @var Debugger $debugger */
+ $debugger = $container['debugger'];
+ $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)
+ {
+ if (!defined('GRAV_USER_INSTANCE')) {
+ define('GRAV_USER_INSTANCE', 'DATA');
+ }
+
+ // Use User class for backwards compatibility.
+ return new DataUser\UserCollection(User::class);
+ }
+
+ protected function flexAccounts(Container $container)
+ {
+ if (!defined('GRAV_USER_INSTANCE')) {
+ 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
new file mode 100644
index 0000000..004da9c
--- /dev/null
+++ b/system/src/Grav/Common/Service/AssetsServiceProvider.php
@@ -0,0 +1,24 @@
+setup();
+
+ return $backups;
+ };
+ }
+}
diff --git a/system/src/Grav/Common/Service/ConfigServiceProvider.php b/system/src/Grav/Common/Service/ConfigServiceProvider.php
new file mode 100644
index 0000000..c874441
--- /dev/null
+++ b/system/src/Grav/Common/Service/ConfigServiceProvider.php
@@ -0,0 +1,172 @@
+init();
+
+ return $setup;
+ };
+
+ $container['blueprints'] = function ($c) {
+ return static::blueprints($c);
+ };
+
+ $container['config'] = function ($c) {
+ $config = static::load($c);
+
+ // After configuration has been loaded, we can disable YAML compatibility if strict mode has been enabled.
+ if (!$config->get('system.strict_mode.yaml_compat', true)) {
+ YamlFile::globalSettings(['compat' => false, 'native' => true]);
+ }
+
+ return $config;
+ };
+
+ $container['languages'] = function ($c) {
+ return static::languages($c);
+ };
+
+ $container['language'] = function ($c) {
+ return new Language($c);
+ };
+ }
+
+ public static function blueprints(Container $container)
+ {
+ /** Setup $setup */
+ $setup = $container['setup'];
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $container['locator'];
+
+ $cache = $locator->findResource('cache://compiled/blueprints', true, true);
+
+ $files = [];
+ $paths = $locator->findResources('blueprints://config');
+ $files += (new ConfigFileFinder)->locateFiles($paths);
+ $paths = $locator->findResources('plugins://');
+ $files += (new ConfigFileFinder)->setBase('plugins')->locateInFolders($paths, 'blueprints');
+
+ $blueprints = new CompiledBlueprints($cache, $files, GRAV_ROOT);
+
+ return $blueprints->name("master-{$setup->environment}")->load();
+ }
+
+ /**
+ * @param Container $container
+ * @return Config
+ */
+ public static function load(Container $container)
+ {
+ /** Setup $setup */
+ $setup = $container['setup'];
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $container['locator'];
+
+ $cache = $locator->findResource('cache://compiled/config', true, true);
+
+ $files = [];
+ $paths = $locator->findResources('config://');
+ $files += (new ConfigFileFinder)->locateFiles($paths);
+ $paths = $locator->findResources('plugins://');
+ $files += (new ConfigFileFinder)->setBase('plugins')->locateInFolders($paths);
+
+ $compiled = new CompiledConfig($cache, $files, GRAV_ROOT);
+ $compiled->setBlueprints(function() use ($container) {
+ return $container['blueprints'];
+ });
+
+ $config = $compiled->name("master-{$setup->environment}")->load();
+ $config->environment = $setup->environment;
+
+ return $config;
+ }
+
+ public static function languages(Container $container)
+ {
+ /** @var Setup $setup */
+ $setup = $container['setup'];
+
+ /** @var Config $config */
+ $config = $container['config'];
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $container['locator'];
+
+ $cache = $locator->findResource('cache://compiled/languages', true, true);
+ $files = [];
+
+ // Process languages only if enabled in configuration.
+ if ($config->get('system.languages.translations', true)) {
+ $paths = $locator->findResources('languages://');
+ $files += (new ConfigFileFinder)->locateFiles($paths);
+ $paths = $locator->findResources('plugins://');
+ $files += (new ConfigFileFinder)->setBase('plugins')->locateInFolders($paths, 'languages');
+ $paths = static::pluginFolderPaths($paths, 'languages');
+ $files += (new ConfigFileFinder)->locateFiles($paths);
+ }
+
+ $languages = new CompiledLanguages($cache, $files, GRAV_ROOT);
+
+ return $languages->name("master-{$setup->environment}")->load();
+ }
+
+ /**
+ * Find specific paths in plugins
+ *
+ * @param array $plugins
+ * @param string $folder_path
+ * @return array
+ */
+ private static function pluginFolderPaths($plugins, $folder_path)
+ {
+ $paths = [];
+
+ foreach ($plugins as $path) {
+ $iterator = new \DirectoryIterator($path);
+
+ /** @var \DirectoryIterator $directory */
+ foreach ($iterator as $directory) {
+ if (!$directory->isDir() || $directory->isDot()) {
+ continue;
+ }
+
+ // Path to the languages folder
+ $lang_path = $directory->getPathName() . '/' . $folder_path;
+
+ // If this folder exists, add it to the list of paths
+ if (file_exists($lang_path)) {
+ $paths []= $lang_path;
+ }
+ }
+ }
+ return $paths;
+ }
+
+}
diff --git a/system/src/Grav/Common/Service/ErrorServiceProvider.php b/system/src/Grav/Common/Service/ErrorServiceProvider.php
new file mode 100644
index 0000000..fce69ff
--- /dev/null
+++ b/system/src/Grav/Common/Service/ErrorServiceProvider.php
@@ -0,0 +1,22 @@
+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
new file mode 100644
index 0000000..a9b2877
--- /dev/null
+++ b/system/src/Grav/Common/Service/OutputServiceProvider.php
@@ -0,0 +1,31 @@
+processSite($page->templateFormat());
+ };
+ }
+}
diff --git a/system/src/Grav/Common/Service/PagesServiceProvider.php b/system/src/Grav/Common/Service/PagesServiceProvider.php
new file mode 100644
index 0000000..f425b6d
--- /dev/null
+++ b/system/src/Grav/Common/Service/PagesServiceProvider.php
@@ -0,0 +1,117 @@
+findResource('system://pages/notfound.md');
+ $page = new Page();
+ $page->init(new \SplFileInfo($path));
+ $page->routable(false);
+
+ return $page;
+ };
+
+ return;
+ }
+
+ $container['page'] = function ($c) {
+ /** @var Grav $c */
+
+ /** @var Pages $pages */
+ $pages = $c['pages'];
+
+ /** @var Config $config */
+ $config = $c['config'];
+
+ /** @var Uri $uri */
+ $uri = $c['uri'];
+
+ $path = $uri->path() ?: '/'; // Don't trim to support trailing slash default routes
+ $page = $pages->dispatch($path);
+
+ // Redirection tests
+ if ($page) {
+ // some debugger override logic
+ if ($page->debugger() === false) {
+ $c['debugger']->enabled(false);
+ }
+
+ if ($config->get('system.force_ssl')) {
+ if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
+ $url = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+ $c->redirect($url);
+ }
+ }
+
+ $url = $pages->route($page->route());
+
+ if ($uri->params()) {
+ if ($url === '/') { //Avoid double slash
+ $url = $uri->params();
+ } else {
+ $url .= $uri->params();
+ }
+ }
+ if ($uri->query()) {
+ $url .= '?' . $uri->query();
+ }
+ if ($uri->fragment()) {
+ $url .= '#' . $uri->fragment();
+ }
+
+ /** @var Language $language */
+ $language = $c['language'];
+
+ // Language-specific redirection scenarios
+ if ($language->enabled() && ($language->isLanguageInUrl() xor $language->isIncludeDefaultLanguage())) {
+ $c->redirect($url);
+ }
+ // Default route test and redirect
+ if ($config->get('system.pages.redirect_default_route') && $page->route() !== $path) {
+ $c->redirect($url);
+ }
+ }
+
+ // if page is not found, try some fallback stuff
+ if (!$page || !$page->routable()) {
+
+ // Try fallback URL stuff...
+ $page = $c->fallbackUrl($path);
+
+ if (!$page) {
+ $path = $c['locator']->findResource('system://pages/notfound.md');
+ $page = new Page();
+ $page->init(new \SplFileInfo($path));
+ $page->routable(false);
+ }
+ }
+
+ return $page;
+ };
+ }
+}
diff --git a/system/src/Grav/Common/Service/RequestServiceProvider.php b/system/src/Grav/Common/Service/RequestServiceProvider.php
new file mode 100644
index 0000000..6410ef7
--- /dev/null
+++ b/system/src/Grav/Common/Service/RequestServiceProvider.php
@@ -0,0 +1,38 @@
+fromGlobals();
+ };
+
+ $container['route'] = $container->factory(function() {
+ return clone 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 @@
+get('system.session.enabled', false);
+ $cookie_secure = (bool)$config->get('system.session.secure', false);
+ $cookie_httponly = (bool)$config->get('system.session.httponly', true);
+ $cookie_lifetime = (int)$config->get('system.session.timeout', 1800);
+ $cookie_path = $config->get('system.session.path');
+ if (null === $cookie_path) {
+ $cookie_path = '/' . trim(Uri::filterPath($uri->rootUrl(false)), '/');
+ }
+ // Session cookie path requires trailing slash.
+ $cookie_path = rtrim($cookie_path, '/') . '/';
+
+ $cookie_domain = $uri->host();
+ if ($cookie_domain === 'localhost') {
+ $cookie_domain = '';
+ }
+
+ // Activate admin if we're inside the admin path.
+ $is_admin = false;
+ if ($config->get('plugins.admin.enabled')) {
+ $admin_base = '/' . trim($config->get('plugins.admin.route'), '/');
+
+ // Uri::route() is not processed yet, let's quickly get what we need.
+ $current_route = str_replace(Uri::filterPath($uri->rootUrl(false)), '', parse_url($uri->url(true), PHP_URL_PATH));
+
+ // Test to see if path starts with a supported language + admin base
+ $lang = Utils::pathPrefixedByLangCode($current_route);
+ $lang_admin_base = '/' . $lang . $admin_base;
+
+ // Check no language, simple language prefix (en) and region specific language prefix (en-US).
+ if (Utils::startsWith($current_route, $admin_base) || Utils::startsWith($current_route, $lang_admin_base)) {
+ $cookie_lifetime = $config->get('plugins.admin.session.timeout', 1800);
+ $enabled = $is_admin = true;
+ }
+ }
+
+ // Fix for HUGE session timeouts.
+ if ($cookie_lifetime > 99999999999) {
+ $cookie_lifetime = 9999999999;
+ }
+
+ $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';
+ }
+
+ // Define session service.
+ $options = [
+ 'name' => $session_name,
+ 'cookie_lifetime' => $cookie_lifetime,
+ 'cookie_path' => $cookie_path,
+ 'cookie_domain' => $cookie_domain,
+ 'cookie_secure' => $cookie_secure,
+ 'cookie_httponly' => $cookie_httponly
+ ] + (array) $config->get('system.session.options');
+
+ $session = new Session($options);
+ $session->setAutoStart($enabled);
+
+ return $session;
+ };
+
+ // Define session message service.
+ $container['messages'] = function ($c) {
+ if (!isset($c['session']) || !$c['session']->isStarted()) {
+ /** @var Debugger $debugger */
+ $debugger = $c['debugger'];
+ $debugger->addMessage('Inactive session: session messages may disappear', 'warming');
+
+ return new Message;
+ }
+
+ /** @var Session $session */
+ $session = $c['session'];
+
+ if (!isset($session->messages)) {
+ $session->messages = new Message;
+ }
+
+ return $session->messages;
+ };
+ }
+}
diff --git a/system/src/Grav/Common/Service/StreamsServiceProvider.php b/system/src/Grav/Common/Service/StreamsServiceProvider.php
new file mode 100644
index 0000000..3bbbea1
--- /dev/null
+++ b/system/src/Grav/Common/Service/StreamsServiceProvider.php
@@ -0,0 +1,48 @@
+initializeLocator($locator);
+
+ return $locator;
+ };
+
+ $container['streams'] = function(Container $container) {
+ /** @var Setup $setup */
+ $setup = $container['setup'];
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $container['locator'];
+
+ // Set locator to both streams.
+ Stream::setLocator($locator);
+ ReadOnlyStream::setLocator($locator);
+
+ return new StreamBuilder($setup->getStreams());
+ };
+ }
+}
diff --git a/system/src/Grav/Common/Service/TaskServiceProvider.php b/system/src/Grav/Common/Service/TaskServiceProvider.php
new file mode 100644
index 0000000..c22121a
--- /dev/null
+++ b/system/src/Grav/Common/Service/TaskServiceProvider.php
@@ -0,0 +1,38 @@
+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
new file mode 100644
index 0000000..cd9b2c1
--- /dev/null
+++ b/system/src/Grav/Common/Session.php
@@ -0,0 +1,166 @@
+getInstance() method instead.
+ */
+ public static function instance()
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use ->getInstance() method instead', E_USER_DEPRECATED);
+
+ return static::getInstance();
+ }
+
+ /**
+ * Initialize session.
+ *
+ * Code in this function has been moved into SessionServiceProvider class.
+ */
+ public function init()
+ {
+ if ($this->autoStart && !$this->isStarted()) {
+ $this->start();
+
+ $this->autoStart = false;
+ }
+ }
+
+ /**
+ * @param bool $auto
+ * @return $this
+ */
+ public function setAutoStart($auto)
+ {
+ $this->autoStart = (bool)$auto;
+
+ return $this;
+ }
+
+ /**
+ * Returns attributes.
+ *
+ * @return array Attributes
+ * @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);
+
+ return $this->getAll();
+ }
+
+ /**
+ * Checks if the session was started.
+ *
+ * @return Boolean
+ * @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);
+
+ return $this->isStarted();
+ }
+
+ /**
+ * Store something in session temporarily.
+ *
+ * @param string $name
+ * @param mixed $object
+ * @return $this
+ */
+ public function setFlashObject($name, $object)
+ {
+ $this->__set($name, serialize($object));
+
+ return $this;
+ }
+
+ /**
+ * Return object and remove it from session.
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function getFlashObject($name)
+ {
+ $serialized = $this->__get($name);
+
+ $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;
+ }
+
+ /**
+ * Store something in cookie temporarily.
+ *
+ * @param string $name
+ * @param mixed $object
+ * @param int $time
+ * @return $this
+ */
+ public function setFlashCookieObject($name, $object, $time = 60)
+ {
+ setcookie($name, json_encode($object), time() + $time, '/');
+
+ return $this;
+ }
+
+ /**
+ * Return object and remove it from the cookie.
+ *
+ * @param string $name
+ * @return mixed|null
+ */
+ public function getFlashCookieObject($name)
+ {
+ if (isset($_COOKIE[$name])) {
+ $object = json_decode($_COOKIE[$name]);
+ setcookie($name, '', time() - 3600, '/');
+ return $object;
+ }
+
+ return null;
+ }
+}
diff --git a/system/src/Grav/Common/Taxonomy.php b/system/src/Grav/Common/Taxonomy.php
new file mode 100644
index 0000000..e323647
--- /dev/null
+++ b/system/src/Grav/Common/Taxonomy.php
@@ -0,0 +1,144 @@
+taxonomy_map = [];
+ $this->grav = $grav;
+ }
+
+ /**
+ * Takes an individual page and processes the taxonomies configured in its header. It
+ * then adds those taxonomies to the map
+ *
+ * @param PageInterface $page the page to process
+ * @param array $page_taxonomy
+ */
+ public function addTaxonomy(PageInterface $page, $page_taxonomy = null)
+ {
+ if (!$page_taxonomy) {
+ $page_taxonomy = $page->taxonomy();
+ }
+
+ if (empty($page_taxonomy) || !$page->published()) {
+ return;
+ }
+
+ /** @var Config $config */
+ $config = $this->grav['config'];
+ if ($config->get('site.taxonomies')) {
+ foreach ((array)$config->get('site.taxonomies') as $taxonomy) {
+ if (isset($page_taxonomy[$taxonomy])) {
+ foreach ((array)$page_taxonomy[$taxonomy] as $item) {
+ $this->taxonomy_map[$taxonomy][(string)$item][$page->path()] = ['slug' => $page->slug()];
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a new Page object with the sub-pages containing all the values set for a
+ * particular taxonomy.
+ *
+ * @param array $taxonomies taxonomies to search, eg ['tag'=>['animal','cat']]
+ * @param string $operator can be 'or' or 'and' (defaults to 'and')
+ *
+ * @return Collection Collection object set to contain matches found in the taxonomy map
+ */
+ public function findTaxonomy($taxonomies, $operator = 'and')
+ {
+ $matches = [];
+ $results = [];
+
+ foreach ((array)$taxonomies as $taxonomy => $items) {
+ foreach ((array)$items as $item) {
+ if (isset($this->taxonomy_map[$taxonomy][$item])) {
+ $matches[] = $this->taxonomy_map[$taxonomy][$item];
+ } else {
+ $matches[] = [];
+ }
+ }
+ }
+
+ if (strtolower($operator) === 'or') {
+ foreach ($matches as $match) {
+ $results = array_merge($results, $match);
+ }
+ } else {
+ $results = $matches ? array_pop($matches) : [];
+ foreach ($matches as $match) {
+ $results = array_intersect_key($results, $match);
+ }
+ }
+
+ return new Collection($results, ['taxonomies' => $taxonomies]);
+ }
+
+ /**
+ * Gets and Sets the taxonomy map
+ *
+ * @param array $var the taxonomy map
+ *
+ * @return array the taxonomy map
+ */
+ public function taxonomy($var = null)
+ {
+ if ($var) {
+ $this->taxonomy_map = $var;
+ }
+
+ return $this->taxonomy_map;
+ }
+
+ /**
+ * Gets item keys per taxonomy
+ *
+ * @param string $taxonomy taxonomy name
+ *
+ * @return array keys of this taxonomy
+ */
+ 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
new file mode 100644
index 0000000..dbc589f
--- /dev/null
+++ b/system/src/Grav/Common/Theme.php
@@ -0,0 +1,96 @@
+config["themes.{$this->name}"];
+ }
+
+ /**
+ * Persists to disk the theme parameters currently stored in the Grav Config object
+ *
+ * @param string $theme_name The name of the theme whose config it should store.
+ *
+ * @return true
+ */
+ public static function saveConfig($theme_name)
+ {
+ if (!$theme_name) {
+ return false;
+ }
+
+ $grav = Grav::instance();
+ $locator = $grav['locator'];
+ $filename = 'config://themes/' . $theme_name . '.yaml';
+ $file = YamlFile::instance($locator->findResource($filename, true, true));
+ $content = $grav['config']->get('themes.' . $theme_name);
+ $file->save($content);
+ $file->free();
+
+ return true;
+ }
+
+ /**
+ * Override the mergeConfig method to work for themes
+ */
+ protected function mergeConfig(PageInterface $page, $deep = 'merge', $params = [], $type = 'themes')
+ {
+ return parent::mergeConfig($page, $deep, $params, $type);
+ }
+
+ /**
+ * Simpler getter for the theme blueprint
+ *
+ * @return mixed
+ */
+ public function getBlueprint()
+ {
+ if (!$this->blueprint) {
+ $this->loadBlueprint();
+ }
+ return $this->blueprint;
+ }
+
+ /**
+ * Load blueprints.
+ */
+ protected function loadBlueprint()
+ {
+ if (!$this->blueprint) {
+ $grav = Grav::instance();
+ $themes = $grav['themes'];
+ $this->blueprint = $themes->get($this->name)->blueprints();
+ }
+ }
+}
diff --git a/system/src/Grav/Common/Themes.php b/system/src/Grav/Common/Themes.php
new file mode 100644
index 0000000..6720d46
--- /dev/null
+++ b/system/src/Grav/Common/Themes.php
@@ -0,0 +1,371 @@
+grav = $grav;
+ $this->config = $grav['config'];
+
+ // Register instance as autoloader for theme inheritance
+ spl_autoload_register([$this, 'autoloadTheme']);
+ }
+
+ public function init()
+ {
+ /** @var Themes $themes */
+ $themes = $this->grav['themes'];
+ $themes->configure();
+
+ $this->initTheme();
+ }
+
+ public function initTheme()
+ {
+ if ($this->inited === false) {
+ /** @var Themes $themes */
+ $themes = $this->grav['themes'];
+
+ try {
+ $instance = $themes->load();
+ } catch (\InvalidArgumentException $e) {
+ throw new \RuntimeException($this->current() . ' theme could not be found');
+ }
+
+ if ($instance instanceof EventSubscriberInterface) {
+ /** @var EventDispatcher $events */
+ $events = $this->grav['events'];
+
+ $events->addSubscriber($instance);
+ }
+
+ $this->grav['theme'] = $instance;
+
+ $this->grav->fireEvent('onThemeInitialized');
+
+ $this->inited = true;
+ }
+ }
+
+ /**
+ * Return list of all theme data with their blueprints.
+ *
+ * @return array
+ */
+ public function all()
+ {
+ $list = [];
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $this->grav['locator'];
+
+ $iterator = $locator->getIterator('themes://');
+
+ /** @var \DirectoryIterator $directory */
+ foreach ($iterator as $directory) {
+ if (!$directory->isDir() || $directory->isDot()) {
+ continue;
+ }
+
+ $theme = $directory->getFilename();
+
+ try {
+ $result = $this->get($theme);
+ } catch (\Exception $e) {
+ $exception = new \RuntimeException(sprintf('Theme %s: %s', $theme, $e->getMessage()), $e->getCode(), $e);
+
+ /** @var Debugger $debugger */
+ $debugger = $this->grav['debugger'];
+ $debugger->addMessage("Theme {$theme} cannot be loaded, please check Exceptions tab", 'error');
+ $debugger->addException($exception);
+
+ continue;
+ }
+
+ if ($result) {
+ $list[$theme] = $result;
+ }
+ }
+ ksort($list);
+
+ return $list;
+ }
+
+ /**
+ * Get theme configuration or throw exception if it cannot be found.
+ *
+ * @param string $name
+ *
+ * @return Data
+ * @throws \RuntimeException
+ */
+ public function get($name)
+ {
+ if (!$name) {
+ throw new \RuntimeException('Theme name not provided.');
+ }
+
+ $blueprints = new Blueprints('themes://');
+ $blueprint = $blueprints->get("{$name}/blueprints");
+
+ // Load default configuration.
+ $file = CompiledYamlFile::instance("themes://{$name}/{$name}" . YAML_EXT);
+
+ // ensure this is a valid theme
+ if (!$file->exists()) {
+ return null;
+ }
+
+ // Find thumbnail.
+ $thumb = "themes://{$name}/thumbnail.jpg";
+ $path = $this->grav['locator']->findResource($thumb, false);
+
+ if ($path) {
+ $blueprint->set('thumbnail', $this->grav['base_url'] . '/' . $path);
+ }
+
+ $obj = new Data((array)$file->content(), $blueprint);
+
+ // Override with user configuration.
+ $obj->merge($this->config->get('themes.' . $name) ?: []);
+
+ // Save configuration always to user/config.
+ $file = CompiledYamlFile::instance("config://themes/{$name}" . YAML_EXT);
+ $obj->file($file);
+
+ return $obj;
+ }
+
+ /**
+ * Return name of the current theme.
+ *
+ * @return string
+ */
+ public function current()
+ {
+ return (string)$this->config->get('system.pages.theme');
+ }
+
+ /**
+ * Load current theme.
+ *
+ * @return Theme
+ */
+ public function load()
+ {
+ // NOTE: ALL THE LOCAL VARIABLES ARE USED INSIDE INCLUDED FILE, DO NOT REMOVE THEM!
+ $grav = $this->grav;
+ $config = $this->config;
+ $name = $this->current();
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $grav['locator'];
+ $file = $locator('theme://theme.php') ?: $locator("theme://{$name}.php");
+
+ $inflector = $grav['inflector'];
+
+ if ($file) {
+ // Local variables available in the file: $grav, $config, $name, $file
+ $class = include $file;
+
+ if (!is_object($class)) {
+ $themeClassFormat = [
+ 'Grav\\Theme\\' . ucfirst($name),
+ 'Grav\\Theme\\' . $inflector->camelize($name)
+ ];
+
+ foreach ($themeClassFormat as $themeClass) {
+ if (class_exists($themeClass)) {
+ $class = new $themeClass($grav, $config, $name);
+ break;
+ }
+ }
+ }
+ } elseif (!$locator('theme://') && !defined('GRAV_CLI')) {
+ exit("Theme '$name' does not exist, unable to display page.");
+ }
+
+ $this->config->set('theme', $config->get('themes.' . $name));
+
+ if (empty($class)) {
+ $class = new Theme($grav, $config, $name);
+ }
+
+ return $class;
+ }
+
+ /**
+ * Configure and prepare streams for current template.
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function configure()
+ {
+ $name = $this->current();
+ $config = $this->config;
+
+ $this->loadConfiguration($name, $config);
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $this->grav['locator'];
+
+ $registered = stream_get_wrappers();
+
+ $schemes = $config->get("themes.{$name}.streams.schemes", []);
+ $schemes += [
+ 'theme' => [
+ 'type' => 'ReadOnlyStream',
+ 'paths' => $locator->findResources("themes://{$name}", false)
+ ]
+ ];
+
+ foreach ($schemes as $scheme => $config) {
+ if (isset($config['paths'])) {
+ $locator->addPath($scheme, '', $config['paths']);
+ }
+ if (isset($config['prefixes'])) {
+ foreach ($config['prefixes'] as $prefix => $paths) {
+ $locator->addPath($scheme, $prefix, $paths);
+ }
+ }
+
+ if (\in_array($scheme, $registered, true)) {
+ stream_wrapper_unregister($scheme);
+ }
+ $type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
+ if ($type[0] !== '\\') {
+ $type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type;
+ }
+
+ if (!stream_wrapper_register($scheme, $type)) {
+ throw new \InvalidArgumentException("Stream '{$type}' could not be initialized.");
+ }
+ }
+
+ // Load languages after streams has been properly initialized
+ $this->loadLanguages($this->config);
+ }
+
+ /**
+ * Load theme configuration.
+ *
+ * @param string $name Theme name
+ * @param Config $config Configuration class
+ */
+ protected function loadConfiguration($name, Config $config)
+ {
+ $themeConfig = CompiledYamlFile::instance("themes://{$name}/{$name}" . YAML_EXT)->content();
+ $config->joinDefaults("themes.{$name}", $themeConfig);
+ }
+
+ /**
+ * Load theme languages.
+ *
+ * @param Config $config Configuration class
+ */
+ protected function loadLanguages(Config $config)
+ {
+ /** @var UniformResourceLocator $locator */
+ $locator = $this->grav['locator'];
+
+ if ($config->get('system.languages.translations', true)) {
+ $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');
+ if (file_exists($languages_folder)) {
+ $languages = [];
+ $iterator = new \DirectoryIterator($languages_folder);
+
+ /** @var \DirectoryIterator $directory */
+ foreach ($iterator as $file) {
+ if ($file->getExtension() !== 'yaml') {
+ continue;
+ }
+ $languages[$file->getBasename('.yaml')] = CompiledYamlFile::instance($file->getPathname())->content();
+ }
+ $this->grav['languages']->mergeRecursive($languages);
+ }
+ }
+ }
+
+ /**
+ * Autoload theme classes for inheritance
+ *
+ * @param string $class Class name
+ *
+ * @return mixed false FALSE if unable to load $class; Class name if
+ * $class is successfully loaded
+ */
+ protected function autoloadTheme($class)
+ {
+ $prefix = 'Grav\\Theme\\';
+ if (false !== strpos($class, $prefix)) {
+ // Remove prefix from class
+ $class = substr($class, strlen($prefix));
+ $locator = $this->grav['locator'];
+
+ // First try lowercase version of the classname.
+ $path = strtolower($class);
+ $file = $locator("themes://{$path}/theme.php") ?: $locator("themes://{$path}/{$path}.php");
+
+ if ($file) {
+ return include_once $file;
+ }
+
+ // Replace namespace tokens to directory separators
+ $path = $this->grav['inflector']->hyphenize($class);
+ $file = $locator("themes://{$path}/theme.php") ?: $locator("themes://{$path}/{$path}.php");
+
+ // Load class
+ if ($file) {
+ return include_once $file;
+ }
+
+ // Try Old style theme classes
+ $path = strtolower(preg_replace('#\\\|_(?!.+\\\)#', '/', $class));
+ $file = $locator("themes://{$path}/theme.php") ?: $locator("themes://{$path}/{$path}.php");
+
+ // Load class
+ if ($file) {
+ return include_once $file;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeCache.php b/system/src/Grav/Common/Twig/Node/TwigNodeCache.php
new file mode 100644
index 0000000..b0bb919
--- /dev/null
+++ b/system/src/Grav/Common/Twig/Node/TwigNodeCache.php
@@ -0,0 +1,56 @@
+ $body), array( 'key' => $key, 'lifetime' => $lifetime), $lineno, $tag);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function compile(Compiler $compiler)
+ {
+ $boo = $this->getAttribute('key');
+ $compiler
+ ->addDebugInfo($this)
+ ->write("\$cache = \\Grav\\Common\\Grav::instance()['cache'];\n")
+ ->write("\$key = \"twigcache-\" . \"" . $this->getAttribute('key') . "\";\n")
+ ->write("\$lifetime = " . $this->getAttribute('lifetime') . ";\n")
+ ->write("\$cache_body = \$cache->fetch(\$key);\n")
+ ->write("if (\$cache_body === false) {\n")
+ ->indent()
+ ->write("ob_start();\n")
+ ->indent()
+ ->subcompile($this->getNode('body'))
+ ->outdent()
+ ->write("\n")
+ ->write("\$cache_body = ob_get_clean();\n")
+ ->write("\$cache->save(\$key, \$cache_body, \$lifetime);\n")
+ ->outdent()
+ ->write("}\n")
+ ->write("echo \$cache_body;\n")
+ ;
+ }
+}
diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeMarkdown.php b/system/src/Grav/Common/Twig/Node/TwigNodeMarkdown.php
new file mode 100644
index 0000000..7bb2bfc
--- /dev/null
+++ b/system/src/Grav/Common/Twig/Node/TwigNodeMarkdown.php
@@ -0,0 +1,46 @@
+ $body], [], $lineno, $tag);
+ }
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Compiler $compiler A Twig_Compiler instance
+ */
+ public function compile(Compiler $compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write('ob_start();' . PHP_EOL)
+ ->subcompile($this->getNode('body'))
+ ->write('$content = ob_get_clean();' . PHP_EOL)
+ ->write('preg_match("/^\s*/", $content, $matches);' . PHP_EOL)
+ ->write('$lines = explode("\n", $content);' . PHP_EOL)
+ ->write('$content = preg_replace(\'/^\' . $matches[0]. \'/\', "", $lines);' . PHP_EOL)
+ ->write('$content = join("\n", $content);' . PHP_EOL)
+ ->write('echo $this->env->getExtension(\'Grav\Common\Twig\TwigExtension\')->markdownFunction($context, $content);' . PHP_EOL);
+ }
+}
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
new file mode 100644
index 0000000..4f6f776
--- /dev/null
+++ b/system/src/Grav/Common/Twig/Node/TwigNodeScript.php
@@ -0,0 +1,101 @@
+ $body, 'file' => $file, 'group' => $group, 'priority' => $priority, 'attributes' => $attributes], [], $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("\$assets = \\Grav\\Common\\Grav::instance()['assets'];\n");
+
+ if ($this->getNode('attributes') !== null) {
+ $compiler
+ ->write('$attributes = ')
+ ->subcompile($this->getNode('attributes'))
+ ->raw(";\n")
+ ->write("if (!is_array(\$attributes)) {\n")
+ ->indent()
+ ->write("throw new UnexpectedValueException('{% {$this->tagName} with x %}: x is not an array');\n")
+ ->outdent()
+ ->write("}\n");
+ } else {
+ $compiler->write('$attributes = [];' . "\n");
+ }
+
+ if ($this->getNode('group') !== null) {
+ $compiler
+ ->write("\$attributes['group'] = ")
+ ->subcompile($this->getNode('group'))
+ ->raw(";\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");
+ }
+
+ if ($this->getNode('priority') !== null) {
+ $compiler
+ ->write("\$attributes['priority'] = (int)(")
+ ->subcompile($this->getNode('priority'))
+ ->raw(");\n");
+ }
+
+ if ($this->getNode('file') !== null) {
+ $compiler
+ ->write('$assets->addJs(')
+ ->subcompile($this->getNode('file'))
+ ->raw(", \$attributes);\n");
+ } else {
+ $compiler
+ ->write("ob_start();\n")
+ ->subcompile($this->getNode('body'))
+ ->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
new file mode 100644
index 0000000..22f3302
--- /dev/null
+++ b/system/src/Grav/Common/Twig/Node/TwigNodeStyle.php
@@ -0,0 +1,101 @@
+ $body, 'file' => $file, 'group' => $group, 'priority' => $priority, 'attributes' => $attributes], [], $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("\$assets = \\Grav\\Common\\Grav::instance()['assets'];\n");
+
+ if ($this->getNode('attributes') !== null) {
+ $compiler
+ ->write('$attributes = ')
+ ->subcompile($this->getNode('attributes'))
+ ->raw(";\n")
+ ->write("if (!is_array(\$attributes)) {\n")
+ ->indent()
+ ->write("throw new UnexpectedValueException('{% {$this->tagName} with x %}: x is not an array');\n")
+ ->outdent()
+ ->write("}\n");
+ } else {
+ $compiler->write('$attributes = [];' . "\n");
+ }
+
+ if ($this->getNode('group') !== null) {
+ $compiler
+ ->write("\$attributes['group'] = ")
+ ->subcompile($this->getNode('group'))
+ ->raw(";\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");
+ }
+
+ if ($this->getNode('priority') !== null) {
+ $compiler
+ ->write("\$attributes['priority'] = (int)(")
+ ->subcompile($this->getNode('priority'))
+ ->raw(");\n");
+ }
+
+ if ($this->getNode('file') !== null) {
+ $compiler
+ ->write('$assets->addCss(')
+ ->subcompile($this->getNode('file'))
+ ->raw(", \$attributes);\n");
+ } else {
+ $compiler
+ ->write("ob_start();\n")
+ ->subcompile($this->getNode('body'))
+ ->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
new file mode 100644
index 0000000..f29ed6d
--- /dev/null
+++ b/system/src/Grav/Common/Twig/Node/TwigNodeSwitch.php
@@ -0,0 +1,85 @@
+ $value, 'cases' => $cases, 'default' => $default), array(), $lineno, $tag);
+ }
+
+ /**
+ * Compiles the node to PHP.
+ *
+ * @param Compiler $compiler A Twig_Compiler instance
+ */
+ public function compile(Compiler $compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write('switch (')
+ ->subcompile($this->getNode('value'))
+ ->raw(") {\n")
+ ->indent();
+
+ foreach ($this->getNode('cases') as $case) {
+ if (!$case->hasNode('body')) {
+ continue;
+ }
+
+ foreach ($case->getNode('values') as $value) {
+ $compiler
+ ->write('case ')
+ ->subcompile($value)
+ ->raw(":\n");
+ }
+
+ $compiler
+ ->write("{\n")
+ ->indent()
+ ->subcompile($case->getNode('body'))
+ ->write("break;\n")
+ ->outdent()
+ ->write("}\n");
+ }
+
+ if ($this->hasNode('default') && $this->getNode('default') !== null) {
+ $compiler
+ ->write("default:\n")
+ ->write("{\n")
+ ->indent()
+ ->subcompile($this->getNode('default'))
+ ->outdent()
+ ->write("}\n");
+ }
+
+ $compiler
+ ->outdent()
+ ->write("}\n");
+ }
+}
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
new file mode 100644
index 0000000..4028e74
--- /dev/null
+++ b/system/src/Grav/Common/Twig/Node/TwigNodeTryCatch.php
@@ -0,0 +1,68 @@
+ $try, 'catch' => $catch], [], $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('try {')
+ ;
+
+ $compiler
+ ->indent()
+ ->subcompile($this->getNode('try'))
+ ;
+
+ if ($this->hasNode('catch') && null !== $this->getNode('catch')) {
+ $compiler
+ ->outdent()
+ ->write('} catch (\Exception $e) {' . "\n")
+ ->indent()
+ ->write('if (isset($context[\'grav\'][\'debugger\'])) $context[\'grav\'][\'debugger\']->addException($e);' . "\n")
+ ->write('$context[\'e\'] = $e;' . "\n")
+ ->subcompile($this->getNode('catch'))
+ ;
+ }
+
+ $compiler
+ ->outdent()
+ ->write("}\n");
+ }
+}
diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserCache.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserCache.php
new file mode 100644
index 0000000..3f56f14
--- /dev/null
+++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserCache.php
@@ -0,0 +1,68 @@
+getLine();
+ $stream = $this->parser->getStream();
+ $key = $this->parser->getVarName() . $lineno;
+ $lifetime = Grav::instance()['cache']->getLifetime();
+
+ // Check for optional lifetime override
+ if (!$stream->test(Token::BLOCK_END_TYPE)) {
+ $lifetime_expr = $this->parser->getExpressionParser()->parseExpression();
+ $lifetime = $lifetime_expr->getAttribute('value');
+ }
+
+ $stream->expect(Token::BLOCK_END_TYPE);
+ $body = $this->parser->subparse(array($this, 'decideCacheEnd'), true);
+ $stream->expect(Token::BLOCK_END_TYPE);
+
+ return new TwigNodeCache($key, $lifetime, $body, $lineno, $this->getTag());
+ }
+
+ /**
+ * Decide if current token marks end of cache block.
+ *
+ * @param Token $token
+ * @return bool
+ */
+ public function decideCacheEnd(Token $token)
+ {
+ return $token->test('endcache');
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public function getTag()
+ {
+ return 'cache';
+ }
+}
diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserMarkdown.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserMarkdown.php
new file mode 100644
index 0000000..7a1d139
--- /dev/null
+++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserMarkdown.php
@@ -0,0 +1,56 @@
+getLine();
+ $this->parser->getStream()->expect(Token::BLOCK_END_TYPE);
+ $body = $this->parser->subparse(array($this, 'decideMarkdownEnd'), true);
+ $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 Token $token
+ * @return bool
+ */
+ public function decideMarkdownEnd(Token $token)
+ {
+ return $token->test('endmarkdown');
+ }
+ /**
+ * {@inheritdoc}
+ */
+ public function getTag()
+ {
+ return 'markdown';
+ }
+}
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
new file mode 100644
index 0000000..bb04707
--- /dev/null
+++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserScript.php
@@ -0,0 +1,104 @@
+getLine();
+ $stream = $this->parser->getStream();
+
+ list($file, $group, $priority, $attributes) = $this->parseArguments($token);
+
+ $content = null;
+ if ($file === null) {
+ $content = $this->parser->subparse([$this, 'decideBlockEnd'], true);
+ $stream->expect(Token::BLOCK_END_TYPE);
+ }
+
+ return new TwigNodeScript($content, $file, $group, $priority, $attributes, $lineno, $this->getTag());
+ }
+
+ /**
+ * @param Token $token
+ * @return array
+ */
+ protected function parseArguments(Token $token)
+ {
+ $stream = $this->parser->getStream();
+
+ $file = null;
+ 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(Token::OPERATOR_TYPE, 'in')) {
+ $group = $this->parser->getExpressionParser()->parseExpression();
+ }
+
+ $priority = null;
+ if ($stream->nextIf(Token::NAME_TYPE, 'priority')) {
+ $stream->expect(Token::PUNCTUATION_TYPE, ':');
+ $priority = $this->parser->getExpressionParser()->parseExpression();
+ }
+
+ $attributes = null;
+ if ($stream->nextIf(Token::NAME_TYPE, 'with')) {
+ $attributes = $this->parser->getExpressionParser()->parseExpression();
+ }
+
+ $stream->expect(Token::BLOCK_END_TYPE);
+
+ return [$file, $group, $priority, $attributes];
+ }
+
+ /**
+ * @param Token $token
+ * @return bool
+ */
+ public function decideBlockEnd(Token $token)
+ {
+ return $token->test('endscript');
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'script';
+ }
+}
diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserStyle.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserStyle.php
new file mode 100644
index 0000000..5a5beb6
--- /dev/null
+++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserStyle.php
@@ -0,0 +1,103 @@
+getLine();
+ $stream = $this->parser->getStream();
+
+ list ($file, $group, $priority, $attributes) = $this->parseArguments($token);
+
+ $content = null;
+ if (!$file) {
+ $content = $this->parser->subparse([$this, 'decideBlockEnd'], true);
+ $stream->expect(Token::BLOCK_END_TYPE);
+ }
+
+ return new TwigNodeStyle($content, $file, $group, $priority, $attributes, $lineno, $this->getTag());
+ }
+
+ /**
+ * @param Token $token
+ * @return array
+ */
+ protected function parseArguments(Token $token)
+ {
+ $stream = $this->parser->getStream();
+
+ $file = null;
+ 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(Token::OPERATOR_TYPE, 'in')) {
+ $group = $this->parser->getExpressionParser()->parseExpression();
+ }
+
+ $priority = null;
+ if ($stream->nextIf(Token::NAME_TYPE, 'priority')) {
+ $stream->expect(Token::PUNCTUATION_TYPE, ':');
+ $priority = $this->parser->getExpressionParser()->parseExpression();
+ }
+
+ $attributes = null;
+ if ($stream->nextIf(Token::NAME_TYPE, 'with')) {
+ $attributes = $this->parser->getExpressionParser()->parseExpression();
+ }
+
+ $stream->expect(Token::BLOCK_END_TYPE);
+
+ return [$file, $group, $priority, $attributes];
+ }
+
+ /**
+ * @param Token $token
+ * @return bool
+ */
+ public function decideBlockEnd(Token $token)
+ {
+ return $token->test('endstyle');
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'style';
+ }
+}
diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserSwitch.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserSwitch.php
new file mode 100644
index 0000000..7bd3ba9
--- /dev/null
+++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserSwitch.php
@@ -0,0 +1,130 @@
+getLine();
+ $stream = $this->parser->getStream();
+
+ $name = $this->parser->getExpressionParser()->parseExpression();
+ $stream->expect(Token::BLOCK_END_TYPE);
+
+ // There can be some whitespace between the {% switch %} and first {% case %} tag.
+ while ($stream->getCurrent()->getType() === Token::TEXT_TYPE && trim($stream->getCurrent()->getValue()) === '') {
+ $stream->next();
+ }
+
+ $stream->expect(Token::BLOCK_START_TYPE);
+
+ $expressionParser = $this->parser->getExpressionParser();
+
+ $default = null;
+ $cases = [];
+ $end = false;
+
+ while (!$end) {
+ $next = $stream->next();
+
+ switch ($next->getValue()) {
+ case 'case':
+ $values = [];
+
+ while (true) {
+ $values[] = $expressionParser->parsePrimaryExpression();
+ // Multiple allowed values?
+ if ($stream->test(Token::OPERATOR_TYPE, 'or')) {
+ $stream->next();
+ } else {
+ break;
+ }
+ }
+
+ $stream->expect(Token::BLOCK_END_TYPE);
+ $body = $this->parser->subparse(array($this, 'decideIfFork'));
+ $cases[] = new Node([
+ 'values' => new Node($values),
+ 'body' => $body
+ ]);
+ break;
+
+ case 'default':
+ $stream->expect(Token::BLOCK_END_TYPE);
+ $default = $this->parser->subparse(array($this, 'decideIfEnd'));
+ break;
+
+ case 'endswitch':
+ $end = true;
+ break;
+
+ default:
+ 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(Token::BLOCK_END_TYPE);
+
+ return new TwigNodeSwitch($name, new Node($cases), $default, $lineno, $this->getTag());
+ }
+
+ /**
+ * Decide if current token marks switch logic.
+ *
+ * @param Token $token
+ * @return bool
+ */
+ public function decideIfFork(Token $token)
+ {
+ return $token->test(array('case', 'default', 'endswitch'));
+ }
+
+ /**
+ * Decide if current token marks end of swtich block.
+ *
+ * @param Token $token
+ * @return bool
+ */
+ public function decideIfEnd(Token $token)
+ {
+ return $token->test(array('endswitch'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTag()
+ {
+ return 'switch';
+ }
+}
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
new file mode 100644
index 0000000..95adef3
--- /dev/null
+++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserTryCatch.php
@@ -0,0 +1,72 @@
+
+ * {% try %}
+ * {{ user.get('name') }}
+ * {% catch %}
+ * {{ e.message }}
+ * {% endcatch %}
+ *
+ */
+class TwigTokenParserTryCatch 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();
+
+ $stream->expect(Token::BLOCK_END_TYPE);
+ $try = $this->parser->subparse([$this, 'decideCatch']);
+ $stream->next();
+ $stream->expect(Token::BLOCK_END_TYPE);
+ $catch = $this->parser->subparse([$this, 'decideEnd']);
+ $stream->next();
+ $stream->expect(Token::BLOCK_END_TYPE);
+
+ return new TwigNodeTryCatch($try, $catch, $lineno, $this->getTag());
+ }
+
+ public function decideCatch(Token $token)
+ {
+ return $token->test(array('catch'));
+ }
+
+ public function decideEnd(Token $token)
+ {
+ return $token->test(array('endtry')) || $token->test(array('endcatch'));
+ }
+
+ /**
+ * Gets the tag name associated with this token parser.
+ *
+ * @return string The tag name
+ */
+ public function getTag()
+ {
+ return 'try';
+ }
+}
diff --git a/system/src/Grav/Common/Twig/Twig.php b/system/src/Grav/Common/Twig/Twig.php
new file mode 100644
index 0000000..5c9008f
--- /dev/null
+++ b/system/src/Grav/Common/Twig/Twig.php
@@ -0,0 +1,454 @@
+grav = $grav;
+ $this->twig_paths = [];
+ }
+
+ /**
+ * Twig initialization that sets the twig loader chain, then the environment, then extensions
+ * and also the base set of twig vars
+ */
+ public function init()
+ {
+ 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'];
+
+ $active_language = $language->getActive();
+
+ // handle language templates if available
+ if ($language->enabled()) {
+ $lang_templates = $locator->findResource('theme://templates/' . ($active_language ? $active_language : $language->getDefault()));
+ if ($lang_templates) {
+ $this->twig_paths[] = $lang_templates;
+ }
+ }
+
+ $this->twig_paths = array_merge($this->twig_paths, $locator->findResources('theme://templates'));
+
+ $this->grav->fireEvent('onTwigTemplatePaths');
+
+ // Add Grav core templates location
+ $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);
+
+ // Register all other prefixes as namespaces in twig
+ foreach ($locator->getPaths('theme') as $prefix => $_) {
+ if ($prefix === '') {
+ continue;
+ }
+
+ $twig_paths = [];
+
+ // handle language templates if available
+ if ($language->enabled()) {
+ $lang_templates = $locator->findResource('theme://'.$prefix.'templates/' . ($active_language ? $active_language : $language->getDefault()));
+ if ($lang_templates) {
+ $twig_paths[] = $lang_templates;
+ }
+ }
+
+ $twig_paths = array_merge($twig_paths, $locator->findResources('theme://'.$prefix.'templates'));
+
+ $namespace = trim($prefix, '/');
+ $this->loader->setPaths($twig_paths, $namespace);
+ }
+
+ $this->grav->fireEvent('onTwigLoader');
+
+ $this->loaderArray = new \Twig_Loader_Array([]);
+ $loader_chain = new \Twig_Loader_Chain([$this->loaderArray, $this->loader]);
+
+ $params = $config->get('system.twig');
+ if (!empty($params['cache'])) {
+ $cachePath = $locator->findResource('cache://twig', true, true);
+ $params['cache'] = new \Twig_Cache_Filesystem($cachePath, \Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION);
+ }
+
+ if (!$config->get('system.strict_mode.twig_compat', true)) {
+ // Force autoescape on for all files if in strict mode.
+ $params['autoescape'] = 'html';
+ } elseif (!empty($this->autoescape)) {
+ $params['autoescape'] = $this->autoescape ? 'html' : false;
+ }
+
+ if (empty($params['autoescape'])) {
+ user_error('Grav 2.0 will have Twig auto-escaping forced on (can be emulated by turning off \'system.strict_mode.twig_compat\' setting in your configuration)', E_USER_DEPRECATED);
+ }
+
+ $this->twig = new TwigEnvironment($loader_chain, $params);
+
+ if ($config->get('system.twig.undefined_functions')) {
+ $this->twig->registerUndefinedFunctionCallback(function ($name) {
+ if (function_exists($name)) {
+ return new \Twig_SimpleFunction($name, $name);
+ }
+
+ return new \Twig_SimpleFunction($name, function () {
+ });
+ });
+ }
+
+ if ($config->get('system.twig.undefined_filters')) {
+ $this->twig->registerUndefinedFilterCallback(function ($name) {
+ if (function_exists($name)) {
+ return new \Twig_SimpleFilter($name, $name);
+ }
+
+ return new \Twig_SimpleFilter($name, function () {
+ });
+ });
+ }
+
+ $this->grav->fireEvent('onTwigInitialized');
+
+ // set default date format if set in config
+ if ($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');
+
+ /** @var Pages $pages */
+ $pages = $this->grav['pages'];
+
+ // Set some standard variables for twig
+ $this->twig_vars += [
+ 'config' => $config,
+ 'system' => $config->get('system'),
+ 'theme' => $config->get('theme'),
+ 'site' => $config->get('site'),
+ 'uri' => $this->grav['uri'],
+ 'assets' => $this->grav['assets'],
+ 'taxonomy' => $this->grav['taxonomy'],
+ 'browser' => $this->grav['browser'],
+ 'base_dir' => rtrim(ROOT_DIR, '/'),
+ 'home_url' => $pages->homeUrl($active_language),
+ 'base_url' => $pages->baseUrl($active_language),
+ 'base_url_absolute' => $pages->baseUrl($active_language, true),
+ 'base_url_relative' => $pages->baseUrl($active_language, false),
+ 'base_url_simple' => $this->grav['base_url'],
+ 'theme_dir' => $locator->findResource('theme://'),
+ 'theme_url' => $this->grav['base_url'] . '/' . $locator->findResource('theme://', false),
+ 'html_lang' => $this->grav['language']->getActive() ?: $config->get('site.default_lang', 'en'),
+ 'language_codes' => new LanguageCodes,
+ ];
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return \Twig_Environment
+ */
+ public function twig()
+ {
+ return $this->twig;
+ }
+
+ /**
+ * @return \Twig_Loader_Filesystem
+ */
+ public function loader()
+ {
+ return $this->loader;
+ }
+
+ /**
+ * Adds or overrides a template.
+ *
+ * @param string $name The template name
+ * @param string $template The template source
+ */
+ public function setTemplate($name, $template)
+ {
+ $this->loaderArray->setTemplate($name, $template);
+ }
+
+ /**
+ * Twig process that renders a page item. It supports two variations:
+ * 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 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(PageInterface $item, $content = null)
+ {
+ $content = $content ?? $item->content();
+
+ // override the twig header vars for local resolution
+ $this->grav->fireEvent('onTwigPageVariables', new Event(['page' => $item]));
+ $twig_vars = $this->twig_vars;
+
+ $twig_vars['page'] = $item;
+ $twig_vars['media'] = $item->media();
+ $twig_vars['header'] = $item->header();
+
+ $local_twig = clone $this->twig;
+
+ $output = '';
+ try {
+ // Process Modular Twig
+ if ($item->modularTwig()) {
+ $twig_vars['content'] = $content;
+ $extension = $item->templateFormat();
+ $extension = $extension ? ".{$extension}.twig" : TEMPLATE_EXT;
+ $template = $item->template() . $extension;
+ $output = $content = $local_twig->render($template, $twig_vars);
+ }
+
+ // Process in-page Twig
+ if ($item->shouldProcess('twig')) {
+ $name = '@Page:' . $item->path();
+ $this->setTemplate($name, $content);
+ $output = $local_twig->render($name, $twig_vars);
+ }
+
+ } catch (\Twig_Error_Loader $e) {
+ throw new \RuntimeException($e->getRawMessage(), 404, $e);
+ }
+
+ return $output;
+ }
+
+ /**
+ * Process a Twig template directly by using a template name
+ * and optional array of variables
+ *
+ * @param string $template template to render with
+ * @param array $vars Optional variables
+ *
+ * @return string
+ */
+ public function processTemplate($template, $vars = [])
+ {
+ // override the twig header vars for local resolution
+ $this->grav->fireEvent('onTwigTemplateVariables');
+ $vars += $this->twig_vars;
+
+ try {
+ $output = $this->twig->render($template, $vars);
+ } catch (\Twig_Error_Loader $e) {
+ throw new \RuntimeException($e->getRawMessage(), 404, $e);
+ }
+
+ return $output;
+
+ }
+
+
+ /**
+ * Process a Twig template directly by using a Twig string
+ * and optional array of variables
+ *
+ * @param string $string string to render.
+ * @param array $vars Optional variables
+ *
+ * @return string
+ */
+ public function processString($string, array $vars = [])
+ {
+ // override the twig header vars for local resolution
+ $this->grav->fireEvent('onTwigStringVariables');
+ $vars += $this->twig_vars;
+
+ $name = '@Var:' . $string;
+ $this->setTemplate($name, $string);
+
+ try {
+ $output = $this->twig->render($name, $vars);
+ } catch (\Twig_Error_Loader $e) {
+ throw new \RuntimeException($e->getRawMessage(), 404, $e);
+ }
+
+ return $output;
+ }
+
+ /**
+ * Twig process that renders the site layout. This is the main twig process that renders the overall
+ * page and handles all the layout for the site display.
+ *
+ * @param string $format Output format (defaults to HTML).
+ *
+ * @return string the rendered output
+ * @throws \RuntimeException
+ */
+ public function processSite($format = null, array $vars = [])
+ {
+ // set the page now its been processed
+ $this->grav->fireEvent('onTwigSiteVariables');
+ $pages = $this->grav['pages'];
+ $page = $this->grav['page'];
+ $content = $page->content();
+
+ $twig_vars = $this->twig_vars;
+
+ $twig_vars['theme'] = $this->grav['config']->get('theme');
+ $twig_vars['pages'] = $pages->root();
+ $twig_vars['page'] = $page;
+ $twig_vars['header'] = $page->header();
+ $twig_vars['media'] = $page->media();
+ $twig_vars['content'] = $content;
+ $ext = '.' . ($format ?: 'html') . TWIG_EXT;
+
+ // determine if params are set, if so disable twig cache
+ $params = $this->grav['uri']->params(null, true);
+ if (!empty($params)) {
+ $this->twig->setCache(false);
+ }
+
+ // Get Twig template layout
+ $template = $this->template($page->template() . $ext);
+
+ try {
+ $output = $this->twig->render($template, $vars + $twig_vars);
+ } 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) {
+ try {
+ $page->templateFormat('html');
+ $output = $this->twig->render($page->template() . '.html' . TWIG_EXT, $vars + $twig_vars);
+ } catch (\Twig_Error_Loader $e) {
+ throw new \RuntimeException($error_msg, 400, $e);
+ }
+ } else {
+ throw new \RuntimeException($error_msg, 400, $e);
+ }
+ }
+
+ return $output;
+ }
+
+ /**
+ * Wraps the Twig_Loader_Filesystem addPath method (should be used only in `onTwigLoader()` event
+ * @param string $template_path
+ * @param string $namespace
+ */
+ public function addPath($template_path, $namespace = '__main__')
+ {
+ $this->loader->addPath($template_path, $namespace);
+ }
+
+ /**
+ * Wraps the Twig_Loader_Filesystem prependPath method (should be used only in `onTwigLoader()` event
+ * @param string $template_path
+ * @param string $namespace
+ */
+ public function prependPath($template_path, $namespace = '__main__')
+ {
+ $this->loader->prependPath($template_path, $namespace);
+ }
+
+ /**
+ * Simple helper method to get the twig template if it has already been set, else return
+ * the one being passed in
+ *
+ * @param string $template the template name
+ *
+ * @return string the template name
+ */
+ public function template($template)
+ {
+ return $this->template ?? $template;
+ }
+
+ /**
+ * Overrides the autoescape setting
+ *
+ * @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)
+ {
+ if (!$state) {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '(false) is deprecated since Grav 1.5', E_USER_DEPRECATED);
+ }
+
+ $this->autoescape = (bool) $state;
+ }
+}
diff --git a/system/src/Grav/Common/Twig/TwigEnvironment.php b/system/src/Grav/Common/Twig/TwigEnvironment.php
new file mode 100644
index 0000000..54ef24f
--- /dev/null
+++ b/system/src/Grav/Common/Twig/TwigEnvironment.php
@@ -0,0 +1,15 @@
+grav = Grav::instance();
+ $this->debugger = $this->grav['debugger'] ?? null;
+ $this->config = $this->grav['config'];
+ }
+
+ /**
+ * Register some standard globals
+ *
+ * @return array
+ */
+ public function getGlobals()
+ {
+ return [
+ 'grav' => $this->grav,
+ ];
+ }
+
+ /**
+ * Return a list of all filters.
+ *
+ * @return array
+ */
+ public function getFilters()
+ {
+ return [
+ new \Twig_SimpleFilter('*ize', [$this, 'inflectorFilter']),
+ new \Twig_SimpleFilter('absolute_url', [$this, 'absoluteUrlFilter']),
+ new \Twig_SimpleFilter('contains', [$this, 'containsFilter']),
+ new \Twig_SimpleFilter('chunk_split', [$this, 'chunkSplitFilter']),
+ new \Twig_SimpleFilter('nicenumber', [$this, 'niceNumberFunc']),
+ new \Twig_SimpleFilter('nicefilesize', [$this, 'niceFilesizeFunc']),
+ new \Twig_SimpleFilter('nicetime', [$this, 'nicetimeFunc']),
+ new \Twig_SimpleFilter('defined', [$this, 'definedDefaultFilter']),
+ new \Twig_SimpleFilter('ends_with', [$this, 'endsWithFilter']),
+ new \Twig_SimpleFilter('fieldName', [$this, 'fieldNameFilter']),
+ new \Twig_SimpleFilter('ksort', [$this, 'ksortFilter']),
+ new \Twig_SimpleFilter('ltrim', [$this, 'ltrimFilter']),
+ new \Twig_SimpleFilter('markdown', [$this, 'markdownFunction'], ['needs_context' => true, 'is_safe' => ['html']]),
+ new \Twig_SimpleFilter('md5', [$this, 'md5Filter']),
+ new \Twig_SimpleFilter('base32_encode', [$this, 'base32EncodeFilter']),
+ new \Twig_SimpleFilter('base32_decode', [$this, 'base32DecodeFilter']),
+ new \Twig_SimpleFilter('base64_encode', [$this, 'base64EncodeFilter']),
+ new \Twig_SimpleFilter('base64_decode', [$this, 'base64DecodeFilter']),
+ new \Twig_SimpleFilter('randomize', [$this, 'randomizeFilter']),
+ new \Twig_SimpleFilter('modulus', [$this, 'modulusFilter']),
+ new \Twig_SimpleFilter('rtrim', [$this, 'rtrimFilter']),
+ new \Twig_SimpleFilter('pad', [$this, 'padFilter']),
+ new \Twig_SimpleFilter('regex_replace', [$this, 'regexReplace']),
+ new \Twig_SimpleFilter('safe_email', [$this, 'safeEmailFilter']),
+ new \Twig_SimpleFilter('safe_truncate', ['\Grav\Common\Utils', 'safeTruncate']),
+ new \Twig_SimpleFilter('safe_truncate_html', ['\Grav\Common\Utils', 'safeTruncateHTML']),
+ new \Twig_SimpleFilter('sort_by_key', [$this, 'sortByKeyFilter']),
+ new \Twig_SimpleFilter('starts_with', [$this, 'startsWithFilter']),
+ new \Twig_SimpleFilter('truncate', ['\Grav\Common\Utils', 'truncate']),
+ new \Twig_SimpleFilter('truncate_html', ['\Grav\Common\Utils', 'truncateHTML']),
+ new \Twig_SimpleFilter('json_decode', [$this, 'jsonDecodeFilter']),
+ new \Twig_SimpleFilter('array_unique', 'array_unique'),
+ new \Twig_SimpleFilter('basename', 'basename'),
+ new \Twig_SimpleFilter('dirname', 'dirname'),
+ 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'], ['needs_environment' => true]),
+ new \Twig_SimpleFilter('tl', [$this, 'translateLanguage']),
+ new \Twig_SimpleFilter('ta', [$this, 'translateArray']),
+
+ // Casting values
+ new \Twig_SimpleFilter('string', [$this, 'stringFilter']),
+ new \Twig_SimpleFilter('int', [$this, 'intFilter'], ['is_safe' => ['all']]),
+ 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'])
+ ];
+ }
+
+ /**
+ * Return a list of all functions.
+ *
+ * @return array
+ */
+ public function getFunctions()
+ {
+ return [
+ new \Twig_SimpleFunction('array', [$this, 'arrayFilter']),
+ new \Twig_SimpleFunction('array_key_value', [$this, 'arrayKeyValueFunc']),
+ 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('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']),
+ new \Twig_SimpleFunction('print_r', 'print_r'),
+ new \Twig_SimpleFunction('http_response_code', 'http_response_code'),
+ new \Twig_SimpleFunction('evaluate', [$this, 'evaluateStringFunc'], ['needs_context' => true]),
+ new \Twig_SimpleFunction('evaluate_twig', [$this, 'evaluateTwigFunc'], ['needs_context' => true]),
+ 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('repeat', [$this, 'repeatFunc']),
+ new \Twig_SimpleFunction('regex_replace', [$this, 'regexReplace']),
+ new \Twig_SimpleFunction('regex_filter', [$this, 'regexFilter']),
+ new \Twig_SimpleFunction('string', [$this, 'stringFunc']),
+ new \Twig_SimpleFunction('url', [$this, 'urlFunc']),
+ new \Twig_SimpleFunction('json_decode', [$this, 'jsonDecodeFilter']),
+ new \Twig_SimpleFunction('get_cookie', [$this, 'getCookie']),
+ new \Twig_SimpleFunction('redirect_me', [$this, 'redirectFunc']),
+ new \Twig_SimpleFunction('range', [$this, 'rangeFunc']),
+ new \Twig_SimpleFunction('isajaxrequest', [$this, 'isAjaxFunc']),
+ new \Twig_SimpleFunction('exif', [$this, 'exifFunc']),
+ new \Twig_SimpleFunction('media_directory', [$this, 'mediaDirFunc']),
+ new \Twig_SimpleFunction('body_class', [$this, 'bodyClassFunc']),
+ new \Twig_SimpleFunction('theme_var', [$this, 'themeVarFunc'], ['needs_context' => true]),
+ new \Twig_SimpleFunction('header_var', [$this, 'pageHeaderVarFunc'], ['needs_context' => true]),
+ new \Twig_SimpleFunction('read_file', [$this, 'readFileFunc']),
+ 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('svg_image', [$this, 'svgImageFunction']),
+ new \Twig_SimpleFunction('xss', [$this, 'xssFunc']),
+
+
+ // Translations
+ 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'])
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ public function getTokenParsers()
+ {
+ return [
+ new TwigTokenParserRender(),
+ new TwigTokenParserThrow(),
+ new TwigTokenParserTryCatch(),
+ new TwigTokenParserScript(),
+ new TwigTokenParserStyle(),
+ new TwigTokenParserMarkdown(),
+ new TwigTokenParserSwitch(),
+ new TwigTokenParserCache(),
+ ];
+ }
+
+ /**
+ * Filters field name by changing dot notation into array notation.
+ *
+ * @param string $str
+ *
+ * @return string
+ */
+ public function fieldNameFilter($str)
+ {
+ $path = explode('.', rtrim($str, '.'));
+
+ return array_shift($path) . ($path ? '[' . implode('][', $path) . ']' : '');
+ }
+
+ /**
+ * Protects email address.
+ *
+ * @param string $str
+ *
+ * @return string
+ */
+ public function safeEmailFilter($str)
+ {
+ $email = '';
+ 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);
+ }
+
+ /**
+ * Returns array in a random order.
+ *
+ * @param array $original
+ * @param int $offset Can be used to return only slice of the array.
+ *
+ * @return array
+ */
+ public function randomizeFilter($original, $offset = 0)
+ {
+ if (!\is_array($original)) {
+ return $original;
+ }
+
+ if ($original instanceof \Traversable) {
+ $original = iterator_to_array($original, false);
+ }
+
+ $sorted = [];
+ $random = array_slice($original, $offset);
+ shuffle($random);
+
+ $sizeOf = \count($original);
+ for ($x = 0; $x < $sizeOf; $x++) {
+ if ($x < $offset) {
+ $sorted[] = $original[$x];
+ } else {
+ $sorted[] = array_shift($random);
+ }
+ }
+
+ return $sorted;
+ }
+
+ /**
+ * Returns the modulus of an integer
+ *
+ * @param string|int $number
+ * @param int $divider
+ * @param array $items array of items to select from to return
+ *
+ * @return int
+ */
+ public function modulusFilter($number, $divider, $items = null)
+ {
+ if (\is_string($number)) {
+ $number = strlen($number);
+ }
+
+ $remainder = $number % $divider;
+
+ if (\is_array($items)) {
+ return $items[$remainder] ?? $items[0];
+ }
+
+ return $remainder;
+ }
+
+ /**
+ * Inflector supports following notations:
+ *
+ * `{{ 'person'|pluralize }} => people`
+ * `{{ 'shoes'|singularize }} => shoe`
+ * `{{ 'welcome page'|titleize }} => "Welcome Page"`
+ * `{{ 'send_email'|camelize }} => SendEmail`
+ * `{{ 'CamelCased'|underscorize }} => camel_cased`
+ * `{{ 'Something Text'|hyphenize }} => something-text`
+ * `{{ 'something_text_to_read'|humanize }} => "Something text to read"`
+ * `{{ '181'|monthize }} => 5`
+ * `{{ '10'|ordinalize }} => 10th`
+ *
+ * @param string $action
+ * @param string $data
+ * @param int $count
+ *
+ * @return string
+ */
+ public function inflectorFilter($action, $data, $count = null)
+ {
+ $action .= 'ize';
+
+ $inflector = $this->grav['inflector'];
+
+ if (\in_array(
+ $action,
+ ['titleize', 'camelize', 'underscorize', 'hyphenize', 'humanize', 'ordinalize', 'monthize'],
+ true
+ )) {
+ return $inflector->{$action}($data);
+ }
+
+ if (\in_array($action, ['pluralize', 'singularize'], true)) {
+ return $count ? $inflector->{$action}($data, $count) : $inflector->{$action}($data);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Return MD5 hash from the input.
+ *
+ * @param string $str
+ *
+ * @return string
+ */
+ public function md5Filter($str)
+ {
+ return md5($str);
+ }
+
+ /**
+ * Return Base32 encoded string
+ *
+ * @param string $str
+ * @return string
+ */
+ public function base32EncodeFilter($str)
+ {
+ return Base32::encode($str);
+ }
+
+ /**
+ * Return Base32 decoded string
+ *
+ * @param string $str
+ * @return bool|string
+ */
+ public function base32DecodeFilter($str)
+ {
+ return Base32::decode($str);
+ }
+
+ /**
+ * Return Base64 encoded string
+ *
+ * @param string $str
+ * @return string
+ */
+ public function base64EncodeFilter($str)
+ {
+ return base64_encode($str);
+ }
+
+ /**
+ * Return Base64 decoded string
+ *
+ * @param string $str
+ * @return bool|string
+ */
+ public function base64DecodeFilter($str)
+ {
+ return base64_decode($str);
+ }
+
+
+ /**
+ * Sorts a collection by key
+ *
+ * @param array $input
+ * @param string $filter
+ * @param int $direction
+ * @param int $sort_flags
+ *
+ * @return array
+ */
+ public function sortByKeyFilter($input, $filter, $direction = SORT_ASC, $sort_flags = SORT_REGULAR)
+ {
+ return Utils::sortArrayByKey($input, $filter, $direction, $sort_flags);
+ }
+
+ /**
+ * Return ksorted collection.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function ksortFilter($array)
+ {
+ if (null === $array) {
+ $array = [];
+ }
+ ksort($array);
+
+ return $array;
+ }
+
+ /**
+ * Wrapper for chunk_split() function
+ *
+ * @param string $value
+ * @param int $chars
+ * @param string $split
+ * @return string
+ */
+ public function chunkSplitFilter($value, $chars, $split = '-')
+ {
+ return chunk_split($value, $chars, $split);
+ }
+
+ /**
+ * determine if a string contains another
+ *
+ * @param string $haystack
+ * @param string $needle
+ *
+ * @return bool
+ */
+ public function containsFilter($haystack, $needle)
+ {
+ if (empty($needle)) {
+ return $haystack;
+ }
+
+ return (strpos($haystack, (string) $needle) !== false);
+ }
+
+ /**
+ * Gets a human readable output for cron syntax
+ *
+ * @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 string $date
+ * @param bool $long_strings
+ *
+ * @param bool $show_tense
+ * @return bool
+ */
+ public function nicetimeFunc($date, $long_strings = true, $show_tense = true)
+ {
+ if (empty($date)) {
+ 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'
+ ];
+ } else {
+ $periods = [
+ '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'];
+
+ $now = time();
+
+ // check if unix timestamp
+ if ((string)(int)$date === (string)$date) {
+ $unix_date = $date;
+ } else {
+ $unix_date = strtotime($date);
+ }
+
+ // check validity of date
+ if (empty($unix_date)) {
+ 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('GRAV.NICETIME.AGO', null, true);
+
+ } elseif ($now == $unix_date) {
+ $difference = $now - $unix_date;
+ $tense = $this->grav['language']->translate('GRAV.NICETIME.JUST_NOW', null, false);
+
+ } else {
+ $difference = $unix_date - $now;
+ $tense = $this->grav['language']->translate('GRAV.NICETIME.FROM_NOW', null, true);
+ }
+
+ for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths) - 1; $j++) {
+ $difference /= $lengths[$j];
+ }
+
+ $difference = round($difference);
+
+ if ($difference != 1) {
+ $periods[$j] .= '_PLURAL';
+ }
+
+ if ($this->grav['language']->getTranslation($this->grav['language']->getLanguage(),
+ $periods[$j] . '_MORE_THAN_TWO')
+ ) {
+ if ($difference > 2) {
+ $periods[$j] .= '_MORE_THAN_TWO';
+ }
+ }
+
+ $periods[$j] = $this->grav['language']->translate('GRAV.'.$periods[$j], null, true);
+
+ if ($now == $unix_date) {
+ return $tense;
+ }
+
+ $time = "{$difference} {$periods[$j]}";
+ $time .= $show_tense ? " {$tense}" : '';
+
+ return $time;
+ }
+
+ /**
+ * Allow quick check of a string for XSS Vulnerabilities
+ *
+ * @param string|array $data
+ * @return bool|string|array
+ */
+ public function xssFunc($data)
+ {
+ 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);
+ }
+
+ /**
+ * @param string $string
+ *
+ * @return mixed
+ */
+ public function absoluteUrlFilter($string)
+ {
+ $url = $this->grav['uri']->base();
+ $string = preg_replace('/((?:href|src) *= *[\'"](?!(http|ftp)))/i', "$1$url", $string);
+
+ return $string;
+
+ }
+
+ /**
+ * @param string $string
+ *
+ * @param array $context
+ * @param bool $block Block or Line processing
+ * @return mixed|string
+ */
+ public function markdownFunction($context, $string, $block = true)
+ {
+ $page = $context['page'] ?? null;
+ return Utils::processMarkdown($string, $block, $page);
+ }
+
+ /**
+ * @param string $haystack
+ * @param string $needle
+ *
+ * @return bool
+ */
+ public function startsWithFilter($haystack, $needle)
+ {
+ return Utils::startsWith($haystack, $needle);
+ }
+
+ /**
+ * @param string $haystack
+ * @param string $needle
+ *
+ * @return bool
+ */
+ public function endsWithFilter($haystack, $needle)
+ {
+ return Utils::endsWith($haystack, $needle);
+ }
+
+ /**
+ * @param mixed $value
+ * @param null $default
+ *
+ * @return null
+ */
+ public function definedDefaultFilter($value, $default = null)
+ {
+ return null !== $value ? $value : $default;
+ }
+
+ /**
+ * @param string $value
+ * @param null $chars
+ *
+ * @return string
+ */
+ public function rtrimFilter($value, $chars = null)
+ {
+ return rtrim($value, $chars);
+ }
+
+ /**
+ * @param string $value
+ * @param null $chars
+ *
+ * @return string
+ */
+ public function ltrimFilter($value, $chars = null)
+ {
+ return ltrim($value, $chars);
+ }
+
+ /**
+ * Casts input to string.
+ *
+ * @param mixed $input
+ * @return string
+ */
+ public function stringFilter($input)
+ {
+ return (string) $input;
+ }
+
+
+ /**
+ * Casts input to int.
+ *
+ * @param mixed $input
+ * @return int
+ */
+ public function intFilter($input)
+ {
+ return (int) $input;
+ }
+
+ /**
+ * Casts input to bool.
+ *
+ * @param mixed $input
+ * @return bool
+ */
+ public function boolFilter($input)
+ {
+ return (bool) $input;
+ }
+
+ /**
+ * Casts input to float.
+ *
+ * @param mixed $input
+ * @return float
+ */
+ public function floatFilter($input)
+ {
+ return (float) $input;
+ }
+
+ /**
+ * Casts input to array.
+ *
+ * @param mixed $input
+ * @return array
+ */
+ public function arrayFilter($input)
+ {
+ return (array) $input;
+ }
+
+ /**
+ * @return string
+ */
+ public function translate(\Twig_Environment $twig)
+ {
+ // 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 string|array $args
+ * @param array|null $languages
+ * @param bool $array_support
+ * @param bool $html_out
+ * @return string
+ */
+ public function translateLanguage($args, array $languages = null, $array_support = false, $html_out = false)
+ {
+ /** @var Language $language */
+ $language = $this->grav['language'];
+
+ return $language->translate($args, $languages, $array_support, $html_out);
+ }
+
+ /**
+ * @param string $key
+ * @param string $index
+ * @param array|null $lang
+ * @return string
+ */
+ public function translateArray($key, $index, $lang = null)
+ {
+ /** @var Language $language */
+ $language = $this->grav['language'];
+
+ return $language->translateArray($key, $index, $lang);
+ }
+
+ /**
+ * Repeat given string x times.
+ *
+ * @param string $input
+ * @param int $multiplier
+ *
+ * @return string
+ */
+ public function repeatFunc($input, $multiplier)
+ {
+ return str_repeat($input, $multiplier);
+ }
+
+ /**
+ * Return URL to the resource.
+ *
+ * @example {{ url('theme://images/logo.png')|default('http://www.placehold.it/150x100/f4f4f4') }}
+ *
+ * @param string $input Resource to be located.
+ * @param bool $domain True to include domain name.
+ *
+ * @return string|null Returns url to the resource or null if resource was not found.
+ */
+ public function urlFunc($input, $domain = false)
+ {
+ return Utils::url($input, $domain);
+ }
+
+ /**
+ * This function will evaluate Twig $twig through the $environment, and return its results.
+ *
+ * @param array $context
+ * @param string $twig
+ * @return mixed
+ */
+ public function evaluateTwigFunc($context, $twig ) {
+
+ $loader = new \Twig_Loader_Filesystem('.');
+ $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 array $context
+ * @param string $string
+ * @return mixed
+ */
+ public function evaluateStringFunc($context, $string )
+ {
+ return $this->evaluateTwigFunc($context, "{{ $string }}");
+ }
+
+
+ /**
+ * Based on Twig_Extension_Debug / twig_var_dump
+ * (c) 2011 Fabien Potencier
+ *
+ * @param \Twig_Environment $env
+ * @param string $context
+ */
+ public function dump(\Twig_Environment $env, $context)
+ {
+ if (!$env->isDebug() || !$this->debugger) {
+ return;
+ }
+
+ $count = func_num_args();
+ if (2 === $count) {
+ $data = [];
+ foreach ($context as $key => $value) {
+ if (is_object($value)) {
+ if (method_exists($value, 'toArray')) {
+ $data[$key] = $value->toArray();
+ } else {
+ $data[$key] = "Object (" . get_class($value) . ")";
+ }
+ } else {
+ $data[$key] = $value;
+ }
+ }
+ $this->debugger->addMessage($data, 'debug');
+ } else {
+ for ($i = 2; $i < $count; $i++) {
+ $this->debugger->addMessage(func_get_arg($i), 'debug');
+ }
+ }
+ }
+
+ /**
+ * Output a Gist
+ *
+ * @param string $id
+ * @param string|bool $file
+ *
+ * @return string
+ */
+ public function gistFunc($id, $file = false)
+ {
+ $url = 'https://gist.github.com/' . $id . '.js';
+ if ($file) {
+ $url .= '?file=' . $file;
+ }
+ return '';
+ }
+
+ /**
+ * Generate a random string
+ *
+ * @param int $count
+ *
+ * @return string
+ */
+ public function randomStringFunc($count = 5)
+ {
+ return Utils::generateRandomString($count);
+ }
+
+ /**
+ * Pad a string to a certain length with another string
+ *
+ * @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)
+ {
+ return str_pad($input, (int)$pad_length, $pad_string, $pad_type);
+ }
+
+ /**
+ * Workaround for twig associative array initialization
+ * Returns a key => val array
+ *
+ * @param string $key key of item
+ * @param string $val value of item
+ * @param array $current_array optional array to add to
+ *
+ * @return array
+ */
+ public function arrayKeyValueFunc($key, $val, $current_array = null)
+ {
+ if (empty($current_array)) {
+ return array($key => $val);
+ }
+
+ $current_array[$key] = $val;
+
+ return $current_array;
+ }
+
+ /**
+ * Wrapper for array_intersect() method
+ *
+ * @param array $array1
+ * @param array $array2
+ * @return array
+ */
+ public function arrayIntersectFunc($array1, $array2)
+ {
+ if ($array1 instanceof Collection && $array2 instanceof Collection) {
+ return $array1->intersect($array2);
+ }
+
+ return array_intersect($array1, $array2);
+ }
+
+ /**
+ * Returns a string from a value. If the value is array, return it json encoded
+ *
+ * @param array|string $value
+ *
+ * @return string
+ */
+ public function stringFunc($value)
+ {
+ if (is_array($value)) { //format the array as a string
+ return json_encode($value);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Translate a string
+ *
+ * @return string
+ */
+ public function translateFunc()
+ {
+ return $this->grav['language']->translate(func_get_args());
+ }
+
+ /**
+ * Authorize an action. Returns true if the user is logged in and
+ * has the right to execute $action.
+ *
+ * @param string|array $action An action or a list of actions. Each
+ * entry can be a string like 'group.action'
+ * or without dot notation an associative
+ * array.
+ * @return bool Returns TRUE if the user is authorized to
+ * perform the action, FALSE otherwise.
+ */
+ public function authorize($action)
+ {
+ /** @var UserInterface|null $user */
+ $user = $this->grav['user'] ?? null;
+
+ if (!$user || !$user->authenticated || (isset($user->authorized) && !$user->authorized)) {
+ return false;
+ }
+
+ $action = (array) $action;
+ foreach ($action as $key => $perms) {
+ $prefix = is_int($key) ? '' : $key . '.';
+ $perms = $prefix ? (array) $perms : [$perms => true];
+ foreach ($perms as $action2 => $authenticated) {
+ if ($user->authorize($prefix . $action2)) {
+ return $authenticated;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Used to add a nonce to a form. Call {{ nonce_field('action') }} specifying a string representing the action.
+ *
+ * For maximum protection, ensure that the string representing the action is as specific as possible
+ *
+ * @param string $action the action
+ * @param string $nonceParamName a custom nonce param name
+ *
+ * @return string the nonce input field
+ */
+ public function nonceFieldFunc($action, $nonceParamName = 'nonce')
+ {
+ $string = ' ';
+
+ return $string;
+ }
+
+ /**
+ * Decodes string from JSON.
+ *
+ * @param string $str
+ * @param bool $assoc
+ * @param int $depth
+ * @param int $options
+ * @return array
+ */
+ public function jsonDecodeFilter($str, $assoc = false, $depth = 512, $options = 0)
+ {
+ return json_decode(html_entity_decode($str, ENT_COMPAT | ENT_HTML401, 'UTF-8'), $assoc, $depth, $options);
+ }
+
+ /**
+ * Used to retrieve a cookie value
+ *
+ * @param string $key The cookie name to retrieve
+ *
+ * @return mixed
+ */
+ public function getCookie($key)
+ {
+ return filter_input(INPUT_COOKIE, $key, FILTER_SANITIZE_STRING);
+ }
+
+ /**
+ * Twig wrapper for PHP's preg_replace method
+ *
+ * @param mixed $subject the content to perform the replacement on
+ * @param mixed $pattern the regex pattern to use for matches
+ * @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 string|string[]|null the resulting content
+ */
+ public function regexReplace($subject, $pattern, $replace, $limit = -1)
+ {
+ return preg_replace($pattern, $replace, $subject, $limit);
+ }
+
+ /**
+ * Twig wrapper for PHP's preg_grep method
+ *
+ * @param array $array
+ * @param string $regex
+ * @param int $flags
+ * @return array
+ */
+ public function regexFilter($array, $regex, $flags = 0)
+ {
+ return preg_grep($regex, $array, $flags);
+ }
+
+ /**
+ * redirect browser from twig
+ *
+ * @param string $url the url to redirect to
+ * @param int $statusCode statusCode, default 303
+ */
+ public function redirectFunc($url, $statusCode = 303)
+ {
+ header('Location: ' . $url, true, $statusCode);
+ exit();
+ }
+
+ /**
+ * Generates an array containing a range of elements, optionally stepped
+ *
+ * @param int $start Minimum number, default 0
+ * @param int $end Maximum number, default `getrandmax()`
+ * @param int $step Increment between elements in the sequence, default 1
+ *
+ * @return array
+ */
+ public function rangeFunc($start = 0, $end = 100, $step = 1)
+ {
+ return range($start, $end, $step);
+ }
+
+ /**
+ * Check if HTTP_X_REQUESTED_WITH has been set to xmlhttprequest,
+ * in which case we may unsafely assume ajax. Non critical use only.
+ *
+ * @return bool True if HTTP_X_REQUESTED_WITH exists and has been set to xmlhttprequest
+ */
+ public function isAjaxFunc()
+ {
+ return (
+ !empty($_SERVER['HTTP_X_REQUESTED_WITH'])
+ && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
+ }
+
+ /**
+ * Get the Exif data for a file
+ *
+ * @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'];
+
+ if ($locator->isStream($image)) {
+ $image = $locator->findResource($image);
+ }
+
+ $exif_reader = $this->grav['exif']->getReader();
+
+ if ($image && file_exists($image) && $this->config->get('system.media.auto_metadata_exif') && $exif_reader) {
+
+ $exif_data = $exif_reader->read($image);
+
+ if ($exif_data) {
+ if ($raw) {
+ return $exif_data->getRawData();
+ }
+
+ return $exif_data->getData();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Simple function to read a file based on a filepath and output it
+ *
+ * @param string $filepath
+ * @return bool|string
+ */
+ public function readFileFunc($filepath)
+ {
+ /** @var UniformResourceLocator $locator */
+ $locator = $this->grav['locator'];
+
+ if ($locator->isStream($filepath)) {
+ $filepath = $locator->findResource($filepath);
+ }
+
+ if ($filepath && file_exists($filepath)) {
+ return file_get_contents($filepath);
+ }
+
+ return false;
+ }
+
+ /**
+ * Process a folder as Media and return a media object
+ *
+ * @param string $media_dir
+ * @return Media|null
+ */
+ public function mediaDirFunc($media_dir)
+ {
+ /** @var UniformResourceLocator $locator */
+ $locator = $this->grav['locator'];
+
+ if ($locator->isStream($media_dir)) {
+ $media_dir = $locator->findResource($media_dir);
+ }
+
+ if ($media_dir && file_exists($media_dir)) {
+ return new Media($media_dir);
+ }
+
+ return null;
+ }
+
+ /**
+ * Dump a variable to the browser
+ *
+ * @param mixed $var
+ */
+ public function vardumpFunc($var)
+ {
+ var_dump($var);
+ }
+
+ /**
+ * Returns a nicer more readable filesize based on bytes
+ *
+ * @param int $bytes
+ * @return string
+ */
+ public function niceFilesizeFunc($bytes)
+ {
+ return Utils::prettySize($bytes);
+ }
+
+ /**
+ * Returns a nicer more readable number
+ *
+ * @param int|float|string $n
+ * @return string|bool
+ */
+ public function niceNumberFunc($n)
+ {
+ if (!\is_float($n) && !\is_int($n)) {
+ if (!\is_string($n) || $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';
+ }
+ if ($n > 1000000000) {
+ return round($n/1000000000, 2).' b';
+ }
+ if ($n > 1000000) {
+ return round($n/1000000, 2).' m';
+ }
+ if ($n > 1000) {
+ return round($n/1000, 2).' k';
+ }
+
+ return number_format($n);
+ }
+
+ /**
+ * Get a theme variable
+ * Will try to get the variable for the current page, if not found, it tries it's parent page on up to root.
+ * If still not found, will use the theme's configuration value,
+ * If still not found, will use the $default value passed in
+ *
+ * @param $context Twig Context
+ * @param string $var variable to be found (using dot notation)
+ * @param null $default the default value to be used as last resort
+ * @param null $page an optional page to use for the current page
+ * @param bool $exists toggle to simply return the page where the variable is set, else null
+ * @return string
+ */
+ public function themeVarFunc($context, $var, $default = null, $page = null, $exists = false)
+ {
+ $page = $page ?? $context['page'] ?? Grav::instance()['page'] ?? null;
+
+ // Try to find var in the page headers
+ if ($page instanceof PageInterface && $page->exists()) {
+ // Loop over pages and look for header vars
+ while ($page && !$page->root()) {
+ $header = new Data((array)$page->header());
+ $value = $header->get($var);
+ if (isset($value)) {
+ if ($exists) {
+ return $page;
+ } else {
+ return $value;
+ }
+
+ }
+ $page = $page->parent();
+ }
+ }
+
+ if ($exists) {
+ return false;
+ } else {
+ return Grav::instance()['config']->get('theme.' . $var, $default);
+ }
+ }
+
+ /**
+ * Look for a page header variable in an array of pages working its way through until a value is found
+ *
+ * @param $context
+ * @param string $var the variable to look for in the page header
+ * @param string|string[]|null $pages array of pages to check (current page upwards if not null)
+ * @param bool $exists if true, return the page where the var is found, not the value
+ * @return mixed
+ * @deprecated 1.7 Use themeVarFunc() instead
+ */
+ public function pageHeaderVarFunc($context, $var, $pages = null)
+ {
+ if (is_array($pages)) {
+ $page = array_shift($pages);
+ } else {
+ $page = null;
+ }
+ return $this->themeVarFunc($context, $var, null, $page);
+ }
+
+ /**
+ * 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 string|string[] $classes
+ * @return string
+ */
+ public function bodyClassFunc($classes)
+ {
+
+ $header = $this->grav['page']->header();
+ $body_classes = $header->body_classes ?? '';
+
+ foreach ((array)$classes as $class) {
+ if (!empty($body_classes) && Utils::contains($body_classes, $class)) {
+ continue;
+ }
+
+ $val = $this->config->get('theme.' . $class, false) ? $class : false;
+ $body_classes .= $val ? ' ' . $val : '';
+ }
+
+ return $body_classes;
+ }
+
+ /**
+ * Dump/Encode data into YAML format
+ *
+ * @param array $data
+ * @param int $inline integer number of levels of inline syntax
+ * @return string
+ */
+ public function yamlEncodeFilter($data, $inline = 10)
+ {
+ return Yaml::dump($data, $inline);
+ }
+
+ /**
+ * Decode/Parse data from YAML format
+ *
+ * @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;
+ }
+ }
+
+ /**
+ * Returns the content of an SVG image and adds extra classes as needed
+ *
+ * @param $path
+ * @param $classes
+ * @return string|string[]|null
+ */
+ public static function svgImageFunction($path, $classes)
+ {
+ $path = Utils::fullPath($path);
+
+ if (file_exists($path)) {
+ $svg = file_get_contents($path);
+ $classes = " inline-block $classes";
+ $matched = false;
+
+ //Look for existing class
+ $svg = preg_replace_callback('/^/', function($matches) use ($classes, &$matched) {
+ if (isset($matches[2])) {
+ $new_classes = $matches[2] . $classes;
+ $matched = true;
+ return str_replace($matches[1], "class=\"$new_classes\"", $matches[0]);
+ }
+ }, $svg
+ );
+
+ // no matches found just add the class
+ if (!$matched) {
+ $classes = trim($classes);
+ $svg = str_replace('get('system.twig.umask_fix', false);
+ }
+
+ if (self::$umask) {
+ $dir = dirname($file);
+ if (!is_dir($dir)) {
+ $old = umask(0002);
+ Folder::create($dir);
+ umask($old);
+ }
+ parent::writeCacheFile($file, $content);
+ chmod($file, 0775);
+ } else {
+ parent::writeCacheFile($file, $content);
+ }
+ }
+}
diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php
new file mode 100644
index 0000000..32c6e41
--- /dev/null
+++ b/system/src/Grav/Common/Uri.php
@@ -0,0 +1,1435 @@
+createFromString($env);
+ } else {
+ $this->createFromEnvironment(\is_array($env) ? $env : $_SERVER);
+ }
+ }
+
+ /**
+ * Initialize the URI class with a url passed via parameter.
+ * Used for testing purposes.
+ *
+ * @param string $url the URL to use in the class
+ *
+ * @return $this
+ */
+ public function initializeWithUrl($url = '')
+ {
+ if ($url) {
+ $this->createFromString($url);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Initialize the URI class by providing url and root_path arguments
+ *
+ * @param string $url
+ * @param string $root_path
+ *
+ * @return $this
+ */
+ public function initializeWithUrlAndRootPath($url, $root_path)
+ {
+ $this->initializeWithUrl($url);
+ $this->root_path = $root_path;
+
+ return $this;
+ }
+
+ /**
+ * Validate a hostname
+ *
+ * @param string $hostname The hostname
+ *
+ * @return boolean
+ */
+ public function validateHostname($hostname)
+ {
+ return (bool)preg_match(static::HOSTNAME_REGEX, $hostname);
+ }
+
+ /**
+ * Initializes the URI object based on the url set on the object
+ */
+ public function init()
+ {
+ $grav = Grav::instance();
+
+ /** @var Config $config */
+ $config = $grav['config'];
+
+ /** @var Language $language */
+ $language = $grav['language'];
+
+ // add the port to the base for non-standard ports
+ if ($this->port !== null && $config->get('system.reverse_proxy_setup') === false) {
+ $this->base .= ':' . (string)$this->port;
+ }
+
+ // Handle custom base
+ $custom_base = rtrim($grav['config']->get('system.custom_base_url'), '/');
+
+ if ($custom_base) {
+ $custom_parts = parse_url($custom_base);
+ $orig_root_path = $this->root_path;
+ $this->root_path = isset($custom_parts['path']) ? rtrim($custom_parts['path'], '/') : '';
+ if (isset($custom_parts['scheme'])) {
+ $this->base = $custom_parts['scheme'] . '://' . $custom_parts['host'];
+ $this->root = $custom_base;
+ } else {
+ $this->root = $this->base . $this->root_path;
+ }
+ $this->uri = Utils::replaceFirstOccurrence($orig_root_path, $this->root_path, $this->uri);
+ } else {
+ $this->root = $this->base . $this->root_path;
+ }
+
+ $this->url = $this->base . $this->uri;
+
+ $uri = Utils::replaceFirstOccurrence(static::filterPath($this->root), '', $this->url);
+
+ // remove the setup.php based base if set:
+ $setup_base = $grav['pages']->base();
+ if ($setup_base) {
+ $uri = preg_replace('|^' . preg_quote($setup_base, '|') . '|', '', $uri);
+ }
+
+ // process params
+ $uri = $this->processParams($uri, $config->get('system.param_sep'));
+
+ // set active language
+ $uri = $language->setActiveFromUri($uri);
+
+ // split the URL and params
+ $bits = parse_url($uri);
+
+ //process fragment
+ if (isset($bits['fragment'])) {
+ $this->fragment = $bits['fragment'];
+ }
+
+ // Get the path. If there's no path, make sure pathinfo() still returns dirname variable
+ $path = $bits['path'] ?? '/';
+
+ // remove the extension if there is one set
+ $parts = pathinfo($path);
+
+ // set the original basename
+ $this->basename = $parts['basename'];
+
+ // set the extension
+ if (isset($parts['extension'])) {
+ $this->extension = $parts['extension'];
+ }
+
+ // Strip the file extension for valid page types
+ if ($this->isValidExtension($this->extension)) {
+ $path = Utils::replaceLastOccurrence(".{$this->extension}", '', $path);
+ }
+
+ // set the new url
+ $this->url = $this->root . $path;
+ $this->path = static::cleanPath($path);
+ $this->content_path = trim(Utils::replaceFirstOccurrence($this->base, '', $this->path), '/');
+ if ($this->content_path !== '') {
+ $this->paths = explode('/', $this->content_path);
+ }
+
+ // Set some Grav stuff
+ $grav['base_url_absolute'] = $config->get('system.custom_base_url') ?: $this->rootUrl(true);
+ $grav['base_url_relative'] = $this->rootUrl(false);
+ $grav['base_url'] = $config->get('system.absolute_urls') ? $grav['base_url_absolute'] : $grav['base_url_relative'];
+
+ RouteFactory::setRoot($this->root_path);
+ RouteFactory::setLanguage($language->getLanguageURLPrefix());
+ }
+
+ /**
+ * Return URI path.
+ *
+ * @param string $id
+ *
+ * @return string|string[]
+ */
+ public function paths($id = null)
+ {
+ if ($id !== null) {
+ return $this->paths[$id];
+ }
+
+ return $this->paths;
+ }
+
+ /**
+ * 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.
+ *
+ * @return string
+ */
+ public function route($absolute = false, $domain = false)
+ {
+ return ($absolute ? $this->rootUrl($domain) : '') . '/' . implode('/', $this->paths);
+ }
+
+ /**
+ * 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
+ *
+ * @return string|array Returns an array if $id = null and $raw = true
+ */
+ public function query($id = null, $raw = false)
+ {
+ if ($id !== null) {
+ return $this->queries[$id] ?? null;
+ }
+
+ if ($raw) {
+ return $this->queries;
+ }
+
+ if (!$this->queries) {
+ return '';
+ }
+
+ return http_build_query($this->queries);
+ }
+
+ /**
+ * 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
+ *
+ * @return null|string|array
+ */
+ public function params($id = null, $array = false)
+ {
+ $config = Grav::instance()['config'];
+ $sep = $config->get('system.param_sep');
+
+ $params = null;
+ if ($id === null) {
+ if ($array) {
+ return $this->params;
+ }
+ $output = [];
+ foreach ($this->params as $key => $value) {
+ $output[] = "{$key}{$sep}{$value}";
+ $params = '/' . implode('/', $output);
+ }
+ } elseif (isset($this->params[$id])) {
+ if ($array) {
+ return $this->params[$id];
+ }
+ $params = "/{$id}{$sep}{$this->params[$id]}";
+ }
+
+ return $params;
+ }
+
+ /**
+ * Get URI parameter.
+ *
+ * @param string $id
+ *
+ * @return bool|string
+ */
+ public function param($id)
+ {
+ if (isset($this->params[$id])) {
+ return html_entity_decode(rawurldecode($this->params[$id]), ENT_COMPAT | ENT_HTML401, 'UTF-8');
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the Fragment portion of a URI (eg #target)
+ *
+ * @param string $fragment
+ *
+ * @return string|null
+ */
+ public function fragment($fragment = null)
+ {
+ if ($fragment !== null) {
+ $this->fragment = $fragment;
+ }
+ return $this->fragment;
+ }
+
+ /**
+ * Return URL.
+ *
+ * @param bool $include_host Include hostname.
+ *
+ * @return string
+ */
+ public function url($include_host = false)
+ {
+ if ($include_host) {
+ return $this->url;
+ }
+
+ $url = Utils::replaceFirstOccurrence($this->base, '', rtrim($this->url, '/'));
+
+ return $url ?: '/';
+ }
+
+ /**
+ * Return the Path
+ *
+ * @return String The path of the URI
+ */
+ public function path()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Return the Extension of the URI
+ *
+ * @param string|null $default
+ *
+ * @return string The extension of the URI
+ */
+ public function extension($default = null)
+ {
+ if (!$this->extension) {
+ $this->extension = $default;
+ }
+
+ return $this->extension;
+ }
+
+ public function method()
+ {
+ $method = isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET';
+
+ if ($method === 'POST' && isset($_SERVER['X-HTTP-METHOD-OVERRIDE'])) {
+ $method = strtoupper($_SERVER['X-HTTP-METHOD-OVERRIDE']);
+ }
+
+ return $method;
+ }
+
+ /**
+ * Return the scheme of the URI
+ *
+ * @param bool $raw
+ * @return string The scheme of the URI
+ */
+ public function scheme($raw = false)
+ {
+ if (!$raw) {
+ $scheme = '';
+ if ($this->scheme) {
+ $scheme = $this->scheme . '://';
+ } elseif ($this->host) {
+ $scheme = '//';
+ }
+
+ return $scheme;
+ }
+
+ return $this->scheme;
+ }
+
+
+ /**
+ * Return the host of the URI
+ *
+ * @return string|null The host of the URI
+ */
+ public function host()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Return the port number if it can be figured out
+ *
+ * @param bool $raw
+ * @return int|null
+ */
+ public function port($raw = false)
+ {
+ $port = $this->port;
+ // If not in raw mode and port is not set, figure it out from scheme.
+ if (!$raw && $port === null) {
+ if ($this->scheme === 'http') {
+ $this->port = 80;
+ } elseif ($this->scheme === 'https') {
+ $this->port = 443;
+ }
+ }
+
+ return $this->port;
+ }
+
+ /**
+ * Return user
+ *
+ * @return string|null
+ */
+ public function user()
+ {
+ return $this->user;
+ }
+
+ /**
+ * Return password
+ *
+ * @return string|null
+ */
+ public function password()
+ {
+ return $this->password;
+ }
+
+ /**
+ * Gets the environment name
+ *
+ * @return String
+ */
+ public function environment()
+ {
+ return $this->env;
+ }
+
+
+ /**
+ * Return the basename of the URI
+ *
+ * @return String The basename of the URI
+ */
+ public function basename()
+ {
+ return $this->basename;
+ }
+
+ /**
+ * Return the full uri
+ *
+ * @param bool $include_root
+ * @return mixed
+ */
+ public function uri($include_root = true)
+ {
+ if ($include_root) {
+ return $this->uri;
+ }
+
+ return Utils::replaceFirstOccurrence($this->root_path, '', $this->uri);
+ }
+
+ /**
+ * Return the base of the URI
+ *
+ * @return String The base of the URI
+ */
+ public function base()
+ {
+ return $this->base;
+ }
+
+ /**
+ * Return the base relative URL including the language prefix
+ * or the base relative url if multi-language is not enabled
+ *
+ * @return String The base of the URI
+ */
+ public function baseIncludingLanguage()
+ {
+ $grav = Grav::instance();
+
+ /** @var Pages $pages */
+ $pages = $grav['pages'];
+
+ return $pages->baseUrl(null, false);
+ }
+
+ /**
+ * Return root URL to the site.
+ *
+ * @param bool $include_host Include hostname.
+ *
+ * @return mixed
+ */
+ public function rootUrl($include_host = false)
+ {
+ if ($include_host) {
+ return $this->root;
+ }
+
+ return Utils::replaceFirstOccurrence($this->base, '', $this->root);
+ }
+
+ /**
+ * Return current page number.
+ *
+ * @return int
+ */
+ public function currentPage()
+ {
+ $page = (int)($this->params['page'] ?? 1);
+
+ return max(1, $page);
+ }
+
+ /**
+ * Return relative path to the referrer defaulting to current or given page.
+ *
+ * @param string $default
+ * @param string $attributes
+ *
+ * @return string
+ */
+ public function referrer($default = null, $attributes = null)
+ {
+ $referrer = $_SERVER['HTTP_REFERER'] ?? null;
+
+ // Check that referrer came from our site.
+ $root = $this->rootUrl(true);
+ if ($referrer) {
+ // Referrer should always have host set and it should come from the same base address.
+ if (stripos($referrer, $root) !== 0) {
+ $referrer = null;
+ }
+ }
+
+ if (!$referrer) {
+ $referrer = $default ?: $this->route(true, true);
+ }
+
+ if ($attributes) {
+ $referrer .= $attributes;
+ }
+
+ // Return relative path.
+ return substr($referrer, strlen($root));
+ }
+
+ public function __toString()
+ {
+ return static::buildUrl($this->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' => $path,
+ 'params' => $this->params,
+ 'query' => $this->query,
+ 'fragment' => $this->fragment
+ ];
+ }
+
+ /**
+ * Calculate the parameter regex based on the param_sep setting
+ *
+ * @return string
+ */
+ public static function paramsRegex()
+ {
+ return '/\/([^\:\#\/\?]*' . Grav::instance()['config']->get('system.param_sep') . '[^\:\#\/\?]*)/';
+ }
+
+ /**
+ * Return the IP address of the current user
+ *
+ * @return string ip address
+ */
+ public static function ip()
+ {
+ if (getenv('HTTP_CLIENT_IP')) {
+ $ip = getenv('HTTP_CLIENT_IP');
+ } elseif (getenv('HTTP_X_FORWARDED_FOR') && Grav::instance()['config']->get('system.http_x_forwarded.ip')) {
+ $ip = getenv('HTTP_X_FORWARDED_FOR');
+ } elseif (getenv('HTTP_X_FORWARDED') && Grav::instance()['config']->get('system.http_x_forwarded.ip')) {
+ $ip = getenv('HTTP_X_FORWARDED');
+ } elseif (getenv('HTTP_FORWARDED_FOR')) {
+ $ip = getenv('HTTP_FORWARDED_FOR');
+ } elseif (getenv('HTTP_FORWARDED')) {
+ $ip = getenv('HTTP_FORWARDED');
+ } elseif (getenv('REMOTE_ADDR')){
+ $ip = getenv('REMOTE_ADDR');
+ } else {
+ $ip = 'UNKNOWN';
+ }
+
+ return $ip;
+ }
+
+ /**
+ * Returns current Uri.
+ *
+ * @return \Grav\Framework\Uri\Uri
+ */
+ public static function getCurrentUri()
+ {
+ if (!static::$currentUri) {
+ static::$currentUri = UriFactory::createFromEnvironment($_SERVER);
+ }
+
+ return static::$currentUri;
+ }
+
+ /**
+ * Returns current route.
+ *
+ * @return \Grav\Framework\Route\Route
+ */
+ public static function getCurrentRoute()
+ {
+ if (!static::$currentRoute) {
+ $uri = Grav::instance()['uri'];
+ static::$currentRoute = RouteFactory::createFromParts($uri->toArray());
+ }
+
+ return static::$currentRoute;
+ }
+
+ /**
+ * Is this an external URL? if it starts with `http` then yes, else false
+ *
+ * @param string $url the URL in question
+ *
+ * @return boolean is eternal state
+ */
+ public static function isExternal($url)
+ {
+ return (0 === strpos($url, 'http://') || 0 === strpos($url, 'https://') || 0 === strpos($url, '//'));
+ }
+
+ /**
+ * The opposite of built-in PHP method parse_url()
+ *
+ * @param array $parsed_url
+ *
+ * @return string
+ */
+ public static function buildUrl($parsed_url)
+ {
+ $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . ':' : '';
+ $authority = isset($parsed_url['host']) ? '//' : '';
+ $host = $parsed_url['host'] ?? '';
+ $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
+ $user = $parsed_url['user'] ?? '';
+ $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
+ $pass = ($user || $pass) ? "{$pass}@" : '';
+ $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'] : '';
+
+ return "{$scheme}{$authority}{$user}{$pass}{$host}{$port}{$path}{$query}{$fragment}";
+ }
+
+ /**
+ * @param array $params
+ * @return string
+ */
+ public static function buildParams(array $params)
+ {
+ if (!$params) {
+ return '';
+ }
+
+ $grav = Grav::instance();
+ $sep = $grav['config']->get('system.param_sep');
+
+ $output = [];
+ foreach ($params as $key => $value) {
+ $output[] = "{$key}{$sep}{$value}";
+ }
+
+ return '/' . implode('/', $output);
+ }
+
+ /**
+ * Converts links from absolute '/' or relative (../..) to a Grav friendly format
+ *
+ * @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|array the more friendly formatted url
+ */
+ public static function convertUrl(PageInterface $page, $url, $type = 'link', $absolute = false, $route_only = false)
+ {
+ $grav = Grav::instance();
+
+ $uri = $grav['uri'];
+
+ // Link processing should prepend language
+ $language = $grav['language'];
+ $language_append = '';
+ if ($type === 'link' && $language->enabled()) {
+ $language_append = $language->getLanguageURLPrefix();
+ }
+
+ // Handle Excerpt style $url array
+ $url_path = is_array($url) ? $url['path'] : $url;
+
+ $external = false;
+ $base = $grav['base_url_relative'];
+ $base_url = rtrim($base . $grav['pages']->base(), '/') . $language_append;
+ $pages_dir = $grav['locator']->findResource('page://');
+
+ // if absolute and starts with a base_url move on
+ if (isset($url['scheme']) && Utils::startsWith($url['scheme'], 'http')) {
+ $external = true;
+ } elseif ($url_path === '' && isset($url['fragment'])) {
+ $external = true;
+ } elseif ($url_path === '/' || ($base_url !== '' && Utils::startsWith($url_path, $base_url))) {
+ $url_path = $base_url . $url_path;
+ } else {
+
+ // see if page is relative to this or absolute
+ if (Utils::startsWith($url_path, '/')) {
+ $normalized_url = Utils::normalizePath($base_url . $url_path);
+ $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(rtrim($page_route, '/') . '/' . $url_path);
+ $normalized_path = Utils::normalizePath($page->path() . '/' . $url_path);
+ }
+
+ // special check to see if path checking is required.
+ $just_path = Utils::replaceFirstOccurrence($normalized_url, '', $normalized_path);
+ if ($normalized_url === '/' || $just_path === $page->path()) {
+ $url_path = $normalized_url;
+ } else {
+ $url_bits = static::parseUrl($normalized_path);
+ $full_path = $url_bits['path'];
+ $raw_full_path = rawurldecode($full_path);
+
+ if (file_exists($raw_full_path)) {
+ $full_path = $raw_full_path;
+ } elseif (!file_exists($full_path)) {
+ $full_path = false;
+ }
+
+ if ($full_path) {
+ $path_info = pathinfo($full_path);
+ $page_path = $path_info['dirname'];
+ $filename = '';
+
+ if ($url_path === '..') {
+ $page_path = $full_path;
+ } else {
+ // save the filename if a file is part of the path
+ if (is_file($full_path)) {
+ if ($path_info['extension'] !== 'md') {
+ $filename = '/' . $path_info['basename'];
+ }
+ } else {
+ $page_path = $full_path;
+ }
+ }
+
+ // get page instances and try to find one that fits
+ $instances = $grav['pages']->instances();
+ if (isset($instances[$page_path])) {
+ /** @var PageInterface $target */
+ $target = $instances[$page_path];
+ $url_bits['path'] = $base_url . rtrim($target->route(), '/') . $filename;
+
+ $url_path = Uri::buildUrl($url_bits);
+ } else {
+ $url_path = $normalized_url;
+ }
+ } else {
+ $url_path = $normalized_url;
+ }
+ }
+ }
+
+ // handle absolute URLs
+ if (\is_array($url) && !$external && ($absolute === true || $grav['config']->get('system.absolute_urls', false))) {
+
+ $url['scheme'] = $uri->scheme(true);
+ $url['host'] = $uri->host();
+ $url['port'] = $uri->port(true);
+
+ // check if page exists for this route, and if so, check if it has SSL enabled
+ $pages = $grav['pages'];
+ $routes = $pages->routes();
+
+ // if this is an image, get the proper path
+ $url_bits = pathinfo($url_path);
+ if (isset($url_bits['extension'])) {
+ $target_path = $url_bits['dirname'];
+ } else {
+ $target_path = $url_path;
+ }
+
+ // strip base from this path
+ $target_path = Utils::replaceFirstOccurrence($uri->rootUrl(), '', $target_path);
+
+ // set to / if root
+ if (empty($target_path)) {
+ $target_path = '/';
+ }
+
+ // look to see if this page exists and has ssl enabled
+ if (isset($routes[$target_path])) {
+ $target_page = $pages->get($routes[$target_path]);
+ if ($target_page) {
+ $ssl_enabled = $target_page->ssl();
+ if ($ssl_enabled !== null) {
+ if ($ssl_enabled) {
+ $url['scheme'] = 'https';
+ } else {
+ $url['scheme'] = 'http';
+ }
+ }
+ }
+ }
+ }
+
+ // Handle route only
+ if ($route_only) {
+ $url_path = Utils::replaceFirstOccurrence(static::filterPath($base_url), '', $url_path);
+ }
+
+ // transform back to string/array as needed
+ if (is_array($url)) {
+ $url['path'] = $url_path;
+ } else {
+ $url = $url_path;
+ }
+
+ return $url;
+ }
+
+ public static function parseUrl($url)
+ {
+ $grav = Grav::instance();
+
+ $encodedUrl = preg_replace_callback(
+ '%[^:/@?&=#]+%usD',
+ function ($matches) { return rawurlencode($matches[0]); },
+ $url
+ );
+
+ $parts = parse_url($encodedUrl);
+
+ if (false === $parts) {
+ return false;
+ }
+
+ foreach($parts as $name => $value) {
+ $parts[$name] = rawurldecode($value);
+ }
+
+ if (!isset($parts['path'])) {
+ $parts['path'] = '';
+ }
+
+ list($stripped_path, $params) = static::extractParams($parts['path'], $grav['config']->get('system.param_sep'));
+
+ if (!empty($params)) {
+ $parts['path'] = $stripped_path;
+ $parts['params'] = $params;
+ }
+
+ return $parts;
+ }
+
+ public static function extractParams($uri, $delimiter)
+ {
+ $params = [];
+
+ if (strpos($uri, $delimiter) !== false) {
+ preg_match_all(static::paramsRegex(), $uri, $matches, PREG_SET_ORDER);
+
+ foreach ($matches as $match) {
+ $param = explode($delimiter, $match[1]);
+ 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);
+ }
+ }
+ }
+
+ return [$uri, $params];
+ }
+
+ /**
+ * Converts links from absolute '/' or relative (../..) to a Grav friendly format
+ *
+ * @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(PageInterface $page, $markdown_url, $type = 'link', $relative = null)
+ {
+ $grav = Grav::instance();
+
+ $language = $grav['language'];
+
+ // Link processing should prepend language
+ $language_append = '';
+ if ($type === 'link' && $language->enabled()) {
+ $language_append = $language->getLanguageURLPrefix();
+ }
+ $pages_dir = $grav['locator']->findResource('page://');
+ if ($relative === null) {
+ $base = $grav['base_url'];
+ } else {
+ $base = $relative ? $grav['base_url_relative'] : $grav['base_url_absolute'];
+ }
+
+ $base_url = rtrim($base . $grav['pages']->base(), '/') . $language_append;
+
+ // if absolute and starts with a base_url move on
+ if (pathinfo($markdown_url, PATHINFO_DIRNAME) === '.' && $page->url() === '/') {
+ return '/' . $markdown_url;
+ }
+ // no path to convert
+ if ($base_url !== '' && Utils::startsWith($markdown_url, $base_url)) {
+ return $markdown_url;
+ }
+ // if contains only a fragment
+ if (Utils::startsWith($markdown_url, '#')) {
+ return $markdown_url;
+ }
+
+ $target = null;
+ // see if page is relative to this or absolute
+ if (Utils::startsWith($markdown_url, '/')) {
+ $normalized_url = Utils::normalizePath($base_url . $markdown_url);
+ $normalized_path = Utils::normalizePath($pages_dir . $markdown_url);
+ } else {
+ $normalized_url = $base_url . Utils::normalizePath($page->route() . '/' . $markdown_url);
+ $normalized_path = Utils::normalizePath($page->path() . '/' . $markdown_url);
+ }
+
+ // special check to see if path checking is required.
+ $just_path = Utils::replaceFirstOccurrence($normalized_url, '', $normalized_path);
+ if ($just_path === $page->path()) {
+ return $normalized_url;
+ }
+
+ $url_bits = parse_url($normalized_path);
+ $full_path = $url_bits['path'];
+
+ if (file_exists($full_path)) {
+ // do nothing
+ } elseif (file_exists(rawurldecode($full_path))) {
+ $full_path = rawurldecode($full_path);
+ } else {
+ return $normalized_url;
+ }
+
+ $path_info = pathinfo($full_path);
+ $page_path = $path_info['dirname'];
+ $filename = '';
+
+ if ($markdown_url === '..') {
+ $page_path = $full_path;
+ } else {
+ // save the filename if a file is part of the path
+ if (is_file($full_path)) {
+ if ($path_info['extension'] !== 'md') {
+ $filename = '/' . $path_info['basename'];
+ }
+ } else {
+ $page_path = $full_path;
+ }
+ }
+
+ // get page instances and try to find one that fits
+ $instances = $grav['pages']->instances();
+ if (isset($instances[$page_path])) {
+ /** @var PageInterface $target */
+ $target = $instances[$page_path];
+ $url_bits['path'] = $base_url . rtrim($target->route(), '/') . $filename;
+
+ return static::buildUrl($url_bits);
+ }
+
+ return $normalized_url;
+ }
+
+ /**
+ * Adds the nonce to a URL for a specific action
+ *
+ * @param string $url the url
+ * @param string $action the action
+ * @param string $nonceParamName the param name to use
+ *
+ * @return string the url with the nonce
+ */
+ public static function addNonce($url, $action, $nonceParamName = 'nonce')
+ {
+ $fake = $url && strpos($url, '/') === 0;
+
+ if ($fake) {
+ $url = 'http://domain.com' . $url;
+ }
+ $uri = new static($url);
+ $parts = $uri->toArray();
+ $nonce = Utils::getNonce($action);
+ $parts['params'] = ($parts['params'] ?? []) + [$nonceParamName => $nonce];
+
+ if ($fake) {
+ unset($parts['scheme'], $parts['host']);
+ }
+
+ return static::buildUrl($parts);
+ }
+
+ /**
+ * Is the passed in URL a valid URL?
+ *
+ * @param string $url
+ * @return bool
+ */
+ public static function isValidUrl($url)
+ {
+ $regex = '/^(?:(https?|ftp|telnet):)?\/\/((?:[a-z0-9@:.-]|%[0-9A-F]{2}){3,})(?::(\d+))?((?:\/(?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\@]|%[0-9A-F]{2})*)*)(?:\?((?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\/?@]|%[0-9A-F]{2})*))?(?:#((?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\/?@]|%[0-9A-F]{2})*))?/';
+ if (preg_match($regex, $url)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Removes extra double slashes and fixes back-slashes
+ *
+ * @param string $path
+ * @return mixed|string
+ */
+ public static function cleanPath($path)
+ {
+ $regex = '/(\/)\/+/';
+ $path = str_replace(['\\', '/ /'], '/', $path);
+ $path = preg_replace($regex,'$1',$path);
+
+ return $path;
+ }
+
+ /**
+ * Filters the user info string.
+ *
+ * @param string $info The raw user or password.
+ * @return string The percent-encoded user or password string.
+ */
+ public static function filterUserInfo($info)
+ {
+ return $info !== null ? UriPartsFilter::filterUserInfo($info) : '';
+ }
+
+ /**
+ * Filter Uri path.
+ *
+ * This method percent-encodes all reserved
+ * characters in the provided path string. This method
+ * will NOT double-encode characters that are already
+ * percent-encoded.
+ *
+ * @param string $path The raw uri path.
+ * @return string The RFC 3986 percent-encoded uri path.
+ * @link http://www.faqs.org/rfcs/rfc3986.html
+ */
+ public static function filterPath($path)
+ {
+ return $path !== null ? UriPartsFilter::filterPath($path) : '';
+ }
+
+ /**
+ * Filters the query string or fragment of a URI.
+ *
+ * @param string $query The raw uri query string.
+ * @return string The percent-encoded query string.
+ */
+ public static function filterQuery($query)
+ {
+ return $query !== null ? UriPartsFilter::filterQueryOrFragment($query) : '';
+ }
+
+ /**
+ * @param array $env
+ */
+ protected function createFromEnvironment(array $env)
+ {
+ // Build scheme.
+ if (isset($env['HTTP_X_FORWARDED_PROTO']) && Grav::instance()['config']->get('system.http_x_forwarded.protocol')) {
+ $this->scheme = $env['HTTP_X_FORWARDED_PROTO'];
+ } elseif (isset($env['X-FORWARDED-PROTO'])) {
+ $this->scheme = $env['X-FORWARDED-PROTO'];
+ } elseif (isset($env['HTTP_CLOUDFRONT_FORWARDED_PROTO'])) {
+ $this->scheme = $env['HTTP_CLOUDFRONT_FORWARDED_PROTO'];
+ } elseif (isset($env['REQUEST_SCHEME']) && empty($env['HTTPS'])) {
+ $this->scheme = $env['REQUEST_SCHEME'];
+ } else {
+ $https = $env['HTTPS'] ?? '';
+ $this->scheme = (empty($https) || strtolower($https) === 'off') ? 'http' : 'https';
+ }
+
+ // Build user and password.
+ $this->user = $env['PHP_AUTH_USER'] ?? null;
+ $this->password = $env['PHP_AUTH_PW'] ?? null;
+
+ // Build host.
+ if (isset($env['HTTP_X_FORWARDED_HOST']) && Grav::instance()['config']->get('system.http_x_forwarded.host')) {
+ $hostname = $env['HTTP_X_FORWARDED_HOST'];
+ } else if (isset($env['HTTP_HOST'])) {
+ $hostname = $env['HTTP_HOST'];
+ } elseif (isset($env['SERVER_NAME'])) {
+ $hostname = $env['SERVER_NAME'];
+ } else {
+ $hostname = 'localhost';
+ }
+ // Remove port from HTTP_HOST generated $hostname
+ $hostname = Utils::substrToString($hostname, ':');
+ // Validate the hostname
+ $this->host = $this->validateHostname($hostname) ? $hostname : 'unknown';
+
+ // Build port.
+ if (isset($env['HTTP_X_FORWARDED_PORT']) && Grav::instance()['config']->get('system.http_x_forwarded.port')) {
+ $this->port = (int)$env['HTTP_X_FORWARDED_PORT'];
+ } elseif (isset($env['X-FORWARDED-PORT'])) {
+ $this->port = (int)$env['X-FORWARDED-PORT'];
+ } elseif (isset($env['HTTP_CLOUDFRONT_FORWARDED_PROTO'])) {
+ // Since AWS Cloudfront does not provide a forwarded port header,
+ // we have to build the port using the scheme.
+ $this->port = $this->port();
+ } elseif (isset($env['SERVER_PORT'])) {
+ $this->port = (int)$env['SERVER_PORT'];
+ } else {
+ $this->port = null;
+ }
+
+ if ($this->hasStandardPort()) {
+ $this->port = null;
+ }
+
+ // Build path.
+ $request_uri = $env['REQUEST_URI'] ?? '';
+ $this->path = rawurldecode(parse_url('http://example.com' . $request_uri, PHP_URL_PATH));
+
+ // Build query string.
+ $this->query = $env['QUERY_STRING'] ?? '';
+ if ($this->query === '') {
+ $this->query = parse_url('http://example.com' . $request_uri, PHP_URL_QUERY);
+ }
+
+ // Support ngnix routes.
+ if (strpos($this->query, '_url=') === 0) {
+ parse_str($this->query, $query);
+ unset($query['_url']);
+ $this->query = http_build_query($query);
+ }
+
+ // Build fragment.
+ $this->fragment = null;
+
+ // Filter userinfo, path and query string.
+ $this->user = $this->user !== null ? static::filterUserInfo($this->user) : null;
+ $this->password = $this->password !== null ? static::filterUserInfo($this->password) : null;
+ $this->path = empty($this->path) ? '/' : static::filterPath($this->path);
+ $this->query = static::filterQuery($this->query);
+
+ $this->reset();
+ }
+
+ /**
+ * Does this Uri use a standard port?
+ *
+ * @return bool
+ */
+ protected function hasStandardPort()
+ {
+ return ($this->port === 80 || $this->port === 443);
+ }
+
+ /**
+ * @param string $url
+ */
+ protected function createFromString($url)
+ {
+ // Set Uri parts.
+ $parts = parse_url($url);
+ if ($parts === false) {
+ throw new \RuntimeException('Malformed URL: ' . $url);
+ }
+ $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 = $parts['path'] ?? '';
+ $this->query = $parts['query'] ?? '';
+ $this->fragment = $parts['fragment'] ?? null;
+
+ // Validate the hostname
+ if ($this->host) {
+ $this->host = $this->validateHostname($this->host) ? $this->host : 'unknown';
+ }
+ // Filter userinfo, path, query string and fragment.
+ $this->user = $this->user !== null ? static::filterUserInfo($this->user) : null;
+ $this->password = $this->password !== null ? static::filterUserInfo($this->password) : null;
+ $this->path = empty($this->path) ? '/' : static::filterPath($this->path);
+ $this->query = static::filterQuery($this->query);
+ $this->fragment = $this->fragment !== null ? static::filterQuery($this->fragment) : null;
+
+ $this->reset();
+ }
+
+ protected function reset()
+ {
+ // resets
+ parse_str($this->query, $this->queries);
+ $this->extension = null;
+ $this->basename = null;
+ $this->paths = [];
+ $this->params = [];
+ $this->env = $this->buildEnvironment();
+ $this->uri = $this->path . (!empty($this->query) ? '?' . $this->query : '');
+
+ $this->base = $this->buildBaseUrl();
+ $this->root_path = $this->buildRootPath();
+ $this->root = $this->base . $this->root_path;
+ $this->url = $this->base . $this->uri;
+ }
+
+ /**
+ * Get post from either $_POST or JSON response object
+ * By default returns all data, or can return a single item
+ *
+ * @param string $element
+ * @param string $filter_type
+ * @return array|mixed|null
+ */
+ public function post($element = null, $filter_type = null)
+ {
+ if (!$this->post) {
+ $content_type = $this->getContentType();
+ if ($content_type === 'application/json') {
+ $json = file_get_contents('php://input');
+ $this->post = json_decode($json, true);
+ } elseif (!empty($_POST)) {
+ $this->post = (array)$_POST;
+ }
+
+ $event = new Event(['post' => &$this->post]);
+ Grav::instance()->fireEvent('onHttpPostFilter', $event);
+ }
+
+ if ($this->post && null !== $element) {
+ $item = Utils::getDotNotation($this->post, $element);
+ if ($filter_type) {
+ $item = filter_var($item, $filter_type);
+ }
+ return $item;
+ }
+
+ return $this->post;
+ }
+
+ /**
+ * Get content type from request
+ *
+ * @param bool $short
+ * @return null|string
+ */
+ public function getContentType($short = true)
+ {
+ if (isset($_SERVER['CONTENT_TYPE'])) {
+ $content_type = $_SERVER['CONTENT_TYPE'];
+ if ($short) {
+ return Utils::substrToString($content_type,';');
+ }
+ return $content_type;
+ }
+ return null;
+ }
+
+ /**
+ * Check if this is a valid Grav extension
+ *
+ * @param $extension
+ * @return bool
+ */
+ public function isValidExtension($extension)
+ {
+ $valid_page_types = implode('|', Utils::getSupportPageTypes());
+
+ // 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
+ *
+ * @return string
+ */
+ private function buildBaseUrl()
+ {
+ return $this->scheme() . $this->host;
+ }
+
+ /**
+ * Get the Grav Root Path
+ *
+ * @return string
+ */
+ private function buildRootPath()
+ {
+ // In Windows script path uses backslash, convert it:
+ $scriptPath = str_replace('\\', '/', $_SERVER['PHP_SELF']);
+ $rootPath = str_replace(' ', '%20', rtrim(substr($scriptPath, 0, strpos($scriptPath, 'index.php')), '/'));
+
+ return $rootPath;
+ }
+
+ private function buildEnvironment()
+ {
+ // check for localhost variations
+ if ($this->host === '127.0.0.1' || $this->host === '::1') {
+ return 'localhost';
+ }
+
+ return $this->host ?: 'unknown';
+ }
+
+ /**
+ * Process any params based in this URL, supports any valid delimiter
+ *
+ * @param string $uri
+ * @param string $delimiter
+ *
+ * @return string
+ */
+ private function processParams($uri, $delimiter = ':')
+ {
+ if (strpos($uri, $delimiter) !== false) {
+ preg_match_all(static::paramsRegex(), $uri, $matches, PREG_SET_ORDER);
+
+ foreach ($matches as $match) {
+ $param = explode($delimiter, $match[1]);
+ if (count($param) === 2) {
+ $plain_var = filter_var($param[1], FILTER_SANITIZE_STRING);
+ $this->params[$param[0]] = $plain_var;
+ $uri = str_replace($match[0], '', $uri);
+ }
+ }
+ }
+ return $uri;
+ }
+}
diff --git a/system/src/Grav/Common/User/Authentication.php b/system/src/Grav/Common/User/Authentication.php
new file mode 100644
index 0000000..2447732
--- /dev/null
+++ b/system/src/Grav/Common/User/Authentication.php
@@ -0,0 +1,55 @@
+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..800322a
--- /dev/null
+++ b/system/src/Grav/Common/User/DataUser/UserCollection.php
@@ -0,0 +1,130 @@
+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);
+ }
+
+ public function count(): int
+ {
+ // check for existence of a user account
+ $account_dir = $file_path = Grav::instance()['locator']->findResource('account://');
+ $accounts = glob($account_dir . '/*.yaml') ?: [];
+
+ return count($accounts);
+ }
+}
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());
+ }
+
+ /**
+ * @param string $name
+ * @return Blueprint
+ */
+ public function getBlueprint(string $name = '')
+ {
+ $blueprint = clone parent::getBlueprint($name);
+
+ $blueprint->addDynamicHandler('flex', function (array &$field, $property, array &$call) {
+ $params = (array)$call['params'];
+ $method = array_shift($params);
+
+ if (method_exists($this, $method)) {
+ $value = $this->{$method}(...$params);
+ if (\is_array($value) && isset($field[$property]) && \is_array($field[$property])) {
+ $field[$property] = array_merge_recursive($field[$property], $value);
+ } else {
+ $field[$property] = $value;
+ }
+ }
+ });
+
+ return $blueprint->init();
+ }
+
+ /**
+ * 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()
+ {
+ // TODO: We may want to handle this in the storage layer in the future.
+ $key = $this->getStorageKey();
+ if (!$key || strpos($key, '@@')) {
+ $storage = $this->getFlexDirectory()->getStorage();
+ if ($storage instanceof FileStorage) {
+ $this->setStorageKey($this->getKey());
+ }
+ }
+
+ $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();
+ }
+
+ public function isAuthorized(string $action, string $scope = null, UserInterface $user = null): bool
+ {
+ if (null === $user) {
+ /** @var UserInterface $user */
+ $user = Grav::instance()['user'] ?? null;
+ }
+
+ if ($user instanceof User && $user->getStorageKey() === $this->getStorageKey()) {
+ return true;
+ }
+
+ return parent::isAuthorized($action, $scope, $user);
+ }
+
+ /**
+ * @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
new file mode 100644
index 0000000..9815c0e
--- /dev/null
+++ b/system/src/Grav/Common/User/Group.php
@@ -0,0 +1,155 @@
+get('groups', []);
+ }
+
+ /**
+ * Get the groups list
+ *
+ * @return array
+ */
+ public static function groupNames()
+ {
+ $groups = [];
+
+ foreach(static::groups() as $groupname => $group) {
+ $groups[$groupname] = $group['readableName'] ?? $groupname;
+ }
+
+ return $groups;
+ }
+
+ /**
+ * Checks if a group exists
+ *
+ * @param string $groupname
+ *
+ * @return bool
+ */
+ public static function groupExists($groupname)
+ {
+ return isset(self::groups()[$groupname]);
+ }
+
+ /**
+ * Get a group by name
+ *
+ * @param string $groupname
+ *
+ * @return object
+ */
+ public static function load($groupname)
+ {
+ $groups = self::groups();
+
+ $content = $groups[$groupname] ?? [];
+ $content += ['groupname' => $groupname];
+
+ $blueprints = new Blueprints();
+ $blueprint = $blueprints->get('user/group');
+
+ return new Group($content, $blueprint);
+ }
+
+ /**
+ * Save a group
+ */
+ public function save()
+ {
+ $grav = Grav::instance();
+
+ /** @var Config $config */
+ $config = $grav['config'];
+
+ $blueprints = new Blueprints();
+ $blueprint = $blueprints->get('user/group');
+
+ $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->get('groupname')}.{$value}", $this->items['data'][$value]);
+ }
+ }
+ if ($field['type'] === 'array' || $field['type'] === 'permissions') {
+ $value = $field['name'];
+ $arrayValues = Utils::getDotNotation($this->items['data'], $field['name']);
+
+ if ($arrayValues) {
+ foreach ($arrayValues as $arrayIndex => $arrayValue) {
+ $config->set("groups.{$this->get('groupname')}.{$value}.{$arrayIndex}", $arrayValue);
+ }
+ }
+ }
+ }
+
+ $type = 'groups';
+ $blueprints = $this->blueprints();
+
+ $filename = CompiledYamlFile::instance($grav['locator']->findResource("config://{$type}.yaml"));
+
+ $obj = new Data($config->get($type), $blueprints);
+ $obj->file($filename);
+ $obj->save();
+ }
+
+ /**
+ * Remove a group
+ *
+ * @param string $groupname
+ *
+ * @return bool True if the action was performed
+ */
+ public static function remove($groupname)
+ {
+ $grav = Grav::instance();
+
+ /** @var Config $config */
+ $config = $grav['config'];
+
+ $blueprints = new Blueprints();
+ $blueprint = $blueprints->get('user/group');
+
+ $type = 'groups';
+
+ $groups = $config->get($type);
+ unset($groups[$groupname]);
+ $config->set($type, $groups);
+
+ $filename = CompiledYamlFile::instance($grav['locator']->findResource("config://{$type}.yaml"));
+
+ $obj = new Data($groups, $blueprint);
+ $obj->file($filename);
+ $obj->save();
+
+ return true;
+ }
+}
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..37b9009
--- /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..f711cc9
--- /dev/null
+++ b/system/src/Grav/Common/User/Traits/UserTrait.php
@@ -0,0 +1,171 @@
+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');
+ $provider_options = $this->get($provider);
+ if (\is_array($provider_options)) {
+ if (isset($provider_options['avatar_url']) && \is_string($provider_options['avatar_url'])) {
+ return $provider_options['avatar_url'];
+ }
+ if (isset($provider_options['avatar']) && \is_string($provider_options['avatar'])) {
+ return $provider_options['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
new file mode 100644
index 0000000..ba1dbdc
--- /dev/null
+++ b/system/src/Grav/Common/User/User.php
@@ -0,0 +1,146 @@
+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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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 UserCollectionInterface
+ */
+ protected static function getCollection()
+ {
+ return Grav::instance()['accounts'];
+ }
+ }
+} else {
+ /**
+ * @deprecated 1.6 Use $grav['accounts'] instead of static calls. In type hints, use UserInterface.
+ */
+ class User extends DataUser\User
+ {
+ /**
+ * 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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 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
new file mode 100644
index 0000000..8a6d987
--- /dev/null
+++ b/system/src/Grav/Common/Utils.php
@@ -0,0 +1,1641 @@
+schemeExists($scheme)) {
+ // If scheme does not exists as a stream, assume it's external.
+ return str_replace(' ', '%20', $input);
+ }
+
+ // Attempt to find the resource (because of parse_url() we need to put host back to path).
+ $resource = $locator->findResource("{$scheme}://{$host}{$path}", false);
+
+ if ($resource === false) {
+ if (!$fail_gracefully) {
+ return false;
+ }
+
+ // Return location where the file would be if it was saved.
+ $resource = $locator->findResource("{$scheme}://{$host}{$path}", false, true);
+ }
+
+ } elseif ($host || $port) {
+ // If URL doesn't have scheme but has host or port, it is external.
+ return str_replace(' ', '%20', $input);
+ }
+
+ if (!empty($resource)) {
+ // Add query string back.
+ if (isset($parts['query'])) {
+ $resource .= '?' . $parts['query'];
+ }
+
+ // Add fragment back.
+ if (isset($parts['fragment'])) {
+ $resource .= '#' . $parts['fragment'];
+ }
+ }
+
+ } else {
+ // Not a valid URL (can still be a stream).
+ $resource = $locator->findResource($input, false);
+ }
+
+ } else {
+ $root = $uri->rootUrl();
+
+ if (static::startsWith($input, $root)) {
+ $input = static::replaceFirstOccurrence($root, '', $input);
+ }
+
+ $input = ltrim($input, '/');
+
+ $resource = $input;
+ }
+
+ if (!$fail_gracefully && $resource === false) {
+ return false;
+ }
+
+ $domain = $domain ?: $grav['config']->get('system.absolute_urls', false);
+
+ return rtrim($uri->rootUrl($domain), '/') . '/' . ($resource ?? '');
+ }
+
+ /**
+ * Helper method to find the full path to a file, be it a stream, a relative path, or
+ * already a full path
+ *
+ * @param $path
+ * @return string
+ */
+ public static function fullPath($path)
+ {
+ $locator = Grav::instance()['locator'];
+
+ if ($locator->isStream($path)) {
+ $path = $locator->findResource($path, true);
+ } elseif (!Utils::startsWith($path, GRAV_ROOT)) {
+ $base_url = Grav::instance()['base_url'];
+ $path = GRAV_ROOT . '/' . ltrim(Utils::replaceFirstOccurrence($base_url, '', $path), '/');
+ }
+
+ return $path;
+ }
+
+
+ /**
+ * Check if the $haystack string starts with the substring $needle
+ *
+ * @param string $haystack
+ * @param string|string[] $needle
+ * @param bool $case_sensitive
+ *
+ * @return bool
+ */
+ 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 === '' || $compare_func($haystack, $each_needle) === 0;
+ if ($status) {
+ break;
+ }
+ }
+
+ return $status;
+ }
+
+ /**
+ * Check if the $haystack string ends with the substring $needle
+ *
+ * @param string $haystack
+ * @param string|string[] $needle
+ * @param bool $case_sensitive
+ *
+ * @return bool
+ */
+ 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) {
+ $expectedPosition = mb_strlen($haystack) - mb_strlen($each_needle);
+ $status = $each_needle === '' || $compare_func($haystack, $each_needle, 0) === $expectedPosition;
+ if ($status) {
+ break;
+ }
+ }
+
+ return $status;
+ }
+
+ /**
+ * Check if the $haystack string contains the substring $needle
+ *
+ * @param string $haystack
+ * @param string|string[] $needle
+ * @param bool $case_sensitive
+ *
+ * @return bool
+ */
+ 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 === '' || $compare_func($haystack, $each_needle) !== false;
+ if ($status) {
+ break;
+ }
+ }
+
+ 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 string $haystack
+ * @param string $needle
+ * @param bool $case_sensitive
+ *
+ * @return string
+ */
+ public static function substrToString($haystack, $needle, $case_sensitive = true)
+ {
+ $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;
+ }
+
+ /**
+ * Utility method to replace only the first occurrence in a string
+ *
+ * @param string $search
+ * @param string $replace
+ * @param string $subject
+ *
+ * @return string
+ */
+ public static function replaceFirstOccurrence($search, $replace, $subject)
+ {
+ if (!$search) {
+ return $subject;
+ }
+
+ $pos = mb_strpos($subject, $search);
+ if ($pos !== false) {
+ $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 string $search
+ * @param string $replace
+ * @param string $subject
+ * @return string
+ */
+ public static function replaceLastOccurrence($search, $replace, $subject)
+ {
+ $pos = strrpos($subject, $search);
+
+ if($pos !== false)
+ {
+ $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.
+ *
+ * @param object $obj1
+ * @param object $obj2
+ *
+ * @return object
+ */
+ public static function mergeObjects($obj1, $obj2)
+ {
+ return (object)array_merge((array)$obj1, (array)$obj2);
+ }
+
+ /**
+ * Lowercase an entire array. Useful when combined with `in_array()`
+ *
+ * @param array $a
+ * @return array|false
+ */
+ public static function arrayLower(Array $a)
+ {
+ return array_map('mb_strtolower', $a);
+ }
+
+ /**
+ * Simple function to remove item/s in an array by value
+ *
+ * @param $search array
+ * @param $value string|array
+ * @return array
+ */
+ public static function arrayRemoveValue(Array $search, $value)
+ {
+ foreach ((array) $value as $val) {
+ $key = array_search($val, $search);
+ if ($key !== false) {
+ unset($search[$key]);
+ }
+ }
+ return $search;
+ }
+
+ /**
+ * Recursive Merge with uniqueness
+ *
+ * @param array $array1
+ * @param array $array2
+ * @return array
+ */
+ public static function arrayMergeRecursiveUnique($array1, $array2)
+ {
+ if (empty($array1)) {
+ // Optimize the base case
+ return $array2;
+ }
+
+ foreach ($array2 as $key => $value) {
+ if (is_array($value) && isset($array1[$key]) && is_array($array1[$key])) {
+ $value = static::arrayMergeRecursiveUnique($array1[$key], $value);
+ }
+ $array1[$key] = $value;
+ }
+
+ 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
+ *
+ * @return array
+ */
+ public static function dateFormats()
+ {
+ $now = new \DateTime();
+
+ $date_formats = [
+ 'd-m-Y H:i' => 'd-m-Y H:i (e.g. '.$now->format('d-m-Y H:i').')',
+ 'Y-m-d H:i' => 'Y-m-d H:i (e.g. '.$now->format('Y-m-d H:i').')',
+ 'm/d/Y h:i a' => 'm/d/Y h:i a (e.g. '.$now->format('m/d/Y h:i a').')',
+ 'H:i d-m-Y' => 'H:i d-m-Y (e.g. '.$now->format('H:i d-m-Y').')',
+ 'h:i a m/d/Y' => 'h:i a m/d/Y (e.g. '.$now->format('h:i a m/d/Y').')',
+ ];
+ $default_format = Grav::instance()['config']->get('system.pages.dateformat.default');
+ if ($default_format) {
+ $date_formats = array_merge([$default_format => $default_format.' (e.g. '.$now->format($default_format).')'], $date_formats);
+ }
+
+ 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.
+ *
+ * @param string $string
+ * @param int $limit Max number of characters.
+ * @param bool $up_to_break truncate up to breakpoint after char count
+ * @param string $break Break point.
+ * @param string $pad Appended padding to the end of the string.
+ *
+ * @return string
+ */
+ 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) {
+ return $string;
+ }
+
+ // is $break present between $limit and the end of the string?
+ if ($up_to_break && false !== ($breakpoint = mb_strpos($string, $break, $limit))) {
+ if ($breakpoint < mb_strlen($string) - 1) {
+ $string = mb_substr($string, 0, $breakpoint) . $pad;
+ }
+ } else {
+ $string = mb_substr($string, 0, $limit) . $pad;
+ }
+
+ return $string;
+ }
+
+ /**
+ * Truncate text by number of characters in a "word-safe" manor.
+ *
+ * @param string $string
+ * @param int $limit
+ *
+ * @return string
+ */
+ public static function safeTruncate($string, $limit = 150)
+ {
+ return static::truncate($string, $limit, true);
+ }
+
+
+ /**
+ * Truncate HTML by number of characters. not "word-safe"!
+ *
+ * @param string $text
+ * @param int $length in characters
+ * @param string $ellipsis
+ *
+ * @return string
+ */
+ public static function truncateHtml($text, $length = 100, $ellipsis = '...')
+ {
+ return Truncator::truncateLetters($text, $length, $ellipsis);
+ }
+
+ /**
+ * Truncate HTML by number of characters in a "word-safe" manor.
+ *
+ * @param string $text
+ * @param int $length in words
+ * @param string $ellipsis
+ *
+ * @return string
+ */
+ public static function safeTruncateHtml($text, $length = 25, $ellipsis = '...')
+ {
+ return Truncator::truncateWords($text, $length, $ellipsis);
+ }
+
+ /**
+ * Generate a random string of a given length
+ *
+ * @param int $length
+ *
+ * @return string
+ */
+ public static function generateRandomString($length = 5)
+ {
+ return substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, $length);
+ }
+
+ /**
+ * Provides the ability to download a file to the browser
+ *
+ * @param string $file the full path to the file to be downloaded
+ * @param bool $force_download as opposed to letting browser choose if to download or render
+ * @param int $sec Throttling, try 0.1 for some speed throttling of downloads
+ * @param int $bytes Size of chunks to send in bytes. Default is 1024
+ * @throws \Exception
+ */
+ public static function download($file, $force_download = true, $sec = 0, $bytes = 1024)
+ {
+ if (file_exists($file)) {
+ // fire download event
+ Grav::instance()->fireEvent('onBeforeDownload', new Event(['file' => $file]));
+
+ $file_parts = pathinfo($file);
+ $mimetype = static::getMimeByExtension($file_parts['extension']);
+ $size = filesize($file); // File size
+
+ // clean all buffers
+ while (ob_get_level()) {
+ ob_end_clean();
+ }
+
+ // required for IE, otherwise Content-Disposition may be ignored
+ if (ini_get('zlib.output_compression')) {
+ ini_set('zlib.output_compression', 'Off');
+ }
+
+ header('Content-Type: ' . $mimetype);
+ header('Accept-Ranges: bytes');
+
+ if ($force_download) {
+ // output the regular HTTP headers
+ header('Content-Disposition: attachment; filename="' . $file_parts['basename'] . '"');
+ }
+
+ // multipart-download and download resuming support
+ if (isset($_SERVER['HTTP_RANGE'])) {
+ list($a, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
+ list($range) = explode(',', $range, 2);
+ list($range, $range_end) = explode('-', $range);
+ $range = (int)$range;
+ if (!$range_end) {
+ $range_end = $size - 1;
+ } else {
+ $range_end = (int)$range_end;
+ }
+ $new_length = $range_end - $range + 1;
+ header('HTTP/1.1 206 Partial Content');
+ header("Content-Length: {$new_length}");
+ header("Content-Range: bytes {$range}-{$range_end}/{$size}");
+ } else {
+ $range = 0;
+ $new_length = $size;
+ header('Content-Length: ' . $size);
+
+ if (Grav::instance()['config']->get('system.cache.enabled')) {
+ $expires = Grav::instance()['config']->get('system.pages.expires');
+ if ($expires > 0) {
+ $expires_date = gmdate('D, d M Y H:i:s T', time() + $expires);
+ header('Cache-Control: max-age=' . $expires);
+ header('Expires: ' . $expires_date);
+ header('Pragma: cache');
+ }
+ header('Last-Modified: ' . gmdate('D, d M Y H:i:s T', filemtime($file)));
+
+ // Return 304 Not Modified if the file is already cached in the browser
+ if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
+ strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= filemtime($file))
+ {
+ header('HTTP/1.1 304 Not Modified');
+ exit();
+ }
+ }
+ }
+
+ /* output the file itself */
+ $chunksize = $bytes * 8; //you may want to change this
+ $bytes_send = 0;
+
+ $fp = @fopen($file, 'rb');
+ if ($fp) {
+ if ($range) {
+ fseek($fp, $range);
+ }
+ while (!feof($fp) && (!connection_aborted()) && ($bytes_send < $new_length) ) {
+ $buffer = fread($fp, $chunksize);
+ echo($buffer); //echo($buffer); // is also possible
+ flush();
+ usleep($sec * 1000000);
+ $bytes_send += strlen($buffer);
+ }
+ fclose($fp);
+ } else {
+ throw new \RuntimeException('Error - can not open file.');
+ }
+
+ exit;
+ }
+ }
+
+ /**
+ * Return the mimetype based on filename extension
+ *
+ * @param string $extension Extension of file (eg "txt")
+ * @param string $default
+ *
+ * @return string
+ */
+ public static function getMimeByExtension($extension, $default = 'application/octet-stream')
+ {
+ $extension = strtolower($extension);
+
+ // look for some standard types
+ switch ($extension) {
+ case null:
+ return $default;
+ case 'json':
+ return 'application/json';
+ case 'html':
+ return 'text/html';
+ case 'atom':
+ return 'application/atom+xml';
+ case 'rss':
+ return 'application/rss+xml';
+ case 'xml':
+ return 'application/xml';
+ }
+
+ $media_types = Grav::instance()['config']->get('media.types');
+
+ if (isset($media_types[$extension])) {
+ if (isset($media_types[$extension]['mime'])) {
+ return $media_types[$extension]['mime'];
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Get all the mimetypes for an array of extensions
+ *
+ * @param array $extensions
+ * @return array
+ */
+ public static function getMimeTypes(array $extensions)
+ {
+ $mimetypes = [];
+ foreach ($extensions as $extension) {
+ $mimetype = static::getMimeByExtension($extension, false);
+ if ($mimetype && !in_array($mimetype, $mimetypes)) {
+ $mimetypes[] = $mimetype;
+ }
+ }
+ return $mimetypes;
+ }
+
+
+ /**
+ * Return the mimetype based on filename extension
+ *
+ * @param string $mime mime type (eg "text/html")
+ * @param string $default default value
+ *
+ * @return string
+ */
+ public static function getExtensionByMime($mime, $default = 'html')
+ {
+ $mime = strtolower($mime);
+
+ // look for some standard mime types
+ switch ($mime) {
+ case '*/*':
+ case 'text/*':
+ case 'text/html':
+ return 'html';
+ case 'application/json':
+ return 'json';
+ case 'application/atom+xml':
+ return 'atom';
+ case 'application/rss+xml':
+ return 'rss';
+ case 'application/xml':
+ return 'xml';
+ }
+
+ $media_types = (array)Grav::instance()['config']->get('media.types');
+
+ foreach ($media_types as $extension => $type) {
+ if ($extension === 'defaults') {
+ continue;
+ }
+ if (isset($type['mime']) && $type['mime'] === $mime) {
+ return $extension;
+ }
+ }
+
+ 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.
+ *
+ * @param string $filename
+ * @return bool
+ */
+ public static function checkFilename($filename)
+ {
+ $dangerous_extensions = Grav::instance()['config']->get('security.uploads_dangerous_extensions', []);
+ array_walk($dangerous_extensions, function(&$val) {
+ $val = '.' . $val;
+ });
+
+ $extension = '.' . pathinfo($filename, PATHINFO_EXTENSION);
+
+ return !(
+ // Empty filenames are not allowed.
+ !$filename
+ // Filename should not contain horizontal/vertical tabs, newlines, nils or back/forward slashes.
+ || strtr($filename, "\t\v\n\r\0\\/", '_______') !== $filename
+ // Filename should not start or end with dot or space.
+ || trim($filename, '. ') !== $filename
+ // Filename should not contain .php in it.
+ || static::contains($extension, $dangerous_extensions)
+ );
+ }
+
+ /**
+ * Normalize path by processing relative `.` and `..` syntax and merging path
+ *
+ * @param string $path
+ *
+ * @return string
+ */
+ public static function normalizePath($path)
+ {
+ // Resolve any streams
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+ if ($locator->isStream($path)) {
+ $path = $locator->findResource($path);
+ }
+
+ // 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;
+ }
+
+ /**
+ * Check whether a function is disabled in the PHP settings
+ *
+ * @param string $function the name of the function to check
+ *
+ * @return bool
+ */
+ public static function isFunctionDisabled($function)
+ {
+ return \in_array($function, explode(',', ini_get('disable_functions')), true);
+ }
+
+ /**
+ * Get the formatted timezones list
+ *
+ * @return array
+ */
+ public static function timezones()
+ {
+ $timezones = \DateTimeZone::listIdentifiers(\DateTimeZone::ALL);
+ $offsets = [];
+ $testDate = new \DateTime();
+
+ foreach ($timezones as $zone) {
+ $tz = new \DateTimeZone($zone);
+ $offsets[$zone] = $tz->getOffset($testDate);
+ }
+
+ asort($offsets);
+
+ $timezone_list = [];
+ foreach ($offsets as $timezone => $offset) {
+ $offset_prefix = $offset < 0 ? '-' : '+';
+ $offset_formatted = gmdate('H:i', abs($offset));
+
+ $pretty_offset = "UTC${offset_prefix}${offset_formatted}";
+
+ $timezone_list[$timezone] = "(${pretty_offset}) ".str_replace('_', ' ', $timezone);
+ }
+
+ return $timezone_list;
+ }
+
+ /**
+ * Recursively filter an array, filtering values by processing them through the $fn function argument
+ *
+ * @param array $source the Array to filter
+ * @param callable $fn the function to pass through each array item
+ *
+ * @return array
+ */
+ public static function arrayFilterRecursive(Array $source, $fn)
+ {
+ $result = [];
+ foreach ($source as $key => $value) {
+ if (is_array($value)) {
+ $result[$key] = static::arrayFilterRecursive($value, $fn);
+ continue;
+ }
+ if ($fn($key, $value)) {
+ $result[$key] = $value; // KEEP
+ continue;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Flatten an array
+ *
+ * @param array $array
+ * @return array
+ */
+ public static function arrayFlatten($array)
+ {
+ $flatten = array();
+ foreach ($array as $key => $inner) {
+ if (is_array($inner)) {
+ foreach ($inner as $inner_key => $value) {
+ $flatten[$inner_key] = $value;
+ }
+ } else {
+ $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
+ *
+ * @param string $string The path
+ *
+ * @return bool|string Either false or the language
+ *
+ */
+ public static function pathPrefixedByLangCode($string)
+ {
+ $languages_enabled = Grav::instance()['config']->get('system.languages.supported', []);
+ $parts = explode('/', trim($string, '/'));
+
+ if (count($parts) > 0 && in_array($parts[0], $languages_enabled)) {
+ return $parts[0];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the timestamp of a date
+ *
+ * @param string $date a String expressed in the system.pages.dateformat.default format, with fallback to a
+ * strtotime argument
+ * @param string $format a date format to use if possible
+ * @return int the timestamp
+ */
+ public static function date2timestamp($date, $format = null)
+ {
+ $config = Grav::instance()['config'];
+ $dateformat = $format ?: $config->get('system.pages.dateformat.default');
+
+ // try to use DateTime and default format
+ if ($dateformat) {
+ $datetime = \DateTime::createFromFormat($dateformat, $date);
+ } else {
+ $datetime = new \DateTime($date);
+ }
+
+ // fallback to strtotime() if DateTime approach failed
+ if ($datetime !== false) {
+ return $datetime->getTimestamp();
+ }
+
+ return strtotime($date);
+ }
+
+ /**
+ * @param array $array
+ * @param string $path
+ * @param null $default
+ * @return mixed
+ *
+ * @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);
+
+ return static::getDotNotation($array, $path, $default);
+ }
+
+ /**
+ * Checks if a value is positive
+ *
+ * @param string $value
+ *
+ * @return boolean
+ */
+ public static function isPositive($value)
+ {
+ return in_array($value, [true, 1, '1', 'yes', 'on', 'true'], true);
+ }
+
+ /**
+ * Generates a nonce string to be hashed. Called by self::getNonce()
+ * We removed the IP portion in this version because it causes too many inconsistencies
+ * with reverse proxy setups.
+ *
+ * @param string $action
+ * @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
+ *
+ * @return string the nonce string
+ */
+ private static function generateNonceString($action, $previousTick = false)
+ {
+ $grav = Grav::instance();
+
+ $username = isset($grav['user']) ? $grav['user']->username : '';
+ $token = session_id();
+ $i = self::nonceTick();
+
+ if ($previousTick) {
+ $i--;
+ }
+
+ return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . $grav['config']->get('security.salt'));
+ }
+
+ /**
+ * Get the time-dependent variable for nonce creation.
+ *
+ * Now a tick lasts a day. Once the day is passed, the nonce is not valid any more. Find a better way
+ * to ensure nonces issued near the end of the day do not expire in that small amount of time
+ *
+ * @return int the time part of the nonce. Changes once every 24 hours
+ */
+ private static function nonceTick()
+ {
+ $secondsInHalfADay = 60 * 60 * 12;
+
+ return (int)ceil(time() / $secondsInHalfADay);
+ }
+
+ /**
+ * Creates a hashed nonce tied to the passed action. Tied to the current user and time. The nonce for a given
+ * action is the same for 12 hours.
+ *
+ * @param string $action the action the nonce is tied to (e.g. save-user-admin or move-page-homepage)
+ * @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
+ *
+ * @return string the nonce
+ */
+ public static function getNonce($action, $previousTick = false)
+ {
+ // Don't regenerate this again if not needed
+ if (isset(static::$nonces[$action][$previousTick])) {
+ return static::$nonces[$action][$previousTick];
+ }
+ $nonce = md5(self::generateNonceString($action, $previousTick));
+ static::$nonces[$action][$previousTick] = $nonce;
+
+ return static::$nonces[$action][$previousTick];
+ }
+
+ /**
+ * Verify the passed nonce for the give action
+ *
+ * @param string|string[] $nonce the nonce to verify
+ * @param string $action the action to verify the nonce to
+ *
+ * @return boolean verified or not
+ */
+ public static function verifyNonce($nonce, $action)
+ {
+ //Safety check for multiple nonces
+ if (is_array($nonce)) {
+ $nonce = array_shift($nonce);
+ }
+
+ //Nonce generated 0-12 hours ago
+ if ($nonce === self::getNonce($action)) {
+ return true;
+ }
+
+ //Nonce generated 12-24 hours ago
+ $previousTick = true;
+
+ return $nonce === self::getNonce($action, $previousTick);
+ }
+
+ /**
+ * Simple helper method to get whether or not the admin plugin is active
+ *
+ * @return bool
+ */
+ public static function isAdminPlugin()
+ {
+ if (isset(Grav::instance()['admin'])) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get a portion of an array (passed by reference) with dot-notation key
+ *
+ * @param array $array
+ * @param string|int $key
+ * @param null $default
+ * @return mixed
+ */
+ public static function getDotNotation($array, $key, $default = null)
+ {
+ if (null === $key) {
+ return $array;
+ }
+
+ if (isset($array[$key])) {
+ return $array[$key];
+ }
+
+ foreach (explode('.', $key) as $segment) {
+ if (!is_array($array) || !array_key_exists($segment, $array)) {
+ return $default;
+ }
+
+ $array = $array[$segment];
+ }
+
+ return $array;
+ }
+
+ /**
+ * Set portion of array (passed by reference) for a dot-notation key
+ * and set the value
+ *
+ * @param array $array
+ * @param string|int $key
+ * @param mixed $value
+ * @param bool $merge
+ *
+ * @return mixed
+ */
+ public static function setDotNotation(&$array, $key, $value, $merge = false)
+ {
+ if (null === $key) {
+ return $array = $value;
+ }
+
+ $keys = explode('.', $key);
+
+ while (count($keys) > 1) {
+ $key = array_shift($keys);
+
+ if ( ! isset($array[$key]) || ! is_array($array[$key]))
+ {
+ $array[$key] = array();
+ }
+
+ $array =& $array[$key];
+ }
+
+ $key = array_shift($keys);
+
+ if (!$merge || !isset($array[$key])) {
+ $array[$key] = $value;
+ } else {
+ $array[$key] = array_merge($array[$key], $value);
+ }
+
+ return $array;
+ }
+
+ /**
+ * Utility method to determine if the current OS is Windows
+ *
+ * @return bool
+ */
+ public static function isWindows()
+ {
+ return strncasecmp(PHP_OS, 'WIN', 3) === 0;
+ }
+
+ /**
+ * Utility to determine if the server running PHP is Apache
+ *
+ * @return bool
+ */
+ public static function isApache() {
+ return isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false;
+ }
+
+ /**
+ * Sort a multidimensional array by another array of ordered keys
+ *
+ * @param array $array
+ * @param array $orderArray
+ * @return array
+ */
+ public static function sortArrayByArray(array $array, array $orderArray)
+ {
+ $ordered = array();
+ foreach ($orderArray as $key) {
+ if (array_key_exists($key, $array)) {
+ $ordered[$key] = $array[$key];
+ unset($array[$key]);
+ }
+ }
+ return $ordered + $array;
+ }
+
+ /**
+ * Sort an array by a key value in the array
+ *
+ * @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)
+ {
+ $output = [];
+
+ if (!is_array($array) || !$array) {
+ return $output;
+ }
+
+ foreach ($array as $key => $row) {
+ $output[$key] = $row[$array_key];
+ }
+
+ array_multisort($output, $direction, $sort_flags, $array);
+
+ return $array;
+ }
+
+ /**
+ * Get path based on a token
+ *
+ * @param string $path
+ * @param PageInterface|null $page
+ * @return string
+ * @throws \RuntimeException
+ */
+ public static function getPagePathFromToken($path, PageInterface $page = null)
+ {
+ $path_parts = pathinfo($path);
+ $grav = Grav::instance();
+
+ $basename = '';
+ if (isset($path_parts['extension'])) {
+ $basename = '/' . $path_parts['basename'];
+ $path = rtrim($path_parts['dirname'], ':');
+ }
+
+ $regex = '/(@self|self@)|((?:@page|page@):(?:.*))|((?:@theme|theme@):(?:.*))/';
+ preg_match($regex, $path, $matches);
+
+ if ($matches) {
+ if ($matches[1]) {
+ if (null === $page) {
+ throw new \RuntimeException('Page not available for this self@ reference');
+ }
+ } elseif ($matches[2]) {
+ // page@
+ $parts = explode(':', $path);
+ $route = $parts[1];
+ $page = $grav['page']->find($route);
+ } elseif ($matches[3]) {
+ // theme@
+ $parts = explode(':', $path);
+ $route = $parts[1];
+ $theme = str_replace(ROOT_DIR, '', $grav['locator']->findResource("theme://"));
+
+ return $theme . $route . $basename;
+ }
+ } else {
+ return $path . $basename;
+ }
+
+ if (!$page) {
+ throw new \RuntimeException('Page route not found: ' . $path);
+ }
+
+ $path = str_replace($matches[0], rtrim($page->relativePagePath(), '/'), $path);
+
+ return $path . $basename;
+ }
+
+ public static function getUploadLimit()
+ {
+ static $max_size = -1;
+
+ if ($max_size < 0) {
+ $post_max_size = static::parseSize(ini_get('post_max_size'));
+ if ($post_max_size > 0) {
+ $max_size = $post_max_size;
+ } else {
+ $max_size = 0;
+ }
+
+ $upload_max = static::parseSize(ini_get('upload_max_filesize'));
+ if ($upload_max > 0 && $upload_max < $max_size) {
+ $max_size = $upload_max;
+ }
+ }
+
+ 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 /= 1024 ** $pow;
+ // $bytes /= (1 << (10 * $pow));
+
+ return round($bytes, $precision) . ' ' . $units[$pow];
+ }
+
+ /**
+ * Parse a readable file size and return a value in bytes
+ *
+ * @param string|int $size
+ * @return int
+ */
+ public static function parseSize($size)
+ {
+ $unit = preg_replace('/[^bkmgtpezy]/i', '', $size);
+ $size = preg_replace('/[^0-9\.]/', '', $size);
+
+ if ($unit) {
+ $size = $size * pow(1024, stripos('bkmgtpezy', $unit[0]));
+ }
+
+ return (int) abs(round($size));
+ }
+
+ /**
+ * Multibyte-safe Parse URL function
+ *
+ * @param string $url
+ * @return array
+ * @throws \InvalidArgumentException
+ */
+ public static function multibyteParseUrl($url)
+ {
+ $enc_url = preg_replace_callback(
+ '%[^:/@?&=#]+%usD',
+ function ($matches) {
+ return urlencode($matches[0]);
+ },
+ $url
+ );
+
+ $parts = parse_url($enc_url);
+
+ if($parts === false) {
+ throw new \InvalidArgumentException('Malformed URL: ' . $url);
+ }
+
+ foreach($parts as $name => $value) {
+ $parts[$name] = urldecode($value);
+ }
+
+ return $parts;
+ }
+
+ /**
+ * Process a string as markdown
+ *
+ * @param string $string
+ *
+ * @param bool $block Block or Line processing
+ * @param null $page
+ * @return string
+ * @throws \Exception
+ */
+ public static function processMarkdown($string, $block = true, $page = null)
+ {
+ $grav = Grav::instance();
+ $page = $page ?? $grav['page'] ?? null;
+ $defaults = [
+ 'markdown' => $grav['config']->get('system.pages.markdown', []),
+ 'images' => $grav['config']->get('system.images', [])
+ ];
+ $extra = $defaults['markdown']['extra'] ?? false;
+
+ $excerpts = new Excerpts($page, $defaults);
+
+ // Initialize the preferred variant of Parsedown
+ if ($extra) {
+ $parsedown = new ParsedownExtra($excerpts);
+ } else {
+ $parsedown = new Parsedown($excerpts);
+ }
+
+ 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
+ */
+ public static function getSubnet($ip, $prefix = 64)
+ {
+ if (!filter_var($ip, FILTER_VALIDATE_IP)) {
+ return $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;
+ }
+
+ /**
+ * Wrapper to ensure html, htm in the front of the supported page types
+ *
+ * @param array|null $defaults
+ * @return array|mixed
+ */
+ public static function getSupportPageTypes(array $defaults = null)
+ {
+ $types = Grav::instance()['config']->get('system.pages.types', $defaults);
+
+ // remove html/htm
+ $types = static::arrayRemoveValue($types, ['html', 'htm']);
+
+ // put them back at the front
+ $types = array_merge(['html', 'htm'], $types);
+
+ return $types;
+ }
+}
diff --git a/system/src/Grav/Common/Yaml.php b/system/src/Grav/Common/Yaml.php
new file mode 100644
index 0000000..ad6d81b
--- /dev/null
+++ b/system/src/Grav/Common/Yaml.php
@@ -0,0 +1,48 @@
+decode($data);
+ }
+
+ public static function dump($data, $inline = null, $indent = null)
+ {
+ if (null === static::$yaml) {
+ static::init();
+ }
+
+ return static::$yaml->encode($data, $inline, $indent);
+ }
+
+ private static function init()
+ {
+ $config = [
+ 'inline' => 5,
+ 'indent' => 2,
+ 'native' => true,
+ 'compat' => true
+ ];
+
+ static::$yaml = new YamlFormatter($config);
+ }
+}
diff --git a/system/src/Grav/Console/Cli/BackupCommand.php b/system/src/Grav/Console/Cli/BackupCommand.php
new file mode 100644
index 0000000..54a26cf
--- /dev/null
+++ b/system/src/Grav/Console/Cli/BackupCommand.php
@@ -0,0 +1,120 @@
+setName('backup')
+ ->addArgument(
+ 'id',
+ InputArgument::OPTIONAL,
+ '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.');
+
+ $this->source = getcwd();
+ }
+
+ protected function serve()
+ {
+ $this->progress = new ProgressBar($this->output);
+ $this->progress->setFormat('Archiving %current% files [%bar% ] %percent:3s%% %elapsed:6s% %message% ');
+ $this->progress->setBarWidth(100);
+
+ Grav::instance()['config']->init();
+
+ $io = new SymfonyStyle($this->input, $this->output);
+ $io->title('Grav Backup');
+
+ /** @var Backups $backups */
+ $backups = Grav::instance()['backups'];
+ $backups_list = $backups->getBackupProfiles();
+ $backups_names = $backups->getBackupNames();
+
+ $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 array $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->progress->setMessage($args['message']);
+ $this->progress->display();
+ break;
+ case 'progress':
+ if (isset($args['complete']) && $args['complete']) {
+ $this->progress->finish();
+ } else {
+ $this->progress->advance();
+ }
+ break;
+ }
+ }
+
+}
+
diff --git a/system/src/Grav/Console/Cli/CleanCommand.php b/system/src/Grav/Console/Cli/CleanCommand.php
new file mode 100644
index 0000000..a905973
--- /dev/null
+++ b/system/src/Grav/Console/Cli/CleanCommand.php
@@ -0,0 +1,323 @@
+setName('clean')
+ ->setDescription('Handles cleaning chores for Grav distribution')
+ ->setHelp('The clean clean extraneous folders and data');
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $this->setupConsole($input, $output);
+
+ $this->cleanPaths();
+ }
+
+ private function cleanPaths()
+ {
+ $this->output->writeln('');
+ $this->output->writeln('DELETING ');
+ $anything = false;
+ foreach ($this->paths_to_remove as $path) {
+ $path = ROOT_DIR . $path;
+ if (is_dir($path) && @Folder::delete($path)) {
+ $anything = true;
+ $this->output->writeln('dir: ' . $path);
+ } elseif (is_file($path) && @unlink($path)) {
+ $anything = true;
+ $this->output->writeln('file: ' . $path);
+ }
+ }
+ if (!$anything) {
+ $this->output->writeln('');
+ $this->output->writeln('Nothing to clean... ');
+ }
+ }
+
+ /**
+ * Set colors style definition for the formatter.
+ *
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ */
+ public function setupConsole(InputInterface $input, OutputInterface $output)
+ {
+ $this->input = $input;
+ $this->output = $output;
+
+ $this->output->getFormatter()->setStyle('normal', new OutputFormatterStyle('white'));
+ $this->output->getFormatter()->setStyle('yellow', new OutputFormatterStyle('yellow', null, ['bold']));
+ $this->output->getFormatter()->setStyle('red', new OutputFormatterStyle('red', null, ['bold']));
+ $this->output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan', null, ['bold']));
+ $this->output->getFormatter()->setStyle('green', new OutputFormatterStyle('green', null, ['bold']));
+ $this->output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta', null, ['bold']));
+ $this->output->getFormatter()->setStyle('white', new OutputFormatterStyle('white', null, ['bold']));
+ }
+
+}
diff --git a/system/src/Grav/Console/Cli/ClearCacheCommand.php b/system/src/Grav/Console/Cli/ClearCacheCommand.php
new file mode 100644
index 0000000..8ef6f4f
--- /dev/null
+++ b/system/src/Grav/Console/Cli/ClearCacheCommand.php
@@ -0,0 +1,80 @@
+setName('cache')
+ ->setAliases(['clearcache', 'cache-clear'])
+ ->setDescription('Clears Grav cache')
+ ->addOption('invalidate', null, InputOption::VALUE_NONE, 'Invalidate cache, but do not remove any files')
+ ->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 cache command allows you to interact with Grav cache');
+ }
+
+ protected function serve()
+ {
+ $this->cleanPaths();
+ }
+
+ /**
+ * loops over the array of paths and deletes the files/folders
+ */
+ private function cleanPaths()
+ {
+ $this->output->writeln('');
+
+
+ if ($this->input->getOption('purge')) {
+ $this->output->writeln('Purging old cache ');
+ $this->output->writeln('');
+
+ $msg = Cache::purgeJob();
+ $this->output->writeln($msg);
+ } else {
+ $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';
+ } elseif ($this->input->getOption('invalidate')) {
+ $remove = 'invalidate';
+ } 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
new file mode 100644
index 0000000..ffb4e99
--- /dev/null
+++ b/system/src/Grav/Console/Cli/ComposerCommand.php
@@ -0,0 +1,50 @@
+setName("composer")
+ ->addOption(
+ 'install',
+ 'i',
+ InputOption::VALUE_NONE,
+ 'install the dependencies'
+ )
+ ->addOption(
+ 'update',
+ 'u',
+ InputOption::VALUE_NONE,
+ 'update the dependencies'
+ )
+ ->setDescription("Updates the composer vendor dependencies needed by Grav.")
+ ->setHelp('The composer command updates the composer vendor dependencies needed by Grav');
+ }
+
+ protected function serve()
+ {
+ $action = $this->input->getOption('install') ? 'install' : ($this->input->getOption('update') ? 'update' : 'install');
+
+ if ($this->input->getOption('install')) {
+ $action = 'install';
+ }
+
+ // Updates composer first
+ $this->output->writeln("\nInstalling vendor dependencies");
+ $this->output->writeln($this->composerUpdate(GRAV_ROOT, $action));
+ }
+
+}
diff --git a/system/src/Grav/Console/Cli/InstallCommand.php b/system/src/Grav/Console/Cli/InstallCommand.php
new file mode 100644
index 0000000..05f281e
--- /dev/null
+++ b/system/src/Grav/Console/Cli/InstallCommand.php
@@ -0,0 +1,176 @@
+setName('install')
+ ->addOption(
+ 'symlink',
+ 's',
+ InputOption::VALUE_NONE,
+ 'Symlink the required bits'
+ )
+ ->addArgument(
+ 'destination',
+ InputArgument::OPTIONAL,
+ 'Where to install the required bits (default to current project)'
+ )
+ ->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');
+ }
+
+ protected function serve()
+ {
+ $dependencies_file = '.dependencies';
+ $this->destination = $this->input->getArgument('destination') ?: ROOT_DIR;
+
+ // fix trailing slash
+ $this->destination = rtrim($this->destination, DS) . DS;
+ $this->user_path = $this->destination . USER_PATH;
+ if ($local_config_file = $this->loadLocalConfig()) {
+ $this->output->writeln('Read local config from ' . $local_config_file . ' ');
+ }
+
+ // Look for dependencies file in ROOT and USER dir
+ if (file_exists($this->user_path . $dependencies_file)) {
+ $file = YamlFile::instance($this->user_path . $dependencies_file);
+ } elseif (file_exists($this->destination . $dependencies_file)) {
+ $file = YamlFile::instance($this->destination . $dependencies_file);
+ } else {
+ $this->output->writeln('ERROR Missing .dependencies file in user/ folder');
+ if ($this->input->getArgument('destination')) {
+ $this->output->writeln('HINT Are you trying to install a plugin or a theme? Make sure you use bin/gpm install , not bin/grav install . This command is only used to install Grav skeletons.');
+ } else {
+ $this->output->writeln('HINT Are you trying to install Grav? Grav is already installed. You need to run this command only if you download a skeleton from GitHub directly.');
+ }
+
+ return;
+ }
+
+ $this->config = $file->content();
+ $file->free();
+
+ // If yaml config, process
+ if ($this->config) {
+ if (!$this->input->getOption('symlink')) {
+ // Updates composer first
+ $this->output->writeln("\nInstalling vendor dependencies");
+ $this->output->writeln($this->composerUpdate(GRAV_ROOT, 'install'));
+
+ $this->gitclone();
+ } else {
+ $this->symlink();
+ }
+ } else {
+ $this->output->writeln('ERROR invalid YAML in ' . $dependencies_file);
+ }
+
+
+ }
+
+ /**
+ * Clones from Git
+ */
+ private function gitclone()
+ {
+ $this->output->writeln('');
+ $this->output->writeln('Cloning Bits ');
+ $this->output->writeln('============');
+ $this->output->writeln('');
+
+ foreach ($this->config['git'] as $repo => $data) {
+ $this->destination = rtrim($this->destination, DS);
+ $path = $this->destination . DS . $data['path'];
+ if (!file_exists($path)) {
+ exec('cd "' . $this->destination . '" && git clone -b ' . $data['branch'] . ' --depth 1 ' . $data['url'] . ' ' . $data['path'], $output, $return);
+
+ if (!$return) {
+ $this->output->writeln('SUCCESS cloned ' . $data['url'] . ' -> ' . $path . ' ');
+ } else {
+ $this->output->writeln('ERROR cloning ' . $data['url']);
+
+ }
+
+ $this->output->writeln('');
+ } else {
+ $this->output->writeln('' . $path . ' already exists, skipping... ');
+ $this->output->writeln('');
+ }
+
+ }
+ }
+
+ /**
+ * Symlinks
+ */
+ private function symlink()
+ {
+ $this->output->writeln('');
+ $this->output->writeln('Symlinking Bits ');
+ $this->output->writeln('===============');
+ $this->output->writeln('');
+
+ if (!$this->local_config) {
+ $this->output->writeln('No local configuration available, aborting... ');
+ $this->output->writeln('');
+ return;
+ }
+
+ exec('cd ' . $this->destination);
+ foreach ($this->config['links'] as $repo => $data) {
+ $repos = (array) $this->local_config[$data['scm'] . '_repos'];
+ $from = false;
+ $to = $this->destination . $data['path'];
+
+ foreach ($repos as $repo) {
+ $path = $repo . $data['src'];
+ if (file_exists($path)) {
+ $from = $path;
+ continue;
+ }
+ }
+
+ if (!$from) {
+ $this->output->writeln('source for ' . $data['src'] . ' does not exists, skipping... ');
+ $this->output->writeln('');
+ } else {
+ if (!file_exists($to)) {
+ symlink($from, $to);
+ $this->output->writeln('SUCCESS symlinked ' . $data['src'] . ' -> ' . $data['path'] . ' ');
+ $this->output->writeln('');
+ } else {
+ $this->output->writeln('destination: ' . $to . ' already exists, skipping... ');
+ $this->output->writeln('');
+ }
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000..90583f0
--- /dev/null
+++ b/system/src/Grav/Console/Cli/NewProjectCommand.php
@@ -0,0 +1,59 @@
+setName('new-project')
+ ->setAliases(['newproject'])
+ ->addArgument(
+ 'destination',
+ InputArgument::REQUIRED,
+ 'The destination directory of your new Grav project'
+ )
+ ->addOption(
+ 'symlink',
+ 's',
+ InputOption::VALUE_NONE,
+ 'Symlink the required bits'
+ )
+ ->setDescription('Creates a new Grav project with all the dependencies installed')
+ ->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.");
+ }
+
+ protected function serve()
+ {
+ $sandboxCommand = $this->getApplication()->find('sandbox');
+ $installCommand = $this->getApplication()->find('install');
+
+ $sandboxArguments = new ArrayInput([
+ 'command' => 'sandbox',
+ 'destination' => $this->input->getArgument('destination'),
+ '-s' => $this->input->getOption('symlink')
+ ]);
+
+ $installArguments = new ArrayInput([
+ 'command' => 'install',
+ 'destination' => $this->input->getArgument('destination'),
+ '-s' => $this->input->getOption('symlink')
+ ]);
+
+ $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
new file mode 100644
index 0000000..f961412
--- /dev/null
+++ b/system/src/Grav/Console/Cli/SandboxCommand.php
@@ -0,0 +1,270 @@
+ '/.gitignore',
+ '/.editorconfig' => '/.editorconfig',
+ '/CHANGELOG.md' => '/CHANGELOG.md',
+ '/LICENSE.txt' => '/LICENSE.txt',
+ '/README.md' => '/README.md',
+ '/CONTRIBUTING.md' => '/CONTRIBUTING.md',
+ '/index.php' => '/index.php',
+ '/composer.json' => '/composer.json',
+ '/bin' => '/bin',
+ '/system' => '/system',
+ '/vendor' => '/vendor',
+ '/webserver-configs' => '/webserver-configs',
+ ];
+
+ /** @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
+ ->setName('sandbox')
+ ->setDescription('Setup of a base Grav system in your webroot, good for development, playing around or starting fresh')
+ ->addArgument(
+ 'destination',
+ InputArgument::REQUIRED,
+ 'The destination directory to symlink into'
+ )
+ ->addOption(
+ 'symlink',
+ 's',
+ InputOption::VALUE_NONE,
+ 'Symlink the base grav system'
+ )
+ ->setHelp("The sandbox command help create a development environment that can optionally use symbolic links to link the core of grav to the git cloned repository.\nGood for development, playing around or starting fresh");
+ $this->source = getcwd();
+ }
+
+ protected function serve()
+ {
+ $this->destination = $this->input->getArgument('destination');
+
+ // Symlink the Core Stuff
+ if ($this->input->getOption('symlink')) {
+ // Create Some core stuff if it doesn't exist
+ $this->createDirectories();
+
+ // Loop through the symlink mappings and create the symlinks
+ $this->symlink();
+
+ // Copy the Core STuff
+ } else {
+ // Create Some core stuff if it doesn't exist
+ $this->createDirectories();
+
+ // Loop through the symlink mappings and copy what otherwise would be symlinks
+ $this->copy();
+ }
+
+ $this->pages();
+ $this->initFiles();
+ $this->perms();
+ }
+
+ private function createDirectories()
+ {
+ $this->output->writeln('');
+ $this->output->writeln('Creating Directories ');
+ $dirs_created = false;
+
+ if (!file_exists($this->destination)) {
+ Folder::create($this->destination);
+ }
+
+ foreach ($this->directories as $dir) {
+ if (!file_exists($this->destination . $dir)) {
+ $dirs_created = true;
+ $this->output->writeln(' ' . $dir . ' ');
+ Folder::create($this->destination . $dir);
+ }
+ }
+
+ if (!$dirs_created) {
+ $this->output->writeln(' Directories already exist ');
+ }
+ }
+
+ private function copy()
+ {
+ $this->output->writeln('');
+ $this->output->writeln('Copying Files ');
+
+
+ foreach ($this->mappings as $source => $target) {
+ if ((string)(int)$source === (string)$source) {
+ $source = $target;
+ }
+
+ $from = $this->source . $source;
+ $to = $this->destination . $target;
+
+ $this->output->writeln(' ' . $source . ' -> ' . $to);
+ @Folder::rcopy($from, $to);
+ }
+ }
+
+ private function symlink()
+ {
+ $this->output->writeln('');
+ $this->output->writeln('Resetting Symbolic Links ');
+
+
+ foreach ($this->mappings as $source => $target) {
+ if ((string)(int)$source === (string)$source) {
+ $source = $target;
+ }
+
+ $from = $this->source . $source;
+ $to = $this->destination . $target;
+
+ $this->output->writeln(' ' . $source . ' -> ' . $to);
+
+ if (is_dir($to)) {
+ @Folder::delete($to);
+ } else {
+ @unlink($to);
+ }
+ symlink($from, $to);
+ }
+ }
+
+ private function initFiles()
+ {
+ $this->check();
+
+ $this->output->writeln('');
+ $this->output->writeln('File Initializing ');
+ $files_init = false;
+
+ // Copy files if they do not exist
+ foreach ($this->files as $source => $target) {
+ if ((string)(int)$source === (string)$source) {
+ $source = $target;
+ }
+
+ $from = $this->source . $source;
+ $to = $this->destination . $target;
+
+ if (!file_exists($to)) {
+ $files_init = true;
+ copy($from, $to);
+ $this->output->writeln(' ' . $target . ' -> Created');
+ }
+ }
+
+ if (!$files_init) {
+ $this->output->writeln(' Files already exist ');
+ }
+ }
+
+ private function pages()
+ {
+ $this->output->writeln('');
+ $this->output->writeln('Pages Initializing ');
+
+ // get pages files and initialize if no pages exist
+ $pages_dir = $this->destination . '/user/pages';
+ $pages_files = array_diff(scandir($pages_dir), ['..', '.']);
+
+ if (\count($pages_files) === 0) {
+ $destination = $this->source . '/user/pages';
+ Folder::rcopy($destination, $pages_dir);
+ $this->output->writeln(' ' . $destination . ' -> Created');
+
+ }
+ }
+
+ private function perms()
+ {
+ $this->output->writeln('');
+ $this->output->writeln('Permissions Initializing ');
+
+ $dir_perms = 0755;
+
+ $binaries = glob($this->destination . DS . 'bin' . DS . '*');
+
+ foreach ($binaries as $bin) {
+ chmod($bin, $dir_perms);
+ $this->output->writeln(' bin/' . basename($bin) . ' permissions reset to ' . decoct($dir_perms));
+ }
+
+ $this->output->writeln("");
+ }
+
+ private function check()
+ {
+ $success = true;
+
+ if (!file_exists($this->destination)) {
+ $this->output->writeln(' file: $this->destination does not exist!');
+ $success = false;
+ }
+
+ foreach ($this->directories as $dir) {
+ if (!file_exists($this->destination . $dir)) {
+ $this->output->writeln(' directory: ' . $dir . ' does not exist!');
+ $success = false;
+ }
+ }
+
+ foreach ($this->mappings as $target => $link) {
+ if (!file_exists($this->destination . $target)) {
+ $this->output->writeln(' mappings: ' . $target . ' does not exist!');
+ $success = false;
+ }
+ }
+
+ if (!$success) {
+ $this->output->writeln('');
+ $this->output->writeln('install should be run with --symlink|--s to symlink first ');
+ exit;
+ }
+ }
+}
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
new file mode 100644
index 0000000..7f73086
--- /dev/null
+++ b/system/src/Grav/Console/Cli/SecurityCommand.php
@@ -0,0 +1,106 @@
+setName('security')
+ ->setDescription('Capable of running various Security checks')
+ ->setHelp('The security runs various security checks on your Grav site');
+
+ $this->source = getcwd();
+ }
+
+ protected function serve()
+ {
+ /** @var Grav $grav */
+ $grav = Grav::instance();
+ $grav->setup();
+
+ $grav['uri']->init();
+ $grav['config']->init();
+ $grav['debugger']->enabled(false);
+ $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->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'], false, [$this, 'outputProgress']);
+
+ $io->newline(2);
+
+ if (!empty($output)) {
+
+ $counter = 1;
+ foreach ($output as $route => $results) {
+
+ $results_parts = array_map(function($value, $key) {
+ return $key.': \''.$value . '\'';
+ }, array_values($results), array_keys($results));
+
+ $io->writeln($counter++ .' - ' . $route . ' → ' . implode(', ', $results_parts) . ' ');
+ }
+
+ $io->error('Security Scan complete: ' . \count($output) . ' potential XSS issues found...');
+
+ } else {
+ $io->success('Security Scan complete: No issues found...');
+ }
+
+ $io->newline(1);
+
+ }
+
+ /**
+ * @param array $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);
+ break;
+ case 'progress':
+ if (isset($args['complete']) && $args['complete']) {
+ $this->progress->finish();
+ } else {
+ $this->progress->advance();
+ }
+ 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..894f1c0
--- /dev/null
+++ b/system/src/Grav/Console/Cli/YamlLinterCommand.php
@@ -0,0 +1,82 @@
+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);
+ }
+
+ $io->section('Page Blueprints');
+ $errors = YamlLinter::lintBlueprints();
+
+ if (empty($errors)) {
+ $io->success('No YAML Linting issues with blueprints');
+ } 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
new file mode 100644
index 0000000..c0fd9ad
--- /dev/null
+++ b/system/src/Grav/Console/ConsoleCommand.php
@@ -0,0 +1,186 @@
+setupConsole($input, $output);
+ $this->serve();
+ }
+
+ /**
+ * Override with your implementation.
+ */
+ protected function serve()
+ {
+ }
+
+ /**
+ * Initialize Grav.
+ *
+ * - Load configuration
+ * - Disable debugger
+ * - Set timezone, locale
+ * - Load plugins
+ * - Set Users type to be used in the site
+ *
+ * Safe to be called multiple times.
+ *
+ * @return $this
+ */
+ final protected function initializeGrav()
+ {
+ InitializeProcessor::initializeCli(Grav::instance());
+
+ return $this;
+ }
+
+ /**
+ * Set language to be used in CLI.
+ *
+ * @param string|null $code
+ */
+ final protected function setLanguage(string $code = null)
+ {
+ $this->initializeGrav();
+
+ $grav = Grav::instance();
+ /** @var Language $language */
+ $language = $grav['language'];
+ if ($language->enabled()) {
+ if ($code && $language->validate($code)) {
+ $language->setActive($code);
+ } else {
+ $language->setActive($language->getDefault());
+ }
+ }
+ }
+
+ /**
+ * Properly initialize plugins.
+ *
+ * - call $this->initializeGrav()
+ * - call onPluginsInitialized event
+ *
+ * Safe to be called multiple times.
+ *
+ * @return $this
+ */
+ final protected function initializePlugins()
+ {
+ if (!$this->plugins_initialized) {
+ $this->plugins_initialized = true;
+
+ $this->initializeGrav();
+
+ // Initialize plugins.
+ $grav = Grav::instance();
+ $grav->fireEvent('onPluginsInitialized');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Properly initialize themes.
+ *
+ * - call $this->initializePlugins()
+ * - initialize theme (call onThemeInitialized event)
+ *
+ * Safe to be called multiple times.
+ *
+ * @return $this
+ */
+ final protected function initializeThemes()
+ {
+ if (!$this->themes_initialized) {
+ $this->themes_initialized = true;
+
+ $this->initializePlugins();
+
+ // Initialize themes.
+ $grav = Grav::instance();
+ $grav['themes']->init();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Properly initialize pages.
+ *
+ * - call $this->initializeThemes()
+ * - initialize assets (call onAssetsInitialized event)
+ * - initialize twig (calls the twig events)
+ * - initialize pages (calls onPagesInitialized event)
+ *
+ * Safe to be called multiple times.
+ *
+ * @return $this
+ */
+ final protected function initializePages()
+ {
+ if (!$this->pages_initialized) {
+ $this->pages_initialized = true;
+
+ $this->initializeThemes();
+
+ $grav = Grav::instance();
+
+ // Initialize assets.
+ $grav['assets']->init();
+ $grav->fireEvent('onAssetsInitialized');
+
+ // Initialize twig.
+ $grav['twig']->init();
+
+ // Initialize pages.
+ $pages = $grav['pages'];
+ $pages->init();
+ $grav->fireEvent('onPagesInitialized', new Event(['pages' => $pages]));
+ }
+
+ return $this;
+ }
+
+ protected function displayGPMRelease()
+ {
+ $this->output->writeln('');
+ $this->output->writeln('GPM Releases Configuration: ' . ucfirst(Grav::instance()['config']->get('system.gpm.releases')) . ' ');
+ $this->output->writeln('');
+ }
+
+}
diff --git a/system/src/Grav/Console/ConsoleTrait.php b/system/src/Grav/Console/ConsoleTrait.php
new file mode 100644
index 0000000..1901095
--- /dev/null
+++ b/system/src/Grav/Console/ConsoleTrait.php
@@ -0,0 +1,139 @@
+set('system.cache.cli_compatibility', true);
+ Grav::instance()['cache'];
+
+ $this->argv = $_SERVER['argv'][0];
+ $this->input = $input;
+ $this->output = $output;
+
+ $this->output->getFormatter()->setStyle('normal', new OutputFormatterStyle('white'));
+ $this->output->getFormatter()->setStyle('yellow', new OutputFormatterStyle('yellow', null, array('bold')));
+ $this->output->getFormatter()->setStyle('red', new OutputFormatterStyle('red', null, array('bold')));
+ $this->output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan', null, array('bold')));
+ $this->output->getFormatter()->setStyle('green', new OutputFormatterStyle('green', null, array('bold')));
+ $this->output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta', null, array('bold')));
+ $this->output->getFormatter()->setStyle('white', new OutputFormatterStyle('white', null, array('bold')));
+ }
+
+ /**
+ * @param string $path
+ */
+ public function isGravInstance($path)
+ {
+ if (!file_exists($path)) {
+ $this->output->writeln('');
+ $this->output->writeln("ERROR : Destination doesn't exist:");
+ $this->output->writeln(" $path ");
+ $this->output->writeln('');
+ exit;
+ }
+
+ if (!is_dir($path)) {
+ $this->output->writeln('');
+ $this->output->writeln("ERROR : Destination chosen to install is not a directory:");
+ $this->output->writeln(" $path ");
+ $this->output->writeln('');
+ exit;
+ }
+
+ if (!file_exists($path . DS . 'index.php') || !file_exists($path . DS . '.dependencies') || !file_exists($path . DS . 'system' . DS . 'config' . DS . 'system.yaml')) {
+ $this->output->writeln('');
+ $this->output->writeln('ERROR : Destination chosen to install does not appear to be a Grav instance:');
+ $this->output->writeln(" $path ");
+ $this->output->writeln('');
+ exit;
+ }
+ }
+
+ public function composerUpdate($path, $action = 'install')
+ {
+ $composer = Composer::getComposerExecutor();
+
+ return system($composer . ' --working-dir="'.$path.'" --no-interaction --no-dev --prefer-dist -o '. $action);
+ }
+
+ /**
+ * @param array $all
+ *
+ * @return int
+ * @throws \Exception
+ */
+ public function clearCache($all = [])
+ {
+ if ($all) {
+ $all = ['--all' => true];
+ }
+
+ $command = new ClearCacheCommand();
+ $input = new ArrayInput($all);
+ return $command->run($input, $this->output);
+ }
+
+ public function invalidateCache()
+ {
+ Cache::invalidateCache();
+ }
+
+ /**
+ * Load the local config file
+ *
+ * @return mixed string the local config file name. false if local config does not exist
+ */
+ public function loadLocalConfig()
+ {
+ $home_folder = getenv('HOME') ?: getenv('HOMEDRIVE') . getenv('HOMEPATH');
+ $local_config_file = $home_folder . '/.grav/config';
+
+ if (file_exists($local_config_file)) {
+ $file = YamlFile::instance($local_config_file);
+ $this->local_config = $file->content();
+ $file->free();
+ return $local_config_file;
+ }
+
+ return false;
+ }
+}
diff --git a/system/src/Grav/Console/Gpm/DirectInstallCommand.php b/system/src/Grav/Console/Gpm/DirectInstallCommand.php
new file mode 100644
index 0000000..bd27651
--- /dev/null
+++ b/system/src/Grav/Console/Gpm/DirectInstallCommand.php
@@ -0,0 +1,309 @@
+setName('direct-install')
+ ->setAliases(['directinstall'])
+ ->addArgument(
+ 'package-file',
+ InputArgument::REQUIRED,
+ 'Installable package local or remote . Can install specific version'
+ )
+ ->addOption(
+ 'all-yes',
+ 'y',
+ InputOption::VALUE_NONE,
+ 'Assumes yes (or best approach) instead of prompting'
+ )
+ ->addOption(
+ 'destination',
+ 'd',
+ InputOption::VALUE_OPTIONAL,
+ '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')
+ ->setHelp('The direct-install command installs Grav, plugin, or theme directly from a file or a URL');
+ }
+
+ /**
+ * @return bool
+ */
+ protected function serve()
+ {
+ // Making sure the destination is usable
+ $this->destination = realpath($this->input->getOption('destination'));
+
+ if (
+ !Installer::isGravInstance($this->destination) ||
+ !Installer::isValidDestination($this->destination, [Installer::EXISTS, Installer::IS_LINK])
+ ) {
+ $this->output->writeln('ERROR : ' . Installer::lastErrorMsg());
+ exit;
+ }
+
+
+ $this->all_yes = $this->input->getOption('all-yes');
+
+ $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);
+
+ $answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
+
+ if (!$answer) {
+ $this->output->writeln('exiting...');
+ $this->output->writeln('');
+ exit;
+ }
+
+ $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} ");
+
+
+ if (Response::isRemote($package_file)) {
+ $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('');
+ exit;
+ }
+
+ if ($zip) {
+ $this->output->write("\x0D");
+ $this->output->write(' |- Downloading package... 100%');
+ $this->output->writeln('');
+ }
+ } else {
+ $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->writeln('');
+ }
+ }
+
+ if (file_exists($zip)) {
+ $tmp_source = $tmp_dir . '/Grav-' . uniqid();
+
+ $this->output->write(' |- Extracting package... ');
+ $extracted = Installer::unZip($zip, $tmp_source);
+
+ if (!$extracted) {
+ $this->output->write("\x0D");
+ $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 ');
+
+
+ $type = GPM::getPackageType($extracted);
+
+ if (!$type) {
+ $this->output->writeln(" '- ERROR: Not a valid Grav package ");
+ $this->output->writeln('');
+ Folder::delete($tmp_source);
+ Folder::delete($tmp_zip);
+ exit;
+ }
+
+ $blueprint = GPM::getBlueprints($extracted);
+ if ($blueprint) {
+ if (isset($blueprint['dependencies'])) {
+ $dependencies = [];
+ foreach ($blueprint['dependencies'] as $dependency) {
+ if (is_array($dependency)){
+ if (isset($dependency['name'])) {
+ $dependencies[] = $dependency['name'];
+ }
+ if (isset($dependency['github'])) {
+ $dependencies[] = $dependency['github'];
+ }
+ } else {
+ $dependencies[] = $dependency;
+ }
+ }
+ $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('');
+ Folder::delete($tmp_source);
+ Folder::delete($tmp_zip);
+ exit;
+ }
+ }
+ }
+
+ if ($type === 'grav') {
+
+ $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('');
+ Folder::delete($tmp_source);
+ Folder::delete($tmp_zip);
+ exit;
+ }
+
+ $this->output->write("\x0D");
+ $this->output->writeln(' |- Checking destination... ok ');
+
+ $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('');
+ Folder::delete($tmp_source);
+ Folder::delete($tmp_zip);
+ exit;
+ }
+
+ $install_path = GPM::getInstallPath($type, $name);
+ $is_update = file_exists($install_path);
+
+ $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(" '- ERROR: symlink found... " . GRAV_ROOT . DS . $install_path . ' ');
+ $this->output->writeln('');
+ Folder::delete($tmp_source);
+ Folder::delete($tmp_zip);
+ exit;
+
+ }
+
+ $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')),
+ 'is_update' => $is_update
+ ],
+ $extracted
+ );
+ }
+
+ Folder::delete($tmp_source);
+
+ $this->output->write("\x0D");
+
+ if(Installer::lastErrorCode()) {
+ $this->output->writeln(" '- " . Installer::lastErrorMsg() . ' ');
+ $this->output->writeln('');
+ } else {
+ $this->output->writeln(' |- Installing package... ok ');
+ $this->output->writeln(" '- Success! ");
+ $this->output->writeln('');
+ }
+
+ } else {
+ $this->output->writeln(" '- ERROR: ZIP package could not be found ");
+ }
+
+ Folder::delete($tmp_zip);
+
+ // clear cache after successful upgrade
+ $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
new file mode 100644
index 0000000..06d662b
--- /dev/null
+++ b/system/src/Grav/Console/Gpm/IndexCommand.php
@@ -0,0 +1,265 @@
+setName('index')
+ ->addOption(
+ 'force',
+ 'f',
+ InputOption::VALUE_NONE,
+ 'Force re-fetching the data from remote'
+ )
+ ->addOption(
+ 'filter',
+ 'F',
+ InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
+ 'Allows to limit the results based on one or multiple filters input. This can be either portion of a name/slug or a regex'
+ )
+ ->addOption(
+ 'themes-only',
+ 'T',
+ InputOption::VALUE_NONE,
+ 'Filters the results to only Themes'
+ )
+ ->addOption(
+ 'plugins-only',
+ 'P',
+ InputOption::VALUE_NONE,
+ 'Filters the results to only Plugins'
+ )
+ ->addOption(
+ 'updates-only',
+ 'U',
+ InputOption::VALUE_NONE,
+ 'Filters the results to Updatable Themes and Plugins only'
+ )
+ ->addOption(
+ 'installed-only',
+ 'I',
+ InputOption::VALUE_NONE,
+ 'Filters the results to only the Themes and Plugins you have installed'
+ )
+ ->addOption(
+ 'sort',
+ 's',
+ InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
+ 'Allows to sort (ASC) the results based on one or multiple keys. SORT can be either "name", "slug", "author", "date"',
+ ['date']
+ )
+ ->addOption(
+ 'desc',
+ 'D',
+ InputOption::VALUE_NONE,
+ 'Reverses the order of the output.'
+ )
+ ->setDescription('Lists the plugins and themes available for installation')
+ ->setHelp('The index command lists the plugins and themes available for installation')
+ ;
+ }
+
+ protected function serve()
+ {
+ $this->options = $this->input->getOptions();
+ $this->gpm = new GPM($this->options['force']);
+ $this->displayGPMRelease();
+ $this->data = $this->gpm->getRepository();
+
+ $data = $this->filter($this->data);
+
+ $climate = new CLImate;
+ $climate->extend('Grav\Console\TerminalObjects\Table');
+
+ if (!$data) {
+ $this->output->writeln('No data was found in the GPM repository stored locally.');
+ $this->output->writeln('Please try clearing cache and running the bin/gpm index -f command again');
+ $this->output->writeln('If this doesn\'t work try tweaking your GPM system settings.');
+ $this->output->writeln('');
+ $this->output->writeln('For more help go to:');
+ $this->output->writeln(' -> https://learn.getgrav.org/troubleshooting/common-problems#cannot-connect-to-the-gpm ');
+
+ die;
+ }
+
+ foreach ($data as $type => $packages) {
+ $this->output->writeln('' . strtoupper($type) . ' [ ' . \count($packages) . ' ]');
+ $packages = $this->sort($packages);
+
+ if (!empty($packages)) {
+
+ $table = [];
+ $index = 0;
+
+ foreach ($packages as $slug => $package) {
+ $row = [
+ 'Count' => $index++ + 1,
+ 'Name' => '' . Utils::truncate($package->name, 20, false, ' ', '...') . ' ',
+ 'Slug' => $slug,
+ 'Version'=> $this->version($package),
+ 'Installed' => $this->installed($package)
+ ];
+ $table[] = $row;
+ }
+
+ $climate->table($table);
+ }
+
+ $this->output->writeln('');
+ }
+
+ $this->output->writeln('You can either get more informations about a package by typing:');
+ $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('');
+ }
+
+ /**
+ * @param Package $package
+ *
+ * @return string
+ */
+ private function version($package)
+ {
+ $list = $this->gpm->{'getUpdatable' . ucfirst($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} ";
+ }
+
+ if ($updatable) {
+ return "v{$package->version} -> v{$package->available} ";
+ }
+
+ return '';
+ }
+
+ /**
+ * @param Package $package
+ *
+ * @return string
+ */
+ private function installed($package)
+ {
+ $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 array $data
+ *
+ * @return mixed
+ */
+ public function filter($data)
+ {
+ // filtering and sorting
+ if ($this->options['plugins-only']) {
+ unset($data['themes']);
+ }
+ if ($this->options['themes-only']) {
+ unset($data['plugins']);
+ }
+
+ $filter = [
+ $this->options['filter'],
+ $this->options['installed-only'],
+ $this->options['updates-only'],
+ $this->options['desc']
+ ];
+
+ 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]);
+ }
+
+ // Filtering updatables only
+ 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 ($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) {
+ unset($data[$type][$slug]);
+ }
+ }
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param Packages $packages
+ */
+ public function sort($packages)
+ {
+ foreach ($this->options['sort'] as $key) {
+ $packages = $packages->sort(function ($a, $b) use ($key) {
+ switch ($key) {
+ case 'author':
+ return strcmp($a->{$key}['name'], $b->{$key}['name']);
+ break;
+ default:
+ return strcmp($a->$key, $b->$key);
+ }
+ }, $this->options['desc'] ? true : false);
+ }
+
+ return $packages;
+ }
+}
diff --git a/system/src/Grav/Console/Gpm/InfoCommand.php b/system/src/Grav/Console/Gpm/InfoCommand.php
new file mode 100644
index 0000000..2fe1a2e
--- /dev/null
+++ b/system/src/Grav/Console/Gpm/InfoCommand.php
@@ -0,0 +1,180 @@
+setName('info')
+ ->addOption(
+ 'force',
+ 'f',
+ InputOption::VALUE_NONE,
+ 'Force fetching the new data remotely'
+ )
+ ->addOption(
+ 'all-yes',
+ 'y',
+ InputOption::VALUE_NONE,
+ 'Assumes yes (or best approach) instead of prompting'
+ )
+ ->addArgument(
+ 'package',
+ 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')
+ ->setHelp('The info shows more informations about a package');
+ }
+
+ /**
+ * @return int|null|void
+ */
+ protected function serve()
+ {
+ $this->gpm = new GPM($this->input->getOption('force'));
+
+ $this->all_yes = $this->input->getOption('all-yes');
+
+ $this->displayGPMRelease();
+
+ $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('');
+ $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('');
+ $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 = '';
+ if (isset($foundPackage->author['url'])) {
+ $packageURL = '<' . $foundPackage->author['url'] . '>';
+ }
+
+ $this->output->writeln('' . str_pad('Author',
+ 12) . ': ' . $foundPackage->author['name'] . ' <' . $foundPackage->author['email'] . '> ' . $packageURL);
+
+ foreach ([
+ 'version',
+ 'keywords',
+ 'date',
+ 'homepage',
+ 'demo',
+ 'docs',
+ 'guide',
+ 'repository',
+ 'bugs',
+ 'zipball_url',
+ 'license'
+ ] as $info) {
+ if (isset($foundPackage->{$info})) {
+ $name = ucfirst($info);
+ $data = $foundPackage->{$info};
+
+ if ($info === 'zipball_url') {
+ $name = 'Download';
+ }
+
+ if ($info === 'date') {
+ $name = 'Last Update';
+ $data = date('D, j M Y, H:i:s, P ', strtotime($data));
+ }
+
+ $name = str_pad($name, 12);
+ $this->output->writeln("{$name}: {$data}");
+ }
+ }
+
+ $type = rtrim($foundPackage->package_type, 's');
+ $updatable = $this->gpm->{'is' . $type . 'Updatable'}($foundPackage->slug);
+ $installed = $this->gpm->{'is' . $type . 'Installed'}($foundPackage->slug);
+
+ // display current version if installed and different
+ if ($installed && $updatable) {
+ $local = $this->gpm->{'getInstalled'. $type}($foundPackage->slug);
+ $this->output->writeln('');
+ $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] ',
+ false);
+ $answer = $this->all_yes ? true : $questionHelper->ask($this->input, $this->output, $question);
+
+ if ($answer) {
+ $changelog = $foundPackage->changelog;
+
+ $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]) . ':';
+ }, $log['content']);
+
+ $this->output->writeln("{$title} ");
+ $this->output->writeln(str_repeat('-', \strlen($title)));
+ $this->output->writeln($content);
+ $this->output->writeln('');
+
+ $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('');
+
+ if ($installed && $updatable) {
+ $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('');
+
+ }
+}
diff --git a/system/src/Grav/Console/Gpm/InstallCommand.php b/system/src/Grav/Console/Gpm/InstallCommand.php
new file mode 100644
index 0000000..8d544d4
--- /dev/null
+++ b/system/src/Grav/Console/Gpm/InstallCommand.php
@@ -0,0 +1,691 @@
+setName('install')
+ ->addOption(
+ 'force',
+ 'f',
+ InputOption::VALUE_NONE,
+ 'Force re-fetching the data from remote'
+ )
+ ->addOption(
+ 'all-yes',
+ 'y',
+ InputOption::VALUE_NONE,
+ 'Assumes yes (or best approach) instead of prompting'
+ )
+ ->addOption(
+ 'destination',
+ 'd',
+ InputOption::VALUE_OPTIONAL,
+ 'The destination where the package should be installed at. By default this would be where the grav instance has been launched from',
+ GRAV_ROOT
+ )
+ ->addArgument(
+ 'package',
+ 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')
+ ->setHelp('The install command allows to install plugins and themes');
+ }
+
+ /**
+ * Allows to set the GPM object, used for testing the class
+ *
+ * @param GPM $gpm
+ */
+ public function setGpm(GPM $gpm)
+ {
+ $this->gpm = $gpm;
+ }
+
+ /**
+ * @return bool
+ */
+ protected function serve()
+ {
+ $this->gpm = new GPM($this->input->getOption('force'));
+
+ $this->all_yes = $this->input->getOption('all-yes');
+
+ $this->displayGPMRelease();
+
+ $this->destination = realpath($this->input->getOption('destination'));
+
+ $packages = array_map('strtolower', $this->input->getArgument('package'));
+ $this->data = $this->gpm->findPackages($packages);
+ $this->loadLocalConfig();
+
+ if (
+ !Installer::isGravInstance($this->destination) ||
+ !Installer::isValidDestination($this->destination, [Installer::EXISTS, Installer::IS_LINK])
+ ) {
+ $this->output->writeln('ERROR : ' . Installer::lastErrorMsg());
+ exit;
+ }
+
+ $this->output->writeln('');
+
+ if (!$this->data['total']) {
+ $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'])) . ' ');
+ }
+
+ unset($this->data['not_found'], $this->data['total']);
+
+ if (null !== $this->local_config) {
+ // Symlinks available, ask if Grav should use them
+ $this->use_symlinks = false;
+ $helper = $this->getHelper('question');
+ $question = new ConfirmationQuestion('Should Grav use the symlinks if available? [y|N] ', false);
+
+ $answer = $this->all_yes ? false : $helper->ask($this->input, $this->output, $question);
+
+ if ($answer) {
+ $this->use_symlinks = true;
+ }
+ }
+
+ $this->output->writeln('');
+
+ try {
+ $dependencies = $this->gpm->getDependencies($packages);
+ } 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()} ");
+ 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, '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 ');
+ return false;
+ }
+
+ $this->output->writeln('Dependencies are OK ');
+ $this->output->writeln('');
+ }
+
+
+ //We're done installing dependencies. Install the actual packages
+ 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 ");
+ } else {
+ $is_valid_destination = Installer::isValidDestination($this->destination . DS . $package->install_path);
+ if ($is_valid_destination || Installer::lastErrorCode() == Installer::NOT_FOUND) {
+ $this->processPackage($package, false);
+ } else {
+ if (Installer::lastErrorCode() == Installer::EXISTS) {
+
+ try {
+ $this->askConfirmationIfMajorVersionUpdated($package);
+ $this->gpm->checkNoOtherPackageNeedsThisDependencyInALowerVersion($package->slug, $package->available, array_keys($data));
+ } catch (\Exception $e) {
+ $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);
+ $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 ");
+ }
+ } else {
+ if (Installer::lastErrorCode() == Installer::IS_LINK) {
+ $this->output->writeln("Cannot overwrite existing symlink for {$package_name} ");
+ $this->output->writeln('');
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (\count($this->demo_processing) > 0) {
+ foreach ($this->demo_processing as $package) {
+ $this->installDemoContent($package);
+ }
+ }
+
+ // clear cache after successful upgrade
+ $this->clearCache();
+
+ return true;
+ }
+
+ /**
+ * If the package is updated from an older major release, show warning and ask confirmation
+ *
+ * @param Package $package
+ */
+ public function askConfirmationIfMajorVersionUpdated($package)
+ {
+ $helper = $this->getHelper('question');
+ $package_name = $package->name;
+ $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} ");
+ 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);
+
+ if (!$helper->ask($this->input, $this->output, $question)) {
+ $this->output->writeln("Package {$package_name} not updated ");
+ exit;
+ }
+ }
+ }
+
+ /**
+ * Given a $dependencies list, filters their type according to $type and
+ * shows $message prior to listing them to the user. Then asks the user a confirmation prior
+ * to installing them.
+ *
+ * @param array $dependencies The dependencies array
+ * @param string $type The type of dependency to show: install, update, ignore
+ * @param string $message A message to be shown prior to listing the dependencies
+ * @param bool $required A flag that determines if the installation is required or optional
+ *
+ * @throws \Exception
+ */
+ public function installDependencies($dependencies, $type, $message, $required = true)
+ {
+ $packages = array_filter($dependencies, function ($action) use ($type) { return $action === $type; });
+ if (\count($packages) > 0) {
+ $this->output->writeln($message);
+
+ foreach ($packages as $dependencyName => $dependencyVersion) {
+ $this->output->writeln(" |- Package {$dependencyName} ");
+ }
+
+ $this->output->writeln('');
+
+ $helper = $this->getHelper('question');
+
+ if ($type === 'install') {
+ $questionAction = 'Install';
+ } else {
+ $questionAction = 'Update';
+ }
+
+ if (\count($packages) === 1) {
+ $questionArticle = 'this';
+ } else {
+ $questionArticle = 'these';
+ }
+
+ if (\count($packages) === 1) {
+ $questionNoun = 'package';
+ } else {
+ $questionNoun = 'packages';
+ }
+
+ $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');
+ }
+ $this->output->writeln('');
+ } else {
+ if ($required) {
+ throw new \Exception();
+ }
+ }
+ }
+ }
+
+ /**
+ * @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('');
+ return;
+ }
+
+ $symlink = false;
+ if ($this->use_symlinks) {
+ if (!isset($package->version) || $this->getSymlinkSource($package)) {
+ $symlink = true;
+ }
+ }
+
+ $symlink ? $this->processSymlink($package) : $this->processGpm($package, $is_update);
+
+ $this->processDemo($package);
+ }
+
+ /**
+ * Add package to the queue to process the demo content, if demo content exists
+ *
+ * @param Package $package
+ */
+ private function processDemo($package)
+ {
+ $demo_dir = $this->destination . DS . $package->install_path . DS . '_demo';
+ if (file_exists($demo_dir)) {
+ $this->demo_processing[] = $package;
+ }
+ }
+
+ /**
+ * Prompt to install the demo content of a package
+ *
+ * @param Package $package
+ */
+ private function installDemoContent($package)
+ {
+ $demo_dir = $this->destination . DS . $package->install_path . DS . '_demo';
+
+ if (file_exists($demo_dir)) {
+ $dest_dir = $this->destination . DS . 'user';
+ $pages_dir = $dest_dir . DS . 'pages';
+
+ // Demo content exists, prompt to install it.
+ $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);
+
+ $answer = $helper->ask($this->input, $this->output, $question);
+
+ if (!$answer) {
+ $this->output->writeln(" '- Skipped! ");
+ $this->output->writeln('');
+
+ return;
+ }
+
+ // if pages folder exists in demo
+ if (file_exists($demo_dir . DS . 'pages')) {
+ $pages_backup = 'pages.' . date('m-d-Y-H-i-s');
+ $question = new ConfirmationQuestion('This will backup your current `user/pages` folder to `user/' . $pages_backup . '`, continue? [y|N]', false);
+ $answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
+
+ if (!$answer) {
+ $this->output->writeln(" '- Skipped! ");
+ $this->output->writeln('');
+
+ return;
+ }
+
+ // 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 ');
+ } else {
+ $this->output->writeln(' |- Backing up pages... failed ');
+ }
+ }
+ }
+
+ // Confirmation received, copy over the data
+ $this->output->writeln(' |- Installing demo content... ok ');
+ Folder::rcopy($demo_dir, $dest_dir);
+ $this->output->writeln(" '- Success! ");
+ $this->output->writeln('');
+ }
+ }
+
+ /**
+ * @param Package $package
+ *
+ * @return array|bool
+ */
+ private function getGitRegexMatches($package)
+ {
+ if (isset($package->repository)) {
+ $repository = $package->repository;
+ } else {
+ return false;
+ }
+
+ preg_match(GIT_REGEX, $repository, $matches);
+
+ return $matches;
+ }
+
+ /**
+ * @param Package $package
+ *
+ * @return bool|string
+ */
+ private function getSymlinkSource($package)
+ {
+ $matches = $this->getGitRegexMatches($package);
+
+ foreach ($this->local_config as $paths) {
+ if (Utils::endsWith($matches[2], '.git')) {
+ $repo_dir = preg_replace('/\.git$/', '', $matches[2]);
+ } else {
+ $repo_dir = $matches[2];
+ }
+
+ $paths = (array) $paths;
+ foreach ($paths as $repo) {
+ $path = rtrim($repo, '/') . '/' . $repo_dir;
+ if (file_exists($path)) {
+ return $path;
+ }
+ }
+
+ }
+
+ return false;
+ }
+
+ /**
+ * @param Package $package
+ */
+ private function processSymlink($package)
+ {
+
+ exec('cd ' . $this->destination);
+
+ $to = $this->destination . DS . $package->install_path;
+ $from = $this->getSymlinkSource($package);
+
+ $this->output->writeln("Preparing to Symlink {$package->name} ");
+ $this->output->write(' |- Checking source... ');
+
+ if (file_exists($from)) {
+ $this->output->writeln('ok ');
+
+ $this->output->write(' |- Checking destination... ');
+ $checks = $this->checkDestination($package);
+
+ if (!$checks) {
+ $this->output->writeln(" '- Installation failed or aborted. ");
+ $this->output->writeln('');
+ } else {
+ if (file_exists($to)) {
+ $this->output->writeln(" '- Symlink cannot overwrite an existing package, please remove first ");
+ $this->output->writeln('');
+ } else {
+ symlink($from, $to);
+
+ // extra white spaces to clear out the buffer properly
+ $this->output->writeln(' |- Symlinking package... ok ');
+ $this->output->writeln(" '- Success! ");
+ $this->output->writeln('');
+ }
+ }
+
+ return;
+ }
+
+ $this->output->writeln('not found! ');
+ $this->output->writeln(" '- Installation failed or aborted. ");
+ }
+
+ /**
+ * @param Package $package
+ * @param bool $is_update
+ *
+ * @return bool
+ */
+ private function processGpm($package, $is_update = false)
+ {
+ $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->write(' |- Downloading package... 0%');
+ $this->file = $this->downloadPackage($package, $license);
+
+ if (!$this->file) {
+ $this->output->writeln(" '- Installation failed or aborted. ");
+ $this->output->writeln('');
+
+ return false;
+ }
+
+ $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... ');
+ $installation = $this->installPackage($package, $is_update);
+ if (!$installation) {
+ $this->output->writeln(" '- Installation failed or aborted. ");
+ $this->output->writeln('');
+ } else {
+ $this->output->writeln(" '- Success! ");
+ $this->output->writeln('');
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param Package $package
+ *
+ * @param string $license
+ *
+ * @return string
+ */
+ private function downloadPackage($package, $license = null)
+ {
+ $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('/[\\\\\/:"*?&<>|]+/m', '-', $filename);
+ $query = '';
+
+ if (!empty($package->premium)) {
+ $query = \json_encode(array_merge(
+ $package->premium,
+ [
+ 'slug' => $package->slug,
+ 'filename' => $package->premium['filename'],
+ 'license_key' => $license
+ ]
+ ));
+
+ $query = '?d=' . base64_encode($query);
+ }
+
+ try {
+ $output = Response::get($package->zipball_url . $query, [], [$this, 'progress']);
+ } catch (\Exception $e) {
+ $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(" | '- " . $error);
+
+ return false;
+ }
+
+ Folder::create($this->tmp);
+
+ $this->output->write("\x0D");
+ $this->output->write(' |- Downloading package... 100%');
+ $this->output->writeln('');
+
+ file_put_contents($this->tmp . DS . $filename, $output);
+
+ return $this->tmp . DS . $filename;
+ }
+
+ /**
+ * @param Package $package
+ *
+ * @return bool
+ */
+ private function checkDestination($package)
+ {
+ $question_helper = $this->getHelper('question');
+
+ Installer::isValidDestination($this->destination . DS . $package->install_path);
+
+ if (Installer::lastErrorCode() == Installer::IS_LINK) {
+ $this->output->write("\x0D");
+ $this->output->writeln(' |- Checking destination... symbolic link ');
+
+ if ($this->all_yes) {
+ $this->output->writeln(" | '- Skipped automatically. ");
+
+ return false;
+ }
+
+ $question = new ConfirmationQuestion(" | '- Destination has been detected as symlink, delete symbolic link first? [y|N] ",
+ false);
+ $answer = $question_helper->ask($this->input, $this->output, $question);
+
+ if (!$answer) {
+ $this->output->writeln(" | '- You decided to not delete the symlink automatically. ");
+
+ return false;
+ }
+
+ unlink($this->destination . DS . $package->install_path);
+ }
+
+ $this->output->write("\x0D");
+ $this->output->writeln(' |- Checking destination... ok ');
+
+ return true;
+ }
+
+ /**
+ * Install a package
+ *
+ * @param Package $package
+ * @param bool $is_update True if it's an update. False if it's an install
+ *
+ * @return bool
+ */
+ private function installPackage($package, $is_update = false)
+ {
+ $type = $package->package_type;
+
+ 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(" | '- " . Installer::lastErrorMsg());
+
+ return false;
+ }
+
+ $message = Installer::getMessage();
+ if ($message) {
+ $this->output->write("\x0D");
+ // extra white spaces to clear out the buffer properly
+ $this->output->writeln(" |- {$message}");
+ }
+
+ $this->output->write("\x0D");
+ // extra white spaces to clear out the buffer properly
+ $this->output->writeln(' |- Installing package... ok ');
+
+ return true;
+ }
+
+ /**
+ * @param array $progress
+ */
+ public function progress($progress)
+ {
+ $this->output->write("\x0D");
+ $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
new file mode 100644
index 0000000..62c6120
--- /dev/null
+++ b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php
@@ -0,0 +1,297 @@
+setName('self-upgrade')
+ ->setAliases(['selfupgrade', 'selfupdate'])
+ ->addOption(
+ 'force',
+ 'f',
+ InputOption::VALUE_NONE,
+ 'Force re-fetching the data from remote'
+ )
+ ->addOption(
+ 'all-yes',
+ 'y',
+ InputOption::VALUE_NONE,
+ 'Assumes yes (or best approach) instead of prompting'
+ )
+ ->addOption(
+ 'overwrite',
+ 'o',
+ InputOption::VALUE_NONE,
+ 'Option to overwrite packages if they already exist'
+ )
+ ->setDescription('Detects and performs an update of Grav itself when available')
+ ->setHelp('The update command updates Grav itself when a new version is available');
+ }
+
+ protected function serve()
+ {
+ $this->upgrader = new Upgrader($this->input->getOption('force'));
+ $this->all_yes = $this->input->getOption('all-yes');
+ $this->overwrite = $this->input->getOption('overwrite');
+
+ $this->displayGPMRelease();
+
+ $update = $this->upgrader->getAssets()['grav-update'];
+
+ $local = $this->upgrader->getLocalVersion();
+ $remote = $this->upgrader->getRemoteVersion();
+ $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('');
+ exit;
+ }
+
+ if (!$this->overwrite && !$this->upgrader->isUpgradable()) {
+ $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('');
+ $this->output->writeln("You are currently running a symbolically linked Grav v{$local}. Latest available is v{$remote}.");
+ exit;
+ }
+
+ // not used but preloaded just in case!
+ new ArrayInput([]);
+
+ $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 . ' .');
+
+ if (!$this->all_yes) {
+ $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('');
+ foreach ($changelog as $version => $log) {
+ $title = $version . ' [' . $log['date'] . ']';
+ $content = preg_replace_callback('/\d\.\s\[\]\(#(.*)\)/', function ($match) {
+ return "\n" . ucfirst($match[1]) . ':';
+ }, $log['content']);
+
+ $this->output->writeln($title);
+ $this->output->writeln(str_repeat('-', \strlen($title)));
+ $this->output->writeln($content);
+ $this->output->writeln('');
+ }
+
+ $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);
+ $answer = $questionHelper->ask($this->input, $this->output, $question);
+
+ if (!$answer) {
+ $this->output->writeln('Aborting...');
+
+ exit;
+ }
+ }
+
+ $this->output->writeln('');
+ $this->output->writeln("Preparing to upgrade to v{$remote} ..");
+
+ $this->output->write(" |- Downloading upgrade [{$this->formatBytes($update['size'])}]... 0%");
+ $this->file = $this->download($update);
+
+ $this->output->write(' |- Installing upgrade... ');
+ $installation = $this->upgrade();
+
+ if (!$installation) {
+ $this->output->writeln(" '- Installation failed or aborted. ");
+ $this->output->writeln('');
+ } else {
+ $this->output->writeln(" '- Success! ");
+ $this->output->writeln('');
+ }
+
+ // clear cache after successful upgrade
+ $this->clearCache('all');
+ }
+
+ /**
+ * @param array $package
+ *
+ * @return string
+ */
+ private function download($package)
+ {
+ $tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
+ $this->tmp = $tmp_dir . '/Grav-' . uniqid('', false);
+ $output = Response::get($package['download'], [], [$this, 'progress']);
+
+ Folder::create($this->tmp);
+
+ $this->output->write("\x0D");
+ $this->output->write(" |- Downloading upgrade [{$this->formatBytes($package['size'])}]... 100%");
+ $this->output->writeln('');
+
+ file_put_contents($this->tmp . DS . $package['name'], $output);
+
+ return $this->tmp . DS . $package['name'];
+ }
+
+ /**
+ * @return bool
+ */
+ private function upgrade()
+ {
+ if ($this->file) {
+ $folder = Installer::unZip($this->file, $this->tmp . '/zip');
+ } else {
+ $folder = false;
+ }
+
+ static::upgradeGrav($this->file, $folder);
+
+ $errorCode = Installer::lastErrorCode();
+
+ 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(" | '- " . Installer::lastErrorMsg());
+
+ return false;
+ }
+
+ $this->output->write("\x0D");
+ // extra white spaces to clear out the buffer properly
+ $this->output->writeln(' |- Installing upgrade... ok ');
+
+ return true;
+ }
+
+ /**
+ * @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) . '%');
+ }
+
+ /**
+ * @param int|float $size
+ * @param int $precision
+ *
+ * @return string
+ */
+ public function formatBytes($size, $precision = 2)
+ {
+ $base = log($size) / log(1024);
+ $suffixes = array('', 'k', 'M', 'G', 'T');
+
+ 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
new file mode 100644
index 0000000..99fc81a
--- /dev/null
+++ b/system/src/Grav/Console/Gpm/UninstallCommand.php
@@ -0,0 +1,295 @@
+setName('uninstall')
+ ->addOption(
+ 'all-yes',
+ 'y',
+ InputOption::VALUE_NONE,
+ 'Assumes yes (or best approach) instead of prompting'
+ )
+ ->addArgument(
+ 'package',
+ 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')
+ ->setHelp('The uninstall command allows to uninstall plugins and themes');
+ }
+
+ protected function serve()
+ {
+ $this->gpm = new GPM();
+
+ $this->all_yes = $this->input->getOption('all-yes');
+
+ $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;
+ $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('');
+ exit;
+ }
+
+ if (count($this->data['not_found'])) {
+ $this->output->writeln('These packages were not found installed: ' . implode(' , ',
+ $this->data['not_found']) . ' ');
+ }
+
+ 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->write(' |- Checking destination... ');
+ $checks = $this->checkDestination($slug, $package);
+
+ if (!$checks) {
+ $this->output->writeln(" '- Installation failed or aborted. ");
+ $this->output->writeln('');
+ } else {
+ $uninstall = $this->uninstallPackage($slug, $package);
+
+ if (!$uninstall) {
+ $this->output->writeln(" '- Uninstallation failed or aborted. ");
+ } else {
+ $this->output->writeln(" '- Success! ");
+ }
+ }
+
+ }
+
+ // clear cache after successful upgrade
+ $this->clearCache();
+ }
+
+
+ /**
+ * @param string $slug
+ * @param Package $package
+ *
+ * @return bool
+ */
+ private function uninstallPackage($slug, $package, $is_dependency = false)
+ {
+ if (!$slug) {
+ return false;
+ }
+
+ //check if there are packages that have this as a dependency. Abort and show list
+ $dependent_packages = $this->gpm->getPackagesThatDependOnPackage($slug);
+ if (count($dependent_packages) > ($is_dependency ? 1 : 0)) {
+ $this->output->writeln('');
+ $this->output->writeln('');
+ $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.');
+ } else {
+ $this->output->writeln('The installed package ' . implode(' , ', $dependent_packages) . ' depends on this package. Please remove it first.');
+ }
+
+ $this->output->writeln('');
+ return false;
+ }
+
+ if (isset($package->dependencies)) {
+
+ $dependencies = $package->dependencies;
+
+ if ($is_dependency) {
+ foreach ($dependencies as $key => $dependency) {
+ if (\in_array($dependency['name'], $this->dependencies, true)) {
+ unset($dependencies[$key]);
+ }
+ }
+ } else {
+ if (\count($dependencies) > 0) {
+ $this->output->writeln(' `- Dependencies found...');
+ $this->output->writeln('');
+ }
+ }
+
+ $questionHelper = $this->getHelper('question');
+
+ foreach ($dependencies as $dependency) {
+
+ $this->dependencies[] = $dependency['name'];
+
+ if (\is_array($dependency)) {
+ $dependency = $dependency['name'];
+ }
+ if ($dependency === 'grav' || $dependency === 'php') {
+ continue;
+ }
+
+ $dependencyPackage = $this->gpm->findPackage($dependency);
+
+ $dependency_exists = $this->packageExists($dependency, $dependencyPackage);
+
+ if ($dependency_exists == Installer::EXISTS) {
+ $this->output->writeln("A dependency on {$dependencyPackage->name} [v{$dependencyPackage->version}] was found");
+
+ $question = new ConfirmationQuestion(" |- Uninstall {$dependencyPackage->name} ? [y|N] ", false);
+ $answer = $this->all_yes ? true : $questionHelper->ask($this->input, $this->output, $question);
+
+ if ($answer) {
+ $uninstall = $this->uninstallPackage($dependency, $dependencyPackage, true);
+
+ if (!$uninstall) {
+ $this->output->writeln(" '- Uninstallation failed or aborted. ");
+ } else {
+ $this->output->writeln(" '- Success! ");
+
+ }
+ $this->output->writeln('');
+ } else {
+ $this->output->writeln(" '- You decided not to uninstall {$dependencyPackage->name}. ");
+ $this->output->writeln('');
+ }
+ }
+
+ }
+ }
+
+
+ $locator = Grav::instance()['locator'];
+ $path = $locator->findResource($package->package_type . '://' . $slug);
+ Installer::uninstall($path);
+ $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() . ' ');
+
+ return false;
+ }
+
+ $message = Installer::getMessage();
+ if ($message) {
+ $this->output->writeln(" |- {$message}");
+ }
+
+ if (!$is_dependency && $this->dependencies) {
+ $this->output->writeln("Finishing up uninstalling {$package->name} ");
+ }
+ $this->output->writeln(" |- Uninstalling {$package->name} package... ok ");
+
+
+
+ return true;
+ }
+
+ /**
+ * @param string $slug
+ * @param Package $package
+ *
+ * @return bool
+ */
+
+ private function checkDestination($slug, $package)
+ {
+ $questionHelper = $this->getHelper('question');
+
+ $exists = $this->packageExists($slug, $package);
+
+ if ($exists == Installer::IS_LINK) {
+ $this->output->write("\x0D");
+ $this->output->writeln(' |- Checking destination... symbolic link ');
+
+ if ($this->all_yes) {
+ $this->output->writeln(" | '- Skipped automatically. ");
+
+ return false;
+ }
+
+ $question = new ConfirmationQuestion(" | '- Destination has been detected as symlink, delete symbolic link first? [y|N] ",
+ false);
+ $answer = $this->all_yes ? true : $questionHelper->ask($this->input, $this->output, $question);
+
+ if (!$answer) {
+ $this->output->writeln(" | '- You decided not to delete the symlink automatically. ");
+
+ return false;
+ }
+ }
+
+ $this->output->write("\x0D");
+ $this->output->writeln(' |- Checking destination... ok ');
+
+ return true;
+ }
+
+ /**
+ * Check if package exists
+ *
+ * @param string $slug
+ * @param Package $package
+ * @return int
+ */
+ private function packageExists($slug, $package)
+ {
+ $path = Grav::instance()['locator']->findResource($package->package_type . '://' . $slug);
+ Installer::isValidDestination($path);
+ return Installer::lastErrorCode();
+ }
+}
diff --git a/system/src/Grav/Console/Gpm/UpdateCommand.php b/system/src/Grav/Console/Gpm/UpdateCommand.php
new file mode 100644
index 0000000..d8ea735
--- /dev/null
+++ b/system/src/Grav/Console/Gpm/UpdateCommand.php
@@ -0,0 +1,268 @@
+setName('update')
+ ->addOption(
+ 'force',
+ 'f',
+ InputOption::VALUE_NONE,
+ 'Force re-fetching the data from remote'
+ )
+ ->addOption(
+ 'destination',
+ 'd',
+ InputOption::VALUE_OPTIONAL,
+ 'The grav instance location where the updates should be applied to. By default this would be where the grav cli has been launched from',
+ GRAV_ROOT
+ )
+ ->addOption(
+ 'all-yes',
+ 'y',
+ InputOption::VALUE_NONE,
+ 'Assumes yes (or best approach) instead of prompting'
+ )
+ ->addOption(
+ 'overwrite',
+ 'o',
+ InputOption::VALUE_NONE,
+ 'Option to overwrite packages if they already exist'
+ )
+ ->addOption(
+ 'plugins',
+ 'p',
+ InputOption::VALUE_NONE,
+ 'Update only plugins'
+ )
+ ->addOption(
+ 'themes',
+ 't',
+ InputOption::VALUE_NONE,
+ 'Update only themes'
+ )
+ ->addArgument(
+ 'package',
+ 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')
+ ->setHelp('The update command updates plugins and themes when a new version is available');
+ }
+
+ 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('');
+ $questionHelper = $this->getHelper('question');
+ $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... ');
+ exit;
+ }
+ }
+
+ $this->gpm = new GPM($this->input->getOption('force'));
+
+ $this->all_yes = $this->input->getOption('all-yes');
+ $this->overwrite = $this->input->getOption('overwrite');
+
+ $this->displayGPMRelease();
+
+ $this->destination = realpath($this->input->getOption('destination'));
+
+ if (!Installer::isGravInstance($this->destination)) {
+ $this->output->writeln('ERROR : ' . Installer::lastErrorMsg());
+ exit;
+ }
+ if ($this->input->getOption('plugins') === false && $this->input->getOption('themes') === false) {
+ $list_type = ['plugins' => true, 'themes' => true];
+ } else {
+ $list_type['plugins'] = $this->input->getOption('plugins');
+ $list_type['themes'] = $this->input->getOption('themes');
+ }
+
+ if ($this->overwrite) {
+ $this->data = $this->gpm->getInstallable($list_type);
+ $description = ' can be overwritten';
+ } else {
+ $this->data = $this->gpm->getUpdatable($list_type);
+ $description = ' need updating';
+ }
+
+ $only_packages = array_map('strtolower', $this->input->getArgument('package'));
+
+ if (!$this->overwrite && !$this->data['total']) {
+ $this->output->writeln('Nothing to update.');
+ exit;
+ }
+
+ $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'], $limit_to['total']);
+
+
+ // updates review
+ $slugs = [];
+
+ $index = 0;
+ foreach ($this->data as $packages) {
+ foreach ($packages as $slug => $package) {
+ if (!array_key_exists($slug, $limit_to) && \count($only_packages)) {
+ continue;
+ }
+
+ if (!$package->available) {
+ $package->available = $package->version;
+ }
+
+ $this->output->writeln(
+ // index
+ str_pad($index++ + 1, 2, '0', STR_PAD_LEFT) . '. ' .
+ // name
+ '' . str_pad($package->name, 15) . ' ' .
+ // version
+ "[v{$package->version} -> v{$package->available} ]"
+ );
+ $slugs[] = $slug;
+ }
+ }
+
+ if (!$this->all_yes) {
+ // prompt to continue
+ $this->output->writeln('');
+ $questionHelper = $this->getHelper('question');
+ $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... ');
+ exit;
+ }
+ }
+
+ // finally update
+ $install_command = $this->getApplication()->find('install');
+
+ $args = new ArrayInput([
+ 'command' => 'install',
+ 'package' => $slugs,
+ '-f' => $this->input->getOption('force'),
+ '-d' => $this->destination,
+ '-y' => true
+ ]);
+ $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');
+ exit;
+ }
+ }
+
+ /**
+ * @param array $only_packages
+ *
+ * @return array
+ */
+ private function userInputPackages($only_packages)
+ {
+ $found = ['total' => 0];
+ $ignore = [];
+
+ 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 = $find->slug ?? $only_package;
+ $ignore[$name] = $name;
+ } else {
+ $found[$find->slug] = $find;
+ $found['total']++;
+ }
+ }
+
+ if ($found['total']) {
+ $list = $found;
+ unset($list['total']);
+ $list = array_keys($list);
+
+ if ($found['total'] !== $this->data['total']) {
+ $this->output->write(", only {$found['total']} will be updated");
+ }
+
+ $this->output->writeln('');
+ $this->output->writeln('Limiting updates for only ' . implode(' , ',
+ $list) . ' ');
+ }
+
+ if (\count($ignore)) {
+ $this->output->writeln('');
+ $this->output->writeln('Packages not found or not requiring updates: ' . implode(' , ',
+ $ignore) . ' ');
+
+ }
+ }
+
+ return $found;
+ }
+}
diff --git a/system/src/Grav/Console/Gpm/VersionCommand.php b/system/src/Grav/Console/Gpm/VersionCommand.php
new file mode 100644
index 0000000..becc327
--- /dev/null
+++ b/system/src/Grav/Console/Gpm/VersionCommand.php
@@ -0,0 +1,112 @@
+setName('version')
+ ->addOption(
+ 'force',
+ 'f',
+ InputOption::VALUE_NONE,
+ 'Force re-fetching the data from remote'
+ )
+ ->addArgument(
+ 'package',
+ 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.')
+ ->setHelp('The version command displays the current version of a package installed and, if available, the available version of pending updates');
+ }
+
+ /**
+ * @return int|null|void
+ */
+ protected function serve()
+ {
+ $this->gpm = new GPM($this->input->getOption('force'));
+ $packages = $this->input->getArgument('package');
+
+ $installed = false;
+
+ if (!count($packages)) {
+ $packages = ['grav'];
+ }
+
+ foreach ($packages as $package) {
+ $package = strtolower($package);
+ $name = null;
+ $version = null;
+ $updatable = false;
+
+ if ($package === 'grav') {
+ $name = 'Grav';
+ $version = GRAV_VERSION;
+ $upgrader = new Upgrader();
+
+ if ($upgrader->isUpgradable()) {
+ $updatable = " [upgradable: v{$upgrader->getRemoteVersion()} ]";
+ }
+
+ } else {
+ // get currently installed version
+ $locator = \Grav\Common\Grav::instance()['locator'];
+ $blueprints_path = $locator->findResource('plugins://' . $package . DS . 'blueprints.yaml');
+ if (!file_exists($blueprints_path)) { // theme?
+ $blueprints_path = $locator->findResource('themes://' . $package . DS . 'blueprints.yaml');
+ if (!file_exists($blueprints_path)) {
+ continue;
+ }
+ }
+
+ $file = YamlFile::instance($blueprints_path);
+ $package_yaml = $file->content();
+ $file->free();
+
+ $version = $package_yaml['version'];
+
+ if (!$version) {
+ continue;
+ }
+
+ $installed = $this->gpm->findPackage($package);
+ if ($installed) {
+ $name = $installed->name;
+
+ if ($this->gpm->isUpdatable($package)) {
+ $updatable = " [updatable: v{$installed->available} ]";
+ }
+ }
+ }
+
+ $updatable = $updatable ?: '';
+
+ if ($installed || $package === 'grav') {
+ $this->output->writeln("You are running {$name} v{$version} {$updatable}");
+ } else {
+ $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
new file mode 100644
index 0000000..4f9df63
--- /dev/null
+++ b/system/src/Grav/Console/TerminalObjects/Table.php
@@ -0,0 +1,30 @@
+column_widths = $this->getColumnWidths();
+ $this->table_width = $this->getWidth();
+ $this->border = $this->getBorder();
+
+ $this->buildHeaderRow();
+
+ foreach ($this->data as $key => $columns) {
+ $this->rows[] = $this->buildRow($columns);
+ }
+
+ $this->rows[] = $this->border;
+
+ return $this->rows;
+ }
+}
diff --git a/system/src/Grav/Framework/Cache/AbstractCache.php b/system/src/Grav/Framework/Cache/AbstractCache.php
new file mode 100644
index 0000000..127db45
--- /dev/null
+++ b/system/src/Grav/Framework/Cache/AbstractCache.php
@@ -0,0 +1,31 @@
+init($namespace, $defaultLifetime);
+ }
+}
diff --git a/system/src/Grav/Framework/Cache/Adapter/ChainCache.php b/system/src/Grav/Framework/Cache/Adapter/ChainCache.php
new file mode 100644
index 0000000..e1eea7b
--- /dev/null
+++ b/system/src/Grav/Framework/Cache/Adapter/ChainCache.php
@@ -0,0 +1,198 @@
+caches = array_values($caches);
+ $this->count = \count($caches);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doGet($key, $miss)
+ {
+ foreach ($this->caches as $i => $cache) {
+ $value = $cache->doGet($key, $miss);
+ if ($value !== $miss) {
+ while (--$i >= 0) {
+ // Update all the previous caches with missing value.
+ $this->caches[$i]->doSet($key, $value, $this->getDefaultLifetime());
+ }
+
+ return $value;
+ }
+ }
+
+ return $miss;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doSet($key, $value, $ttl)
+ {
+ $success = true;
+ $i = $this->count;
+
+ while ($i--) {
+ $success = $this->caches[$i]->doSet($key, $value, $ttl) && $success;
+ }
+
+ return $success;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doDelete($key)
+ {
+ $success = true;
+ $i = $this->count;
+
+ while ($i--) {
+ $success = $this->caches[$i]->doDelete($key) && $success;
+ }
+
+ return $success;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doClear()
+ {
+ $success = true;
+ $i = $this->count;
+
+ while ($i--) {
+ $success = $this->caches[$i]->doClear() && $success;
+ }
+ return $success;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doGetMultiple($keys, $miss)
+ {
+ $list = [];
+ foreach ($this->caches as $i => $cache) {
+ $list[$i] = $cache->doGetMultiple($keys, $miss);
+
+ $keys = array_diff_key($keys, $list[$i]);
+
+ if (!$keys) {
+ break;
+ }
+ }
+
+ $values = [];
+ // Update all the previous caches with missing values.
+ foreach (array_reverse($list) as $i => $items) {
+ $values += $items;
+ if ($i && $values) {
+ $this->caches[$i-1]->doSetMultiple($values, $this->getDefaultLifetime());
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doSetMultiple($values, $ttl)
+ {
+ $success = true;
+ $i = $this->count;
+
+ while ($i--) {
+ $success = $this->caches[$i]->doSetMultiple($values, $ttl) && $success;
+ }
+
+ return $success;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doDeleteMultiple($keys)
+ {
+ $success = true;
+ $i = $this->count;
+
+ while ($i--) {
+ $success = $this->caches[$i]->doDeleteMultiple($keys) && $success;
+ }
+
+ return $success;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doHas($key)
+ {
+ foreach ($this->caches as $cache) {
+ if ($cache->doHas($key)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php b/system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php
new file mode 100644
index 0000000..45c9d07
--- /dev/null
+++ b/system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php
@@ -0,0 +1,116 @@
+getNamespace();
+ if ($namespace) {
+ $doctrineCache->setNamespace($namespace);
+ }
+
+ $this->driver = $doctrineCache;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doGet($key, $miss)
+ {
+ $value = $this->driver->fetch($key);
+
+ // Doctrine cache does not differentiate between no result and cached 'false'. Make sure that we do.
+ return $value !== false || $this->driver->contains($key) ? $value : $miss;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doSet($key, $value, $ttl)
+ {
+ return $this->driver->save($key, $value, (int) $ttl);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doDelete($key)
+ {
+ return $this->driver->delete($key);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doClear()
+ {
+ return $this->driver->deleteAll();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doGetMultiple($keys, $miss)
+ {
+ return $this->driver->fetchMultiple($keys);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doSetMultiple($values, $ttl)
+ {
+ return $this->driver->saveMultiple($values, (int) $ttl);
+ }
+
+ /**
+ * @inheritdoc
+ * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
+ */
+ public function doDeleteMultiple($keys)
+ {
+ return $this->driver->deleteMultiple($keys);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doHas($key)
+ {
+ return $this->driver->contains($key);
+ }
+}
diff --git a/system/src/Grav/Framework/Cache/Adapter/FileCache.php b/system/src/Grav/Framework/Cache/Adapter/FileCache.php
new file mode 100644
index 0000000..a5a60fa
--- /dev/null
+++ b/system/src/Grav/Framework/Cache/Adapter/FileCache.php
@@ -0,0 +1,233 @@
+initFileCache($namespace, $folder ?? '');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doGet($key, $miss)
+ {
+ $now = time();
+ $file = $this->getFile($key);
+
+ if (!file_exists($file) || !$h = @fopen($file, 'rb')) {
+ return $miss;
+ }
+
+ if ($now >= (int) $expiresAt = fgets($h)) {
+ fclose($h);
+ @unlink($file);
+ } else {
+ $i = rawurldecode(rtrim(fgets($h)));
+ $value = stream_get_contents($h);
+ fclose($h);
+
+ if ($i === $key) {
+ return unserialize($value);
+ }
+ }
+
+ return $miss;
+ }
+
+ /**
+ * @inheritdoc
+ * @throws \Psr\SimpleCache\CacheException|InvalidArgumentException
+ */
+ public function doSet($key, $value, $ttl)
+ {
+ $expiresAt = time() + (int)$ttl;
+
+ $result = $this->write(
+ $this->getFile($key, true),
+ $expiresAt . "\n" . rawurlencode($key) . "\n" . serialize($value),
+ $expiresAt
+ );
+
+ if (!$result && !is_writable($this->directory)) {
+ throw new CacheException(sprintf('Cache directory is not writable (%s)', $this->directory));
+ }
+
+ return $result;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doDelete($key)
+ {
+ $file = $this->getFile($key);
+
+ return (!file_exists($file) || @unlink($file) || !file_exists($file));
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doClear()
+ {
+ $result = true;
+ $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS));
+
+ foreach ($iterator as $file) {
+ $result = ($file->isDir() || @unlink($file) || !file_exists($file)) && $result;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function doHas($key)
+ {
+ $file = $this->getFile($key);
+
+ return file_exists($file) && (@filemtime($file) > time() || $this->doGet($key, null));
+ }
+
+ /**
+ * @param string $key
+ * @param bool $mkdir
+ * @return string
+ */
+ protected function getFile($key, $mkdir = false)
+ {
+ $hash = str_replace('/', '-', base64_encode(hash('sha256', static::class . $key, true)));
+ $dir = $this->directory . $hash[0] . DIRECTORY_SEPARATOR . $hash[1] . DIRECTORY_SEPARATOR;
+
+ if ($mkdir) {
+ $this->mkdir($dir);
+ }
+
+ return $dir . substr($hash, 2, 20);
+ }
+
+ /**
+ * @param string $namespace
+ * @param string $directory
+ * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
+ */
+ protected function initFileCache($namespace, $directory)
+ {
+ if (!isset($directory[0])) {
+ $directory = sys_get_temp_dir() . '/grav-cache';
+ } else {
+ $directory = realpath($directory) ?: $directory;
+ }
+
+ if (isset($namespace[0])) {
+ if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) {
+ throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));
+ }
+ $directory .= DIRECTORY_SEPARATOR . $namespace;
+ }
+
+ $this->mkdir($directory);
+
+ $directory .= DIRECTORY_SEPARATOR;
+ // On Windows the whole path is limited to 258 chars
+ if ('\\' === DIRECTORY_SEPARATOR && strlen($directory) > 234) {
+ throw new InvalidArgumentException(sprintf('Cache folder is too long (%s)', $directory));
+ }
+ $this->directory = $directory;
+ }
+
+ /**
+ * @param string $file
+ * @param string $data
+ * @param int|null $expiresAt
+ * @return bool
+ */
+ private function write($file, $data, $expiresAt = null)
+ {
+ set_error_handler(__CLASS__.'::throwError');
+
+ try {
+ if ($this->tmp === null) {
+ $this->tmp = $this->directory . uniqid('', true);
+ }
+
+ file_put_contents($this->tmp, $data);
+
+ if ($expiresAt !== null) {
+ touch($this->tmp, $expiresAt);
+ }
+
+ return rename($this->tmp, $file);
+ } finally {
+ restore_error_handler();
+ }
+ }
+
+ /**
+ * @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
+ */
+ public static function throwError($type, $message, $file, $line)
+ {
+ throw new \ErrorException($message, 0, $type, $file, $line);
+ }
+
+ public function __destruct()
+ {
+ if ($this->tmp !== null && file_exists($this->tmp)) {
+ unlink($this->tmp);
+ }
+ }
+}
diff --git a/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php b/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php
new file mode 100644
index 0000000..2d0fe40
--- /dev/null
+++ b/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php
@@ -0,0 +1,62 @@
+cache)) {
+ return $miss;
+ }
+
+ return $this->cache[$key];
+ }
+
+ public function doSet($key, $value, $ttl)
+ {
+ $this->cache[$key] = $value;
+
+ return true;
+ }
+
+ public function doDelete($key)
+ {
+ unset($this->cache[$key]);
+
+ return true;
+ }
+
+ public function doClear()
+ {
+ $this->cache = [];
+
+ return true;
+ }
+
+ public function doHas($key)
+ {
+ return array_key_exists($key, $this->cache);
+ }
+}
diff --git a/system/src/Grav/Framework/Cache/Adapter/SessionCache.php b/system/src/Grav/Framework/Cache/Adapter/SessionCache.php
new file mode 100644
index 0000000..62c68a0
--- /dev/null
+++ b/system/src/Grav/Framework/Cache/Adapter/SessionCache.php
@@ -0,0 +1,79 @@
+doGetStored($key);
+
+ return $stored ? $stored[self::VALUE] : $miss;
+ }
+
+ public function doSet($key, $value, $ttl)
+ {
+ $stored = [self::VALUE => $value];
+ if (null !== $ttl) {
+ $stored[self::LIFETIME] = time() + $ttl;
+
+ }
+
+ $_SESSION[$this->getNamespace()][$key] = $stored;
+
+ return true;
+ }
+
+ public function doDelete($key)
+ {
+ unset($_SESSION[$this->getNamespace()][$key]);
+
+ return true;
+ }
+
+ public function doClear()
+ {
+ unset($_SESSION[$this->getNamespace()]);
+
+ return true;
+ }
+
+ public function doHas($key)
+ {
+ return $this->doGetStored($key) !== null;
+ }
+
+ public function getNamespace()
+ {
+ return 'cache-' . parent::getNamespace();
+ }
+
+ protected function doGetStored($key)
+ {
+ $stored = $_SESSION[$this->getNamespace()][$key] ?? null;
+
+ if (isset($stored[self::LIFETIME]) && $stored[self::LIFETIME] < time()) {
+ unset($_SESSION[$this->getNamespace()][$key]);
+ $stored = null;
+ }
+
+ return $stored ?: null;
+ }
+}
diff --git a/system/src/Grav/Framework/Cache/CacheInterface.php b/system/src/Grav/Framework/Cache/CacheInterface.php
new file mode 100644
index 0000000..5aefe35
--- /dev/null
+++ b/system/src/Grav/Framework/Cache/CacheInterface.php
@@ -0,0 +1,28 @@
+namespace = (string) $namespace;
+ $this->defaultLifetime = $this->convertTtl($defaultLifetime);
+ $this->miss = new \stdClass;
+ }
+
+ /**
+ * @param bool $validation
+ */
+ public function setValidation($validation)
+ {
+ $this->validation = (bool) $validation;
+ }
+
+ /**
+ * @return string
+ */
+ protected function getNamespace()
+ {
+ return $this->namespace;
+ }
+
+ /**
+ * @return int|null
+ */
+ protected function getDefaultLifetime()
+ {
+ return $this->defaultLifetime;
+ }
+
+ /**
+ * @inheritdoc
+ * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
+ */
+ public function get($key, $default = null)
+ {
+ $this->validateKey($key);
+
+ $value = $this->doGet($key, $this->miss);
+
+ return $value !== $this->miss ? $value : $default;
+ }
+
+ /**
+ * @inheritdoc
+ * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
+ */
+ public function set($key, $value, $ttl = null)
+ {
+ $this->validateKey($key);
+
+ $ttl = $this->convertTtl($ttl);
+
+ // If a negative or zero TTL is provided, the item MUST be deleted from the cache.
+ return null !== $ttl && $ttl <= 0 ? $this->doDelete($key) : $this->doSet($key, $value, $ttl);
+ }
+
+ /**
+ * @inheritdoc
+ * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
+ */
+ public function delete($key)
+ {
+ $this->validateKey($key);
+
+ return $this->doDelete($key);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function clear()
+ {
+ return $this->doClear();
+ }
+
+ /**
+ * @inheritdoc
+ * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
+ */
+ public function getMultiple($keys, $default = null)
+ {
+ if ($keys instanceof \Traversable) {
+ $keys = iterator_to_array($keys, false);
+ } elseif (!\is_array($keys)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'Cache keys must be array or Traversable, "%s" given',
+ \is_object($keys) ? \get_class($keys) : \gettype($keys)
+ )
+ );
+ }
+
+ if (empty($keys)) {
+ return [];
+ }
+
+ $this->validateKeys($keys);
+ $keys = array_unique($keys);
+ $keys = array_combine($keys, $keys);
+
+ $list = $this->doGetMultiple($keys, $this->miss);
+
+ // Make sure that values are returned in the same order as the keys were given.
+ $values = [];
+ foreach ($keys as $key) {
+ if (!array_key_exists($key, $list) || $list[$key] === $this->miss) {
+ $values[$key] = $default;
+ } else {
+ $values[$key] = $list[$key];
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * @inheritdoc
+ * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
+ */
+ public function setMultiple($values, $ttl = null)
+ {
+ if ($values instanceof \Traversable) {
+ $values = iterator_to_array($values, true);
+ } elseif (!is_array($values)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'Cache values must be array or Traversable, "%s" given',
+ \is_object($values) ? \get_class($values) : \gettype($values)
+ )
+ );
+ }
+
+ $keys = array_keys($values);
+
+ if (empty($keys)) {
+ return true;
+ }
+
+ $this->validateKeys($keys);
+
+ $ttl = $this->convertTtl($ttl);
+
+ // If a negative or zero TTL is provided, the item MUST be deleted from the cache.
+ return null !== $ttl && $ttl <= 0 ? $this->doDeleteMultiple($keys) : $this->doSetMultiple($values, $ttl);
+ }
+
+ /**
+ * @inheritdoc
+ * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
+ */
+ public function deleteMultiple($keys)
+ {
+ if ($keys instanceof \Traversable) {
+ $keys = iterator_to_array($keys, false);
+ } elseif (!is_array($keys)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'Cache keys must be array or Traversable, "%s" given',
+ \is_object($keys) ? \get_class($keys) : \gettype($keys)
+ )
+ );
+ }
+
+ if (empty($keys)) {
+ return true;
+ }
+
+ $this->validateKeys($keys);
+
+ return $this->doDeleteMultiple($keys);
+ }
+
+ /**
+ * @inheritdoc
+ * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
+ */
+ public function has($key)
+ {
+ $this->validateKey($key);
+
+ return $this->doHas($key);
+ }
+
+ abstract public function doGet($key, $miss);
+ abstract public function doSet($key, $value, $ttl);
+ abstract public function doDelete($key);
+ abstract public function doClear();
+
+ /**
+ * @param array $keys
+ * @param mixed $miss
+ * @return array
+ */
+ public function doGetMultiple($keys, $miss)
+ {
+ $results = [];
+
+ foreach ($keys as $key) {
+ $value = $this->doGet($key, $miss);
+ if ($value !== $miss) {
+ $results[$key] = $value;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * @param array $values
+ * @param int $ttl
+ * @return bool
+ */
+ public function doSetMultiple($values, $ttl)
+ {
+ $success = true;
+
+ foreach ($values as $key => $value) {
+ $success = $this->doSet($key, $value, $ttl) && $success;
+ }
+
+ return $success;
+ }
+
+ /**
+ * @param array $keys
+ * @return bool
+ */
+ public function doDeleteMultiple($keys)
+ {
+ $success = true;
+
+ foreach ($keys as $key) {
+ $success = $this->doDelete($key) && $success;
+ }
+
+ return $success;
+ }
+
+ abstract public function doHas($key);
+
+ /**
+ * @param string|mixed $key
+ * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
+ */
+ protected function validateKey($key)
+ {
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'Cache key must be string, "%s" given',
+ \is_object($key) ? \get_class($key) : \gettype($key)
+ )
+ );
+ }
+ if (!isset($key[0])) {
+ throw new InvalidArgumentException('Cache key length must be greater than zero');
+ }
+ if (\strlen($key) > 64) {
+ throw new InvalidArgumentException(
+ sprintf('Cache key length must be less than 65 characters, key had %s characters', \strlen($key))
+ );
+ }
+ if (strpbrk($key, '{}()/\@:') !== false) {
+ throw new InvalidArgumentException(
+ sprintf('Cache key "%s" contains reserved characters {}()/\@:', $key)
+ );
+ }
+ }
+
+ /**
+ * @param array $keys
+ * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
+ */
+ protected function validateKeys($keys)
+ {
+ if (!$this->validation) {
+ return;
+ }
+
+ foreach ($keys as $key) {
+ $this->validateKey($key);
+ }
+ }
+
+ /**
+ * @param null|int|\DateInterval|mixed $ttl
+ * @return int|null
+ * @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
+ */
+ protected function convertTtl($ttl)
+ {
+ if ($ttl === null) {
+ return $this->getDefaultLifetime();
+ }
+
+ if (\is_int($ttl)) {
+ return $ttl;
+ }
+
+ if ($ttl instanceof \DateInterval) {
+ $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)
+ )
+ );
+ }
+}
diff --git a/system/src/Grav/Framework/Cache/Exception/CacheException.php b/system/src/Grav/Framework/Cache/Exception/CacheException.php
new file mode 100644
index 0000000..55da2f5
--- /dev/null
+++ b/system/src/Grav/Framework/Cache/Exception/CacheException.php
@@ -0,0 +1,20 @@
+path = $path;
+ $this->flags = self::INCLUDE_FILES | self::INCLUDE_FOLDERS;
+ $this->nestingLimit = 0;
+ $this->createObjectFunction = [$this, 'createObject'];
+
+ $this->setIterator();
+ }
+
+ /**
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * @param Criteria $criteria
+ * @return ArrayCollection
+ * @todo Implement lazy matching
+ */
+ public function matching(Criteria $criteria)
+ {
+ $expr = $criteria->getWhereExpression();
+
+ $oldFilter = $this->filterFunction;
+ if ($expr) {
+ $visitor = new ClosureExpressionVisitor();
+ $filter = $visitor->dispatch($expr);
+ $this->addFilter($filter);
+ }
+
+ $filtered = $this->doInitializeByIterator($this->iterator, $this->nestingLimit);
+ $this->filterFunction = $oldFilter;
+
+ if ($orderings = $criteria->getOrderings()) {
+ $next = null;
+ foreach (array_reverse($orderings) as $field => $ordering) {
+ $next = ClosureExpressionVisitor::sortByField($field, $ordering === Criteria::DESC ? -1 : 1, $next);
+ }
+
+ uasort($filtered, $next);
+ } else {
+ ksort($filtered);
+ }
+
+ $offset = $criteria->getFirstResult();
+ $length = $criteria->getMaxResults();
+
+ if ($offset || $length) {
+ $filtered = \array_slice($filtered, (int)$offset, $length);
+ }
+
+ return new ArrayCollection($filtered);
+ }
+
+ protected function setIterator()
+ {
+ $iteratorFlags = \RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS
+ + \FilesystemIterator::CURRENT_AS_SELF + \FilesystemIterator::FOLLOW_SYMLINKS;
+
+ if (strpos($this->path, '://')) {
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+ $this->iterator = $locator->getRecursiveIterator($this->path, $iteratorFlags);
+ } else {
+ $this->iterator = new \RecursiveDirectoryIterator($this->path, $iteratorFlags);
+ }
+ }
+
+ /**
+ * @param callable $filterFunction
+ * @return $this
+ */
+ protected function addFilter(callable $filterFunction)
+ {
+ if ($this->filterFunction) {
+ $oldFilterFunction = $this->filterFunction;
+ $this->filterFunction = function ($expr) use ($oldFilterFunction, $filterFunction) {
+ return $oldFilterFunction($expr) && $filterFunction($expr);
+ };
+ } else {
+ $this->filterFunction = $filterFunction;
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function doInitialize()
+ {
+ $filtered = $this->doInitializeByIterator($this->iterator, $this->nestingLimit);
+ ksort($filtered);
+
+ $this->collection = new ArrayCollection($filtered);
+ }
+
+ protected function doInitializeByIterator(\SeekableIterator $iterator, $nestingLimit)
+ {
+ $children = [];
+ $objects = [];
+ $filter = $this->filterFunction;
+ $objectFunction = $this->createObjectFunction;
+
+ /** @var \RecursiveDirectoryIterator $file */
+ foreach ($iterator as $file) {
+ // Skip files if they shouldn't be included.
+ if (!($this->flags & static::INCLUDE_FILES) && $file->isFile()) {
+ continue;
+ }
+
+ // Apply main filter.
+ if ($filter && !$filter($file)) {
+ continue;
+ }
+
+ // Include children if the recursive flag is set.
+ if (($this->flags & static::RECURSIVE) && $nestingLimit > 0 && $file->hasChildren()) {
+ $children[] = $file->getChildren();
+ }
+
+ // Skip folders if they shouldn't be included.
+ if (!($this->flags & static::INCLUDE_FOLDERS) && $file->isDir()) {
+ continue;
+ }
+
+ $object = $objectFunction($file);
+ $objects[$object->key] = $object;
+ }
+
+ if ($children) {
+ $objects += $this->doInitializeChildren($children, $nestingLimit - 1);
+ }
+
+ return $objects;
+ }
+
+ /**
+ * @param \RecursiveDirectoryIterator[] $children
+ * @return array
+ */
+ protected function doInitializeChildren(array $children, $nestingLimit)
+ {
+ $objects = [];
+
+ foreach ($children as $iterator) {
+ $objects += $this->doInitializeByIterator($iterator, $nestingLimit);
+ }
+
+ return $objects;
+ }
+
+ /**
+ * @param \RecursiveDirectoryIterator $file
+ * @return object
+ */
+ protected function createObject($file)
+ {
+ return (object) [
+ 'key' => $file->getSubPathname(),
+ 'type' => $file->isDir() ? 'folder' : 'file:' . $file->getExtension(),
+ 'url' => method_exists($file, 'getUrl') ? $file->getUrl() : null,
+ 'pathname' => $file->getPathname(),
+ 'mtime' => $file->getMTime()
+ ];
+ }
+}
diff --git a/system/src/Grav/Framework/Collection/AbstractIndexCollection.php b/system/src/Grav/Framework/Collection/AbstractIndexCollection.php
new file mode 100644
index 0000000..0e4fae2
--- /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'];
+ }
+
+ /**
+ * Implements 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
new file mode 100644
index 0000000..0653caa
--- /dev/null
+++ b/system/src/Grav/Framework/Collection/AbstractLazyCollection.php
@@ -0,0 +1,81 @@
+initialize();
+ return $this->collection->reverse();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function shuffle()
+ {
+ $this->initialize();
+ return $this->collection->shuffle();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function chunk($size)
+ {
+ $this->initialize();
+ return $this->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}
+ */
+ public function jsonSerialize()
+ {
+ $this->initialize();
+ return $this->collection->jsonSerialize();
+ }
+}
diff --git a/system/src/Grav/Framework/Collection/ArrayCollection.php b/system/src/Grav/Framework/Collection/ArrayCollection.php
new file mode 100644
index 0000000..f79ab96
--- /dev/null
+++ b/system/src/Grav/Framework/Collection/ArrayCollection.php
@@ -0,0 +1,95 @@
+createFrom(array_reverse($this->toArray()));
+ }
+
+ /**
+ * Shuffle items.
+ *
+ * @return static
+ */
+ public function shuffle()
+ {
+ $keys = $this->getKeys();
+ shuffle($keys);
+
+ return $this->createFrom(array_replace(array_flip($keys), $this->toArray()));
+ }
+
+ /**
+ * Split collection into chunks.
+ *
+ * @param int $size Size of each chunk.
+ * @return array
+ */
+ public function chunk($size)
+ {
+ return array_chunk($this->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));
+ }
+
+ /**
+ * Implements JsonSerializable interface.
+ *
+ * @return array
+ */
+ public function jsonSerialize()
+ {
+ return $this->toArray();
+ }
+}
diff --git a/system/src/Grav/Framework/Collection/CollectionInterface.php b/system/src/Grav/Framework/Collection/CollectionInterface.php
new file mode 100644
index 0000000..9239d4a
--- /dev/null
+++ b/system/src/Grav/Framework/Collection/CollectionInterface.php
@@ -0,0 +1,60 @@
+flags = (int)($flags ?: self::INCLUDE_FILES | self::INCLUDE_FOLDERS | self::RECURSIVE);
+
+ $this->setIterator();
+ $this->setFilter();
+ $this->setObjectBuilder();
+ $this->setNestingLimit();
+ }
+
+ /**
+ * @return int
+ */
+ public function getFlags()
+ {
+ return $this->flags;
+ }
+
+ /**
+ * @return int
+ */
+ public function getNestingLimit()
+ {
+ return $this->nestingLimit;
+ }
+
+ /**
+ * @param int $limit
+ * @return $this
+ */
+ public function setNestingLimit($limit = 99)
+ {
+ $this->nestingLimit = (int) $limit;
+
+ return $this;
+ }
+
+ /**
+ * @param callable|null $filterFunction
+ * @return $this
+ */
+ public function setFilter(callable $filterFunction = null)
+ {
+ $this->filterFunction = $filterFunction;
+
+ return $this;
+ }
+
+ /**
+ * @param callable $filterFunction
+ * @return $this
+ */
+ public function addFilter(callable $filterFunction)
+ {
+ parent::addFilter($filterFunction);
+
+ return $this;
+ }
+
+ /**
+ * @param callable|null $objectFunction
+ * @return $this
+ */
+ public function setObjectBuilder(callable $objectFunction = null)
+ {
+ $this->createObjectFunction = $objectFunction ?: [$this, 'createObject'];
+
+ return $this;
+ }
+}
diff --git a/system/src/Grav/Framework/Collection/FileCollectionInterface.php b/system/src/Grav/Framework/Collection/FileCollectionInterface.php
new file mode 100644
index 0000000..fec4129
--- /dev/null
+++ b/system/src/Grav/Framework/Collection/FileCollectionInterface.php
@@ -0,0 +1,29 @@
+setContent('my inner content');
+ * $outerBlock = ContentBlock::create();
+ * $outerBlock->setContent(sprintf('Inside my outer block I have %s.', $innerBlock->getToken()));
+ * $outerBlock->addBlock($innerBlock);
+ * echo $outerBlock;
+ *
+ * @package Grav\Framework\ContentBlock
+ */
+class ContentBlock implements ContentBlockInterface
+{
+ protected $version = 1;
+ protected $id;
+ protected $tokenTemplate = '@@BLOCK-%s@@';
+ protected $content = '';
+ protected $blocks = [];
+ protected $checksum;
+ protected $cached = true;
+
+ /**
+ * @param string $id
+ * @return static
+ */
+ public static function create($id = null)
+ {
+ return new static($id);
+ }
+
+ /**
+ * @param array $serialized
+ * @return ContentBlockInterface
+ * @throws \InvalidArgumentException
+ */
+ public static function fromArray(array $serialized)
+ {
+ try {
+ $type = $serialized['_type'] ?? null;
+ $id = $serialized['id'] ?? null;
+
+ if (!$type || !$id || !is_a($type, ContentBlockInterface::class, true)) {
+ throw new \InvalidArgumentException('Bad data');
+ }
+
+ /** @var ContentBlockInterface $instance */
+ $instance = new $type($id);
+ $instance->build($serialized);
+ } catch (\Exception $e) {
+ throw new \InvalidArgumentException(sprintf('Cannot unserialize Block: %s', $e->getMessage()), $e->getCode(), $e);
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Block constructor.
+ *
+ * @param string $id
+ */
+ public function __construct($id = null)
+ {
+ $this->id = $id ? (string) $id : $this->generateId();
+ }
+
+ /**
+ * @return string
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * @return string
+ */
+ public function getToken()
+ {
+ return sprintf($this->tokenTemplate, $this->getId());
+ }
+
+ /**
+ * @return array
+ */
+ public function toArray()
+ {
+ $blocks = [];
+ /**
+ * @var string $id
+ * @var ContentBlockInterface $block
+ */
+ foreach ($this->blocks as $block) {
+ $blocks[$block->getId()] = $block->toArray();
+ }
+
+ $array = [
+ '_type' => \get_class($this),
+ '_version' => $this->version,
+ 'id' => $this->id,
+ 'cached' => $this->cached
+ ];
+
+ if ($this->checksum) {
+ $array['checksum'] = $this->checksum;
+ }
+
+ if ($this->content) {
+ $array['content'] = $this->content;
+ }
+
+ if ($blocks) {
+ $array['blocks'] = $blocks;
+ }
+
+ return $array;
+ }
+
+ /**
+ * @return string
+ */
+ public function toString()
+ {
+ if (!$this->blocks) {
+ return (string) $this->content;
+ }
+
+ $tokens = [];
+ $replacements = [];
+ foreach ($this->blocks as $block) {
+ $tokens[] = $block->getToken();
+ $replacements[] = $block->toString();
+ }
+
+ return str_replace($tokens, $replacements, (string) $this->content);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ try {
+ return $this->toString();
+ } catch (\Exception $e) {
+ return sprintf('Error while rendering block: %s', $e->getMessage());
+ }
+ }
+
+ /**
+ * @param array $serialized
+ * @throws \RuntimeException
+ */
+ public function build(array $serialized)
+ {
+ $this->checkVersion($serialized);
+
+ $this->id = $serialized['id'] ?? $this->generateId();
+ $this->checksum = $serialized['checksum'] ?? null;
+ $this->cached = $serialized['cached'] ?? null;
+
+ if (isset($serialized['content'])) {
+ $this->setContent($serialized['content']);
+ }
+
+ $blocks = isset($serialized['blocks']) ? (array) $serialized['blocks'] : [];
+ foreach ($blocks as $block) {
+ $this->addBlock(self::fromArray($block));
+ }
+ }
+
+ /**
+ * @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
+ */
+ public function setChecksum($checksum)
+ {
+ $this->checksum = $checksum;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getChecksum()
+ {
+ return $this->checksum;
+ }
+
+ /**
+ * @param string $content
+ * @return $this
+ */
+ public function setContent($content)
+ {
+ $this->content = $content;
+
+ return $this;
+ }
+
+ /**
+ * @param ContentBlockInterface $block
+ * @return $this
+ */
+ public function addBlock(ContentBlockInterface $block)
+ {
+ $this->blocks[$block->getId()] = $block;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function serialize()
+ {
+ return serialize($this->toArray());
+ }
+
+ /**
+ * @param string $serialized
+ */
+ public function unserialize($serialized)
+ {
+ $array = unserialize($serialized);
+ $this->build($array);
+ }
+
+ /**
+ * @return string
+ */
+ protected function generateId()
+ {
+ return uniqid('', true);
+ }
+
+ /**
+ * @param array $serialized
+ * @throws \RuntimeException
+ */
+ protected function checkVersion(array $serialized)
+ {
+ $version = isset($serialized['_version']) ? (int) $serialized['_version'] : 1;
+ if ($version !== $this->version) {
+ throw new \RuntimeException(sprintf('Unsupported version %s', $version));
+ }
+ }
+}
diff --git a/system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php b/system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php
new file mode 100644
index 0000000..330901e
--- /dev/null
+++ b/system/src/Grav/Framework/ContentBlock/ContentBlockInterface.php
@@ -0,0 +1,87 @@
+getAssetsFast();
+
+ $this->sortAssets($assets['styles']);
+ $this->sortAssets($assets['scripts']);
+ $this->sortAssets($assets['html']);
+
+ return $assets;
+ }
+
+ /**
+ * @return array
+ */
+ public function getFrameworks()
+ {
+ $assets = $this->getAssetsFast();
+
+ return array_keys($assets['frameworks']);
+ }
+
+ /**
+ * @param string $location
+ * @return array
+ */
+ public function getStyles($location = 'head')
+ {
+ return $this->getAssetsInLocation('styles', $location);
+ }
+
+ /**
+ * @param string $location
+ * @return array
+ */
+ public function getScripts($location = 'head')
+ {
+ return $this->getAssetsInLocation('scripts', $location);
+ }
+
+ /**
+ * @param string $location
+ * @return array
+ */
+ public function getHtml($location = 'bottom')
+ {
+ return $this->getAssetsInLocation('html', $location);
+ }
+
+ /**
+ * @return array[]
+ */
+ public function toArray()
+ {
+ $array = parent::toArray();
+
+ if ($this->frameworks) {
+ $array['frameworks'] = $this->frameworks;
+ }
+ if ($this->styles) {
+ $array['styles'] = $this->styles;
+ }
+ if ($this->scripts) {
+ $array['scripts'] = $this->scripts;
+ }
+ if ($this->html) {
+ $array['html'] = $this->html;
+ }
+
+ return $array;
+ }
+
+ /**
+ * @param array $serialized
+ * @throws \RuntimeException
+ */
+ public function build(array $serialized)
+ {
+ parent::build($serialized);
+
+ $this->frameworks = isset($serialized['frameworks']) ? (array) $serialized['frameworks'] : [];
+ $this->styles = isset($serialized['styles']) ? (array) $serialized['styles'] : [];
+ $this->scripts = isset($serialized['scripts']) ? (array) $serialized['scripts'] : [];
+ $this->html = isset($serialized['html']) ? (array) $serialized['html'] : [];
+ }
+
+ /**
+ * @param string $framework
+ * @return $this
+ */
+ public function addFramework($framework)
+ {
+ $this->frameworks[$framework] = 1;
+
+ return $this;
+ }
+
+ /**
+ * @param string|array $element
+ * @param int $priority
+ * @param string $location
+ * @return bool
+ *
+ * @example $block->addStyle('assets/js/my.js');
+ * @example $block->addStyle(['href' => 'assets/js/my.js', 'media' => 'screen']);
+ */
+ public function addStyle($element, $priority = 0, $location = 'head')
+ {
+ if (!\is_array($element)) {
+ $element = ['href' => (string) $element];
+ }
+ if (empty($element['href'])) {
+ return false;
+ }
+ if (!isset($this->styles[$location])) {
+ $this->styles[$location] = [];
+ }
+
+ $id = !empty($element['id']) ? ['id' => (string) $element['id']] : [];
+ $href = $element['href'];
+ $type = !empty($element['type']) ? (string) $element['type'] : 'text/css';
+ $media = !empty($element['media']) ? (string) $element['media'] : null;
+ unset(
+ $element['tag'],
+ $element['id'],
+ $element['rel'],
+ $element['content'],
+ $element['href'],
+ $element['type'],
+ $element['media']
+ );
+
+ $this->styles[$location][md5($href) . sha1($href)] = [
+ ':type' => 'file',
+ ':priority' => (int) $priority,
+ 'href' => $href,
+ 'type' => $type,
+ 'media' => $media,
+ 'element' => $element
+ ] + $id;
+
+ return true;
+ }
+
+ /**
+ * @param string|array $element
+ * @param int $priority
+ * @param string $location
+ * @return bool
+ */
+ public function addInlineStyle($element, $priority = 0, $location = 'head')
+ {
+ if (!\is_array($element)) {
+ $element = ['content' => (string) $element];
+ }
+ if (empty($element['content'])) {
+ return false;
+ }
+ if (!isset($this->styles[$location])) {
+ $this->styles[$location] = [];
+ }
+
+ $content = (string) $element['content'];
+ $type = !empty($element['type']) ? (string) $element['type'] : 'text/css';
+
+ $this->styles[$location][md5($content) . sha1($content)] = [
+ ':type' => 'inline',
+ ':priority' => (int) $priority,
+ 'content' => $content,
+ 'type' => $type
+ ];
+
+ return true;
+ }
+
+ /**
+ * @param string|array $element
+ * @param int $priority
+ * @param string $location
+ * @return bool
+ */
+ public function addScript($element, $priority = 0, $location = 'head')
+ {
+ if (!\is_array($element)) {
+ $element = ['src' => (string) $element];
+ }
+ if (empty($element['src'])) {
+ return false;
+ }
+ if (!isset($this->scripts[$location])) {
+ $this->scripts[$location] = [];
+ }
+
+ $src = $element['src'];
+ $type = !empty($element['type']) ? (string) $element['type'] : 'text/javascript';
+ $defer = isset($element['defer']) ? true : false;
+ $async = isset($element['async']) ? true : false;
+ $handle = !empty($element['handle']) ? (string) $element['handle'] : '';
+
+ $this->scripts[$location][md5($src) . sha1($src)] = [
+ ':type' => 'file',
+ ':priority' => (int) $priority,
+ 'src' => $src,
+ 'type' => $type,
+ 'defer' => $defer,
+ 'async' => $async,
+ 'handle' => $handle
+ ];
+
+ return true;
+ }
+
+ /**
+ * @param string|array $element
+ * @param int $priority
+ * @param string $location
+ * @return bool
+ */
+ public function addInlineScript($element, $priority = 0, $location = 'head')
+ {
+ if (!\is_array($element)) {
+ $element = ['content' => (string) $element];
+ }
+ if (empty($element['content'])) {
+ return false;
+ }
+ if (!isset($this->scripts[$location])) {
+ $this->scripts[$location] = [];
+ }
+
+ $content = (string) $element['content'];
+ $type = !empty($element['type']) ? (string) $element['type'] : 'text/javascript';
+
+ $this->scripts[$location][md5($content) . sha1($content)] = [
+ ':type' => 'inline',
+ ':priority' => (int) $priority,
+ 'content' => $content,
+ 'type' => $type
+ ];
+
+ return true;
+ }
+
+ /**
+ * @param string $html
+ * @param int $priority
+ * @param string $location
+ * @return bool
+ */
+ public function addHtml($html, $priority = 0, $location = 'bottom')
+ {
+ if (empty($html) || !\is_string($html)) {
+ return false;
+ }
+ if (!isset($this->html[$location])) {
+ $this->html[$location] = [];
+ }
+
+ $this->html[$location][md5($html) . sha1($html)] = [
+ ':priority' => (int) $priority,
+ 'html' => $html
+ ];
+
+ return true;
+ }
+
+ /**
+ * @return array
+ */
+ protected function getAssetsFast()
+ {
+ $assets = [
+ 'frameworks' => $this->frameworks,
+ 'styles' => $this->styles,
+ 'scripts' => $this->scripts,
+ 'html' => $this->html
+ ];
+
+ foreach ($this->blocks as $block) {
+ if ($block instanceof self) {
+ $blockAssets = $block->getAssetsFast();
+ $assets['frameworks'] += $blockAssets['frameworks'];
+
+ foreach ($blockAssets['styles'] as $location => $styles) {
+ if (!isset($assets['styles'][$location])) {
+ $assets['styles'][$location] = $styles;
+ } elseif ($styles) {
+ $assets['styles'][$location] += $styles;
+ }
+ }
+
+ foreach ($blockAssets['scripts'] as $location => $scripts) {
+ if (!isset($assets['scripts'][$location])) {
+ $assets['scripts'][$location] = $scripts;
+ } elseif ($scripts) {
+ $assets['scripts'][$location] += $scripts;
+ }
+ }
+
+ foreach ($blockAssets['html'] as $location => $htmls) {
+ if (!isset($assets['html'][$location])) {
+ $assets['html'][$location] = $htmls;
+ } elseif ($htmls) {
+ $assets['html'][$location] += $htmls;
+ }
+ }
+ }
+ }
+
+ return $assets;
+ }
+
+ /**
+ * @param string $type
+ * @param string $location
+ * @return array
+ */
+ protected function getAssetsInLocation($type, $location)
+ {
+ $assets = $this->getAssetsFast();
+
+ if (empty($assets[$type][$location])) {
+ return [];
+ }
+
+ $styles = $assets[$type][$location];
+ $this->sortAssetsInLocation($styles);
+
+ return $styles;
+ }
+
+ /**
+ * @param array $items
+ */
+ protected function sortAssetsInLocation(array &$items)
+ {
+ $count = 0;
+ foreach ($items as &$item) {
+ $item[':order'] = ++$count;
+ }
+ unset($item);
+
+ uasort(
+ $items,
+ function ($a, $b) {
+ return ($a[':priority'] === $b[':priority'])
+ ? $a[':order'] - $b[':order'] : $a[':priority'] - $b[':priority'];
+ }
+ );
+ }
+
+ /**
+ * @param array $array
+ */
+ protected function sortAssets(array &$array)
+ {
+ foreach ($array as $location => &$items) {
+ $this->sortAssetsInLocation($items);
+ }
+ }
+}
diff --git a/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php b/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php
new file mode 100644
index 0000000..ee486d2
--- /dev/null
+++ b/system/src/Grav/Framework/ContentBlock/HtmlBlockInterface.php
@@ -0,0 +1,95 @@
+addStyle('assets/js/my.js');
+ * @example $block->addStyle(['href' => 'assets/js/my.js', 'media' => 'screen']);
+ */
+ public function addStyle($element, $priority = 0, $location = 'head');
+
+ /**
+ * @param string|array $element
+ * @param int $priority
+ * @param string $location
+ * @return bool
+ */
+ public function addInlineStyle($element, $priority = 0, $location = 'head');
+
+ /**
+ * @param string|array $element
+ * @param int $priority
+ * @param string $location
+ * @return bool
+ */
+ public function addScript($element, $priority = 0, $location = 'head');
+
+
+ /**
+ * @param string|array $element
+ * @param int $priority
+ * @param string $location
+ * @return bool
+ */
+ public function addInlineScript($element, $priority = 0, $location = 'head');
+
+ /**
+ * @param string $html
+ * @param int $priority
+ * @param string $location
+ * @return bool
+ */
+ public function addHtml($html, $priority = 0, $location = 'bottom');
+}
diff --git a/system/src/Grav/Framework/DI/Container.php b/system/src/Grav/Framework/DI/Container.php
new file mode 100644
index 0000000..c4b3ef4
--- /dev/null
+++ b/system/src/Grav/Framework/DI/Container.php
@@ -0,0 +1,27 @@
+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..e412a73
--- /dev/null
+++ b/system/src/Grav/Framework/File/Formatter/CsvFormatter.php
@@ -0,0 +1,127 @@
+ ['.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 = $this->encodeLine($header, $delimiter);
+
+ // Encode the data
+ foreach ($data as $row) {
+ $string .= $this->encodeLine($row, $delimiter);
+ }
+
+ 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);
+
+ // Allow for replacing a null string with null/empty value
+ $null_replace = $this->getConfig('null');
+
+ // Get the data
+ $list = [];
+ $line = null;
+ try {
+ foreach ($lines as $line) {
+ if (!empty($line)) {
+ $csv_line = str_getcsv($line, $delimiter);
+
+ if ($null_replace) {
+ array_walk($csv_line, function(&$el) use ($null_replace) {
+ $el = str_replace($null_replace, "\0", $el);
+ });
+ }
+
+ $list[] = array_combine($header, $csv_line);
+ }
+ }
+ } catch (\Exception $e) {
+ throw new \Exception('Badly formatted CSV line: ' . $line);
+ }
+
+ return $list;
+ }
+
+ protected function encodeLine(array $line, $delimiter = null): string
+ {
+ foreach ($line as $key => &$value) {
+ $value = $this->escape((string)$value);
+ }
+ unset($value);
+
+ return implode($delimiter, $line). "\n";
+ }
+
+ protected function escape(string $value)
+ {
+ if (preg_match('/[,"\r\n]/u', $value)) {
+ $value = '"' . preg_replace('/"/', '""', $value) . '"';
+ }
+
+ return $value;
+ }
+}
diff --git a/system/src/Grav/Framework/File/Formatter/FormatterInterface.php b/system/src/Grav/Framework/File/Formatter/FormatterInterface.php
new file mode 100644
index 0000000..a8d5bc5
--- /dev/null
+++ b/system/src/Grav/Framework/File/Formatter/FormatterInterface.php
@@ -0,0 +1,10 @@
+ '.ini'
+ ];
+
+ parent::__construct($config);
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileFormatterInterface::encode()
+ */
+ public function encode($data): string
+ {
+ $string = '';
+ foreach ($data as $key => $value) {
+ $string .= $key . '="' . preg_replace(
+ ['/"/', '/\\\/', "/\t/", "/\n/", "/\r/"],
+ ['\"', '\\\\', '\t', '\n', '\r'],
+ $value
+ ) . "\"\n";
+ }
+
+ return $string;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileFormatterInterface::decode()
+ */
+ public function decode($data): array
+ {
+ $decoded = @parse_ini_string($data);
+
+ if ($decoded === false) {
+ throw new \RuntimeException('Decoding INI failed');
+ }
+
+ return $decoded;
+ }
+}
diff --git a/system/src/Grav/Framework/File/Formatter/JsonFormatter.php b/system/src/Grav/Framework/File/Formatter/JsonFormatter.php
new file mode 100644
index 0000000..1ae16ae
--- /dev/null
+++ b/system/src/Grav/Framework/File/Formatter/JsonFormatter.php
@@ -0,0 +1,100 @@
+ '.json',
+ 'encode_options' => 0,
+ 'decode_assoc' => true,
+ 'decode_depth' => 512,
+ 'decode_options' => 0
+ ];
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Returns options used in encode() function.
+ *
+ * @return int
+ */
+ public function getEncodeOptions(): int
+ {
+ return $this->getConfig('encode_options');
+ }
+
+ /**
+ * 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 encode($data): string
+ {
+ $encoded = @json_encode($data, $this->getEncodeOptions());
+
+ if ($encoded === false && json_last_error() !== JSON_ERROR_NONE) {
+ throw new \RuntimeException('Encoding JSON failed: ' . json_last_error_msg());
+ }
+
+ return $encoded;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileFormatterInterface::decode()
+ */
+ public function decode($data)
+ {
+ $decoded = @json_decode($data, $this->getDecodeAssoc(), $this->getDecodeDepth(), $this->getDecodeOptions());
+
+ 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
new file mode 100644
index 0000000..a2f84e8
--- /dev/null
+++ b/system/src/Grav/Framework/File/Formatter/MarkdownFormatter.php
@@ -0,0 +1,138 @@
+ '.md',
+ 'header' => 'header',
+ 'body' => 'markdown',
+ 'raw' => 'frontmatter',
+ 'yaml' => ['inline' => 20]
+ ];
+
+ parent::__construct($config);
+
+ $this->headerFormatter = $headerFormatter ?: new YamlFormatter($config['yaml']);
+ }
+
+ /**
+ * Returns header field used in both encode() and decode().
+ *
+ * @return string
+ */
+ public function getHeaderField(): string
+ {
+ return $this->getConfig('header');
+ }
+
+ /**
+ * 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 encode($data): string
+ {
+ $headerVar = $this->getHeaderField();
+ $bodyVar = $this->getBodyField();
+
+ $header = isset($data[$headerVar]) ? (array) $data[$headerVar] : [];
+ $body = isset($data[$bodyVar]) ? (string) $data[$bodyVar] : '';
+
+ // Create Markdown file with YAML header.
+ $encoded = '';
+ if ($header) {
+ $encoded = "---\n" . trim($this->getHeaderFormatter()->encode($data['header'])) . "\n---\n\n";
+ }
+ $encoded .= $body;
+
+ // Normalize line endings to Unix style.
+ $encoded = preg_replace("/(\r\n|\r)/", "\n", $encoded);
+
+ return $encoded;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileFormatterInterface::decode()
+ */
+ public function decode($data): array
+ {
+ $headerVar = $this->getHeaderField();
+ $bodyVar = $this->getBodyField();
+ $rawVar = $this->getRawField();
+
+ // Define empty content
+ $content = [
+ $headerVar => [],
+ $bodyVar => ''
+ ];
+
+ $headerRegex = "/^---\n(.+?)\n---\n{0,}(.*)$/uis";
+
+ // Normalize line endings to Unix style.
+ $data = preg_replace("/(\r\n|\r)/", "\n", $data);
+
+ // Parse header.
+ preg_match($headerRegex, ltrim($data), $matches);
+ if(empty($matches)) {
+ $content[$bodyVar] = $data;
+ } else {
+ // Normalize frontmatter.
+ $frontmatter = preg_replace("/\n\t/", "\n ", $matches[1]);
+ if ($rawVar) {
+ $content[$rawVar] = $frontmatter;
+ }
+ $content[$headerVar] = $this->getHeaderFormatter()->decode($frontmatter);
+ $content[$bodyVar] = $matches[2];
+ }
+
+ return $content;
+ }
+}
diff --git a/system/src/Grav/Framework/File/Formatter/SerializeFormatter.php b/system/src/Grav/Framework/File/Formatter/SerializeFormatter.php
new file mode 100644
index 0000000..73ed812
--- /dev/null
+++ b/system/src/Grav/Framework/File/Formatter/SerializeFormatter.php
@@ -0,0 +1,89 @@
+ '.ser',
+ 'decode_options' => ['allowed_classes' => [\stdClass::class]]
+ ];
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Returns options used in decode().
+ *
+ * By default only allow stdClass class.
+ *
+ * @return array|bool
+ */
+ public function getOptions()
+ {
+ return $this->getConfig('decode_options');
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileFormatterInterface::encode()
+ */
+ 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, $this->getOptions());
+
+ if ($decoded === false && $data !== serialize(false)) {
+ throw new \RuntimeException('Decoding serialized data failed');
+ }
+
+ return $this->preserveLines($decoded, ['\\n', '\\r'], ["\n", "\r"]);
+ }
+
+ /**
+ * Preserve new lines, recursive function.
+ *
+ * @param mixed $data
+ * @param array $search
+ * @param array $replace
+ * @return mixed
+ */
+ protected function preserveLines($data, array $search, array $replace)
+ {
+ if (\is_string($data)) {
+ $data = str_replace($search, $replace, $data);
+ } elseif (\is_array($data)) {
+ foreach ($data as &$value) {
+ $value = $this->preserveLines($value, $search, $replace);
+ }
+ unset($value);
+ }
+
+ return $data;
+ }
+}
\ No newline at end of file
diff --git a/system/src/Grav/Framework/File/Formatter/YamlFormatter.php b/system/src/Grav/Framework/File/Formatter/YamlFormatter.php
new file mode 100644
index 0000000..189626d
--- /dev/null
+++ b/system/src/Grav/Framework/File/Formatter/YamlFormatter.php
@@ -0,0 +1,114 @@
+ '.yaml',
+ 'inline' => 5,
+ 'indent' => 2,
+ 'native' => true,
+ 'compat' => true
+ ];
+
+ parent::__construct($config);
+ }
+
+ /**
+ * @return int
+ */
+ public function getInlineOption(): int
+ {
+ return $this->getConfig('inline');
+ }
+
+ /**
+ * @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 encode($data, $inline = null, $indent = null): string
+ {
+ try {
+ return YamlParser::dump(
+ $data,
+ $inline ? (int) $inline : $this->getInlineOption(),
+ $indent ? (int) $indent : $this->getIndentOption(),
+ YamlParser::DUMP_EXCEPTION_ON_INVALID_TYPE
+ );
+ } catch (DumpException $e) {
+ throw new \RuntimeException('Encoding YAML failed: ' . $e->getMessage(), 0, $e);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ * @see FileFormatterInterface::decode()
+ */
+ public function decode($data): array
+ {
+ // Try native PECL YAML PHP extension first if available.
+ if (\function_exists('yaml_parse') && $this->useNativeDecoder()) {
+ // Safely decode YAML.
+ $saved = @ini_get('yaml.decode_php');
+ @ini_set('yaml.decode_php', '0');
+ $decoded = @yaml_parse($data);
+ @ini_set('yaml.decode_php', $saved);
+
+ if ($decoded !== false) {
+ return (array) $decoded;
+ }
+ }
+
+ try {
+ return (array) YamlParser::parse($data);
+ } catch (ParseException $e) {
+ if ($this->useCompatibleDecoder()) {
+ return (array) FallbackYamlParser::parse($data);
+ }
+
+ throw new \RuntimeException('Decoding YAML failed: ' . $e->getMessage(), 0, $e);
+ }
+ }
+}
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..e87e903
--- /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[$type] = $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..5e5431b
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/FlexCollection.php
@@ -0,0 +1,525 @@
+ 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|Collection
+ */
+ 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",
+ "flex-objects/layouts/_default/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..c75a901
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/FlexDirectory.php
@@ -0,0 +1,669 @@
+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'])) {
+ $cache = new MemoryCache('flex-objects-' . $this->getFlexType());
+ } else {
+ $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 {
+ if (!$cache instanceof MemoryCache) {
+ $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);
+ if (!$cache instanceof MemoryCache) {
+ $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..6ded345
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/FlexForm.php
@@ -0,0 +1,344 @@
+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|object
+ */
+ 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()->getFormValue($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 Template|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..8b96b4a
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/FlexIndex.php
@@ -0,0 +1,704 @@
+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) {
+ $field = (string)$field;
+ 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..7fbdbc0
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/FlexObject.php
@@ -0,0 +1,877 @@
+ 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', []);
+ if (!$properties) {
+ foreach ($this->getFlexDirectory()->getConfig('admin.list.fields', []) as $property => $value) {
+ if (!empty($value['link'])) {
+ $properties[] = $property;
+ }
+ }
+ }
+
+ $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'] ?? 1);
+ }
+
+ 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 && $key ? $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 ?: null);
+ $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 && $key && $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 = (string)key($result);
+ if ($value && $storageKey) {
+ $this->setStorageKey($storageKey);
+ if (!$this->hasKey()) {
+ $this->setKey($storageKey);
+ }
+ }
+
+ // FIXME: For some reason locator caching isn't cleared for the file, investigate!
+ $locator = Grav::instance()['locator'];
+ $locator->clearCache();
+
+ // Make sure that the object exists before continuing (just in case).
+ if (!$this->exists()) {
+ throw new \RuntimeException('Saving failed: Object does not exist!');
+ }
+
+ if (method_exists($this, 'saveUpdatedMedia')) {
+ $this->saveUpdatedMedia();
+ }
+
+ try {
+ $this->getFlexDirectory()->clearCache();
+ if (method_exists($this, 'clearMediaCache')) {
+ $this->clearMediaCache();
+ }
+ } catch (\Exception $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 (\Exception $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|null $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 Template|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",
+ "flex-objects/layouts/_default/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..cc460cc
--- /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..76397c2
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/Storage/FolderStorage.php
@@ -0,0 +1,470 @@
+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();
+
+ /** @var string $pattern */
+ $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..088b952
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/Storage/SimpleStorage.php
@@ -0,0 +1,323 @@
+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) {
+ if (strpos($key, '@@')) {
+ $key = $this->getNewKey();
+ }
+ $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;
+
+ $data = array_combine($keys, $this->data);
+ if (false === $data) {
+ throw new \LogicException('Bad data');
+ }
+
+ $this->data = $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..e1b63d9
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php
@@ -0,0 +1,60 @@
+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';
+ }
+
+ $directory = $this instanceof FlexDirectory ? $this : $this->getFlexDirectory();
+ $config = $directory->getConfig();
+ $allowed = $config->get("{$scope}.actions.{$action}") ?? $config->get("actions.{$action}") ?? true;
+
+ return $allowed && $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..c894634
--- /dev/null
+++ b/system/src/Grav/Framework/Flex/Traits/FlexMediaTrait.php
@@ -0,0 +1,346 @@
+ $this->getUpdatedMedia()
+ ];
+ }
+
+ /**
+ * @return string
+ */
+ public function getStorageFolder()
+ {
+ return $this->exists() ? $this->getFlexDirectory()->getStorageFolder($this->getStorageKey()) : '';
+ }
+
+ /**
+ * @return string
+ */
+ public function getMediaFolder()
+ {
+ return $this->exists() ? $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 (!$path) {
+ $language = $grav['language'];
+
+ throw new RuntimeException($language->translate('PLUGIN_ADMIN.FAILED_TO_MOVE_UPLOADED_FILE'), 400);
+ }
+
+ 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();
+ $path = $media->getPath();
+ if (!$path) {
+ return;
+ }
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $grav['locator'];
+
+ $targetPath = $path . '/' . $dirname;
+ $targetFile = $path . '/' . $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(): string;
+}
diff --git a/system/src/Grav/Framework/Form/FormFlash.php b/system/src/Grav/Framework/Form/FormFlash.php
new file mode 100644
index 0000000..e2ba826
--- /dev/null
+++ b/system/src/Grav/Framework/Form/FormFlash.php
@@ -0,0 +1,519 @@
+ $args[0],
+ 'unique_id' => $args[1] ?? null,
+ 'form_name' => $args[2] ?? null,
+ ];
+ }
+
+ $this->sessionId = $config['session_id'] ?? 'no-session';
+ $this->uniqueId = $config['unique_id'] ?? '';
+
+ $folder = $config['folder'] ?? ($this->sessionId ? 'tmp://forms/' . $this->sessionId : '');
+
+ /** @var UniformResourceLocator $locator */
+ $locator = Grav::instance()['locator'];
+
+ $this->folder = $folder && $locator->isStream($folder) ? $locator->findResource($folder, true, true) : $folder;
+ $file = $this->getTmpIndex();
+ $this->exists = $file->exists();
+
+ if ($this->exists) {
+ try {
+ $data = (array)$file->content();
+ } catch (\Exception $e) {
+ $data = [];
+ }
+ $this->formName = $content['form'] ?? $config['form_name'] ?? '';
+ $this->url = $data['url'] ?? '';
+ $this->user = $data['user'] ?? null;
+ $this->updatedTimestamp = $data['timestamps']['updated'] ?? time();
+ $this->createdTimestamp = $data['timestamps']['created'] ?? $this->updatedTimestamp;
+ $this->data = $data['data'] ?? null;
+ $this->files = $data['files'] ?? [];
+ } else {
+ $this->formName = $config['form_name'] ?? '';
+ $this->url = '';
+ $this->createdTimestamp = $this->updatedTimestamp = time();
+ $this->files = [];
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getSessionId(): string
+ {
+ return $this->sessionId;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getUniqueId(): string
+ {
+ return $this->uniqueId;
+ }
+
+ /**
+ * @deprecated 1.6.11 Use '->getUniqueId()' method instead.
+ */
+ public function getUniqieId(): string
+ {
+ user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6.11, use ->getUniqueId() method instead', E_USER_DEPRECATED);
+
+ return $this->getUniqueId();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFormName(): string
+ {
+ return $this->formName;
+ }
+
+
+ /**
+ * @inheritDoc
+ */
+ public function getUrl(): string
+ {
+ return $this->url;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getUsername(): string
+ {
+ return $this->user['username'] ?? '';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getUserEmail(): string
+ {
+ return $this->user['email'] ?? '';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getCreatedTimestamp(): int
+ {
+ return $this->createdTimestamp;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getUpdatedTimestamp(): int
+ {
+ return $this->updatedTimestamp;
+ }
+
+
+ /**
+ * @inheritDoc
+ */
+ public function getData(): ?array
+ {
+ return $this->data;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setData(?array $data): void
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function exists(): bool
+ {
+ return $this->exists;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function save(): self
+ {
+ if (!($this->folder && $this->uniqueId)) {
+ return $this;
+ }
+
+ if ($this->data || $this->files) {
+ // Only save if there is data or files to be saved.
+ $file = $this->getTmpIndex();
+ $file->save($this->jsonSerialize());
+ $this->exists = true;
+ } elseif ($this->exists) {
+ // Delete empty form flash if it exists (it carries no information).
+ return $this->delete();
+ }
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function delete(): self
+ {
+ if ($this->folder && $this->uniqueId) {
+ $this->removeTmpDir();
+ $this->files = [];
+ $this->exists = false;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ 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];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ 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;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ 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;
+ }
+
+
+ /**
+ * @inheritDoc
+ */
+ 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;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ 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;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function clearFiles()
+ {
+ foreach ($this->files as $field => $files) {
+ foreach ($files as $name => $upload) {
+ $this->removeTmpFile($upload['tmp_name'] ?? '');
+ }
+ }
+
+ $this->files = [];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function jsonSerialize(): array
+ {
+ return [
+ 'form' => $this->formName,
+ 'unique_id' => $this->uniqueId,
+ 'url' => $this->url,
+ 'user' => $this->user,
+ 'timestamps' => [
+ 'created' => $this->createdTimestamp,
+ 'updated' => time(),
+ ],
+ 'data' => $this->data,
+ 'files' => $this->files
+ ];
+ }
+
+ /**
+ * @param string $url
+ * @return $this
+ */
+ public function setUrl(string $url): self
+ {
+ $this->url = $url;
+
+ return $this;
+ }
+
+ /**
+ * @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;
+ }
+
+ /**
+ * @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;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTmpDir(): string
+ {
+ return $this->folder && $this->uniqueId ? "{$this->folder}/{$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
+ {
+ $tmpDir = $this->getTmpDir();
+ $filename = $tmpDir ? $tmpDir . '/' . $name : '';
+ if ($name && $filename && is_file($filename)) {
+ unlink($filename);
+ }
+ }
+
+ protected function removeTmpDir(): void
+ {
+ $tmpDir = $this->getTmpDir();
+ if ($tmpDir && 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
+ {
+ if (!($this->folder && $this->uniqueId)) {
+ throw new \RuntimeException('Cannot upload files: form flash folder not defined');
+ }
+
+ $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()->get('form/buttons') ?? [];
+ }
+
+ public function getTasks(): array
+ {
+ return $this->getBlueprint()->get('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) {
+ $grav = Grav::instance();
+ $config = [
+ 'session_id' => $this->getSessionId(),
+ 'unique_id' => $this->getUniqueId(),
+ 'form_name' => $this->getName(),
+ 'folder' => $this->getFlashFolder()
+ ];
+
+
+ $this->flash = new FormFlash($config);
+ $this->flash->setUrl($grav['uri']->url)->setUser($grav['user'] ?? null);
+ }
+
+ return $this->flash;
+ }
+
+ /**
+ * Get all available form flash objects for this form.
+ *
+ * @return FormFlash[]
+ */
+ public function getAllFlashes(): array
+ {
+ $folder = $this->getFlashFolder();
+ if (!$folder || !is_dir($folder)) {
+ return [];
+ }
+
+ $name = $this->getName();
+
+ $list = [];
+ /** @var \SplFileInfo $file */
+ foreach (new \FilesystemIterator($folder) as $file) {
+ $uniqueId = $file->getFilename();
+ $config = [
+ 'session_id' => $this->getSessionId(),
+ 'unique_id' => $uniqueId,
+ 'form_name' => $name,
+ 'folder' => $this->getFlashFolder()
+ ];
+ $flash = new FormFlash($config);
+ if ($flash->exists() && $flash->getFormName() === $name) {
+ $list[] = $flash;
+ }
+ }
+
+ return $list;
+
+ }
+
+ /**
+ * {@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 getSessionId(): string
+ {
+ /** @var Grav $grav */
+ $grav = Grav::instance();
+
+ /** @var SessionInterface $session */
+ $session = $grav['session'] ?? null;
+
+ return $session ? ($session->getId() ?? '') : '';
+ }
+
+ protected function unsetFlash(): void
+ {
+ $this->flash = null;
+ }
+
+ protected function getFlashFolder(): ?string
+ {
+ $grav = Grav::instance();
+
+ /** @var UserInterface $user */
+ $user = $grav['user'] ?? null;
+ $userExists = $user && $user->exists();
+ $username = $userExists ? $user->username : null;
+ $mediaFolder = $userExists ? $user->getMediaFolder() : null;
+ $session = $grav['session'] ?? null;
+ $sessionId = $session ? $session->getId() : null;
+
+ // Fill template token keys/value pairs.
+ $dataMap = [
+ '[FORM_NAME]' => $this->getName(),
+ '[SESSIONID]' => $sessionId ?? '!!',
+ '[USERNAME]' => $username ?? '!!',
+ '[USERNAME_OR_SESSIONID]' => $username ?? $sessionId ?? '!!',
+ '[ACCOUNT]' => $mediaFolder ?? '!!'
+ ];
+
+ $flashFolder = $this->getBlueprint()->get('form/flash_folder', 'tmp://forms/[SESSIONID]');
+
+ $path = str_replace(array_keys($dataMap), array_values($dataMap), $flashFolder);
+
+ // Make sure we only return valid paths.
+ return strpos($path, '!!') === false ? rtrim($path, '/') : 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 (trim($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..ca6d9bc
--- /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 $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);
+ }
+
+ /**
+ * Returns the value at specified offset.
+ *
+ * @param mixed $offset The offset to retrieve.
+ * @return mixed Can return all value types.
+ */
+ public function offsetGet($offset)
+ {
+ return $this->getProperty($offset);
+ }
+
+ /**
+ * Assigns a value to the specified offset.
+ *
+ * @param mixed $offset The offset to assign the value to.
+ * @param mixed $value The value to set.
+ */
+ public function offsetSet($offset, $value)
+ {
+ $this->setProperty($offset, $value);
+ }
+
+ /**
+ * Unsets an offset.
+ *
+ * @param mixed $offset The offset to unset.
+ */
+ public function offsetUnset($offset)
+ {
+ $this->unsetProperty($offset);
+ }
+
+ abstract public function hasProperty($property);
+ abstract public function getProperty($property, $default = null);
+ abstract public function setProperty($property, $value);
+ abstract public function unsetProperty($property);
+}
diff --git a/system/src/Grav/Framework/Object/Access/NestedArrayAccessTrait.php b/system/src/Grav/Framework/Object/Access/NestedArrayAccessTrait.php
new file mode 100644
index 0000000..858b391
--- /dev/null
+++ b/system/src/Grav/Framework/Object/Access/NestedArrayAccessTrait.php
@@ -0,0 +1,65 @@
+hasNestedProperty($offset);
+ }
+
+ /**
+ * Returns the value at specified offset.
+ *
+ * @param mixed $offset The offset to retrieve.
+ * @return mixed Can return all value types.
+ */
+ public function offsetGet($offset)
+ {
+ return $this->getNestedProperty($offset);
+ }
+
+ /**
+ * Assigns a value to the specified offset.
+ *
+ * @param mixed $offset The offset to assign the value to.
+ * @param mixed $value The value to set.
+ */
+ public function offsetSet($offset, $value)
+ {
+ $this->setNestedProperty($offset, $value);
+ }
+
+ /**
+ * Unsets an offset.
+ *
+ * @param mixed $offset The offset to unset.
+ */
+ public function offsetUnset($offset)
+ {
+ $this->unsetNestedProperty($offset);
+ }
+
+ abstract public function hasNestedProperty($property, $separator = null);
+ abstract public function getNestedProperty($property, $default = null, $separator = null);
+ abstract public function setNestedProperty($property, $value, $separator = null);
+ abstract public function unsetNestedProperty($property, $separator = null);
+}
diff --git a/system/src/Grav/Framework/Object/Access/NestedPropertyCollectionTrait.php b/system/src/Grav/Framework/Object/Access/NestedPropertyCollectionTrait.php
new file mode 100644
index 0000000..5ac0390
--- /dev/null
+++ b/system/src/Grav/Framework/Object/Access/NestedPropertyCollectionTrait.php
@@ -0,0 +1,125 @@
+getIterator() as $id => $element) {
+ $list[$id] = $element->hasNestedProperty($property, $separator);
+ }
+
+ return $list;
+ }
+
+ /**
+ * @param string $property Object property to be fetched.
+ * @param mixed $default Default value if not set.
+ * @param string $separator Separator, defaults to '.'
+ * @return array Key/Value pairs of the properties.
+ */
+ public function getNestedProperty($property, $default = null, $separator = null)
+ {
+ $list = [];
+
+ /** @var NestedObjectInterface $element */
+ foreach ($this->getIterator() as $id => $element) {
+ $list[$id] = $element->getNestedProperty($property, $default, $separator);
+ }
+
+ return $list;
+ }
+
+ /**
+ * @param string $property Object property to be updated.
+ * @param string $value New value.
+ * @param string $separator Separator, defaults to '.'
+ * @return $this
+ */
+ public function setNestedProperty($property, $value, $separator = null)
+ {
+ /** @var NestedObjectInterface $element */
+ foreach ($this->getIterator() as $element) {
+ $element->setNestedProperty($property, $value, $separator);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $property Object property to be updated.
+ * @param string $separator Separator, defaults to '.'
+ * @return $this
+ */
+ public function unsetNestedProperty($property, $separator = null)
+ {
+ /** @var NestedObjectInterface $element */
+ foreach ($this->getIterator() as $element) {
+ $element->unsetNestedProperty($property, $separator);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $property Object property to be updated.
+ * @param string $default Default value.
+ * @param string $separator Separator, defaults to '.'
+ * @return $this
+ */
+ public function defNestedProperty($property, $default, $separator = null)
+ {
+ /** @var NestedObjectInterface $element */
+ foreach ($this->getIterator() as $element) {
+ $element->defNestedProperty($property, $default, $separator);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Group items in the collection by a field.
+ *
+ * @param string $property Object property to be used to make groups.
+ * @param string $separator Separator, defaults to '.'
+ * @return array
+ */
+ public function group($property, $separator = null)
+ {
+ $list = [];
+
+ /** @var NestedObjectInterface $element */
+ foreach ($this->getIterator() as $element) {
+ $list[(string) $element->getNestedProperty($property, null, $separator)][] = $element;
+ }
+
+ return $list;
+ }
+
+ /**
+ * @return \Traversable
+ */
+ abstract public function getIterator();
+}
diff --git a/system/src/Grav/Framework/Object/Access/NestedPropertyTrait.php b/system/src/Grav/Framework/Object/Access/NestedPropertyTrait.php
new file mode 100644
index 0000000..2200871
--- /dev/null
+++ b/system/src/Grav/Framework/Object/Access/NestedPropertyTrait.php
@@ -0,0 +1,183 @@
+getNestedProperty($property, $test, $separator) !== $test;
+ }
+
+ /**
+ * @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)
+ {
+ $separator = $separator ?: '.';
+ $path = explode($separator, $property) ?: [];
+ $offset = array_shift($path) ?? '';
+
+ if (!$this->hasProperty($offset)) {
+ return $default;
+ }
+
+ $current = $this->getProperty($offset);
+
+ while ($path) {
+ // Get property of nested Object.
+ if ($current instanceof ObjectInterface) {
+ if (method_exists($current, 'getNestedProperty')) {
+ return $current->getNestedProperty(implode($separator, $path), $default, $separator);
+ }
+ return $current->getProperty(implode($separator, $path), $default);
+ }
+
+ $offset = array_shift($path);
+
+ if ((\is_array($current) || is_a($current, 'ArrayAccess')) && isset($current[$offset])) {
+ $current = $current[$offset];
+ } elseif (\is_object($current) && isset($current->{$offset})) {
+ $current = $current->{$offset};
+ } else {
+ return $default;
+ }
+ };
+
+ return $current;
+ }
+
+
+ /**
+ * @param string $property Object property to be updated.
+ * @param string $value New value.
+ * @param string $separator Separator, defaults to '.'
+ * @return $this
+ * @throws \RuntimeException
+ */
+ public function setNestedProperty($property, $value, $separator = null)
+ {
+ $separator = $separator ?: '.';
+ $path = explode($separator, $property) ?: [];
+ $offset = array_shift($path) ?? '';
+
+ if (!$path) {
+ $this->setProperty($offset, $value);
+
+ return $this;
+ }
+
+ $current = &$this->doGetProperty($offset, null, true);
+
+ while ($path) {
+ $offset = array_shift($path);
+
+ // Handle arrays and scalars.
+ if ($current === null) {
+ $current = [$offset => []];
+ } elseif (\is_array($current)) {
+ if (!isset($current[$offset])) {
+ $current[$offset] = [];
+ }
+ } else {
+ throw new \RuntimeException("Cannot set nested property {$property} on non-array value");
+ }
+
+ $current = &$current[$offset];
+ };
+
+ $current = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param string $property Object property to be updated.
+ * @param string $separator Separator, defaults to '.'
+ * @return $this
+ * @throws \RuntimeException
+ */
+ public function unsetNestedProperty($property, $separator = null)
+ {
+ $separator = $separator ?: '.';
+ $path = explode($separator, $property) ?: [];
+ $offset = array_shift($path) ?? '';
+
+ if (!$path) {
+ $this->unsetProperty($offset);
+
+ return $this;
+ }
+
+ $last = array_pop($path);
+ $current = &$this->doGetProperty($offset, null, true);
+
+ while ($path) {
+ $offset = array_shift($path);
+
+ // Handle arrays and scalars.
+ if ($current === null) {
+ return $this;
+ }
+ if (\is_array($current)) {
+ if (!isset($current[$offset])) {
+ return $this;
+ }
+ } else {
+ throw new \RuntimeException("Cannot unset nested property {$property} on non-array value");
+ }
+
+ $current = &$current[$offset];
+ };
+
+ unset($current[$last]);
+
+ return $this;
+ }
+
+ /**
+ * @param string $property Object property to be updated.
+ * @param mixed $default Default value.
+ * @param string $separator Separator, defaults to '.'
+ * @return $this
+ * @throws \RuntimeException
+ */
+ public function defNestedProperty($property, $default, $separator = null)
+ {
+ if (!$this->hasNestedProperty($property, $separator)) {
+ $this->setNestedProperty($property, $default, $separator);
+ }
+
+ return $this;
+ }
+
+
+ abstract public function hasProperty($property);
+ abstract public function getProperty($property, $default = null);
+ abstract public function setProperty($property, $value);
+ abstract public function unsetProperty($property);
+ abstract protected function &doGetProperty($property, $default = null, $doCreate = false);
+}
diff --git a/system/src/Grav/Framework/Object/Access/OverloadedPropertyTrait.php b/system/src/Grav/Framework/Object/Access/OverloadedPropertyTrait.php
new file mode 100644
index 0000000..029dbba
--- /dev/null
+++ b/system/src/Grav/Framework/Object/Access/OverloadedPropertyTrait.php
@@ -0,0 +1,65 @@
+hasProperty($offset);
+ }
+
+ /**
+ * Returns the value at specified offset.
+ *
+ * @param mixed $offset The offset to retrieve.
+ * @return mixed Can return all value types.
+ */
+ public function __get($offset)
+ {
+ return $this->getProperty($offset);
+ }
+
+ /**
+ * Assigns a value to the specified offset.
+ *
+ * @param mixed $offset The offset to assign the value to.
+ * @param mixed $value The value to set.
+ */
+ public function __set($offset, $value)
+ {
+ $this->setProperty($offset, $value);
+ }
+
+ /**
+ * Magic method to unset the attribute
+ *
+ * @param mixed $offset The name value to unset
+ */
+ public function __unset($offset)
+ {
+ $this->unsetProperty($offset);
+ }
+
+ abstract public function hasProperty($property);
+ abstract public function getProperty($property, $default = null);
+ abstract public function setProperty($property, $value);
+ abstract public function unsetProperty($property);
+}
diff --git a/system/src/Grav/Framework/Object/ArrayObject.php b/system/src/Grav/Framework/Object/ArrayObject.php
new file mode 100644
index 0000000..a7062e8
--- /dev/null
+++ b/system/src/Grav/Framework/Object/ArrayObject.php
@@ -0,0 +1,29 @@
+getIterator() as $key => $value) {
+ $list[$key] = \is_object($value) ? clone $value : $value;
+ }
+
+ return $this->createFrom($list);
+ }
+
+ /**
+ * @return array
+ */
+ public function getObjectKeys()
+ {
+ return $this->call('getKey');
+ }
+
+ /**
+ * @param string $property Object property to be matched.
+ * @return bool[] Key/Value pairs of the properties.
+ */
+ public function doHasProperty($property)
+ {
+ $list = [];
+
+ /** @var ObjectInterface $element */
+ foreach ($this->getIterator() as $id => $element) {
+ $list[$id] = $element->hasProperty($property);
+ }
+
+ return $list;
+ }
+
+ /**
+ * @param string $property Object property to be fetched.
+ * @param mixed $default Default value if not set.
+ * @return mixed[] Key/Value pairs of the properties.
+ */
+ public function doGetProperty($property, $default = null)
+ {
+ $list = [];
+
+ /** @var ObjectInterface $element */
+ foreach ($this->getIterator() as $id => $element) {
+ $list[$id] = $element->getProperty($property, $default);
+ }
+
+ return $list;
+ }
+
+ /**
+ * @param string $property Object property to be updated.
+ * @param mixed $value New value.
+ * @return $this
+ */
+ public function doSetProperty($property, $value)
+ {
+ /** @var ObjectInterface $element */
+ foreach ($this->getIterator() as $element) {
+ $element->setProperty($property, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $property Object property to be updated.
+ * @return $this
+ */
+ public function doUnsetProperty($property)
+ {
+ /** @var ObjectInterface $element */
+ foreach ($this->getIterator() as $element) {
+ $element->unsetProperty($property);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $property Object property to be updated.
+ * @param mixed $default Default value.
+ * @return $this
+ */
+ public function doDefProperty($property, $default)
+ {
+ /** @var ObjectInterface $element */
+ foreach ($this->getIterator() as $element) {
+ $element->defProperty($property, $default);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $method Method name.
+ * @param array $arguments List of arguments passed to the function.
+ * @return mixed[] Return values.
+ */
+ public function call($method, array $arguments = [])
+ {
+ $list = [];
+
+ /**
+ * @var string|int $id
+ * @var ObjectInterface $element
+ */
+ foreach ($this->getIterator() as $id => $element) {
+ $callable = method_exists($element, $method) ? [$element, $method] : null;
+ $list[$id] = $callable ? \call_user_func_array($callable, $arguments) : null;
+ }
+
+ return $list;
+ }
+
+ /**
+ * Group items in the collection by a field and return them as associated array.
+ *
+ * @param string $property
+ * @return array
+ */
+ public function group($property)
+ {
+ $list = [];
+
+ /** @var ObjectInterface $element */
+ foreach ($this->getIterator() as $element) {
+ $list[(string) $element->getProperty($property)][] = $element;
+ }
+
+ return $list;
+ }
+
+ /**
+ * Group items in the collection by a field and return them as associated array of collections.
+ *
+ * @param string $property
+ * @return static[]
+ */
+ public function collectionGroup($property)
+ {
+ $collections = [];
+ foreach ($this->group($property) as $id => $elements) {
+ $collection = $this->createFrom($elements);
+
+ $collections[$id] = $collection;
+ }
+
+ return $collections;
+ }
+
+ /**
+ * @return \Traversable
+ */
+ abstract public function getIterator();
+}
diff --git a/system/src/Grav/Framework/Object/Base/ObjectTrait.php b/system/src/Grav/Framework/Object/Base/ObjectTrait.php
new file mode 100644
index 0000000..baeeb2b
--- /dev/null
+++ b/system/src/Grav/Framework/Object/Base/ObjectTrait.php
@@ -0,0 +1,204 @@
+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);
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasKey()
+ {
+ return !empty($this->_key);
+ }
+
+ /**
+ * @param string $property Object property name.
+ * @return bool|bool[] True if property has been defined (can be null).
+ */
+ public function hasProperty($property)
+ {
+ return $this->doHasProperty($property);
+ }
+
+ /**
+ * @param string $property Object property to be fetched.
+ * @param mixed $default Default value if property has not been set.
+ * @return mixed|mixed[] Property value.
+ */
+ public function getProperty($property, $default = null)
+ {
+ return $this->doGetProperty($property, $default);
+ }
+
+ /**
+ * @param string $property Object property to be updated.
+ * @param mixed $value New value.
+ * @return $this
+ */
+ public function setProperty($property, $value)
+ {
+ $this->doSetProperty($property, $value);
+
+ return $this;
+ }
+
+ /**
+ * @param string $property Object property to be unset.
+ * @return $this
+ */
+ public function unsetProperty($property)
+ {
+ $this->doUnsetProperty($property);
+
+ return $this;
+ }
+
+ /**
+ * @param string $property Object property to be defined.
+ * @param mixed $default Default value.
+ * @return $this
+ */
+ public function defProperty($property, $default)
+ {
+ if (!$this->hasProperty($property)) {
+ $this->setProperty($property, $default);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Implements Serializable interface.
+ *
+ * @return string
+ */
+ public function serialize()
+ {
+ return serialize($this->doSerialize());
+ }
+
+ /**
+ * @param string $serialized
+ */
+ public function unserialize($serialized)
+ {
+ $data = unserialize($serialized);
+
+ if (method_exists($this, 'initObjectProperties')) {
+ $this->initObjectProperties();
+ }
+ $this->doUnserialize($data);
+ }
+
+ /**
+ * @return array
+ */
+ protected function doSerialize()
+ {
+ return ['key' => $this->getKey(), 'type' => $this->getType(), 'elements' => $this->getElements()];
+ }
+
+ /**
+ * @param array $serialized
+ */
+ protected function doUnserialize(array $serialized)
+ {
+ if (!isset($serialized['key'], $serialized['type'], $serialized['elements']) || $serialized['type'] !== $this->getType()) {
+ throw new \InvalidArgumentException("Cannot unserialize '{$this->getType()}': Bad data");
+ }
+
+ $this->setKey($serialized['key']);
+ $this->setElements($serialized['elements']);
+ }
+
+ /**
+ * Implements JsonSerializable interface.
+ *
+ * @return array
+ */
+ public function jsonSerialize()
+ {
+ return $this->doSerialize();
+ }
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getKey();
+ }
+
+ /**
+ * @param string $key
+ * @return $this
+ */
+ protected function setKey($key)
+ {
+ $this->_key = (string) $key;
+
+ return $this;
+ }
+
+ abstract protected function doHasProperty($property);
+ abstract protected function &doGetProperty($property, $default = null, $doCreate = false);
+ abstract protected function doSetProperty($property, $value);
+ abstract protected function doUnsetProperty($property);
+ abstract protected function getElements();
+ abstract protected function setElements(array $elements);
+}
diff --git a/system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php b/system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php
new file mode 100644
index 0000000..0eefb78
--- /dev/null
+++ b/system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php
@@ -0,0 +1,199 @@
+{$accessor}();
+ break;
+ }
+ }
+
+ if ($op) {
+ $function = 'filter' . ucfirst(strtolower($op));
+ if (method_exists(static::class, $function)) {
+ $value = static::$function($value);
+ }
+ }
+
+ return $value;
+ }
+
+ public static function filterLower($str)
+ {
+ return mb_strtolower($str);
+ }
+
+ public static function filterUpper($str)
+ {
+ return mb_strtoupper($str);
+ }
+
+ public static function filterLength($str)
+ {
+ return mb_strlen($str);
+ }
+
+ public static function filterLtrim($str)
+ {
+ return ltrim($str);
+ }
+
+ public static function filterRtrim($str)
+ {
+ return rtrim($str);
+ }
+
+ public static function filterTrim($str)
+ {
+ return trim($str);
+ }
+
+ /**
+ * Helper for sorting arrays of objects based on multiple fields + orientations.
+ *
+ * @param string $name
+ * @param int $orientation
+ * @param \Closure $next
+ *
+ * @return \Closure
+ */
+ public static function sortByField($name, $orientation = 1, \Closure $next = null)
+ {
+ if (!$next) {
+ $next = function($a, $b) {
+ return 0;
+ };
+ }
+
+ return function ($a, $b) use ($name, $next, $orientation) {
+ $aValue = static::getObjectFieldValue($a, $name);
+ $bValue = static::getObjectFieldValue($b, $name);
+
+ if ($aValue === $bValue) {
+ return $next($a, $b);
+ }
+
+ return (($aValue > $bValue) ? 1 : -1) * $orientation;
+ };
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function walkComparison(Comparison $comparison)
+ {
+ $field = $comparison->getField();
+ $value = $comparison->getValue()->getValue(); // shortcut for walkValue()
+
+ switch ($comparison->getOperator()) {
+ case Comparison::EQ:
+ return function ($object) use ($field, $value) {
+ return static::getObjectFieldValue($object, $field) === $value;
+ };
+
+ case Comparison::NEQ:
+ return function ($object) use ($field, $value) {
+ return static::getObjectFieldValue($object, $field) !== $value;
+ };
+
+ case Comparison::LT:
+ return function ($object) use ($field, $value) {
+ return static::getObjectFieldValue($object, $field) < $value;
+ };
+
+ case Comparison::LTE:
+ return function ($object) use ($field, $value) {
+ return static::getObjectFieldValue($object, $field) <= $value;
+ };
+
+ case Comparison::GT:
+ return function ($object) use ($field, $value) {
+ return static::getObjectFieldValue($object, $field) > $value;
+ };
+
+ case Comparison::GTE:
+ return function ($object) use ($field, $value) {
+ return static::getObjectFieldValue($object, $field) >= $value;
+ };
+
+ case Comparison::IN:
+ return function ($object) use ($field, $value) {
+ return \in_array(static::getObjectFieldValue($object, $field), $value, true);
+ };
+
+ case Comparison::NIN:
+ return function ($object) use ($field, $value) {
+ return !\in_array(static::getObjectFieldValue($object, $field), $value, true);
+ };
+
+ case Comparison::CONTAINS:
+ return function ($object) use ($field, $value) {
+ return false !== strpos(static::getObjectFieldValue($object, $field), $value);
+ };
+
+ case Comparison::MEMBER_OF:
+ return function ($object) use ($field, $value) {
+ $fieldValues = static::getObjectFieldValue($object, $field);
+ if (!\is_array($fieldValues)) {
+ $fieldValues = iterator_to_array($fieldValues);
+ }
+ return \in_array($value, $fieldValues, true);
+ };
+
+ case Comparison::STARTS_WITH:
+ return function ($object) use ($field, $value) {
+ return 0 === strpos(static::getObjectFieldValue($object, $field), $value);
+ };
+
+ case Comparison::ENDS_WITH:
+ return function ($object) use ($field, $value) {
+ return $value === substr(static::getObjectFieldValue($object, $field), -strlen($value));
+ };
+
+
+ default:
+ throw new \RuntimeException("Unknown comparison operator: " . $comparison->getOperator());
+ }
+ }
+}
diff --git a/system/src/Grav/Framework/Object/Interfaces/NestedObjectInterface.php b/system/src/Grav/Framework/Object/Interfaces/NestedObjectInterface.php
new file mode 100644
index 0000000..8d6025a
--- /dev/null
+++ b/system/src/Grav/Framework/Object/Interfaces/NestedObjectInterface.php
@@ -0,0 +1,58 @@
+setElements($elements));
+
+ $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));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function matching(Criteria $criteria)
+ {
+ $expr = $criteria->getWhereExpression();
+ $filtered = $this->getElements();
+
+ if ($expr) {
+ $visitor = new ObjectExpressionVisitor();
+ $filter = $visitor->dispatch($expr);
+ $filtered = array_filter($filtered, $filter);
+ }
+
+ 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);
+ }
+
+ if ($next) {
+ uasort($filtered, $next);
+ }
+ }
+
+ $offset = $criteria->getFirstResult();
+ $length = $criteria->getMaxResults();
+
+ if ($offset || $length) {
+ $filtered = \array_slice($filtered, (int)$offset, $length);
+ }
+
+ return $this->createFrom($filtered);
+ }
+
+ protected function getElements()
+ {
+ return $this->toArray();
+ }
+
+ protected function setElements(array $elements)
+ {
+ return $elements;
+ }
+}
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
new file mode 100644
index 0000000..7092e15
--- /dev/null
+++ b/system/src/Grav/Framework/Object/Property/ArrayPropertyTrait.php
@@ -0,0 +1,110 @@
+setElements($elements);
+ $this->setKey($key ?? '');
+ }
+
+ /**
+ * @param string $property Object property name.
+ * @return bool True if property has been defined (can be null).
+ */
+ protected function doHasProperty($property)
+ {
+ return array_key_exists($property, $this->_elements);
+ }
+
+ /**
+ * @param string $property Object property to be fetched.
+ * @param mixed $default Default value if property has not been set.
+ * @param bool $doCreate Set true to create variable.
+ * @return mixed Property value.
+ */
+ protected function &doGetProperty($property, $default = null, $doCreate = false)
+ {
+ if (!array_key_exists($property, $this->_elements)) {
+ if ($doCreate) {
+ $this->_elements[$property] = null;
+ } else {
+ return $default;
+ }
+ }
+
+ return $this->_elements[$property];
+ }
+
+ /**
+ * @param string $property Object property to be updated.
+ * @param mixed $value New value.
+ */
+ protected function doSetProperty($property, $value)
+ {
+ $this->_elements[$property] = $value;
+ }
+
+ /**
+ * @param string $property Object property to be unset.
+ */
+ protected function doUnsetProperty($property)
+ {
+ unset($this->_elements[$property]);
+ }
+
+ /**
+ * @param string $property
+ * @param mixed|null $default
+ * @return mixed|null
+ */
+ protected function getElement($property, $default = null)
+ {
+ return array_key_exists($property, $this->_elements) ? $this->_elements[$property] : $default;
+ }
+
+ /**
+ * @return array
+ */
+ protected function getElements()
+ {
+ return array_filter($this->_elements, function ($val) { return $val !== null; });
+ }
+
+ /**
+ * @param array $elements
+ */
+ protected function setElements(array $elements)
+ {
+ $this->_elements = $elements;
+ }
+
+ abstract protected function setKey($key);
+}
diff --git a/system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php b/system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php
new file mode 100644
index 0000000..1066b99
--- /dev/null
+++ b/system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php
@@ -0,0 +1,118 @@
+offsetLoad($offset, $value)` called first time object property gets accessed
+ * - `$this->offsetPrepare($offset, $value)` called on every object property set
+ * - `$this->offsetSerialize($offset, $value)` called when the raw or serialized object property value is needed
+ *
+ * @package Grav\Framework\Object\Property
+ */
+trait LazyPropertyTrait
+{
+ use ArrayPropertyTrait, ObjectPropertyTrait {
+ ObjectPropertyTrait::__construct insteadof ArrayPropertyTrait;
+ ArrayPropertyTrait::doHasProperty as hasArrayProperty;
+ ArrayPropertyTrait::doGetProperty as getArrayProperty;
+ ArrayPropertyTrait::doSetProperty as setArrayProperty;
+ ArrayPropertyTrait::doUnsetProperty as unsetArrayProperty;
+ ArrayPropertyTrait::getElement as getArrayElement;
+ ArrayPropertyTrait::getElements as getArrayElements;
+ ArrayPropertyTrait::setElements insteadof ObjectPropertyTrait;
+ ObjectPropertyTrait::doHasProperty as hasObjectProperty;
+ ObjectPropertyTrait::doGetProperty as getObjectProperty;
+ ObjectPropertyTrait::doSetProperty as setObjectProperty;
+ ObjectPropertyTrait::doUnsetProperty as unsetObjectProperty;
+ ObjectPropertyTrait::getElement as getObjectElement;
+ ObjectPropertyTrait::getElements as getObjectElements;
+ }
+
+ /**
+ * @param string $property Object property name.
+ * @return bool True if property has been defined (can be null).
+ */
+ protected function doHasProperty($property)
+ {
+ return $this->hasArrayProperty($property) || $this->hasObjectProperty($property);
+ }
+
+ /**
+ * @param string $property Object property to be fetched.
+ * @param mixed $default Default value if property has not been set.
+ * @return mixed Property value.
+ */
+ protected function &doGetProperty($property, $default = null, $doCreate = false)
+ {
+ if ($this->hasObjectProperty($property)) {
+ return $this->getObjectProperty($property, $default, function ($default = null) use ($property) {
+ return $this->getArrayProperty($property, $default);
+ });
+ }
+
+ return $this->getArrayProperty($property, $default, $doCreate);
+ }
+
+ /**
+ * @param string $property Object property to be updated.
+ * @param mixed $value New value.
+ * @return $this
+ */
+ protected function doSetProperty($property, $value)
+ {
+ if ($this->hasObjectProperty($property)) {
+ $this->setObjectProperty($property, $value);
+ } else {
+ $this->setArrayProperty($property, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $property Object property to be unset.
+ * @return $this
+ */
+ protected function doUnsetProperty($property)
+ {
+ $this->hasObjectProperty($property) ?
+ $this->unsetObjectProperty($property) : $this->unsetArrayProperty($property);
+
+ return $this;
+ }
+
+ /**
+ * @param string $property
+ * @param mixed|null $default
+ * @return mixed|null
+ */
+ protected function getElement($property, $default = null)
+ {
+ if ($this->isPropertyLoaded($property)) {
+ return $this->getObjectElement($property, $default);
+ }
+
+ return $this->getArrayElement($property, $default);
+ }
+
+ /**
+ * @return array
+ */
+ protected function getElements()
+ {
+ return $this->getObjectElements() + $this->getArrayElements();
+ }
+}
diff --git a/system/src/Grav/Framework/Object/Property/MixedPropertyTrait.php b/system/src/Grav/Framework/Object/Property/MixedPropertyTrait.php
new file mode 100644
index 0000000..149ac40
--- /dev/null
+++ b/system/src/Grav/Framework/Object/Property/MixedPropertyTrait.php
@@ -0,0 +1,123 @@
+offsetLoad($offset, $value)` called first time object property gets accessed
+ * - `$this->offsetPrepare($offset, $value)` called on every object property set
+ * - `$this->offsetSerialize($offset, $value)` called when the raw or serialized object property value is needed
+
+ *
+ * @package Grav\Framework\Object\Property
+ */
+trait MixedPropertyTrait
+{
+ use ArrayPropertyTrait, ObjectPropertyTrait {
+ ObjectPropertyTrait::__construct insteadof ArrayPropertyTrait;
+ ArrayPropertyTrait::doHasProperty as hasArrayProperty;
+ ArrayPropertyTrait::doGetProperty as getArrayProperty;
+ ArrayPropertyTrait::doSetProperty as setArrayProperty;
+ ArrayPropertyTrait::doUnsetProperty as unsetArrayProperty;
+ ArrayPropertyTrait::getElement as getArrayElement;
+ ArrayPropertyTrait::getElements as getArrayElements;
+ ArrayPropertyTrait::setElements as setArrayElements;
+ ObjectPropertyTrait::doHasProperty as hasObjectProperty;
+ ObjectPropertyTrait::doGetProperty as getObjectProperty;
+ ObjectPropertyTrait::doSetProperty as setObjectProperty;
+ ObjectPropertyTrait::doUnsetProperty as unsetObjectProperty;
+ ObjectPropertyTrait::getElement as getObjectElement;
+ ObjectPropertyTrait::getElements as getObjectElements;
+ ObjectPropertyTrait::setElements as setObjectElements;
+ }
+
+ /**
+ * @param string $property Object property name.
+ * @return bool True if property has been defined (can be null).
+ */
+ protected function doHasProperty($property)
+ {
+ return $this->hasArrayProperty($property) || $this->hasObjectProperty($property);
+ }
+
+ /**
+ * @param string $property Object property to be fetched.
+ * @param mixed $default Default value if property has not been set.
+ * @return mixed Property value.
+ */
+ protected function &doGetProperty($property, $default = null, $doCreate = false)
+ {
+ if ($this->hasObjectProperty($property)) {
+ return $this->getObjectProperty($property);
+ }
+
+ return $this->getArrayProperty($property, $default, $doCreate);
+ }
+
+ /**
+ * @param string $property Object property to be updated.
+ * @param mixed $value New value.
+ * @return $this
+ */
+ protected function doSetProperty($property, $value)
+ {
+ $this->hasObjectProperty($property)
+ ? $this->setObjectProperty($property, $value) : $this->setArrayProperty($property, $value);
+
+ return $this;
+ }
+
+ /**
+ * @param string $property Object property to be unset.
+ * @return $this
+ */
+ protected function doUnsetProperty($property)
+ {
+ $this->hasObjectProperty($property) ?
+ $this->unsetObjectProperty($property) : $this->unsetArrayProperty($property);
+
+ return $this;
+ }
+
+ /**
+ * @param string $property
+ * @param mixed|null $default
+ * @return mixed|null
+ */
+ protected function getElement($property, $default = null)
+ {
+ if ($this->hasObjectProperty($property)) {
+ return $this->getObjectElement($property, $default);
+ }
+
+ return $this->getArrayElement($property, $default);
+ }
+
+ /**
+ * @return array
+ */
+ protected function getElements()
+ {
+ return $this->getObjectElements() + $this->getArrayElements();
+ }
+
+ /**
+ * @param array $elements
+ */
+ protected function setElements(array $elements)
+ {
+ $this->setObjectElements(array_intersect_key($elements, $this->_definedProperties));
+ $this->setArrayElements(array_diff_key($elements, $this->_definedProperties));
+ }
+}
diff --git a/system/src/Grav/Framework/Object/Property/ObjectPropertyTrait.php b/system/src/Grav/Framework/Object/Property/ObjectPropertyTrait.php
new file mode 100644
index 0000000..5f8fdf0
--- /dev/null
+++ b/system/src/Grav/Framework/Object/Property/ObjectPropertyTrait.php
@@ -0,0 +1,207 @@
+offsetLoad($offset, $value)` called first time object property gets accessed
+ * - `$this->offsetPrepare($offset, $value)` called on every object property set
+ * - `$this->offsetSerialize($offset, $value)` called when the raw or serialized object property value is needed
+ *
+ * @package Grav\Framework\Object\Property
+ */
+trait ObjectPropertyTrait
+{
+ /**
+ * @var array
+ */
+ private $_definedProperties;
+
+ /**
+ * @param array $elements
+ * @param string $key
+ * @throws \InvalidArgumentException
+ */
+ public function __construct(array $elements = [], $key = null)
+ {
+ $this->initObjectProperties();
+ $this->setElements($elements);
+ $this->setKey($key ?? '');
+ }
+
+ /**
+ * @param string $property Object property name.
+ * @return bool True if property has been loaded.
+ */
+ protected function isPropertyLoaded($property)
+ {
+ return !empty($this->_definedProperties[$property]);
+ }
+
+ /**
+ * @param string $offset
+ * @param mixed $value
+ * @return mixed
+ */
+ protected function offsetLoad($offset, $value)
+ {
+ $methodName = "offsetLoad_{$offset}";
+
+ return method_exists($this, $methodName)? $this->{$methodName}($value) : $value;
+ }
+
+ /**
+ * @param string $offset
+ * @param mixed $value
+ * @return mixed
+ */
+ protected function offsetPrepare($offset, $value)
+ {
+ $methodName = "offsetPrepare_{$offset}";
+
+ return method_exists($this, $methodName) ? $this->{$methodName}($value) : $value;
+ }
+
+ /**
+ * @param string $offset
+ * @param mixed $value
+ * @return mixed
+ */
+ protected function offsetSerialize($offset, $value)
+ {
+ $methodName = "offsetSerialize_{$offset}";
+
+ return method_exists($this, $methodName) ? $this->{$methodName}($value) : $value;
+ }
+
+ /**
+ * @param string $property Object property name.
+ * @return bool True if property has been defined (can be null).
+ */
+ protected function doHasProperty($property)
+ {
+ return array_key_exists($property, $this->_definedProperties);
+ }
+
+ /**
+ * @param string $property Object property to be fetched.
+ * @param mixed $default Default value if property has not been set.
+ * @param callable|bool $doCreate Set true to create variable.
+ * @return mixed Property value.
+ */
+ protected function &doGetProperty($property, $default = null, $doCreate = false)
+ {
+ if (!array_key_exists($property, $this->_definedProperties)) {
+ throw new \InvalidArgumentException("Property '{$property}' does not exist in the object!");
+ }
+
+ if (empty($this->_definedProperties[$property])) {
+ if ($doCreate === true) {
+ $this->_definedProperties[$property] = true;
+ $this->{$property} = null;
+ } elseif (\is_callable($doCreate)) {
+ $this->_definedProperties[$property] = true;
+ $this->{$property} = $this->offsetLoad($property, $doCreate());
+ } else {
+ return $default;
+ }
+ }
+
+ return $this->{$property};
+ }
+
+ /**
+ * @param string $property Object property to be updated.
+ * @param mixed $value New value.
+ * @throws \InvalidArgumentException
+ */
+ protected function doSetProperty($property, $value)
+ {
+ if (!array_key_exists($property, $this->_definedProperties)) {
+ throw new \InvalidArgumentException("Property '{$property}' does not exist in the object!");
+ }
+
+ $this->_definedProperties[$property] = true;
+ $this->{$property} = $this->offsetPrepare($property, $value);
+ }
+
+ /**
+ * @param string $property Object property to be unset.
+ */
+ protected function doUnsetProperty($property)
+ {
+ if (!array_key_exists($property, $this->_definedProperties)) {
+ return;
+ }
+
+ $this->_definedProperties[$property] = false;
+ unset($this->{$property});
+ }
+
+ protected function initObjectProperties()
+ {
+ $this->_definedProperties = [];
+ foreach (\get_object_vars($this) as $property => $value) {
+ if ($property[0] !== '_') {
+ $this->_definedProperties[$property] = ($value !== null);
+ }
+ }
+ }
+
+ /**
+ * @param string $property
+ * @param mixed|null $default
+ * @return mixed|null
+ */
+ protected function getElement($property, $default = null)
+ {
+ if (empty($this->_definedProperties[$property])) {
+ return $default;
+ }
+
+ return $this->offsetSerialize($property, $this->{$property});
+ }
+
+ /**
+ * @return array
+ */
+ protected function getElements()
+ {
+ $properties = array_intersect_key(get_object_vars($this), array_filter($this->_definedProperties));
+
+ $elements = [];
+ foreach ($properties as $offset => $value) {
+ $serialized = $this->offsetSerialize($offset, $value);
+ if ($serialized !== null) {
+ $elements[$offset] = $this->offsetSerialize($offset, $value);
+ }
+ }
+
+ return $elements;
+ }
+
+ /**
+ * @param array $elements
+ */
+ protected function setElements(array $elements)
+ {
+ foreach ($elements as $property => $value) {
+ $this->setProperty($property, $value);
+ }
+ }
+
+ abstract public function setProperty($property, $value);
+ abstract protected function setKey($key);
+}
diff --git a/system/src/Grav/Framework/Object/PropertyObject.php b/system/src/Grav/Framework/Object/PropertyObject.php
new file mode 100644
index 0000000..f740fc6
--- /dev/null
+++ b/system/src/Grav/Framework/Object/PropertyObject.php
@@ -0,0 +1,29 @@
+ '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/Parsedown/Parsedown.php b/system/src/Grav/Framework/Parsedown/Parsedown.php
new file mode 100644
index 0000000..20b634d
--- /dev/null
+++ b/system/src/Grav/Framework/Parsedown/Parsedown.php
@@ -0,0 +1,1554 @@
+DefinitionData = array();
+
+ # standardize line breaks
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+ # remove surrounding line breaks
+ $text = trim($text, "\n");
+
+ # split text into lines
+ $lines = explode("\n", $text);
+
+ # iterate through lines to identify blocks
+ $markup = $this->lines($lines);
+
+ # trim line breaks
+ $markup = trim($markup, "\n");
+
+ return $markup;
+ }
+
+ #
+ # Setters
+ #
+
+ function setBreaksEnabled($breaksEnabled)
+ {
+ $this->breaksEnabled = $breaksEnabled;
+
+ return $this;
+ }
+
+ protected $breaksEnabled;
+
+ function setMarkupEscaped($markupEscaped)
+ {
+ $this->markupEscaped = $markupEscaped;
+
+ return $this;
+ }
+
+ protected $markupEscaped;
+
+ function setUrlsLinked($urlsLinked)
+ {
+ $this->urlsLinked = $urlsLinked;
+
+ return $this;
+ }
+
+ protected $urlsLinked = true;
+
+ #
+ # Lines
+ #
+
+ protected $BlockTypes = array(
+ '#' => array('Header'),
+ '*' => array('Rule', 'List'),
+ '+' => array('List'),
+ '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
+ '0' => array('List'),
+ '1' => array('List'),
+ '2' => array('List'),
+ '3' => array('List'),
+ '4' => array('List'),
+ '5' => array('List'),
+ '6' => array('List'),
+ '7' => array('List'),
+ '8' => array('List'),
+ '9' => array('List'),
+ ':' => array('Table'),
+ '<' => array('Comment', 'Markup'),
+ '=' => array('SetextHeader'),
+ '>' => array('Quote'),
+ '[' => array('Reference'),
+ '_' => array('Rule'),
+ '`' => array('FencedCode'),
+ '|' => array('Table'),
+ '~' => array('FencedCode'),
+ );
+
+ # ~
+
+ protected $unmarkedBlockTypes = array(
+ 'Code',
+ );
+
+ #
+ # Blocks
+ #
+
+ protected function lines(array $lines)
+ {
+ $CurrentBlock = null;
+
+ foreach ($lines as $line)
+ {
+ if (chop($line) === '')
+ {
+ if (isset($CurrentBlock))
+ {
+ $CurrentBlock['interrupted'] = true;
+ }
+
+ continue;
+ }
+
+ if (strpos($line, "\t") !== false)
+ {
+ $parts = explode("\t", $line);
+
+ $line = $parts[0];
+
+ unset($parts[0]);
+
+ foreach ($parts as $part)
+ {
+ $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
+
+ $line .= str_repeat(' ', $shortage);
+ $line .= $part;
+ }
+ }
+
+ $indent = 0;
+
+ while (isset($line[$indent]) and $line[$indent] === ' ')
+ {
+ $indent ++;
+ }
+
+ $text = $indent > 0 ? substr($line, $indent) : $line;
+
+ # ~
+
+ $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
+
+ # ~
+
+ if (isset($CurrentBlock['continuable']))
+ {
+ $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
+
+ if (isset($Block))
+ {
+ $CurrentBlock = $Block;
+
+ continue;
+ }
+ else
+ {
+ if ($this->isBlockCompletable($CurrentBlock['type']))
+ {
+ $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
+ }
+ }
+ }
+
+ # ~
+
+ $marker = $text[0];
+
+ # ~
+
+ $blockTypes = $this->unmarkedBlockTypes;
+
+ if (isset($this->BlockTypes[$marker]))
+ {
+ foreach ($this->BlockTypes[$marker] as $blockType)
+ {
+ $blockTypes []= $blockType;
+ }
+ }
+
+ #
+ # ~
+
+ foreach ($blockTypes as $blockType)
+ {
+ $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
+
+ if (isset($Block))
+ {
+ $Block['type'] = $blockType;
+
+ if ( ! isset($Block['identified']))
+ {
+ $Blocks []= $CurrentBlock;
+
+ $Block['identified'] = true;
+ }
+
+ if ($this->isBlockContinuable($blockType))
+ {
+ $Block['continuable'] = true;
+ }
+
+ $CurrentBlock = $Block;
+
+ continue 2;
+ }
+ }
+
+ # ~
+
+ if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
+ {
+ $CurrentBlock['element']['text'] .= "\n".$text;
+ }
+ else
+ {
+ $Blocks []= $CurrentBlock;
+
+ $CurrentBlock = $this->paragraph($Line);
+
+ $CurrentBlock['identified'] = true;
+ }
+ }
+
+ # ~
+
+ if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
+ {
+ $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
+ }
+
+ # ~
+
+ $Blocks []= $CurrentBlock;
+
+ unset($Blocks[0]);
+
+ # ~
+
+ $markup = '';
+
+ foreach ($Blocks as $Block)
+ {
+ if (isset($Block['hidden']))
+ {
+ continue;
+ }
+
+ $markup .= "\n";
+ $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
+ }
+
+ $markup .= "\n";
+
+ # ~
+
+ return $markup;
+ }
+
+ protected function isBlockContinuable($Type)
+ {
+ return method_exists($this, 'block'.$Type.'Continue');
+ }
+
+ protected function isBlockCompletable($Type)
+ {
+ return method_exists($this, 'block'.$Type.'Complete');
+ }
+
+ #
+ # Code
+
+ protected function blockCode($Line, $Block = null)
+ {
+ if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if ($Line['indent'] >= 4)
+ {
+ $text = substr($Line['body'], 4);
+
+ $Block = array(
+ 'element' => array(
+ 'name' => 'pre',
+ 'handler' => 'element',
+ 'text' => array(
+ 'name' => 'code',
+ 'text' => $text,
+ ),
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockCodeContinue($Line, $Block)
+ {
+ if ($Line['indent'] >= 4)
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['text']['text'] .= "\n";
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['element']['text']['text'] .= "\n";
+
+ $text = substr($Line['body'], 4);
+
+ $Block['element']['text']['text'] .= $text;
+
+ return $Block;
+ }
+ }
+
+ protected function blockCodeComplete($Block)
+ {
+ $text = $Block['element']['text']['text'];
+
+ $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
+
+ $Block['element']['text']['text'] = $text;
+
+ return $Block;
+ }
+
+ #
+ # Comment
+
+ protected function blockComment($Line)
+ {
+ if ($this->markupEscaped)
+ {
+ return;
+ }
+
+ if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
+ {
+ $Block = array(
+ 'markup' => $Line['body'],
+ );
+
+ if (preg_match('/-->$/', $Line['text']))
+ {
+ $Block['closed'] = true;
+ }
+
+ return $Block;
+ }
+ }
+
+ protected function blockCommentContinue($Line, array $Block)
+ {
+ if (isset($Block['closed']))
+ {
+ return;
+ }
+
+ $Block['markup'] .= "\n" . $Line['body'];
+
+ if (preg_match('/-->$/', $Line['text']))
+ {
+ $Block['closed'] = true;
+ }
+
+ return $Block;
+ }
+
+ #
+ # Fenced Code
+
+ protected function blockFencedCode($Line)
+ {
+ if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
+ {
+ $Element = array(
+ 'name' => 'code',
+ 'text' => '',
+ );
+
+ if (isset($matches[1]))
+ {
+ $class = 'language-'.$matches[1];
+
+ $Element['attributes'] = array(
+ 'class' => $class,
+ );
+ }
+
+ $Block = array(
+ 'char' => $Line['text'][0],
+ 'element' => array(
+ 'name' => 'pre',
+ 'handler' => 'element',
+ 'text' => $Element,
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockFencedCodeContinue($Line, $Block)
+ {
+ if (isset($Block['complete']))
+ {
+ return;
+ }
+
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['text']['text'] .= "\n";
+
+ unset($Block['interrupted']);
+ }
+
+ if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
+ {
+ $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
+
+ $Block['complete'] = true;
+
+ return $Block;
+ }
+
+ $Block['element']['text']['text'] .= "\n".$Line['body'];
+
+ return $Block;
+ }
+
+ protected function blockFencedCodeComplete($Block)
+ {
+ $text = $Block['element']['text']['text'];
+
+ $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
+
+ $Block['element']['text']['text'] = $text;
+
+ return $Block;
+ }
+
+ #
+ # Header
+
+ protected function blockHeader($Line)
+ {
+ if (isset($Line['text'][1]))
+ {
+ $level = 1;
+
+ while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
+ {
+ $level ++;
+ }
+
+ if ($level > 6)
+ {
+ return;
+ }
+
+ $text = trim($Line['text'], '# ');
+
+ $Block = array(
+ 'element' => array(
+ 'name' => 'h' . min(6, $level),
+ 'text' => $text,
+ 'handler' => 'line',
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # List
+
+ protected function blockList($Line)
+ {
+ list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
+
+ if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
+ {
+ $Block = array(
+ 'indent' => $Line['indent'],
+ 'pattern' => $pattern,
+ 'element' => array(
+ 'name' => $name,
+ 'handler' => 'elements',
+ ),
+ );
+
+ if($name === 'ol')
+ {
+ $listStart = stristr($matches[0], '.', true);
+
+ if($listStart !== '1')
+ {
+ $Block['element']['attributes'] = array('start' => $listStart);
+ }
+ }
+
+ $Block['li'] = array(
+ 'name' => 'li',
+ 'handler' => 'li',
+ 'text' => array(
+ $matches[2],
+ ),
+ );
+
+ $Block['element']['text'] []= & $Block['li'];
+
+ return $Block;
+ }
+ }
+
+ protected function blockListContinue($Line, array $Block)
+ {
+ if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['li']['text'] []= '';
+
+ unset($Block['interrupted']);
+ }
+
+ unset($Block['li']);
+
+ $text = isset($matches[1]) ? $matches[1] : '';
+
+ $Block['li'] = array(
+ 'name' => 'li',
+ 'handler' => 'li',
+ 'text' => array(
+ $text,
+ ),
+ );
+
+ $Block['element']['text'] []= & $Block['li'];
+
+ return $Block;
+ }
+
+ if ($Line['text'][0] === '[' and $this->blockReference($Line))
+ {
+ return $Block;
+ }
+
+ if ( ! isset($Block['interrupted']))
+ {
+ $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
+
+ $Block['li']['text'] []= $text;
+
+ return $Block;
+ }
+
+ if ($Line['indent'] > 0)
+ {
+ $Block['li']['text'] []= '';
+
+ $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
+
+ $Block['li']['text'] []= $text;
+
+ unset($Block['interrupted']);
+
+ return $Block;
+ }
+ }
+
+ #
+ # Quote
+
+ protected function blockQuote($Line)
+ {
+ if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'blockquote',
+ 'handler' => 'lines',
+ 'text' => (array) $matches[1],
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockQuoteContinue($Line, array $Block)
+ {
+ if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['text'] []= '';
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['element']['text'] []= $matches[1];
+
+ return $Block;
+ }
+
+ if ( ! isset($Block['interrupted']))
+ {
+ $Block['element']['text'] []= $Line['text'];
+
+ return $Block;
+ }
+ }
+
+ #
+ # Rule
+
+ protected function blockRule($Line)
+ {
+ if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'hr'
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # Setext
+
+ protected function blockSetextHeader($Line, array $Block = null)
+ {
+ if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if (chop($Line['text'], $Line['text'][0]) === '')
+ {
+ $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
+
+ return $Block;
+ }
+ }
+
+ #
+ # Markup
+
+ protected function blockMarkup($Line)
+ {
+ if ($this->markupEscaped)
+ {
+ return;
+ }
+
+ if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
+ {
+ $element = strtolower($matches[1]);
+
+ if (in_array($element, $this->textLevelElements))
+ {
+ return;
+ }
+
+ $Block = array(
+ 'name' => $matches[1],
+ 'depth' => 0,
+ 'markup' => $Line['text'],
+ );
+
+ $length = strlen($matches[0]);
+
+ $remainder = substr($Line['text'], $length);
+
+ if (trim($remainder) === '')
+ {
+ if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
+ {
+ $Block['closed'] = true;
+
+ $Block['void'] = true;
+ }
+ }
+ else
+ {
+ if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
+ {
+ return;
+ }
+
+ if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
+ {
+ $Block['closed'] = true;
+ }
+ }
+
+ return $Block;
+ }
+ }
+
+ protected function blockMarkupContinue($Line, array $Block)
+ {
+ if (isset($Block['closed']))
+ {
+ return;
+ }
+
+ if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
+ {
+ $Block['depth'] ++;
+ }
+
+ if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
+ {
+ if ($Block['depth'] > 0)
+ {
+ $Block['depth'] --;
+ }
+ else
+ {
+ $Block['closed'] = true;
+ }
+ }
+
+ if (isset($Block['interrupted']))
+ {
+ $Block['markup'] .= "\n";
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['markup'] .= "\n".$Line['body'];
+
+ return $Block;
+ }
+
+ #
+ # Reference
+
+ protected function blockReference($Line)
+ {
+ if (preg_match('/^\[(.+?)\]:[ ]*(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
+ {
+ $id = strtolower($matches[1]);
+
+ $Data = array(
+ 'url' => $matches[2],
+ 'title' => null,
+ );
+
+ if (isset($matches[3]))
+ {
+ $Data['title'] = $matches[3];
+ }
+
+ $this->DefinitionData['Reference'][$id] = $Data;
+
+ $Block = array(
+ 'hidden' => true,
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # Table
+
+ protected function blockTable($Line, array $Block = null)
+ {
+ if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
+ {
+ $alignments = array();
+
+ $divider = $Line['text'];
+
+ $divider = trim($divider);
+ $divider = trim($divider, '|');
+
+ $dividerCells = explode('|', $divider);
+
+ foreach ($dividerCells as $dividerCell)
+ {
+ $dividerCell = trim($dividerCell);
+
+ if ($dividerCell === '')
+ {
+ continue;
+ }
+
+ $alignment = null;
+
+ if ($dividerCell[0] === ':')
+ {
+ $alignment = 'left';
+ }
+
+ if (substr($dividerCell, - 1) === ':')
+ {
+ $alignment = $alignment === 'left' ? 'center' : 'right';
+ }
+
+ $alignments []= $alignment;
+ }
+
+ # ~
+
+ $HeaderElements = array();
+
+ $header = $Block['element']['text'];
+
+ $header = trim($header);
+ $header = trim($header, '|');
+
+ $headerCells = explode('|', $header);
+
+ foreach ($headerCells as $index => $headerCell)
+ {
+ $headerCell = trim($headerCell);
+
+ $HeaderElement = array(
+ 'name' => 'th',
+ 'text' => $headerCell,
+ 'handler' => 'line',
+ );
+
+ if (isset($alignments[$index]))
+ {
+ $alignment = $alignments[$index];
+
+ $HeaderElement['attributes'] = array(
+ 'style' => 'text-align: '.$alignment.';',
+ );
+ }
+
+ $HeaderElements []= $HeaderElement;
+ }
+
+ # ~
+
+ $Block = array(
+ 'alignments' => $alignments,
+ 'identified' => true,
+ 'element' => array(
+ 'name' => 'table',
+ 'handler' => 'elements',
+ ),
+ );
+
+ $Block['element']['text'] []= array(
+ 'name' => 'thead',
+ 'handler' => 'elements',
+ );
+
+ $Block['element']['text'] []= array(
+ 'name' => 'tbody',
+ 'handler' => 'elements',
+ 'text' => array(),
+ );
+
+ $Block['element']['text'][0]['text'] []= array(
+ 'name' => 'tr',
+ 'handler' => 'elements',
+ 'text' => $HeaderElements,
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockTableContinue($Line, array $Block)
+ {
+ if (isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
+ {
+ $Elements = array();
+
+ $row = $Line['text'];
+
+ $row = trim($row);
+ $row = trim($row, '|');
+
+ preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
+
+ foreach ($matches[0] as $index => $cell)
+ {
+ $cell = trim($cell);
+
+ $Element = array(
+ 'name' => 'td',
+ 'handler' => 'line',
+ 'text' => $cell,
+ );
+
+ if (isset($Block['alignments'][$index]))
+ {
+ $Element['attributes'] = array(
+ 'style' => 'text-align: '.$Block['alignments'][$index].';',
+ );
+ }
+
+ $Elements []= $Element;
+ }
+
+ $Element = array(
+ 'name' => 'tr',
+ 'handler' => 'elements',
+ 'text' => $Elements,
+ );
+
+ $Block['element']['text'][1]['text'] []= $Element;
+
+ return $Block;
+ }
+ }
+
+ #
+ # ~
+ #
+
+ protected function paragraph($Line)
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'p',
+ 'text' => $Line['text'],
+ 'handler' => 'line',
+ ),
+ );
+
+ return $Block;
+ }
+
+ #
+ # Inline Elements
+ #
+
+ protected $InlineTypes = array(
+ '"' => array('SpecialCharacter'),
+ '!' => array('Image'),
+ '&' => array('SpecialCharacter'),
+ '*' => array('Emphasis'),
+ ':' => array('Url'),
+ '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
+ '>' => array('SpecialCharacter'),
+ '[' => array('Link'),
+ '_' => array('Emphasis'),
+ '`' => array('Code'),
+ '~' => array('Strikethrough'),
+ '\\' => array('EscapeSequence'),
+ );
+
+ # ~
+
+ protected $inlineMarkerList = '!"*_&[:<>`~\\';
+
+ #
+ # ~
+ #
+
+ public function line($text)
+ {
+ $markup = '';
+
+ # $excerpt is based on the first occurrence of a marker
+
+ while ($excerpt = strpbrk($text, $this->inlineMarkerList))
+ {
+ $marker = $excerpt[0];
+
+ $markerPosition = strpos($text, $marker);
+
+ $Excerpt = array('text' => $excerpt, 'context' => $text);
+
+ foreach ($this->InlineTypes[$marker] as $inlineType)
+ {
+ $Inline = $this->{'inline'.$inlineType}($Excerpt);
+
+ if ( ! isset($Inline))
+ {
+ continue;
+ }
+
+ # makes sure that the inline belongs to "our" marker
+
+ if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
+ {
+ continue;
+ }
+
+ # sets a default inline position
+
+ if ( ! isset($Inline['position']))
+ {
+ $Inline['position'] = $markerPosition;
+ }
+
+ # the text that comes before the inline
+ $unmarkedText = substr($text, 0, $Inline['position']);
+
+ # compile the unmarked text
+ $markup .= $this->unmarkedText($unmarkedText);
+
+ # compile the inline
+ $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
+
+ # remove the examined text
+ $text = substr($text, $Inline['position'] + $Inline['extent']);
+
+ continue 2;
+ }
+
+ # the marker does not belong to an inline
+
+ $unmarkedText = substr($text, 0, $markerPosition + 1);
+
+ $markup .= $this->unmarkedText($unmarkedText);
+
+ $text = substr($text, $markerPosition + 1);
+ }
+
+ $markup .= $this->unmarkedText($text);
+
+ return $markup;
+ }
+
+ #
+ # ~
+ #
+
+ protected function inlineCode($Excerpt)
+ {
+ $marker = $Excerpt['text'][0];
+
+ if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(? strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'code',
+ 'text' => $text,
+ ),
+ );
+ }
+ }
+
+ protected function inlineEmailTag($Excerpt)
+ {
+ if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
+ {
+ $url = $matches[1];
+
+ if ( ! isset($matches[2]))
+ {
+ $url = 'mailto:' . $url;
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $matches[1],
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+ }
+ }
+
+ protected function inlineEmphasis($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]))
+ {
+ return;
+ }
+
+ $marker = $Excerpt['text'][0];
+
+ if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
+ {
+ $emphasis = 'strong';
+ }
+ elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
+ {
+ $emphasis = 'em';
+ }
+ else
+ {
+ return;
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => $emphasis,
+ 'handler' => 'line',
+ 'text' => $matches[1],
+ ),
+ );
+ }
+
+ protected function inlineEscapeSequence($Excerpt)
+ {
+ if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
+ {
+ return array(
+ 'markup' => $Excerpt['text'][1],
+ 'extent' => 2,
+ );
+ }
+ }
+
+ protected function inlineImage($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
+ {
+ return;
+ }
+
+ $Excerpt['text']= substr($Excerpt['text'], 1);
+
+ $Link = $this->inlineLink($Excerpt);
+
+ if ($Link === null)
+ {
+ return;
+ }
+
+ $Inline = array(
+ 'extent' => $Link['extent'] + 1,
+ 'element' => array(
+ 'name' => 'img',
+ 'attributes' => array(
+ 'src' => $Link['element']['attributes']['href'],
+ 'alt' => $Link['element']['text'],
+ ),
+ ),
+ );
+
+ $Inline['element']['attributes'] += $Link['element']['attributes'];
+
+ unset($Inline['element']['attributes']['href']);
+
+ return $Inline;
+ }
+
+ protected function inlineLink($Excerpt)
+ {
+ $Element = array(
+ 'name' => 'a',
+ 'handler' => 'line',
+ 'text' => null,
+ 'attributes' => array(
+ 'href' => null,
+ 'title' => null,
+ ),
+ );
+
+ $extent = 0;
+
+ $remainder = $Excerpt['text'];
+
+ if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
+ {
+ $Element['text'] = $matches[1];
+
+ $extent += strlen($matches[0]);
+
+ $remainder = substr($remainder, $extent);
+ }
+ else
+ {
+ return;
+ }
+
+ if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
+ {
+ $Element['attributes']['href'] = $matches[1];
+
+ if (isset($matches[2]))
+ {
+ $Element['attributes']['title'] = substr($matches[2], 1, - 1);
+ }
+
+ $extent += strlen($matches[0]);
+ }
+ else
+ {
+ if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
+ {
+ $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
+ $definition = strtolower($definition);
+
+ $extent += strlen($matches[0]);
+ }
+ else
+ {
+ $definition = strtolower($Element['text']);
+ }
+
+ if ( ! isset($this->DefinitionData['Reference'][$definition]))
+ {
+ return;
+ }
+
+ $Definition = $this->DefinitionData['Reference'][$definition];
+
+ $Element['attributes']['href'] = $Definition['url'];
+ $Element['attributes']['title'] = $Definition['title'];
+ }
+
+ $Element['attributes']['href'] = str_replace(array('&', '<'), array('&', '<'), $Element['attributes']['href']);
+
+ return array(
+ 'extent' => $extent,
+ 'element' => $Element,
+ );
+ }
+
+ protected function inlineMarkup($Excerpt)
+ {
+ if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false)
+ {
+ return;
+ }
+
+ if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'markup' => $matches[0],
+ 'extent' => strlen($matches[0]),
+ );
+ }
+
+ if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'markup' => $matches[0],
+ 'extent' => strlen($matches[0]),
+ );
+ }
+
+ if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'markup' => $matches[0],
+ 'extent' => strlen($matches[0]),
+ );
+ }
+ }
+
+ protected function inlineSpecialCharacter($Excerpt)
+ {
+ if ($Excerpt['text'][0] === '&' and ! preg_match('/^?\w+;/', $Excerpt['text']))
+ {
+ return array(
+ 'markup' => '&',
+ 'extent' => 1,
+ );
+ }
+
+ $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
+
+ if (isset($SpecialCharacter[$Excerpt['text'][0]]))
+ {
+ return array(
+ 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
+ 'extent' => 1,
+ );
+ }
+ }
+
+ protected function inlineStrikethrough($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]))
+ {
+ return;
+ }
+
+ if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'del',
+ 'text' => $matches[1],
+ 'handler' => 'line',
+ ),
+ );
+ }
+ }
+
+ protected function inlineUrl($Excerpt)
+ {
+ if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
+ {
+ return;
+ }
+
+ if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
+ {
+ $Inline = array(
+ 'extent' => strlen($matches[0][0]),
+ 'position' => $matches[0][1],
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $matches[0][0],
+ 'attributes' => array(
+ 'href' => $matches[0][0],
+ ),
+ ),
+ );
+
+ return $Inline;
+ }
+ }
+
+ protected function inlineUrlTag($Excerpt)
+ {
+ if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
+ {
+ $url = str_replace(array('&', '<'), array('&', '<'), $matches[1]);
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $url,
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+ }
+ }
+
+ # ~
+
+ protected function unmarkedText($text)
+ {
+ if ($this->breaksEnabled)
+ {
+ $text = preg_replace('/[ ]*\n/', " \n", $text);
+ }
+ else
+ {
+ $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', " \n", $text);
+ $text = str_replace(" \n", "\n", $text);
+ }
+
+ return $text;
+ }
+
+ #
+ # Handlers
+ #
+
+ protected function element(array $Element)
+ {
+ $markup = '<'.$Element['name'];
+
+ if (isset($Element['attributes']))
+ {
+ foreach ($Element['attributes'] as $name => $value)
+ {
+ if ($value === null)
+ {
+ continue;
+ }
+
+ $markup .= ' '.$name.'="'.$value.'"';
+ }
+ }
+
+ if (isset($Element['text']))
+ {
+ $markup .= '>';
+
+ if (isset($Element['handler']))
+ {
+ $markup .= $this->{$Element['handler']}($Element['text']);
+ }
+ else
+ {
+ $markup .= $Element['text'];
+ }
+
+ $markup .= ''.$Element['name'].'>';
+ }
+ else
+ {
+ $markup .= ' />';
+ }
+
+ return $markup;
+ }
+
+ protected function elements(array $Elements)
+ {
+ $markup = '';
+
+ foreach ($Elements as $Element)
+ {
+ $markup .= "\n" . $this->element($Element);
+ }
+
+ $markup .= "\n";
+
+ return $markup;
+ }
+
+ # ~
+
+ protected function li($lines)
+ {
+ $markup = $this->lines($lines);
+
+ $trimmedMarkup = trim($markup);
+
+ if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '')
+ {
+ $markup = $trimmedMarkup;
+ $markup = substr($markup, 3);
+
+ $position = strpos($markup, "
");
+
+ $markup = substr_replace($markup, '', $position, 4);
+ }
+
+ return $markup;
+ }
+
+ #
+ # Deprecated Methods
+ #
+
+ function parse($text)
+ {
+ $markup = $this->text($text);
+
+ return $markup;
+ }
+
+ #
+ # Static Methods
+ #
+
+ static function instance($name = 'default')
+ {
+ if (isset(self::$instances[$name]))
+ {
+ return self::$instances[$name];
+ }
+
+ $instance = new static();
+
+ self::$instances[$name] = $instance;
+
+ return $instance;
+ }
+
+ private static $instances = array();
+
+ #
+ # Fields
+ #
+
+ protected $DefinitionData;
+
+ #
+ # Read-Only
+
+ protected $specialCharacters = array(
+ '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
+ );
+
+ protected $StrongRegex = array(
+ '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
+ '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
+ );
+
+ protected $EmRegex = array(
+ '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
+ '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
+ );
+
+ protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
+
+ protected $voidElements = array(
+ 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
+ );
+
+ protected $textLevelElements = array(
+ 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
+ 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
+ 'i', 'rp', 'del', 'code', 'strike', 'marquee',
+ 'q', 'rt', 'ins', 'font', 'strong',
+ 's', 'tt', 'kbd', 'mark',
+ 'u', 'xm', 'sub', 'nobr',
+ 'sup', 'ruby',
+ 'var', 'span',
+ 'wbr', 'time',
+ );
+}
diff --git a/system/src/Grav/Framework/Parsedown/ParsedownExtra.php b/system/src/Grav/Framework/Parsedown/ParsedownExtra.php
new file mode 100644
index 0000000..8219005
--- /dev/null
+++ b/system/src/Grav/Framework/Parsedown/ParsedownExtra.php
@@ -0,0 +1,532 @@
+BlockTypes[':'] []= 'DefinitionList';
+ $this->BlockTypes['*'] []= 'Abbreviation';
+
+ # identify footnote definitions before reference definitions
+ array_unshift($this->BlockTypes['['], 'Footnote');
+
+ # identify footnote markers before before links
+ array_unshift($this->InlineTypes['['], 'FootnoteMarker');
+ }
+
+ #
+ # ~
+
+ function text($text)
+ {
+ $markup = parent::text($text);
+
+ # merge consecutive dl elements
+
+ $markup = preg_replace('/<\/dl>\s+\s+/', '', $markup);
+
+ # add footnotes
+
+ if (isset($this->DefinitionData['Footnote']))
+ {
+ $Element = $this->buildFootnoteElement();
+
+ $markup .= "\n" . $this->element($Element);
+ }
+
+ return $markup;
+ }
+
+ #
+ # Blocks
+ #
+
+ #
+ # Abbreviation
+
+ protected function blockAbbreviation($Line)
+ {
+ if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches))
+ {
+ $this->DefinitionData['Abbreviation'][$matches[1]] = $matches[2];
+
+ $Block = array(
+ 'hidden' => true,
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # Footnote
+
+ protected function blockFootnote($Line)
+ {
+ if (preg_match('/^\[\^(.+?)\]:[ ]?(.*)$/', $Line['text'], $matches))
+ {
+ $Block = array(
+ 'label' => $matches[1],
+ 'text' => $matches[2],
+ 'hidden' => true,
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockFootnoteContinue($Line, $Block)
+ {
+ if ($Line['text'][0] === '[' and preg_match('/^\[\^(.+?)\]:/', $Line['text']))
+ {
+ return;
+ }
+
+ if (isset($Block['interrupted']))
+ {
+ if ($Line['indent'] >= 4)
+ {
+ $Block['text'] .= "\n\n" . $Line['text'];
+
+ return $Block;
+ }
+ }
+ else
+ {
+ $Block['text'] .= "\n" . $Line['text'];
+
+ return $Block;
+ }
+ }
+
+ protected function blockFootnoteComplete($Block)
+ {
+ $this->DefinitionData['Footnote'][$Block['label']] = array(
+ 'text' => $Block['text'],
+ 'count' => null,
+ 'number' => null,
+ );
+
+ return $Block;
+ }
+
+ #
+ # Definition List
+
+ protected function blockDefinitionList($Line, $Block)
+ {
+ if ( ! isset($Block) or isset($Block['type']))
+ {
+ return;
+ }
+
+ $Element = array(
+ 'name' => 'dl',
+ 'handler' => 'elements',
+ 'text' => array(),
+ );
+
+ $terms = explode("\n", $Block['element']['text']);
+
+ foreach ($terms as $term)
+ {
+ $Element['text'] []= array(
+ 'name' => 'dt',
+ 'handler' => 'line',
+ 'text' => $term,
+ );
+ }
+
+ $Block['element'] = $Element;
+
+ $Block = $this->addDdElement($Line, $Block);
+
+ return $Block;
+ }
+
+ protected function blockDefinitionListContinue($Line, array $Block)
+ {
+ if ($Line['text'][0] === ':')
+ {
+ $Block = $this->addDdElement($Line, $Block);
+
+ return $Block;
+ }
+ else
+ {
+ if (isset($Block['interrupted']) and $Line['indent'] === 0)
+ {
+ return;
+ }
+
+ if (isset($Block['interrupted']))
+ {
+ $Block['dd']['handler'] = 'text';
+ $Block['dd']['text'] .= "\n\n";
+
+ unset($Block['interrupted']);
+ }
+
+ $text = substr($Line['body'], min($Line['indent'], 4));
+
+ $Block['dd']['text'] .= "\n" . $text;
+
+ return $Block;
+ }
+ }
+
+ #
+ # Header
+
+ protected function blockHeader($Line)
+ {
+ $Block = parent::blockHeader($Line);
+
+ if ($Block !== null && preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
+ {
+ $attributeString = $matches[1][0];
+
+ $Block['element']['attributes'] = $this->parseAttributeData($attributeString);
+
+ $Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]);
+ }
+
+ return $Block;
+ }
+
+ #
+ # Markup
+
+ protected function blockMarkupComplete($Block)
+ {
+ if ( ! isset($Block['void']))
+ {
+ $Block['markup'] = $this->processTag($Block['markup']);
+ }
+
+ return $Block;
+ }
+
+ #
+ # Setext
+
+ protected function blockSetextHeader($Line, array $Block = null)
+ {
+ $Block = parent::blockSetextHeader($Line, $Block);
+
+ if ($Block !== null && preg_match('/[ ]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
+ {
+ $attributeString = $matches[1][0];
+
+ $Block['element']['attributes'] = $this->parseAttributeData($attributeString);
+
+ $Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]);
+ }
+
+ return $Block;
+ }
+
+ #
+ # Inline Elements
+ #
+
+ #
+ # Footnote Marker
+
+ protected function inlineFootnoteMarker($Excerpt)
+ {
+ if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches))
+ {
+ $name = $matches[1];
+
+ if ( ! isset($this->DefinitionData['Footnote'][$name]))
+ {
+ return;
+ }
+
+ $this->DefinitionData['Footnote'][$name]['count'] ++;
+
+ if ( ! isset($this->DefinitionData['Footnote'][$name]['number']))
+ {
+ $this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » &
+ }
+
+ $Element = array(
+ 'name' => 'sup',
+ 'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name),
+ 'handler' => 'element',
+ 'text' => array(
+ 'name' => 'a',
+ 'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'),
+ 'text' => $this->DefinitionData['Footnote'][$name]['number'],
+ ),
+ );
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => $Element,
+ );
+ }
+ }
+
+ private $footnoteCount = 0;
+
+ #
+ # Link
+
+ protected function inlineLink($Excerpt)
+ {
+ $Link = parent::inlineLink($Excerpt);
+
+ $remainder = $Link !== null ? substr($Excerpt['text'], $Link['extent']) : '';
+
+ if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches))
+ {
+ $Link['element']['attributes'] += $this->parseAttributeData($matches[1]);
+
+ $Link['extent'] += strlen($matches[0]);
+ }
+
+ return $Link;
+ }
+
+ #
+ # ~
+ #
+
+ protected function unmarkedText($text)
+ {
+ $text = parent::unmarkedText($text);
+
+ if (isset($this->DefinitionData['Abbreviation']))
+ {
+ foreach ($this->DefinitionData['Abbreviation'] as $abbreviation => $meaning)
+ {
+ $pattern = '/\b'.preg_quote($abbreviation, '/').'\b/';
+
+ $text = preg_replace($pattern, ''.$abbreviation.' ', $text);
+ }
+ }
+
+ return $text;
+ }
+
+ #
+ # Util Methods
+ #
+
+ protected function addDdElement(array $Line, array $Block)
+ {
+ $text = substr($Line['text'], 1);
+ $text = trim($text);
+
+ unset($Block['dd']);
+
+ $Block['dd'] = array(
+ 'name' => 'dd',
+ 'handler' => 'line',
+ 'text' => $text,
+ );
+
+ if (isset($Block['interrupted']))
+ {
+ $Block['dd']['handler'] = 'text';
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['element']['text'] []= & $Block['dd'];
+
+ return $Block;
+ }
+
+ protected function buildFootnoteElement()
+ {
+ $Element = array(
+ 'name' => 'div',
+ 'attributes' => array('class' => 'footnotes'),
+ 'handler' => 'elements',
+ 'text' => array(
+ array(
+ 'name' => 'hr',
+ ),
+ array(
+ 'name' => 'ol',
+ 'handler' => 'elements',
+ 'text' => array(),
+ ),
+ ),
+ );
+
+ uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes');
+
+ foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData)
+ {
+ if ( ! isset($DefinitionData['number']))
+ {
+ continue;
+ }
+
+ $text = $DefinitionData['text'];
+
+ $text = parent::text($text);
+
+ $numbers = range(1, $DefinitionData['count']);
+
+ $backLinksMarkup = '';
+
+ foreach ($numbers as $number)
+ {
+ $backLinksMarkup .= ' ';
+ }
+
+ $backLinksMarkup = substr($backLinksMarkup, 1);
+
+ if (substr($text, - 4) === '
')
+ {
+ $backLinksMarkup = ' '.$backLinksMarkup;
+
+ $text = substr_replace($text, $backLinksMarkup.'', - 4);
+ }
+ else
+ {
+ $text .= "\n".''.$backLinksMarkup.'
';
+ }
+
+ $Element['text'][1]['text'] []= array(
+ 'name' => 'li',
+ 'attributes' => array('id' => 'fn:'.$definitionId),
+ 'text' => "\n".$text."\n",
+ );
+ }
+
+ return $Element;
+ }
+
+ # ~
+
+ protected function parseAttributeData($attributeString)
+ {
+ $Data = array();
+
+ $attributes = preg_split('/[ ]+/', $attributeString, - 1, PREG_SPLIT_NO_EMPTY);
+
+ foreach ($attributes as $attribute)
+ {
+ if ($attribute[0] === '#')
+ {
+ $Data['id'] = substr($attribute, 1);
+ }
+ else # "."
+ {
+ $classes []= substr($attribute, 1);
+ }
+ }
+
+ if (isset($classes))
+ {
+ $Data['class'] = implode(' ', $classes);
+ }
+
+ return $Data;
+ }
+
+ # ~
+
+ protected function processTag($elementMarkup) # recursive
+ {
+ # http://stackoverflow.com/q/1148928/200145
+ libxml_use_internal_errors(true);
+
+ $DOMDocument = new \DOMDocument;
+
+ # http://stackoverflow.com/q/11309194/200145
+ $elementMarkup = mb_convert_encoding($elementMarkup, 'HTML-ENTITIES', 'UTF-8');
+
+ # http://stackoverflow.com/q/4879946/200145
+ $DOMDocument->loadHTML($elementMarkup);
+ $DOMDocument->removeChild($DOMDocument->doctype);
+ $DOMDocument->replaceChild($DOMDocument->firstChild->firstChild->firstChild, $DOMDocument->firstChild);
+
+ $elementText = '';
+
+ if ($DOMDocument->documentElement->getAttribute('markdown') === '1')
+ {
+ foreach ($DOMDocument->documentElement->childNodes as $Node)
+ {
+ $elementText .= $DOMDocument->saveHTML($Node);
+ }
+
+ $DOMDocument->documentElement->removeAttribute('markdown');
+
+ $elementText = "\n".$this->text($elementText)."\n";
+ }
+ else
+ {
+ foreach ($DOMDocument->documentElement->childNodes as $Node)
+ {
+ $nodeMarkup = $DOMDocument->saveHTML($Node);
+
+ if ($Node instanceof \DOMElement and ! in_array($Node->nodeName, $this->textLevelElements))
+ {
+ $elementText .= $this->processTag($nodeMarkup);
+ }
+ else
+ {
+ $elementText .= $nodeMarkup;
+ }
+ }
+ }
+
+ # because we don't want for markup to get encoded
+ $DOMDocument->documentElement->nodeValue = 'placeholder\x1A';
+
+ $markup = $DOMDocument->saveHTML($DOMDocument->documentElement);
+ $markup = str_replace('placeholder\x1A', $elementText, $markup);
+
+ return $markup;
+ }
+
+ # ~
+
+ protected function sortFootnotes($A, $B) # callback
+ {
+ return $A['number'] - $B['number'];
+ }
+
+ #
+ # Fields
+ #
+
+ protected $regexAttribute = '(?:[#.][-\w]+[ ]*)';
+}
diff --git a/system/src/Grav/Framework/Psr7/AbstractUri.php b/system/src/Grav/Framework/Psr7/AbstractUri.php
new file mode 100644
index 0000000..e0d46b6
--- /dev/null
+++ b/system/src/Grav/Framework/Psr7/AbstractUri.php
@@ -0,0 +1,408 @@
+ 80,
+ 'https' => 443
+ ];
+
+ /** @var string Uri scheme. */
+ private $scheme = '';
+
+ /** @var string Uri user. */
+ private $user = '';
+
+ /** @var string Uri password. */
+ private $password = '';
+
+ /** @var string Uri host. */
+ private $host = '';
+
+ /** @var int|null Uri port. */
+ private $port;
+
+ /** @var string Uri path. */
+ private $path = '';
+
+ /** @var string Uri query string (without ?). */
+ private $query = '';
+
+ /** @var string Uri fragment (without #). */
+ private $fragment = '';
+
+ /**
+ * Please define constructor which calls $this->init().
+ */
+ abstract public function __construct();
+
+ /**
+ * @inheritdoc
+ */
+ public function getScheme()
+ {
+ return $this->scheme;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAuthority()
+ {
+ $authority = $this->host;
+
+ $userInfo = $this->getUserInfo();
+ if ($userInfo !== '') {
+ $authority = $userInfo . '@' . $authority;
+ }
+
+ if ($this->port !== null) {
+ $authority .= ':' . $this->port;
+ }
+
+ return $authority;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getUserInfo()
+ {
+ $userInfo = $this->user;
+
+ if ($this->password !== '') {
+ $userInfo .= ':' . $this->password;
+ }
+
+ return $userInfo;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getFragment()
+ {
+ return $this->fragment;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function withScheme($scheme)
+ {
+ $scheme = UriPartsFilter::filterScheme($scheme);
+
+ if ($this->scheme === $scheme) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->scheme = $scheme;
+ $new->unsetDefaultPort();
+ $new->validate();
+
+ return $new;
+ }
+
+ /**
+ * @inheritdoc
+ * @throws \InvalidArgumentException
+ */
+ public function withUserInfo($user, $password = null)
+ {
+ $user = UriPartsFilter::filterUserInfo($user);
+ $password = UriPartsFilter::filterUserInfo($password ?? '');
+
+ if ($this->user === $user && $this->password === $password) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->user = $user;
+ $new->password = $user !== '' ? $password : '';
+ $new->validate();
+
+ return $new;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function withHost($host)
+ {
+ $host = UriPartsFilter::filterHost($host);
+
+ if ($this->host === $host) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->host = $host;
+ $new->validate();
+
+ return $new;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function withPort($port)
+ {
+ $port = UriPartsFilter::filterPort($port);
+
+ if ($this->port === $port) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->port = $port;
+ $new->unsetDefaultPort();
+ $new->validate();
+
+ return $new;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function withPath($path)
+ {
+ $path = UriPartsFilter::filterPath($path);
+
+ if ($this->path === $path) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->path = $path;
+ $new->validate();
+
+ return $new;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function withQuery($query)
+ {
+ $query = UriPartsFilter::filterQueryOrFragment($query);
+
+ if ($this->query === $query) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->query = $query;
+
+ return $new;
+ }
+
+ /**
+ * @inheritdoc
+ * @throws \InvalidArgumentException
+ */
+ public function withFragment($fragment)
+ {
+ $fragment = UriPartsFilter::filterQueryOrFragment($fragment);
+
+ if ($this->fragment === $fragment) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->fragment = $fragment;
+
+ return $new;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getUrl();
+ }
+
+ /**
+ * @return array
+ */
+ protected function getParts()
+ {
+ return [
+ 'scheme' => $this->scheme,
+ 'host' => $this->host,
+ 'port' => $this->port,
+ 'user' => $this->user,
+ 'pass' => $this->password,
+ 'path' => $this->path,
+ 'query' => $this->query,
+ 'fragment' => $this->fragment
+ ];
+ }
+
+ /**
+ * Return the fully qualified base URL ( like http://getgrav.org ).
+ *
+ * Note that this method never includes a trailing /
+ *
+ * @return string
+ */
+ protected function getBaseUrl()
+ {
+ $uri = '';
+
+ $scheme = $this->getScheme();
+ if ($scheme !== '') {
+ $uri .= $scheme . ':';
+ }
+
+ $authority = $this->getAuthority();
+ if ($authority !== '' || $scheme === 'file') {
+ $uri .= '//' . $authority;
+ }
+
+ return $uri;
+ }
+
+ /**
+ * @return string
+ */
+ protected function getUrl()
+ {
+ $uri = $this->getBaseUrl() . $this->getPath();
+
+ $query = $this->getQuery();
+ if ($query !== '') {
+ $uri .= '?' . $query;
+ }
+
+ $fragment = $this->getFragment();
+ if ($fragment !== '') {
+ $uri .= '#' . $fragment;
+ }
+
+ return $uri;
+ }
+
+ /**
+ * @return string
+ */
+ protected function getUser()
+ {
+ return $this->user;
+ }
+
+ /**
+ * @return string
+ */
+ protected function getPassword()
+ {
+ return $this->password;
+ }
+
+ /**
+ * @param array $parts
+ * @throws \InvalidArgumentException
+ */
+ protected function initParts(array $parts)
+ {
+ $this->scheme = isset($parts['scheme']) ? UriPartsFilter::filterScheme($parts['scheme']) : '';
+ $this->user = isset($parts['user']) ? UriPartsFilter::filterUserInfo($parts['user']) : '';
+ $this->password = isset($parts['pass']) ? UriPartsFilter::filterUserInfo($parts['pass']) : '';
+ $this->host = isset($parts['host']) ? UriPartsFilter::filterHost($parts['host']) : '';
+ $this->port = isset($parts['port']) ? UriPartsFilter::filterPort((int)$parts['port']) : null;
+ $this->path = isset($parts['path']) ? UriPartsFilter::filterPath($parts['path']) : '';
+ $this->query = isset($parts['query']) ? UriPartsFilter::filterQueryOrFragment($parts['query']) : '';
+ $this->fragment = isset($parts['fragment']) ? UriPartsFilter::filterQueryOrFragment($parts['fragment']) : '';
+
+ $this->unsetDefaultPort();
+ $this->validate();
+ }
+
+ /**
+ * @throws \InvalidArgumentException
+ */
+ private function validate()
+ {
+ if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
+ throw new \InvalidArgumentException('Uri with a scheme must have a host');
+ }
+
+ if ($this->getAuthority() === '') {
+ if (0 === strpos($this->path, '//')) {
+ throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes \'//\'');
+ }
+ if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
+ throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon');
+ }
+ } elseif (isset($this->path[0]) && $this->path[0] !== '/') {
+ throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash \'/\' or be empty');
+ }
+ }
+
+ protected function isDefaultPort()
+ {
+ $scheme = $this->scheme;
+ $port = $this->port;
+
+ return $this->port === null
+ || (isset(static::$defaultPorts[$scheme]) && $port === static::$defaultPorts[$scheme]);
+ }
+
+ private function unsetDefaultPort()
+ {
+ if ($this->isDefaultPort()) {
+ $this->port = null;
+ }
+ }
+}
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
new file mode 100644
index 0000000..541dc4f
--- /dev/null
+++ b/system/src/Grav/Framework/Route/Route.php
@@ -0,0 +1,396 @@
+initParts($parts);
+ }
+
+ /**
+ * @return array
+ */
+ public function getParts()
+ {
+ return [
+ '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,
+ ],
+ ];
+ }
+
+ /**
+ * @return string
+ */
+ public function getRootPrefix()
+ {
+ return $this->root;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLanguagePrefix()
+ {
+ return $this->language !== '' ? '/' . $this->language : '';
+ }
+
+ /**
+ * @param int $offset
+ * @param int|null $length
+ * @return string
+ */
+ public function getRoute($offset = 0, $length = null)
+ {
+ if ($offset !== 0 || $length !== null) {
+ return ($offset === 0 ? '/' : '') . implode('/', $this->getRouteParts($offset, $length));
+ }
+
+ return '/' . $this->route;
+ }
+
+ /**
+ * @return string
+ */
+ public function getExtension()
+ {
+ return $this->extension;
+ }
+
+ /**
+ * @param int $offset
+ * @param int|null $length
+ * @return array
+ */
+ public function getRouteParts($offset = 0, $length = null)
+ {
+ $parts = explode('/', $this->route);
+
+ if ($offset !== 0 || $length !== null) {
+ $parts = \array_slice($parts, $offset, $length);
+ }
+
+ return $parts;
+ }
+
+ /**
+ * Return array of both query and Grav parameters.
+ *
+ * If a parameter exists in both, prefer Grav parameter.
+ *
+ * @return array
+ */
+ public function getParams()
+ {
+ return $this->gravParams + $this->queryParams;
+ }
+
+ /**
+ * @return array
+ */
+ public function getGravParams()
+ {
+ return $this->gravParams;
+ }
+
+ /**
+ * @return array
+ */
+ public function getQueryParams()
+ {
+ return $this->queryParams;
+ }
+
+ /**
+ * Return value of the parameter, looking into both Grav parameters and query parameters.
+ *
+ * If the parameter exists in both, return Grav parameter.
+ *
+ * @param string $param
+ * @return string|array|null
+ */
+ public function getParam($param)
+ {
+ return $this->getGravParam($param) ?? $this->getQueryParam($param);
+ }
+
+ /**
+ * @param string $param
+ * @return string|null
+ */
+ public function getGravParam($param)
+ {
+ return $this->gravParams[$param] ?? null;
+ }
+
+ /**
+ * @param string $param
+ * @return string|array|null
+ */
+ public function getQueryParam($param)
+ {
+ 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;
+ }
+
+ /**
+ * @param string $param
+ * @param mixed $value
+ * @return Route
+ */
+ public function withGravParam($param, $value)
+ {
+ return $this->withParam('gravParams', $param, null !== $value ? (string)$value : null);
+ }
+
+ /**
+ * @param string $param
+ * @param mixed $value
+ * @return Route
+ */
+ public function withQueryParam($param, $value)
+ {
+ return $this->withParam('queryParams', $param, $value);
+ }
+
+ public function withoutParams()
+ {
+ return $this->withoutGravParams()->withoutQueryParams();
+ }
+
+ public function withoutGravParams()
+ {
+ $this->gravParams = [];
+
+ return $this;
+ }
+
+ public function withoutQueryParams()
+ {
+ $this->queryParams = [];
+
+ return $this;
+ }
+
+ /**
+ * @return \Grav\Framework\Uri\Uri
+ */
+ public function getUri()
+ {
+ return UriFactory::createFromParts($this->getParts());
+ }
+
+ /**
+ * @param bool $includeRoot
+ * @return string
+ */
+ public function toString(bool $includeRoot = false)
+ {
+ $url = $this->getUriPath($includeRoot);
+
+ if ($this->queryParams) {
+ $url .= '?' . $this->getUriQuery();
+ }
+
+ return rtrim($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
+ * @param mixed $value
+ * @return static
+ */
+ protected function withParam($type, $param, $value)
+ {
+ $values = $this->{$type} ?? [];
+ $oldValue = $values[$param] ?? null;
+
+ if ($oldValue === $value) {
+ return $this;
+ }
+
+ $new = $this->copy();
+ if ($value === null) {
+ unset($values[$param]);
+ } else {
+ $values[$param] = $value;
+ }
+
+ $new->{$type} = $values;
+
+ return $new;
+ }
+
+ protected function copy()
+ {
+ return clone $this;
+ }
+
+ /**
+ * @param bool $includeRoot
+ * @return string
+ */
+ protected function getUriPath($includeRoot = false)
+ {
+ $parts = $includeRoot ? [$this->root] : [''];
+
+ if ($this->language !== '') {
+ $parts[] = $this->language;
+ }
+
+ $parts[] = $this->extension ? $this->route . '.' . $this->extension : $this->route;
+
+
+ if ($this->gravParams) {
+ $parts[] = RouteFactory::buildParams($this->gravParams);
+ }
+
+ return implode('/', $parts);
+ }
+
+ /**
+ * @return string
+ */
+ protected function getUriQuery()
+ {
+ return UriFactory::buildQuery($this->queryParams);
+ }
+
+ /**
+ * @param array $parts
+ */
+ protected function initParts(array $parts)
+ {
+ if (isset($parts['grav'])) {
+ $gravParts = $parts['grav'];
+ $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'] ?: [];
+
+ } else {
+ $this->root = RouteFactory::getRoot();
+ $this->language = RouteFactory::getLanguage();
+
+ $path = $parts['path'] ?? '/';
+ if (isset($parts['params'])) {
+ $this->route = trim(rawurldecode($path), '/');
+ $this->gravParams = $parts['params'];
+ } else {
+ $this->route = trim(RouteFactory::stripParams($path, true), '/');
+ $this->gravParams = RouteFactory::getParams($path);
+ }
+ if (isset($parts['query'])) {
+ $this->queryParams = UriFactory::parseQuery($parts['query']);
+ }
+ }
+ }
+}
diff --git a/system/src/Grav/Framework/Route/RouteFactory.php b/system/src/Grav/Framework/Route/RouteFactory.php
new file mode 100644
index 0000000..6b3414a
--- /dev/null
+++ b/system/src/Grav/Framework/Route/RouteFactory.php
@@ -0,0 +1,157 @@
+ $path,
+ 'query' => '',
+ 'query_params' => [],
+ 'grav' => [
+ 'root' => self::$root,
+ 'language' => self::$language,
+ 'route' => $path,
+ 'params' => ''
+ ],
+ ];
+ return new Route($parts);
+ }
+
+ public static function getRoot()
+ {
+ return self::$root;
+ }
+
+ public static function setRoot($root)
+ {
+ self::$root = rtrim($root, '/');
+ }
+
+ public static function getLanguage()
+ {
+ return self::$language;
+ }
+
+ public static function setLanguage($language)
+ {
+ self::$language = trim($language, '/');
+ }
+
+ public static function getParamValueDelimiter()
+ {
+ return self::$delimiter;
+ }
+
+ public static function setParamValueDelimiter($delimiter)
+ {
+ self::$delimiter = $delimiter ?: ':';
+ }
+
+ /**
+ * @param array $params
+ * @return string
+ */
+ public static function buildParams(array $params)
+ {
+ if (!$params) {
+ return '';
+ }
+
+ $delimiter = self::$delimiter;
+
+ $output = [];
+ foreach ($params as $key => $value) {
+ $output[] = "{$key}{$delimiter}{$value}";
+ }
+
+ return implode('/', $output);
+ }
+
+ /**
+ * @param string $path
+ * @param bool $decode
+ * @return string
+ */
+ public static function stripParams($path, $decode = false)
+ {
+ $pos = strpos($path, self::$delimiter);
+
+ if ($pos === false) {
+ return $path;
+ }
+
+ $path = \dirname(substr($path, 0, $pos));
+ if ($path === '.') {
+ return '';
+ }
+
+ return $decode ? rawurldecode($path) : $path;
+ }
+
+ /**
+ * @param string $path
+ * @return array
+ */
+ public static function getParams($path)
+ {
+ $params = ltrim(substr($path, \strlen(static::stripParams($path))), '/');
+
+ return $params !== '' ? static::parseParams($params) : [];
+ }
+
+ /**
+ * @param string $str
+ * @return array
+ */
+ public static function parseParams($str)
+ {
+ if ($str === '') {
+ return [];
+ }
+
+ $delimiter = self::$delimiter;
+
+ /** @var array $params */
+ $params = explode('/', $str);
+ foreach ($params as &$param) {
+ /** @var array $parts */
+ $parts = explode($delimiter, $param, 2);
+ if (isset($parts[1])) {
+ $var = rawurldecode($parts[0]);
+ $val = rawurldecode($parts[1]);
+ $param = [$var => $val];
+ }
+ }
+
+ return $params;
+ }
+}
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 @@
+isSessionStarted()) {
+ session_unset();
+ session_destroy();
+ }
+
+ // Set default options.
+ $options += [
+ 'cache_limiter' => 'nocache',
+ 'use_trans_sid' => 0,
+ 'use_cookies' => 1,
+ 'lazy_write' => 1,
+ 'use_strict_mode' => 1
+ ];
+
+ $this->setOptions($options);
+
+ session_register_shutdown();
+
+ self::$instance = $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getId()
+ {
+ return session_id();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setId($id)
+ {
+ session_id($id);
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getName()
+ {
+ return session_name();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setName($name)
+ {
+ session_name($name);
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setOptions(array $options)
+ {
+ if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
+ return;
+ }
+
+ $allowedOptions = [
+ 'save_path' => true,
+ 'name' => true,
+ 'save_handler' => true,
+ 'gc_probability' => true,
+ 'gc_divisor' => true,
+ 'gc_maxlifetime' => true,
+ 'serialize_handler' => true,
+ 'cookie_lifetime' => true,
+ 'cookie_path' => true,
+ 'cookie_domain' => true,
+ 'cookie_secure' => true,
+ 'cookie_httponly' => true,
+ 'use_strict_mode' => true,
+ 'use_cookies' => true,
+ 'use_only_cookies' => true,
+ 'referer_check' => true,
+ 'cache_limiter' => true,
+ 'cache_expire' => true,
+ 'use_trans_sid' => true,
+ '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
+ ];
+
+ foreach ($options as $key => $value) {
+ if (\is_array($value)) {
+ // Allow nested options.
+ foreach ($value as $key2 => $value2) {
+ $ckey = "{$key}.{$key2}";
+ if (isset($value2, $allowedOptions[$ckey])) {
+ $this->setOption($ckey, $value2);
+ }
+ }
+ } elseif (isset($value, $allowedOptions[$key])) {
+ $this->setOption($key, $value);
+ }
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function start($readonly = false)
+ {
+ if (\PHP_SAPI === 'cli') {
+ return $this;
+ }
+
+ $sessionName = session_name();
+ $sessionExists = isset($_COOKIE[$sessionName]);
+
+ // Protection against invalid session cookie names throwing exception: http://php.net/manual/en/function.session-id.php#116836
+ if ($sessionExists && !preg_match('/^[-,a-zA-Z0-9]{1,128}$/', $_COOKIE[$sessionName])) {
+ unset($_COOKIE[$sessionName]);
+ $sessionExists = false;
+ }
+
+ $options = $this->options;
+ if ($readonly) {
+ $options['read_and_close'] = '1';
+ }
+
+ $success = @session_start($options);
+ $user = $success ? $this->__get('user') : null;
+ if (!$success) {
+ $last = error_get_last();
+ $error = $last ? $last['message'] : 'Unknown error';
+
+ throw new SessionException('Failed to start session: ' . $error, 500);
+ }
+
+ $this->started = true;
+
+ if ($user && (!$user instanceof UserInterface || !$user->isValid())) {
+ $this->invalidate();
+
+ throw new SessionException('Invalid User object, session destroyed.', 500);
+ }
+
+ // Extend the lifetime of the session.
+ if ($sessionExists) {
+ $params = session_get_cookie_params();
+
+ setcookie(
+ $sessionName,
+ session_id(),
+ time() + $params['lifetime'],
+ $params['path'],
+ $params['domain'],
+ $params['secure'],
+ $params['httponly']
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function invalidate()
+ {
+ $params = session_get_cookie_params();
+ setcookie(
+ session_name(),
+ '',
+ time() - 42000,
+ $params['path'],
+ $params['domain'],
+ $params['secure'],
+ $params['httponly']
+ );
+
+ if ($this->isSessionStarted()) {
+ session_unset();
+ session_destroy();
+ }
+
+ $this->started = false;
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function close()
+ {
+ if ($this->started) {
+ session_write_close();
+ }
+
+ $this->started = false;
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function clear()
+ {
+ session_unset();
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAll()
+ {
+ return $_SESSION;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($_SESSION);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function isStarted()
+ {
+ return $this->started;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function __isset($name)
+ {
+ return isset($_SESSION[$name]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function __get($name)
+ {
+ return $_SESSION[$name] ?? null;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function __set($name, $value)
+ {
+ $_SESSION[$name] = $value;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function __unset($name)
+ {
+ unset($_SESSION[$name]);
+ }
+
+ /**
+ * http://php.net/manual/en/function.session-status.php#113468
+ * Check if session is started nicely.
+ * @return bool
+ */
+ protected function isSessionStarted()
+ {
+ return \PHP_SAPI !== 'cli' ? \PHP_SESSION_ACTIVE === session_status() : false;
+ }
+
+ /**
+ * @param string $key
+ * @param mixed $value
+ */
+ protected function setOption($key, $value)
+ {
+ if (!\is_string($value)) {
+ if (\is_bool($value)) {
+ $value = $value ? '1' : '0';
+ } else {
+ $value = (string)$value;
+ }
+ }
+
+ $this->options[$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
new file mode 100644
index 0000000..75df0e9
--- /dev/null
+++ b/system/src/Grav/Framework/Session/SessionInterface.php
@@ -0,0 +1,148 @@
+initParts($parts);
+ }
+
+ /**
+ * @return string
+ */
+ public function getUser()
+ {
+ return parent::getUser();
+ }
+
+ /**
+ * @return string
+ */
+ public function getPassword()
+ {
+ return parent::getPassword();
+ }
+
+ /**
+ * @return array
+ */
+ public function getParts()
+ {
+ return parent::getParts();
+ }
+
+ /**
+ * @return string
+ */
+ public function getUrl()
+ {
+ return parent::getUrl();
+ }
+
+ /**
+ * @return string
+ */
+ public function getBaseUrl()
+ {
+ return parent::getBaseUrl();
+ }
+
+ /**
+ * @param string $key
+ * @return string|null
+ */
+ public function getQueryParam($key)
+ {
+ $queryParams = $this->getQueryParams();
+
+ return $queryParams[$key] ?? null;
+ }
+
+ /**
+ * @param string $key
+ * @return UriInterface
+ */
+ public function withoutQueryParam($key)
+ {
+ return GuzzleUri::withoutQueryValue($this, $key);
+ }
+
+ /**
+ * @param string $key
+ * @param string|null $value
+ * @return UriInterface
+ */
+ public function withQueryParam($key, $value)
+ {
+ return GuzzleUri::withQueryValue($this, $key, $value);
+ }
+
+ /**
+ * @return array
+ */
+ public function getQueryParams()
+ {
+ if ($this->queryParams === null) {
+ $this->queryParams = UriFactory::parseQuery($this->getQuery());
+ }
+
+ return $this->queryParams;
+ }
+
+ /**
+ * @param array $params
+ * @return UriInterface
+ */
+ public function withQueryParams(array $params)
+ {
+ $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()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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()
+ {
+ 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)
+ {
+ return GuzzleUri::isSameDocumentReference($this, $base);
+ }
+}
diff --git a/system/src/Grav/Framework/Uri/UriFactory.php b/system/src/Grav/Framework/Uri/UriFactory.php
new file mode 100644
index 0000000..e5cb1d3
--- /dev/null
+++ b/system/src/Grav/Framework/Uri/UriFactory.php
@@ -0,0 +1,166 @@
+ $scheme,
+ 'user' => $user,
+ 'pass' => $pass,
+ 'host' => $host,
+ 'port' => $port,
+ 'path' => $path,
+ 'query' => $query
+ ];
+ }
+
+ /**
+ * UTF-8 aware parse_url() implementation.
+ *
+ * @param string $url
+ * @return array
+ * @throws \InvalidArgumentException
+ */
+ public static function parseUrl($url)
+ {
+ if (!\is_string($url)) {
+ throw new \InvalidArgumentException('URL must be a string');
+ }
+
+ $encodedUrl = preg_replace_callback(
+ '%[^:/@?&=#]+%u',
+ function ($matches) { return rawurlencode($matches[0]); },
+ $url
+ );
+
+ $parts = \is_string($encodedUrl) ? parse_url($encodedUrl) : false;
+ if ($parts === false) {
+ throw new \InvalidArgumentException("Malformed URL: {$url}");
+ }
+
+ return $parts;
+ }
+
+ /**
+ * Parse query string and return it as an array.
+ *
+ * @param string $query
+ * @return mixed
+ */
+ public static function parseQuery($query)
+ {
+ parse_str($query, $params);
+
+ return $params;
+ }
+
+ /**
+ * Build query string from variables.
+ *
+ * @param array $params
+ * @return string
+ */
+ public static function buildQuery(array $params)
+ {
+ if (!$params) {
+ return '';
+ }
+
+ $separator = ini_get('arg_separator.output') ?: '&';
+
+ return http_build_query($params, '', $separator, PHP_QUERY_RFC3986);
+ }
+}
diff --git a/system/src/Grav/Framework/Uri/UriPartsFilter.php b/system/src/Grav/Framework/Uri/UriPartsFilter.php
new file mode 100644
index 0000000..51a971a
--- /dev/null
+++ b/system/src/Grav/Framework/Uri/UriPartsFilter.php
@@ -0,0 +1,141 @@
+= 0 && $port <= 65535))) {
+ return $port;
+ }
+
+ throw new \InvalidArgumentException('Uri port must be null or an integer between 0 and 65535');
+ }
+
+ /**
+ * Filter Uri path.
+ *
+ * This method percent-encodes all reserved characters in the provided path string. This method
+ * will NOT double-encode characters that are already percent-encoded.
+ *
+ * @param string $path The raw uri path.
+ * @return string The RFC 3986 percent-encoded uri path.
+ * @throws \InvalidArgumentException If the path is invalid.
+ * @link http://www.faqs.org/rfcs/rfc3986.html
+ */
+ public static function filterPath($path)
+ {
+ if (!\is_string($path)) {
+ throw new \InvalidArgumentException('Uri path must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^a-zA-Z0-9_\-\.~:@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/u',
+ function ($match) {
+ return rawurlencode($match[0]);
+ },
+ $path
+ ) ?? '';
+ }
+
+ /**
+ * Filters the query string or fragment of a URI.
+ *
+ * @param string $query The raw uri query string.
+ * @return string The percent-encoded query string.
+ * @throws \InvalidArgumentException If the query is invalid.
+ */
+ public static function filterQueryOrFragment($query)
+ {
+ if (!\is_string($query)) {
+ throw new \InvalidArgumentException('Uri query string and fragment must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/u',
+ function ($match) {
+ 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/system/templates/partials/messages.html.twig b/system/templates/partials/messages.html.twig
new file mode 100644
index 0000000..261b3fc
--- /dev/null
+++ b/system/templates/partials/messages.html.twig
@@ -0,0 +1,14 @@
+{% set status_mapping = {'info':'green', 'error': 'red', 'warning': 'yellow'} %}
+
+{% if grav.messages.all %}
+
+ {% for message in grav.messages.fetch %}
+
+ {% set scope = message.scope|e %}
+ {% set color = status_mapping[scope] %}
+
+
{{ message.message|raw }}
+
+ {% endfor %}
+
+{% endif %}
diff --git a/system/templates/partials/metadata.html.twig b/system/templates/partials/metadata.html.twig
new file mode 100644
index 0000000..bf323e7
--- /dev/null
+++ b/system/templates/partials/metadata.html.twig
@@ -0,0 +1,3 @@
+{% for meta in page.metadata %}
+
+{% endfor %}
\ No newline at end of file
diff --git a/tmp/.gitkeep b/tmp/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/user/accounts/.gitkeep b/user/accounts/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/user/config/site.yaml b/user/config/site.yaml
new file mode 100644
index 0000000..4be78ed
--- /dev/null
+++ b/user/config/site.yaml
@@ -0,0 +1,7 @@
+title: Grav
+author:
+ name: Joe Bloggs
+ email: 'joe@example.com'
+metadata:
+ description: 'Grav is an easy to use, yet powerful, open source flat-file CMS'
+
diff --git a/user/config/system.yaml b/user/config/system.yaml
new file mode 100644
index 0000000..99a627e
--- /dev/null
+++ b/user/config/system.yaml
@@ -0,0 +1,45 @@
+absolute_urls: false
+
+home:
+ alias: '/home'
+
+pages:
+ theme: quark
+ markdown:
+ extra: false
+ process:
+ markdown: true
+ twig: false
+
+cache:
+ enabled: true
+ check:
+ method: file
+ driver: auto
+ prefix: 'g'
+
+twig:
+ cache: true
+ debug: true
+ auto_reload: true
+ autoescape: false
+
+assets:
+ css_pipeline: false
+ css_minify: true
+ css_rewrite: true
+ js_pipeline: false
+ js_minify: true
+
+errors:
+ display: true
+ log: true
+
+debugger:
+ enabled: false
+ twig: true
+ shutdown:
+ close_connection: true
+gpm:
+ releases: stable
+ verify_peer: true
diff --git a/user/data/.gitkeep b/user/data/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/user/pages/01.home/default.md b/user/pages/01.home/default.md
new file mode 100644
index 0000000..13b2376
--- /dev/null
+++ b/user/pages/01.home/default.md
@@ -0,0 +1,42 @@
+---
+title: Home
+body_classes: title-center title-h1h2
+---
+
+# Say Hello to Grav!
+## installation successful...
+
+Congratulations! You have installed the **Base Grav Package** that provides a **simple page** and the default **Quark** theme to get you started.
+
+!! If you see a **404 Error** when you click `Typography` in the menu, please refer to the [troubleshooting guide](http://learn.getgrav.org/troubleshooting/page-not-found).
+
+### Find out all about Grav
+
+* Learn about **Grav** by checking out our dedicated [Learn Grav](http://learn.getgrav.org) site.
+* Download **plugins**, **themes**, as well as other Grav **skeleton** packages from the [Grav Downloads](http://getgrav.org/downloads) page.
+* Check out our [Grav Development Blog](http://getgrav.org/blog) to find out the latest goings on in the Grav-verse.
+
+!!! If you want a more **full-featured** base install, you should check out [**Skeleton** packages available in the downloads](http://getgrav.org/downloads).
+
+### Edit this Page
+
+To edit this page, simply navigate to the folder you installed **Grav** into, and then browse to the `user/pages/01.home` folder and open the `default.md` file in your [editor of choice](http://learn.getgrav.org/basics/requirements). You will see the content of this page in [Markdown format](http://learn.getgrav.org/content/markdown).
+
+### Create a New Page
+
+Creating a new page is a simple affair in **Grav**. Simply follow these simple steps:
+
+1. Navigate to your pages folder: `user/pages/` and create a new folder. In this example, we will use [explicit default ordering](http://learn.getgrav.org/content/content-pages) and call the folder `03.mypage`.
+2. Launch your text editor and paste in the following sample code:
+
+ ---
+ title: My New Page
+ ---
+ # My New Page!
+
+ This is the body of **my new page** and I can easily use _Markdown_ syntax here.
+
+3. Save this file in the `user/pages/03.mypage/` folder as `default.md`. This will tell **Grav** to render the page using the **default** template.
+4. That is it! Reload your browser to see your new page in the menu.
+
+! NOTE: The page will automatically show up in the Menu after the "Typography" menu item. If you wish to change the name that shows up in the Menu, simple add: `menu: My Page` between the dashes in the page content. This is called the YAML front matter, and it is where you configure page-specific options.
diff --git a/user/pages/02.typography/default.md b/user/pages/02.typography/default.md
new file mode 100644
index 0000000..d3aa848
--- /dev/null
+++ b/user/pages/02.typography/default.md
@@ -0,0 +1,155 @@
+---
+title: Typography
+---
+
+! Details on the full capabilities of Spectre.css can be found in the [Official Spectre Documentation](https://picturepan2.github.io/spectre/elements.html)
+
+The [Quark theme](https://github.com/getgrav/grav-theme-quark) is the new default theme for Grav built with [Spectre.css](https://picturepan2.github.io/spectre/) the lightweight, responsive and modern CSS framework. Spectre provides basic styles for typography, elements, and a responsive layout system that utilizes best practices and consistent language design.
+
+### Headings
+
+# H1 Heading `40px`
+
+## H2 Heading `32px`
+
+### H3 Heading `28px`
+
+#### H4 Heading `24px`
+
+##### H5 Heading `20px`
+
+###### H6 Heading `16px`
+
+```html
+# H1 Heading
+# H1 Heading `40px``
+
+H1 Heading
+```
+
+### Paragraphs
+
+Lorem ipsum dolor sit amet, consectetur [adipiscing elit. Praesent risus leo, dictum in vehicula sit amet](#), feugiat tempus tellus. Duis quis sodales risus. Etiam euismod ornare consequat.
+
+Climb leg rub face on everything give attitude nap all day for under the bed. Chase mice attack feet but rub face on everything hopped up on goofballs.
+
+### Markdown Semantic Text Elements
+
+**Bold** `**Bold**`
+
+_Italic_ `_Italic_`
+
+~~Deleted~~ `~~Deleted~~`
+
+`Inline Code` `` `Inline Code` ``
+
+### HTML Semantic Text Elements
+
+I18N ``
+
+Citation ``
+
+Ctrl + S ``
+
+TextSuperscripted ``
+
+TextSubscripted ``
+
+Underlined ``
+
+Highlighted ``
+
+20:14 ``
+
+x = y + 2 ``
+
+### Blockquote
+
+> The advance of technology is based on making it fit in so that you don't really even notice it,
+> so it's part of everyday life.
+>
+> - Bill Gates
+
+```markdown
+> The advance of technology is based on making it fit in so that you don't really even notice it,
+> so it's part of everyday life.
+>
+> - Bill Gates
+```
+
+### Unordered List
+
+* list item 1
+* list item 2
+ * list item 2.1
+ * list item 2.2
+ * list item 2.3
+* list item 3
+
+```markdown
+* list item 1
+* list item 2
+ * list item 2.1
+ * list item 2.2
+ * list item 2.3
+* list item 3
+```
+
+### Ordered List
+
+1. list item 1
+1. list item 2
+ 1. list item 2.1
+ 1. list item 2.2
+ 1. list item 2.3
+1. list item 3
+
+```markdown
+1. list item 1
+1. list item 2
+ 1. list item 2.1
+ 1. list item 2.2
+ 1. list item 2.3
+1. list item 3
+```
+
+### Table
+
+| Name | Genre | Release date |
+| :-------------------------- | :---------------------------: | -------------------: |
+| The Shawshank Redemption | Crime, Drama | 14 October 1994 |
+| The Godfather | Crime, Drama | 24 March 1972 |
+| Schindler's List | Biography, Drama, History | 4 February 1994 |
+| Se7en | Crime, Drama, Mystery | 22 September 1995 |
+
+```markdown
+| Name | Genre | Release date |
+| :-------------------------- | :---------------------------: | -------------------: |
+| The Shawshank Redemption | Crime, Drama | 14 October 1994 |
+| The Godfather | Crime, Drama | 24 March 1972 |
+| Schindler's List | Biography, Drama, History | 4 February 1994 |
+| Se7en | Crime, Drama, Mystery | 22 September 1995 |
+```
+
+### Notices
+
+The notices styles are actually provided by the `markdown-notices` plugin but are useful enough to include here:
+
+! This is a warning notification
+
+!! This is a error notification
+
+!!! This is a default notification
+
+!!!! This is a success notification
+
+```markdown
+! This is a warning notification
+
+!! This is a error notification
+
+!!! This is a default notification
+
+!!!! This is a success notification
+```
+
diff --git a/user/plugins/.gitkeep b/user/plugins/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/user/plugins/admin/.gitattributes b/user/plugins/admin/.gitattributes
new file mode 100644
index 0000000..e3cf70c
--- /dev/null
+++ b/user/plugins/admin/.gitattributes
@@ -0,0 +1,8 @@
+# Linguist Normalizer
+*.yaml linguistic-language=PHP
+*.twig linguistic-language=PHP
+**/gulpfile.js linguist-vendored
+**/webpack.conf.js linguist-vendored
+**/js/*.js linguist-vendored
+**/js/*.json linguist-vendored
+**/css-compiled/*.css linguist-vendored
diff --git a/user/plugins/admin/.github/FUNDING.yml b/user/plugins/admin/.github/FUNDING.yml
new file mode 100644
index 0000000..e84f52b
--- /dev/null
+++ b/user/plugins/admin/.github/FUNDING.yml
@@ -0,0 +1,8 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: grav
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+custom: # Replace with a single custom sponsorship URL
diff --git a/user/plugins/admin/CHANGELOG.md b/user/plugins/admin/CHANGELOG.md
new file mode 100644
index 0000000..be51e65
--- /dev/null
+++ b/user/plugins/admin/CHANGELOG.md
@@ -0,0 +1,1775 @@
+# v1.9.17
+## 10/07/2020
+
+1. [](#new)
+ * Support premium themes
+ * Back-ported functionality from Admin 1.10 required for upcoming WYSIWYM Nextgen Editor
+1. [](#improved)
+ * Improved some error messages for better readability
+1. [](#bugfix)
+ * Fixed issue that would force a page reload when failing to install/update a plugin or theme
+ * Fixed proxy/browser caching issues in admin pages
+
+# v1.9.16
+## 09/01/2020
+
+1. [](#bugfix)
+ * Fixed a glitch which allows user to delete entire pages directory [#1941](https://github.com/getgrav/grav-plugin-admin/issues/1941)
+ * Fixed the hidden login plugin toggle
+
+# v1.9.15
+## 06/08/2020
+
+1. [](#bugfix)
+ * Support markdown in `fieldset.text` [#2934](https://github.com/getgrav/grav/issues/2934)
+ * Fix data URLs in avatar images [#1889](https://github.com/getgrav/grav/issues/1889)
+ * Fix for deleting files in plugin configurations
+
+# v1.9.14
+## 04/27/2020
+
+1. [](#improved)
+ * Added `slug` and `type` to blueprints
+1. [](#bugfix)
+ * Support markdown in `fieldset.text` [#2934](https://github.com/getgrav/grav/issues/2934)
+
+# v1.9.13
+## 03/05/2020
+
+1. [](#improved)
+ * Updated vendor libs
+1. [](#bugfix)
+ * Fixed toggleable buttons no longer holding false state [form#406](ttps://github.com/getgrav/grav-plugin-form/issues/406)
+
+# v1.9.12
+## 12/04/2019
+
+1. [](#bugfix)
+ * Fixed saving configuration in PHP 7.4
+
+# v1.9.11
+## 11/06/2019
+
+1. [](#improved)
+ * Added new "secure delete" functionality [#1752](https://github.com/getgrav/grav-plugin-admin/issues/1752)
+ * Center text logo [#1751](https://github.com/getgrav/grav-plugin-admin/issues/1751)
+ * Added required span to editor field [#1748](https://github.com/getgrav/grav-plugin-admin/issues/1748)
+ * Warn users if JS is disabled [#1722](https://github.com/getgrav/grav-plugin-admin/issues/1722)
+ * Added target rule to quick links [#1518](https://github.com/getgrav/grav-plugin-admin/issues/1518)
+1. [](#bugfix)
+ * Fixed `Badly encoded JSON data` warning when uploading files [grav#2663](https://github.com/getgrav/grav/issues/2663)
+ * Fixed `accept` for SVG in `file` uploaders [#1732](https://github.com/getgrav/grav-plugin-admin/issues/1732)
+
+# v1.9.10
+## 09/19/2019
+
+1. [](#bugfix)
+ * Fixed `Badly encoded JSON data` warning when uploading files [grav#2663](https://github.com/getgrav/grav/issues/2663)
+
+# v1.9.9
+## 08/21/2019
+
+1. [](#bugfix)
+ * Fixed regression with files in admin not allowing types other than images [#1737](https://github.com/getgrav/grav-plugin-admin/issues/1737)
+ * Fixed preview link for non-images files in **Page Media** [#1727](https://github.com/getgrav/grav-plugin-admin/issues/1727)
+
+# v1.9.8
+## 08/11/2019
+
+1. [](#improved)
+ * Better support for `array` field into `list` field
+ * Attach `_list_index` to fields within list items so that the index/key is available
+1. [](#bugfix)
+ * Fixed 2FA regenerate for Flex Users
+ * Added missing closing in language loops
+ * Fixed issue with nested `list` fields both utilizing the custom `key` functionality
+ * Fixed issue with `array` field nested in `list` that were losing their index order when the list reordered
+ * Fixed file form field failing resolution checks in certain circumstances
+ * Fixed issue with deleting files in config based YAML files
+
+# v1.9.7
+## 06/21/2019
+
+1. [](#bugfix)
+ * Fixed issue with charts in dashboard where label would cut off [#1700](https://github.com/getgrav/grav-plugin-admin/issues/1700)
+ * Resetting a user's password clears the user's site access [grav#2528](https://github.com/getgrav/grav/issues/2528)
+ * Fixed issue with permissions toggle [#1702](https://github.com/getgrav/grav-plugin-admin/issues/1702)
+
+# v1.9.6
+## 06/15/2019
+
+1. [](#bugfix)
+ * Fixed regression issue with `parents_levels` defaulting to `2`
+
+# v1.9.5
+## 06/14/2019
+
+1. [](#improved)
+ * Display error message if GPM class fails to initialize
+ * Better append/prepend logic that was breaking some layouts
+ * Default `backups` to an array if used outside of tools
+ * PSR 7 fixes
+
+# v1.9.4
+## 05/09/2019
+
+1. [](#new)
+ * Added support for `field.copy-to-clipboard` on Text input fields
+1. [](#improved)
+ * Only invalidate cache on creating new/deleting page to speed up the recovery
+ * Updated language strings from https://crowdin.com/project/grav-admin
+ * Use `plugins://` stream rather than `user://plugins` [#1674](https://github.com/getgrav/grav-plugin-admin/issues/1674)
+1. [](#bugfix)
+ * Fixed admin cache to detect moved and deleted pages
+ * Fixed avatar URLs with `?` in them being broken
+ * Fixed issue saving page with language that was not exactly `2` or `5` chars long [#1667](https://github.com/getgrav/grav-plugin-admin/issues/1667)
+ * Fixed admin not detecting any existing users when Flex users are being used
+ * Fixed issue with append/prepend not respecting `size:`
+ * Fixed issue with `unset` on file fields [#1427](https://github.com/getgrav/grav/issues/1427), [#1670](https://github.com/getgrav/grav/issues/1670), [#1982](https://github.com/getgrav/grav/issues/1982)
+
+# v1.9.3
+## 04/22/2019
+
+1. [](#new)
+ * Added a new **YAML Linter** report to the `Tools - Reports` section
+1. [](#improved)
+ * Updated package.json scripts to properly use gulp compiler
+
+# v1.9.2
+## 04/15/2019
+
+1. [](#bugfix)
+ * Fix for homepage admin preview [#2426](https://github.com/getgrav/grav/issues/2426)
+ * Uploaded Avatar removed from user's yaml when editing the user [#1647](https://github.com/getgrav/grav-plugin-admin/issues/1647)
+
+# v1.9.1
+## 04/13/2019
+
+1. [](#bugfix)
+ * Fix for Page saving issues [#1648](https://github.com/getgrav/grav-plugin-admin/issues/1648)
+ * Remove status message when picking folder for move [#1650](https://github.com/getgrav/grav-plugin-admin/issues/1650)
+
+# v1.9.0
+## 04/11/2019
+
+1. [](#new)
+ * New `Scheduler` configuration panel in tools
+ * New `Backups` configuration panel in tools
+ * New `Cache::purge()` option in cache drop-down to clear out old cache only
+ * New `Tools - Reports` section with event `onAdminGenerateReports()` for 3rd party plugin support
+ * Added support for the new `Flex User` object
+ * Allow admin forms to use `Form` classes
+ * Added new `Logs` section to tools to allow quick view of Grav log files
+1. [](#improved)
+ * Improved the UI for the Parent Page Route dropdown when adding a new Page / Folder
+ * Use `$grav['accounts']` instead of `$grav['users']`
+ * Improved image background overlay and tools
+ * Better unauthorized user rendering
+ * Update all Form classes to rely on `PageInterface` instead of `Page` class
+ * Removed `media.upload_limit` references
+ * Improve error when upload exceeds `upload_max_filesize`
+ * Delegate Dropzone for checking maximum file size and avoid uploading if not necessary
+ * Low level unauthorized user handling in `base-root.html.twig`
+ * Refactored "NewsFeeds" and "Notifications" for better performance and to address CORS issues
+ * Flex user profile now uses Flex Form
+ * Moved dashboard `notifications` logic to server-side for increased performance (1 request instead of 3)
+ * Refactored feeds logic for better performance
+ * Better logic for delete action to support Ajax. Fixes Flex lists
+ * Cleanly handle session corruption due to changing Flex object types
+ * Implemented [ForkAwesome](https://forkawesome.github.io/Fork-Awesome/) and removed FontAwesome + LineAwesome
+ * Various default admin theme improvements and cleanup
+ * Make new System Config layout responsive [#1579](https://github.com/getgrav/grav-plugin-admin/issues/1579)
+ * Homepage link should be `https://` [#1564](https://github.com/getgrav/grav-plugin-admin/issues/1564)
+ * Improve lang string to describe XSS security settings [#1566](https://github.com/getgrav/grav-plugin-admin/issues/1566)
+ * Take admin setting for 2FA into account when showing user 2FA badge [#1568](https://github.com/getgrav/grav-plugin-admin/issues/1568)
+ * Moved `ignore` and `key` field into form plugin
+ * Improved usability of `System` configuration blueprint with side-tabs
+ * Cleaned up UI in `Scheduler` tools page
+ * Updated languages
+1. [](#bugfix)
+ * Fixed user edit links if Flex Objects plugin is installed but user isn't Flex User
+ * Fixed deprecated `sameas()` Twig test
+ * Regression: Fixed lost user access when saving user profile without super user permissions [#1639](https://github.com/getgrav/grav-plugin-admin/issues/1639)
+ * Fixed `Page.menu` displaying in edit view rather than `Page.title` [#1642](https://github.com/getgrav/grav-plugin-admin/issues/1642)
+ * Regression from beta.8: Deleting files other than from plugins/themes fail on error
+ * Fixed issue with Safari browser and blueprint fields with `toggleable: true` [#1643](https://github.com/getgrav/grav-plugin-admin/issues/1643)
+ * Incorrect 2FA lang code [#1618](https://github.com/getgrav/grav-plugin-admin/issues/1618)
+ * Fixed potential undefined property in `onPageNotFound` event handling
+ * Proper fix for `vUndefined` when updating plugins/themes
+ * Text in Tab Tools/Direct install disappears [#1613](https://github.com/getgrav/grav-plugin-admin/issues/1613)
+ * Fallback to page `slug` in Pages list if title is empty [grav#2267](https://github.com/getgrav/grav/issues/2267)
+ * Fixes backup button issues with `;` param separator [#1602](https://github.com/getgrav/grav-plugin-admin/issues/1602) [#1502](https://github.com/getgrav/grav-plugin-admin/issues/1502)
+ * Set default state for `show_modular` to `true` [#1599](https://github.com/getgrav/grav-plugin-admin/issues/1599)
+ * Removed `tabs`, `tab`, and `toggle` fields as they are now in Form plugin
+ * Fix issue with new page always showing modular page templates [#1573](https://github.com/getgrav/grav-plugin-admin/issues/1573)
+ * Fixed issue deleting files in plugins/themes/config
+ * Fixed array support in admin languages, e.g. `DAYS_OF_THE_WEEK`
+ * Fixed user login / remember me triggering before admin gets initialized
+ * Fixed a bug when deleting files via AJAX
+ * Fixed error page not to be the frontend version
+ * Added `merge_items` option for `field.selectize` to allow storing custom items [#1461](https://github.com/getgrav/grav-plugin-admin/issues/1461)
+ * Better handling of unset in uploaded files [#1427](https://github.com/getgrav/grav-plugin-admin/issues/1427)
+ * Prefix Backup/Scheduler titles with `Tools`
+ * Regression: Media settings have bad layout [#1529](https://github.com/getgrav/grav-plugin-admin/issues/1529)
+ * Fixed Direct Install Uploader, failing to validate the uploaded files
+ * Regression: Editing interface does not keep settings properly without manual intervention on each edit [#1527](https://github.com/getgrav/grav-plugin-admin/issues/1527)
+ * Removed duplicate language strings
+ * Fixed default `job_at` so it does not fail if missing
+ * Minor JS group `bottom` fix
+
+# v1.8.20
+## 03/20/2019
+
+1. [](#improved)
+ * Added security field to column [#1622](https://github.com/getgrav/grav-plugin-admin/pull/1622)
+
+# v1.8.19
+## 02/13/2019
+
+1. [](#bugfix)
+ * Moved `show_modular` to proper place - Doh! [grav#2362](https://github.com/getgrav/grav/issues/2362)
+
+# v1.8.18
+## 02/12/2019
+
+1. [](#bugfix)
+ * Set default value for `show_modular` [grav#2362](https://github.com/getgrav/grav/issues/2362)
+
+# v1.8.17
+## 02/07/2019
+
+1. [](#improved)
+ * Improved Grav Core installer/updater to run installer script (if available)
+ * Added `unauthorized.html.twig` file that was missing [#1609](https://github.com/getgrav/grav-plugin-admin/pull/1609)
+1. [](#bugfix)
+ * Fixed direct install deleting backups and logs if used with full Grav package instead of with update package
+
+# v1.8.16
+## 01/25/2019
+
+1. [](#improved)
+ * IP pseudonymization for rate limiter [#1589](https://github.com/getgrav/grav-plugin-admin/pull/1589)
+ * Add option to hide modular pages in parent select [#1571](https://github.com/getgrav/grav-plugin-admin/pull/1571)
+ * Added `admin.tools` permission [#1550](https://github.com/getgrav/grav-plugin-admin/pull/1550)
+1. [](#bugfix)
+ * Fixed calendar js module not properly loading for datetime field [#1581](https://github.com/getgrav/grav-plugin-admin/issues/1581)
+ * Fixed deleting file when using file field type [#1558](https://github.com/getgrav/grav-plugin-admin/issues/1558)
+ * Unset state from user if not super or user admin
+
+# v1.8.15
+## 12/14/2018
+
+1. [](#improved)
+ * Fire `onAdminSave()` event during `AdminController::taskSaveAs()` [#1544](https://github.com/getgrav/grav-plugin-admin/issues/1544)
+1. [](#bugfix)
+ * Clean user post to ensure dynamically added form fields are not saved
+
+# v1.8.14
+## 11/12/2018
+
+1. [](#bugfix)
+ * Fixed Grav core update potentially spinning forever because of an error which happens after a successful upgrade
+ * Saving in expert mode can cause `undefined index: header` error [#1537](https://github.com/getgrav/grav-plugin-admin/issues/1537)
+
+# v1.8.13
+## 11/05/2018
+
+1. [](#new)
+ * Added new `|nested()` Twig filter to access array objects with dot notation syntax
+1. [](#bugfix)
+ * Fixed issue with complex lists structure and nested dot-notation [admin#2236](https://github.com/getgrav/grav/issues/2236)
+
+# v1.8.12
+## 10/24/2018
+
+1. [](#improved)
+ * Updated various lang strings
+ * Removed duplicate lang strings
+1. [](#bugfix)
+ * Fix XSS checking when empty content [#1533](https://github.com/getgrav/grav-plugin-admin/issues/1533)
+ * Fix DirectInstall not working [#1535](https://github.com/getgrav/grav-plugin-admin/issues/1535)
+
+# v1.8.11
+## 10/08/2018
+
+1. [](#improved)
+ * Change usage of basename where possible [#1480](https://github.com/getgrav/grav-plugin-admin/pull/1480)
+ * Improved filename validation (requires Grav 1.5.3)
+ * Updated various lang codes
+1. [](#bugfix)
+ * File Uploads: Do not trust mimetype sent by the browser
+ * Fixed file extension detection
+ * Fix for HTML entities in page slug [#1524](https://github.com/getgrav/grav-plugin-admin/issues/1524)
+ * Fix for port in backup download links [#1521](https://github.com/getgrav/grav-plugin-admin/issues/1521)
+
+# v1.8.10
+## 10/01/2018
+
+1. [](#new)
+ * IMPORTANT: Non `admin.super` users are now subject to XSS validation in Page content. Configurable via Configuration / Security
+ * New XSS content warnings and integration into page save
+ * Added new event `onAdminPage()` which allows plugins to customize `Page` object in `$event['page']`
+1. [](#improved)
+ * Use `Url:post()` to get the `$_POST` variable (allows common security checks/filtering for the POST data)
+ * Requires Grav 1.5.2
+1. [](#bugfix)
+ * Fixed redirect to correct URL after failed login
+ * Fixed issue in `filepicker` where missing images would cause a loop to try to load them
+ * Twig 2 compatibility fixes for macros
+ * Updated `composer.json` to better match Grav 1.5
+ * Remove `package-lock.json` as it was referencing an insecure JS package
+
+# v1.8.9
+## 08/23/2018
+
+1. [](#improved)
+ * Make order field to use context, not data
+ * Switched to new Grav Yaml class to support Native + Fallback YAML libraries
+ * Minor fix for `file` thumbnails display
+ * Requires Grav 1.5.1
+
+# v1.8.8
+## 08/17/2018
+
+1. [](#improved)
+ * Support URI Params and Query attributes in Login redirect
+ * Added support for textarea value type in `array` field
+ * Added some new lang strings for Grav 1.5.0
+1. [](#bugfix)
+ * Support params and querystring in login redirect
+ * Added field name nesting with tab field
+
+# v1.8.7
+## 07/31/2018
+
+1. [](#bugfix)
+ * Fix for deleting 'extra' media files [grav#2100](https://githubcom/getgrav/grav/issues/2100)
+
+# v1.8.6
+## 07/13/2018
+
+1. [](#bugfix)
+ * Force `html` for markdown preview [grav#2066](https://github.com/getgrav/grav/issues/2066)
+ * Add missing `authorizeTask()` checks in controller [#1483](https://github.com/getgrav/grav/issues/1483)
+ * Add support for `force_ssl` to admin URLs [#1479](https://github.com/getgrav/grav-plugin-admin/issues/1479)
+
+# v1.8.5
+## 06/20/2018
+
+1. [](#bugfix)
+ * Fixed broken folder attribute on filepicker [#1465](https://github.com/getgrav/grav-plugin-admin/issues/1465)
+ * Added translation for system.session.initialize
+ * Slight updates on new translation strings
+
+# v1.8.4
+## 06/11/2018
+
+1. [](#improved)
+ * Including EXIF JS library in the modules dependencies to fix orientation when uploading images
+1. [](#bugfix)
+ * Initialize session on setup [#1451](https://github.com/getgrav/grav-plugin-admin/issues/1451)
+ * Force a `null` order when empty in the post request
+ * Fixed some 2FA form styling issues
+
+# v1.8.3
+## 05/31/2018
+
+1. [](#new)
+ * Added support for selectize plugins as options in the selectize field
+1. [](#bugfix)
+ * Fixed deep linking in admin after login [#1456](https://github.com/getgrav/grav-plugin-admin/issues/1456)
+ * Fixed Undefined property: `stdClass::$image` in v1.8.2 [#1454](https://github.com/getgrav/grav-plugin-admin/issues/1454)
+ * Pass media order when calling `task:listmedia`
+
+# v1.8.2
+## 05/24/2018
+
+1. [](#new)
+ * Added custom object support for filepicker field
+ * Don't allow saving of a user with no local account file
+ * Controls for `list` field were not in sync between top and bottom
+1. [](#improved)
+ * More subtle `fieldset` styling
+1. [](#bugfix)
+ * Check if `$object->blueprints()` exists in `onAdminAfterSave`
+ * When creating first user, check `admin.login` not `site.login`
+ * Fix admin login redirects for multisite setups
+ * Fixed issue with filepicker field where images wouldn't properly merge with the current value if in a page header
+ * Fixed media delete for streams
+
+# v1.8.1
+## 05/15/2018
+
+1. [](#improved)
+ * use SHA1 hashing of IP addressed to support GDPR rules [#1436](https://github.com/getgrav/grav-plugin-admin/pull/1436)
+1. [](#bugfix)
+ * Fixed 2FA form showing up even if user has not turned on the feature [#1442](https://github.com/getgrav/grav-plugin-admin/issues/1442)
+ * Fixed previews of images in Pagemedia field not properly URI encoded [#1438](https://github.com/getgrav/grav-plugin-admin/issues/1438)
+
+# v1.8.0
+## 05/11/2018
+
+1. [](#new)
+ * Moved 2FA authentication to login plugin
+ * Admin login now uses login plugin events
+ * Added new decoupled `pagemedia` field that is no longer tied to just pages
+ * Updated plugin dependencies (Grav >= 1.4.4, Form >=2.14.0, Login >=2.7.0, Email >=2.7.0)
+1. [](#improved)
+ * Added support for JavaScript `bottom` block [#1425](https://github.com/getgrav/grav-plugin-admin/pull/1425)
+ * Added better typography styling for blockquote and markdown in `display` field
+ * Vendor updates
+1. [](#bugfix)
+ * Added missing MarkdownExtra strings [#1385](https://github.com/getgrav/grav-plugin-admin/pull/1385)
+ * Updated `blueprints.yaml` with missing `step` attribute [#1415](https://github.com/getgrav/grav-plugin-admin/pull/1415)
+ * Fixed preview target setting [#1430](https://github.com/getgrav/grav-plugin-admin/pull/1430)
+ * Added new modular string [#1433](https://github.com/getgrav/grav-plugin-admin/pull/1433)
+ * Fixed Firefox issue with the Regenerate button for 2FA. Forcing the page to reload
+ * Fixed jumpiness behavior for Regenerate button when on active state.
+ * Prevent the prompt for unsaved state when Regenerating a 2FA code and trying to reload/leave the page.
+
+# v1.7.4
+## 04/02/2018
+
+1. [](#bugfix)
+ * Fixed a bug for page copy caused by last release [#1409](https://github.com/getgrav/grav-plugin-admin/pull/1409)
+ * Fixed collapsible `list` option [#1410](https://github.com/getgrav/grav-plugin-admin/pull/1410)
+ * Fixed a minor typo in a label [#1397](https://github.com/getgrav/grav-plugin-admin/pull/1397)
+
+# v1.7.3
+## 04/01/2018
+
+1. [](#new)
+ * Implemented Resize Media and Resolution ('resizeWidth', 'resizeHeight', 'resizeQuality', 'resolution')
+ * Updated Dropzone to latest
+1. [](#bugfix)
+ * Implemented workaround for required text fields [#1390](https://github.com/getgrav/grav-plugin-admin/issues/1390)
+ * Fixed highlight color in Firefox [getgrav/grav#1949](https://github.com/getgrav/grav/issues/1949)
+ * Fix for bad redirect on saving simplesearch (possibly others)
+
+# v1.7.2
+## 03/21/2018
+
+1. [](#improved)
+ * Table CSS improvements for use in 3rd party plugins
+ * Translatable `add_modals` button labels [#1388](https://github.com/getgrav/grav-plugin-admin/issues/1388)
+ * Check for `SHIFT` key on editor save shortcut [#1383](https://github.com/getgrav/grav-plugin-admin/issues/1383)
+ * Fixed User permissions responsive UI [#1379](https://github.com/getgrav/grav-plugin-admin/issues/1379)
+ * Optimization to stop admin for looking for pages in disabled plugins
+ * Added configuration option to choose if you want to use new 'inline' preview or `new tab'
+1. [](#bugfix)
+ * Fix redirect bug when changing admin route to `admin-*`
+ * Changed Twig `|count` to `|length` filter [#1391](https://github.com/getgrav/grav-plugin-admin/issues/1391)
+ * Fix for page preview when `HTTP_REFERRER` is not set [grav#1930](https://github.com/getgrav/grav/issues/1930)
+
+# v1.7.1
+## 03/11/2018
+
+1. [](#new)
+ * New built-in page preview system
+1. [](#improved)
+ * Added `CTRL+K` / `CMD+K` shortcuts for editor links [#1279](https://github.com/getgrav/grav-plugin-admin/issues/1279)
+1. [](#bugfix)
+ * Automatically redirect to new `admin_route` after changing it [#1371](https://github.com/getgrav/grav-plugin-admin/issues/1371)
+ * Remove bad-shadows on alerts
+ * Fixed notifications titles not html escaped [#1272](https://github.com/getgrav/grav-plugin-admin/issues/1272)
+ * Fixed extra horizontal scrollbar with `Editor` field
+ * Fixed `mediapicker` field in lists [#1369](https://github.com/getgrav/grav-plugin-admin/issues/1369)
+
+# v1.7.0
+## 03/09/2018
+
+1. [](#new)
+ * Added styling and lang for **Route Overrides** in the default page blueprint
+ * Added clear cache permanently to quick-tray [#1353](https://github.com/getgrav/grav-plugin-admin/issues/1353)
+1. [](#improved)
+ * Added option to toggle between `line-awesome` and `font-awesome` icon sets [#1334](https://github.com/getgrav/grav-plugin-admin/issues/1334)
+ * Added preview from page list view [#1250](https://github.com/getgrav/grav-plugin-admin/pull/1250)
+ * Added `Add` plugins button to plugins details page [#1352](https://github.com/getgrav/grav-plugin-admin/pull/1352)
+ * Added support for `default` and `options` fields in taxonomy field [#1364](https://github.com/getgrav/grav-plugin-admin/issues/1364)
+ * Added support to limit parent field levels [#1298](https://github.com/getgrav/grav-plugin-admin/issues/1298)
+1. [](#bugfix)
+ * Fixed issue with custom logo text overlapping the sidebar toggle [#1334](https://github.com/getgrav/grav-plugin-admin/issues/1334)
+ * Fixed issues with minimum PHP versions in resource upgrades
+ * Fixed issue with default lang translation in admin [#1361](https://github.com/getgrav/grav-plugin-admin/issues/1361)
+ * Typos in `Tools` -> `Direct Install` page [#1345](https://github.com/getgrav/grav-plugin-admin/issues/1345)
+ * Fixed bug with frontmatter being killed when in `Expert Mode` [#1354](https://github.com/getgrav/grav-plugin-admin/issues/1354)
+
+# v1.7.0-rc.3
+## 02/15/2018
+
+1. [](#improved)
+ * Tab optimization with fixes for 'onpage' tabs
+ * Stopped Chrome from auto-completing admin user profile form [grav#1847](https://github.com/getgrav/grav/issues/1847)
+ * Added a fixed `ga-theme-17x` body class to help styling compatibility
+ * Outputs an iterable field as a string if `yaml: true` or `validate: type: yaml` set in blueprint
+1. [](#bugfix)
+ * Rolled back JS to known working versions [#1323](https://github.com/getgrav/grav-plugin-admin/issues/1323)
+ * Fixed missing translation in order field [#1324](https://github.com/getgrav/grav-plugin-admin/issues/1324)
+ * Fixed UI issue with last drop-down in button group [1325](https://github.com/getgrav/grav-plugin-admin/issues/1325)
+ * Fixed fieldset field outdated rendering [#1313](https://github.com/getgrav/grav-plugin-admin/issues/1313)
+
+# v1.7.0-rc.2
+## 01/24/2018
+
+1. [](#new)
+ * Moved to LineAwesome icons rather than FontAwesome (still compatible w/FA 4.7.0)
+1. [](#improved)
+ * Simplified open/close nav button
+ * Tidied Tools panel and added translations
+ * Tooltip and new icon for site preview
+ * Updated JS library dependencies
+ * Changed CodeMirror editor to use sans-serif font for readability
+1. [](#bugfix)
+ * Fixed z-index issue in fullscreen mode [#1317](https://github.com/getgrav/grav-plugin-admin/issues/1317)
+
+# v1.7.0-rc.1
+## 01/22/2018
+
+1. [](#new)
+ * Added support for markdown in all form fields for `label`, `help`, and `description` when `markdown: true` is set on field
+ * Changed "made by" to Trilby Media from RocketTheme
+1. [](#improved)
+ * Lightened tabs in new theme
+ * Sort languages by key [#1303](https://github.com/getgrav/grav-plugin-admin/issues/1303)
+ * Add limit to Parent Levels [#1298](https://github.com/getgrav/grav-plugin-admin/pull/1298)
+1. [](#bugfix)
+ * Fixed alignment issue with language drop-down
+ * Fixed a z-index issue with fullscreen editor [#1302](https://github.com/getgrav/grav-plugin-admin/issues/1302)
+ * Fixed missing background on register [#1307](https://github.com/getgrav/grav-plugin-admin/issues/1307)
+ * Fixed some style issues with field descriptions
+ * Fixed an issue with `File` field losing download size setting
+ * Fixed distorted thumbnails in `File` field by using `object-fit: cover`
+
+# v1.7.0-beta.1
+## 12/29/2017
+
+1. [](#new)
+ * New lighter-and-tighter admin theme developed
+1. [](#improved)
+ * Added simple value support for list field type
+ * Added checks to automatically hide collapse buttons when there's only single value in list type
+
+# v1.6.7
+## 12/05/2017
+
+1. [](#new)
+ * Logout of admin goes straight to login form with a message (that then fades out)
+ * Added `sl`, `id`, `he`, `eu`, `et` languages
+1. [](#improved)
+ * Added code to use new `GPM::loadRemoteGrav` if it exists in Gav [grav#1746](https://github.com/getgrav/grav/pull/1746)
+ * Add vertical style for order field [#1253](https://github.com/getgrav/grav-plugin-admin/pull/1253)
+ * Added classes to pagemedia field [#1274](https://github.com/getgrav/grav-plugin-admin/issues/1274)
+ * Fixed selectize field not properly updating value when `option` is provided [#1236](https://github.com/getgrav/grav-plugin-admin/pull/1236)
+ * Tab layout tweaks
+ * Updated all language files with latest from [Crowdin](https://crowdin.com/project/grav-admin)
+1. [](#bugfix)
+ * Manual image metadata can now display in pagemedia when auto-generation is disabled [#1275](https://github.com/getgrav/grav-plugin-admin/issues/1275)
+ * Removed broken `home.hide_in_urls` code in `AdminBaseController::save()` that was throwing move errors
+ * Security fix to ensure file uploads are not manipulated mid-post - thnx @FLH!
+
+# v1.6.6
+## 10/27/2017
+
+1. [](#new)
+ * Fixed issue where sortable media in expert mode would reset frontmatter [#1252](https://github.com/getgrav/grav-plugin-admin/issues/1252)
+
+# v1.6.5
+## 10/26/2017
+
+1. [](#new)
+ * Added ability to **order** page media (requires latest Grav update)
+
+# v1.6.4
+## 10/11/2017
+
+1. [](#improved)
+ * Use system PHP size for upload limit rather than `system.media.upload_limit` or `file.filesize` plugin options
+1. [](#bugfix)
+ * Fixed Dropzone timeout to address slow internet connections [#1239](https://github.com/getgrav/grav-plugin-admin/pull/1239)
+
+# v1.6.3
+## 10/02/2017
+
+1. [](#bugfix)
+ * Fixed chart labels not parsing HTML [#1234](https://github.com/getgrav/grav-plugin-admin/issues/1234)
+
+# v1.6.2
+## 09/29/2017
+
+1. [](#improved)
+ * Removed extraneous files in vendor folder for smaller download package
+
+# v1.6.1
+## 09/29/2017
+
+1. [](#improved)
+ * Added support for Latin Extended fonts [#1211](https://github.com/getgrav/grav-plugin-admin/pull/1221)
+ * Added collapsible attribute to lists [#1231](https://github.com/getgrav/grav-plugin-admin/pull/1231)
+1. [](#bugfix)
+ * Fix editor not clickable in list field [#1224](https://github.com/getgrav/grav-plugin-admin/pull/1124)
+ * Updated Google Font URLs to always connect over HTTPS. [#1106](https://github.com/getgrav/grav-plugin-admin/pull/1106)
+ * Fixed fieldset field not allowing to properly save when contained within a list [#1225](https://github.com/getgrav/grav-plugin-admin/issues/1225)
+ * Fixed Video markdown syntax when drag & dropping in the content editor [#1160](https://github.com/getgrav/grav-plugin-admin/issues/1160)
+ * Fixed headers drop-down in editor to properly align
+ * Fixed fields not working in Microsoft Edge with Selectize.js [#1222](https://github.com/getgrav/grav-plugin-admin/pull/1222)
+ * Replaced a left-over "is empty" check [#1232](https://github.com/getgrav/grav-plugin-admin/pull/1232)
+ * Fixed headers drop-down in editor to align properly
+
+
+# v1.6.0
+## 09/07/2017
+
+1. [](#new)
+ * **Added 2-Factor Authentication support to the admin!**
+ * **Added rate-limiting for "failed login attempts" and "forgot password"**
+1. [](#improved)
+ * Revamped the toggle switch CSS so it's more flexible and works better [#1198](https://github.com/getgrav/grav-plugin-admin/issues/1198)
+ * Improved toggle/button alignment on Page edit view
+1. [](#bugfix)
+ * Fixed an issue where icon-picker style was hiding field elements [#1199](https://github.com/getgrav/grav-plugin-admin/issues/1199)
+ * Fixed https -> http redirect issue [#1195](https://github.com/getgrav/grav-plugin-admin/issues/1195)
+ * Also check `/.` for home route [#1191](https://github.com/getgrav/grav-plugin-admin/issues/1191)
+ * Fixed administration being broken in multi-site environments with plugin overrides
+ * Fixed lang-switcher broken in MS Edge browser [#1213](https://github.com/getgrav/grav-plugin-admin/pull/1213)
+ * Added custom `form_id` attribute for modal forms [#1216](https://github.com/getgrav/grav-plugin-admin/issues/1216)
+ * Fixed partially cropped line in Markdown editor for MS Edge/Firefox [#1219](https://github.com/getgrav/grav-plugin-admin/pull/1219)
+ * Downgraded Babel libraries to v6.x for compatibility with webpack [#1218](https://github.com/getgrav/grav-plugin-admin/pull/1218)
+
+# v1.5.2
+## 08/16/2017
+
+1. [](#new)
+ * Added a new icon quick-tray in side navigation that plugins can utilize
+ * Added ability to set and retrieve temporary admin messages
+1. [](#improved)
+ * Allow different field to be used as page label in list of pages [#1122](https://github.com/getgrav/grav-plugin-admin/pull/1122)
+ * Updated `en` language for `cache-control` + `clear_images_by_default` system settings
+ * Allow sorting of page based on custom ordering [#1182](https://github.com/getgrav/grav-plugin-admin/pull/1182)
+ * Search for pages by slug and folder name [#1183](https://github.com/getgrav/grav-plugin-admin/pull/1183)
+ * Allow all page data to be used during `onAdminCreatePageFrontmatter()` event [#1175](https://github.com/getgrav/grav-plugin-admin/pull/1175)
+ * Remove single quotes when slugifying title [#1178](https://github.com/getgrav/grav-plugin-admin/pull/1178)
+1. [](#bugfix)
+ * Ignore missing Twig files [#1169](https://github.com/getgrav/grav-plugin-admin/issues/1169)
+ * If from is already defined, don't override it [#1129](https://github.com/getgrav/grav-plugin-admin/issues/1129)
+ * Fixed SelectUnique field not working with files with spaces
+
+# v1.5.1
+## 07/19/2017
+
+1. [](#bugfix)
+ * Fixes issue when saving pages without a `folder` element [#1163](https://github.com/getgrav/grav-plugin-admin/issues/1163)
+ * Fixed mediapicker field inside lists not properly updating the value on the target input [#1157](https://github.com/getgrav/grav-plugin-admin/issues/1157)
+
+# v1.5.0
+## 07/16/2017
+
+1. [](#new)
+ * Implemented Offline mode. Notifies in the admin when disconnected.
+1. [](#bugfix)
+ * Fixed fetch issue throwing error when request not completed and while unloading the page [#1301](https://github.com/getgrav/grav-plugin-admin/issues/1301)
+ * Fixed ordering when > 100 pages [grav#1564](https://github.com/getgrav/grav/pull/1564)
+ * Fixed Lists issue when reindexing, causing Radio fields to potentially lose their `checked` status ([#1154](https://github.com/getgrav/grav-plugin-admin/issues/1154) | related: [1d55ffc](https://github.com/getgrav/grav-plugin-admin/commit/1d55ffc616125047f245efe9f2180ef2c16b4949))
+
+# v1.5.0-rc.4
+## 07/05/2017
+
+1. [](#new)
+ * New `multilevel` field, useful for defining collections definitions, metadata and other complex YAML data [#1135](https://github.com/getgrav/grav-plugin-admin/pull/1135) - (EXPERIMENTAL)
+ * Fix plugins hooked nav authorize not working with array of permissions [#1148](https://github.com/getgrav/grav-plugin-admin/pull/1148)
+1. [](#improved)
+ * Add badge to plugins hooked into nav [#1147](https://github.com/getgrav/grav-plugin-admin/pull/1147)
+ * Added `field.outerclasses` to default form field [#1124](https://github.com/getgrav/grav-plugin-admin/pull/1124)
+ * Reverted back to textarea/YAML for `media.yaml` image options
+ * Fixed color of textarea fields in admin
+1. [](#bugfix)
+ * Fix for bad referenced to `shouldLoadAdditionalFilesInBackground()` [#1145](https://github.com/getgrav/grav-plugin-admin/pull/1145)
+ * Expose Page Media instance to Grav Admin JS API
+ * Fixed mediapicker issue where newly added list items would not work
+ * Fixed issue with min/max setting of list collections. Removing a list item would not refresh properly the count
+ * If folder is empty/not sent, fallback to page slug [#1146](https://github.com/getgrav/grav-plugin-admin/issues/1146)
+ * Escape the URI basename before using it in Twig
+ * Ignore missing Twig file in the Tools page
+
+# v1.5.0-rc.3
+## 06/22/2017
+
+1. [](#new)
+ * New `Admin::getPageMedia()` static method that can be used in blueprints
+ * Added a new `mediapicker` form field which allows to select a media from any page [#1125](https://github.com/getgrav/grav-plugin-admin/pull/1125)
+ * Added info metadata button for images to view EXIF and other useful details about an image
+1. [](#improved)
+ * Pass original image filename via the `AdminController::taskListedia()` task
+ * Various form styling improvements
+ * Provided an option to control how parent select field displays
+1. [](#bugfix)
+ * Fix referencing DI element when not initialized [#1141](https://github.com/getgrav/grav-plugin-admin/pull/1141)
+
+# v1.5.0-rc.2
+## 05/22/2017
+
+1. [](#improved)
+ * Remove save button and save location notification on Config Info tab [#1116](https://github.com/getgrav/grav-plugin-admin/pull/1116)
+ * Allow taxonomy field to just list one or more specific taxonomies if the `taxonomies` field is filled in the blueprint
+ * `File` field now renders thumbnail previews of the selected value on load
+ * Use new unified `Utils::getPagePathFromToken()` method rather
+1. [](#bugfix)
+ * Fix for undefined `include_metadata` error
+
+
+# v1.5.0-rc.1
+## 05/16/2017
+
+1. [](#new)
+ * Add support for a single array field in forms
+ * Added Prev/Next support on page editing view [#1112](https://github.com/getgrav/grav-plugin-admin/pull/1112)
+1. [](#improved)
+ * Improved full-screen editor for better browser compatibility [#1093](https://github.com/getgrav/grav-plugin-admin/pull/1093)
+ * Added ability to choose how you want the preview button to open [#1096](https://github.com/getgrav/grav-plugin-admin/pull/1096)
+ * `base.html.twig` now extends a `base-root.html.twig` file
+ * Add month+date indication to the stats graph to avoid confusion when there are days without visits
+ * Added `min` and `max` options for `list` form field [#1113](https://github.com/getgrav/grav-plugin-admin/pull/1113)
+ * Remove page metadata file on deletion of media
+ * Improved layout on pages list for pages with long titles [#1102](https://github.com/getgrav/grav-plugin-admin/pull/1102)
+ * Added option to make custom "Add page" dropdown entries [#1104](https://github.com/getgrav/grav-plugin-admin/pull/1104)
+1. [](#bugfix)
+ * Fixed issue with tab widths on Pages overlapping non-english toggle switch [#1089](https://github.com/getgrav/grav-plugin-admin/issues/1089)
+ * Added `vendor` to ignores for direct install of Grav
+ * Translated `field.default` for `editor` form field
+ * Fixed an quote error in `en.yaml`
+ * Resolved z-index issues with mobile nav and pages form elements
+ * Fixed issue with file picker where the selected file preview would not show
+ * Refresh page media on media upload
+ * Default to config file slug if translation is missing, otherwise use translation also in the tab title, not just in the page heading [#1039](https://github.com/getgrav/grav-plugin-admin/issues/1039)
+ * Fix language toggle button in admin top bar visible also in fullscreen mode [#1110](https://github.com/getgrav/grav-plugin-admin/issues/1110)
+ * Fix for editor padding [#1111](https://github.com/getgrav/grav-plugin-admin/issues/1111)
+ * Fix tabs inside blueprint overlapping above content [#1115](https://github.com/getgrav/grav-plugin-admin/pull/1115)
+
+# v1.4.2
+## 04/24/2017
+
+1. [](#new)
+ * Added a new `Content Padding` option to tighten up UI padding space (default `true`)
+1. [](#bugfix)
+ * Added back `Admin::initTheme()` relying on Grav fix [#1069](https://github.com/getgrav/grav-plugin-admin/pull/1069) as it conflicts ith Gantry5
+ * Fix for missing scrollbar when in full-size editor for Firefox [#1077](https://github.com/getgrav/grav-plugin-admin/issues/1077)
+ * Fix for overlay of Add-Page button in full-size editor [#1077](https://github.com/getgrav/grav-plugin-admin/issues/1077)
+ * Better fix for session-based parent overriding root page parents [#1078](https://github.com/getgrav/grav-plugin-admin/issues/1078)
+ * Allow support for `Pages::getList()` with `show_modular` option [#1080](https://github.com/getgrav/grav-plugin-admin/issues/1080)
+ * Added `[tmp,user]` ignores for direct install of Grav [grav#1447](https://github.com/getgrav/grav/issues/1447)
+
+# v1.4.1
+## 04/19/2017
+
+1. [](#bugfix)
+ * Reverted [#1069](https://github.com/getgrav/grav-plugin-admin/pull/1069) as it conflicts ith Gantry5
+
+# v1.4.0
+## 04/19/2017
+
+1. [](#new)
+ * Added ability to add new pages/folders while editing existing page
+1. [](#improved)
+ * Initialize theme in Admin Plugin [#1069](https://github.com/getgrav/grav-plugin-admin/pull/1069)
+ * Use new system configuration entries for username and password format
+ * Reworked Page parent field to use `Pages::getList()` rather than logic in Twig field itself
+ * More robust styling of admin themes page [#1067](https://github.com/getgrav/grav-plugin-admin/pull/1067)
+ * Fix fullscreen editor height [#1065](https://github.com/getgrav/grav-plugin-admin/pull/1065)
+ * Fix small UI issue in the editor with `codemirror.lineNumbers` && `codemirror.styleActiveLine` enabled
+ * Fix UI performance issue in the dashboard [#1064](https://github.com/getgrav/grav-plugin-admin/issues/1064)
+1. [](#bugfix)
+ * Fixed issue with parent not working with custom slug [#1068](https://github.com/getgrav/grav-plugin-admin/issues/1068)
+ * Fixed issue with new page modal not remembering last choice [#1072](https://github.com/getgrav/grav-plugin-admin/issues/1072)
+
+# v1.3.3
+## 04/12/2017
+
+1. [](#bugfix)
+ * Fix for regression introduced in the automatic page template switch when changing page parent [#1059](https://github.com/getgrav/grav-plugin-admin/issues/1059) [grav#1403](https://github.com/getgrav/grav/issues/1403) [#1062](https://github.com/getgrav/grav-plugin-admin/issues/1062)
+ * Fix issue with editor field in lists [#1037](https://github.com/getgrav/grav-plugin-admin/issues/1037)
+
+# v1.3.2
+## 04/10/2017
+
+1. [](#improved)
+ * Added new 'parents' field and switched Page blueprints to use this
+1. [](#bugfix)
+ * Fix for regression in h3 style in the Spacer field [#267](https://github.com/getgrav/grav-plugin-admin/issues/267)
+ * Fix missing preview in page media for SVG images [#1051](https://github.com/getgrav/grav-plugin-admin/issues/1051)
+ * Fix missing check when reordering [#1053](https://github.com/getgrav/grav-plugin-admin/issues/1053)
+ * Fix for editors not getting refreshed when changing tab [#1052](https://github.com/getgrav/grav-plugin-admin/issues/1052)
+ * Fix for mobile tabs in page editing [#1057](https://github.com/getgrav/grav-plugin-admin/issues/1057)
+
+# v1.3.1
+## 03/31/2017
+
+1. [](#bugfix)
+ * Fix for `Undefined index: file_path` error with Direct Install [#1043](https://github.com/getgrav/grav-plugin-admin/issues/1043)
+
+# v1.3.0
+## 03/31/2017
+
+1. [](#new)
+ * User uploadable avatar (still falls back to Gravatar if not provided)
+1. [](#improved)
+ * Improved tabs CSS to handle long titles [#1036](https://github.com/getgrav/grav-plugin-admin/issues/1036)
+ * Fixed `step` in range field [Form#136](https://github.com/getgrav/grav-plugin-form/issues/136)
+1. [](#bugfix)
+ * Fixed issue with exception thrown when `copying` and `moving` a page [#1042](https://github.com/getgrav/grav-plugin-admin/issues/1042)
+ * Automatically calculate the *next* numeric folder prefix [Core#1386](https://github.com/getgrav/grav/issues/1386)
+
+# v1.3.0-rc.3
+## 03/22/2017
+
+1. [](#new)
+ * All new `Page Ordering` implementation. Completely revamped and will only reorder with folder-prefix enabled. You can now reorder all siblings at the same time.
+ * Added a new `Advanced - Override` to allow option to display pages by folder name (default) or Collection definition
+ * Improved `range` form field with touch and counter support [#1016](https://github.com/getgrav/grav-plugin-admin/pull/1016)
+1. [](#bugfix)
+ * Cleanup package files via GPM install to make them more windows-friendly [#1361](https://github.com/getgrav/grav/pull/1361)
+
+# v1.3.0-rc.2
+## 03/17/2017
+
+1. [](#improved)
+ * Do not attempt to fetch any notification if settings are disabled [#942](https://github.com/getgrav/grav-plugin-admin/issues/942)
+
+# v1.3.0-rc.1
+## 03/13/2017
+
+1. [](#new)
+ * New flex-based/js Tabs system for better flexibility and improved UX.
+ * Added new **toolbox** with `Direct-Install` option via ZIP or URL.
+ * Added an option to reinstall a plugin/theme already installed [#984](https://github.com/getgrav/grav-plugin-admin/issues/984)
+ * Added a new **range field** [#995](https://github.com/getgrav/grav-plugin-admin/issues/995)
+ * When creating a new page, automatically select the Page Template based on Parent Page Child Type [#1008](https://github.com/getgrav/grav-plugin-admin/issues/1008)
+1. [](#improved)
+ * Page Media field now is available when folder is created, not just markdown file [#1000](https://github.com/getgrav/grav-plugin-admin/issues/1000)
+ * Separated user details and avatar in separate twig to allow more granular overriding in plugins [#989](https://github.com/getgrav/grav-plugin-admin/issues/989)
+ * Nicer layout of themes list on wider screen
+ * Editor full-screen option displays title/save options [#948](https://github.com/getgrav/grav-plugin-admin/issues/948)
+ * Use native OS highlight colors for the editor [#977](https://github.com/getgrav/grav-plugin-admin/issues/977)
+ * Force admin pages to set `Page::expires(0)` so it's not cached [#1009](https://github.com/getgrav/grav-plugin-admin/issues/1009)
+ * Added support for up to 15 tabs (was 10) [#954](https://github.com/getgrav/grav-plugin-admin/issues/954)
+ * Only reorder pages in the admin if collection uses `@self` and `order.by`
+ * Improved configuration tab sizes when you have lots of tabs
+ * Modified default media select size from 150px x 100px to 200px x 150px
+1. [](#bugfix)
+ * Fixed rendering issue with Chrome and sortables collections [#1002](https://github.com/getgrav/grav-plugin-admin/issues/1002)
+ * Fixed issue with removal of file that has been just uploaded and stored in the session
+
+
+# v1.2.14
+## 02/17/2017
+
+1. [](#bugfix)
+ * Fixed bad bug with `GPM::install()` from a change in Admin v1.2.13
+
+# v1.2.13
+## 02/17/2017
+
+1. [](#bugfix)
+ * Fix issue with validating page when switching language [#963](https://github.com/getgrav/grav-plugin-admin/issues/963)
+ * Fix issue with quotes in Admin strings used in JS [#965](https://github.com/getgrav/grav-plugin-admin/issues/965)
+ * Refactored `AdminController::taskGetUpdates` to use standard task/response [#980](https://github.com/getgrav/grav-plugin-admin/issues/980)
+ * Sync Admin pages blueprints with core [core#212d35221a9bbcc242508ba49a551b3f6e62af8e](https://github.com/getgrav/grav/commit/212d35221a9bbcc242508ba49a551b3f6e62af8e)
+
+# v1.2.12
+## 02/12/2017
+
+1. [](#bugfix)
+ * Rebuilt the JS bundle to address various JS-related issues that cropped up in `v1.2.11`
+ * Fixed Firefox Network Error issue when updating multiple plugins/themes at concurrently [#1301](https://github.com/getgrav/grav/issues/1301)
+
+# v1.2.11
+## 02/10/2017
+
+1. [](#new)
+ * Added lang strings for `CLI_COMPATIBILITY` which is new in Grav v1.1.16
+1. [](#improved)
+ * Allow plugin to set custom 'authorize' and 'location' in `onAdminMenu()` event
+ * Updated all language files with latest from [Crowdin](https://crowdin.com/project/grav-admin)
+1. [](#bugfix)
+ * Fixed issue `admin.super` or `admin.users` users changing the account when saving another user [#713](https://github.com/getgrav/grav-plugin-admin/issues/713)
+ * Fix issue where non `admin.super`/`admin.users` users could see other users profiles [#713](https://github.com/getgrav/grav-plugin-admin/issues/713)
+ * Fix removing responsive image from page media [#111](https://github.com/getgrav/grav-plugin-admin/issues/111) [#952](https://github.com/getgrav/grav-plugin-admin/issues/952)
+ * Use @2x & @3x fallback images in the filepicker. [#952](https://github.com/getgrav/grav-plugin-admin/issues/952)
+
+# v1.2.10
+## 1/30/2017
+
+1. [](#improved)
+ * It is now possible to manually specify a format for the `datetime` field [#1261](https://github.com/getgrav/grav/issues/1261)
+ * Allow to see plugins and themes list without internet connection. Also add a more helpful message in the "add" view [grav#1008](https://github.com/getgrav/grav/issues/1008)
+1. [](#bugfix)
+ * Fixed issue with downloaded package when installing a testing release
+ * Allow non admin.super users to change their account information. Allow `admin.super` and `admin.users` to change other users information. [#943](https://github.com/getgrav/grav/issues/943)
+ * Handle removing a media file also if it's not a json request. Was not working after https://github.com/getgrav/grav-plugin-admin/commit/6b343365996ce838759d80fa3917d4d994f1aeb4
+
+# v1.2.9
+## 01/18/2017
+
+1. [](#improved)
+ * Added lang strings for `ALLOW_WEBSERVER_GZIP` in System configuration
+
+# v1.2.8
+## 01/17/2017
+
+1. [](#improved)
+ * Allow the ability to clear the cache if `admin.maintenance`, as stated in the docs [#908](https://github.com/getgrav/grav-plugin-admin/issues/908)
+ * Added lang strings for `DEFAULT_LANG` in Site configuration
+ * Added lang strings for `NEVER_CACHE_TWIG` in System and Page configuration
+1. [](#bugfix)
+ * Fixed saving the configuration if not `admin.super`
+ * Show the clear cache buttons if the user has `admin.cache` permissions [#908](https://github.com/getgrav/grav-plugin-admin/issues/908#issuecomment-270748616)
+ * Fix colorpicker validation when transparency is set to 1.00 [#921](https://github.com/getgrav/grav-plugin-admin/issues/921)
+ * Fix html markup in section twig [#922](https://github.com/getgrav/grav-plugin-admin/pull/922)
+ * Fix bug in deleting a file uploaded with the `file` field [#920](github.com/getgrav/grav-plugin-admin/issues/920)
+ * Fix for plugin throwing event-based errors when plugin is removed and no longer available to process said event
+
+# v1.2.7
+## 12/22/2016
+
+1. [](#improved)
+ * Fixed an issue with non `.html` extensions not setting application type properly when fallback template not found.
+1. [](#bugfix)
+ * Fix plugins and themes json calls after the introduction of [HTML fallback for templates not found](https://github.com/getgrav/grav/commit/364209a27da0f5dfba5fde9c4b07b6d5844cda47)
+
+
+# v1.2.6
+## 12/21/2016
+
+1. [](#improved)
+ * Added a delay before reloading the page when a plugin or theme get installed
+ * Fix prompting to remove Grav itself when removing a package that requires a specific Grav version
+ * Remove cli-server exception since we now have compatibility with a custom router in Grav [#1219](https://github.com/getgrav/grav/pull/1219)
+1. [](#bugfix)
+ * Fix issue with array field and `value_only: true`
+
+# v1.2.5
+## 12/13/2016
+
+1. [](#new)
+ * RC released as stable
+1. [](#bugfix)
+ * YAML syntax fixes
+
+# v1.2.5-rc.4
+## 12/07/2016
+
+1. [](#new)
+ * Added a new `permissions` form field, used in the user profile to simplify editing permissions
+ * Added several new `onAdminAfter...()` events to allow for more 3rd party plugin interaction
+1. [](#bugfix)
+ * Updated admin-user-details to allow longer user names in the sidebar [#879](https://github.com/getgrav/grav-plugin-admin/issues/879)
+ * Redirect to a 404 page when accessing nonexistent plugins and themes [#880](https://github.com/getgrav/grav-plugin-admin/issues/880)
+
+# v1.2.5-rc.3
+## 11/26/2016
+
+1. [](#bugfix)
+ * Update class namespace for Admin class [#874](https://github.com/getgrav/grav-plugin-admin/issues/874)
+ * Fix updating/installing packages from admin
+
+# v1.2.5-rc.2
+## 11/19/2016
+
+1. [](#bugfix)
+ * Make default value work for filepicker [#859](https://github.com/getgrav/grav-plugin-admin/issues/859)
+
+# v1.2.5-rc.1
+## 11/09/2016
+
+1. [](#new)
+ * Updated to FontAwesome 4.7.0 with [Grav icon](http://fontawesome.io/icon/grav/)
+1. [](#improved)
+ * Always delete image alternatives in AdminController#taskDelmedia [#814](https://github.com/getgrav/grav-plugin-admin/issues/814)
+ * Use Media class to retrieve files in AdminController#taskGetFilesInFolder [#842](https://github.com/getgrav/grav-plugin-admin/issues/842)
+ * Increased specificity for Colorpicker field to prevent 3rd party conflicts
+1. [](#bugfix)
+ * Editor link button doesn't prefix links with `http://` anymore [#813](https://github.com/getgrav/grav-plugin-admin/issues/813)
+ * Dashboard Charts now always refresh no matter what [#753](https://github.com/getgrav/grav-plugin-admin/issues/753)
+ * Use rawRoute for parent too when saving [#843](https://github.com/getgrav/grav-plugin-admin/issues/843)
+ * Avoid different output when users exist or not in password recovery [#849](https://github.com/getgrav/grav/issues/849)
+ * Fix login to admin with permission inherited from group [#857](https://github.com/getgrav/grav-plugin-admin/issues/857)
+
+
+# v1.2.4
+## 10/22/2016
+
+1. [](#bugfix)
+ * Fix for accented media files [#833](https://github.com/getgrav/grav-plugin-admin/issues/833)
+ * Fix for `CTRL + s` not saving in editor [#832](https://github.com/getgrav/grav-plugin-admin/pull/832)
+ * Fix for missing REDIS translations in admin [#1123](https://github.com/getgrav/grav/issues/1123)
+
+# v1.2.3
+## 10/19/2016
+
+1. [](#new)
+ * Added new `onAdminCreatePageFrontmatter()` event to support plugins such as `auto-date` by allowing frontmatter to be modified by plugins.
+ * Added a new independent `cache_enabled` option for admin plugin (default is `false`). Should fix various sync issues.
+ * Add an `onAdminData` event to allow plugins to add additional blueprints data
+1. [](#improved)
+ * Handle errors when a resource fails to install
+ * Page media and File field images thumbnail are now properly proportionate and 150x100
+ * Added the Codeception testing suite with an initial test
+1. [](#bugfix)
+ * Fix [#1034](https://github.com/getgrav/grav/issues/1034) redirect of page creation procedure when system.home.hide_in_urls is enabled
+ * Media (Page): Do not extend parent metehod for sending files since Safari and IE API for FormData don’t implement `delete` ([#772](https://github.com/getgrav/grav-plugin-admin/issues/772))
+ * Clean up POST keys containing square brackets, allows for regex ranges in routes ([#776](https://github.com/getgrav/grav-plugin-admin/issues/776))
+ * Fix [#773](https://github.com/getgrav/grav-plugin-admin/issues/773) allow filepicker work inside lists, respond to mutation event
+ * Better error handling for Feed when unable to connect
+ * Fixed UI for Pagemedia note when files cannot yet be uploaded ([#798](https://github.com/getgrav/grav-plugin-admin/issues/798))
+ * Fixed Submit buttons getting disabled in case of form invalidity disallowing to submit again ([#802](https://github.com/getgrav/grav-plugin-admin/issues/802))
+ * Fixed issue when reading the file size setting if set to `0` (in Pagemedia and File fields)
+ * Fixed issue with `file` field in collections that caused unexpected duplication of items ([#775](https://github.com/getgrav/grav-plugin-admin/issues/775))
+ * Dramatically improved `filepicker` performance. Data is only ever loaded when the drop-down is on focus, as it was supposed to be. Image preview of a selected item won't be rendered unless the field gains focus to avoid wasting resources. ([#788](https://github.com/getgrav/grav-plugin-admin/issues/788))
+ * Allow `filepicker` field to peak at the pending uploaded files and optimistically select them ([#792](https://github.com/getgrav/grav-plugin-admin/issues/792))
+ * Fix [#821](https://github.com/getgrav/grav-plugin-admin/issues/821) issue in saving a page to a new language when the filename does not contain the filename yet.
+
+# v1.2.2
+## 09/08/2016
+
+1. [](#bugfix)
+ * Fix [#767](https://github.com/getgrav/grav-plugin-admin/issues/767) Add styling for new HTML5 input field types
+ * Fix issue with checking the package dependencies when more than one package is being inspected
+
+# v1.2.1
+## 09/07/2016
+
+1. [](#bugfix)
+ * Fixed `tmp://` stream issue with Admin updated to 1.2 before Grav updated 1.1.4
+
+# v1.2.0
+## 09/07/2016
+
+1. [](#new)
+ * All new `file` field. All files get uploaded via Ajax and are stored upon Save. This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB)
+ * Added a new `filepicker` field, which allows to pick any file from an ajax-powered select box. The `pagemediaselect` field now internally uses the `filepicker` field to live-reload the available files, and to show image previews.
+1. [](#improved)
+ * Better error handling for 500 Internal Server Errors, when Fetch fails
+ * Various notifications style and other CSS fixes
+ * More language strings added
+ * Added `clear-tmp` to cache clear drop-down
+ * Unified JSON twig templates
+ * Better error handling for 500 Internal Server Errors, when Fetch fails.
+ * Updated vendor Libraries
+1. [](#bugfix)
+ * Curl fix for invalid cert errors with News Feed
+ * Avoid requiring `admin.super` for ajax calls [#739](https://github.com/getgrav/grav-plugin-admin/issues/739)
+ * Fix showing HTML in notifications, in the feed
+ * Fixed broken page type filtering
+ * Fixed `beforeunload` event not prompting to offer the choice to stay on the page in case of unsaved changes
+ * Fixed click-away detection for preventing loss of changes, that would get ignored in some circumstances (ie, from modal confirmation)
+ * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way
+ * Fixed composer dependencies missing error message
+
+# v1.1.4
+## 08/14/2016
+
+1. [](#bugfix)
+ * Fixed Firefox News Feed dashboard widget layout
+
+# v1.1.3
+## 08/10/2016
+
+1. [](#new)
+ * Admin notifications system. Admin will pull and cache notifications. This will be used to announce important updates, security vulnerabilities, and general interest news.
+ * Ability to disable widgets in the dashboard
+ * Added news feed widget to the dashboard
+1. [](#improved)
+ * Updated FontAwesome to v4.6.3
+ * Use new List functionality for Media Configuration
+ * Get fresh media list for `Controller::getListMedia()` rather that cache so always latest.
+ * Add translation strings for the new system.force_ssl option
+ * Reworked List UI to better handle drag & drop sort. To sort it is now required to use the left drag handle [#724](https://github.com/getgrav/grav-plugin-admin/issues/724)
+ * Lists now features a new YAML option `controls: [top|bottom|both]` (default: bottom) which will display the "Add Item" button at the Top and/or Bottom position relative to the list. When the Top button is pressed, a new item will be added at the beginning of the list, when the Bottom button is pressed, a new item will be appended to the list.
+ * Lists now features two new YAML options `sortby: [field]` (default: disabled) and `sortby_dir: [asc|desc]` (default: asc) which will display a new Sorting button in the list allowing to automatically reindex the collection based on the given sort field set.
+ * Lists now features a new YAML option `collapsed: [true|false]` (default: false) and a new UI/UX that allows for collapsing / expanding collection items, allowing to better managing long lists of items. It is advised to always put as first field the most significant one, so that when a list is collapsed it can be still easily browsed.
+ * It is now possible to sort Array fields via drag & drop [#950](https://github.com/getgrav/grav/issues/950)
+1. [](#bugfix)
+ * Fixed issue in Admin favicon URL [#704](https://github.com/getgrav/grav-plugin-admin/issues/704)
+ * Fixed issue in `selfupgrade` where the package would get downloaded in the wrong destination
+ * Hide tab when user is not authorized to access it [#712](https://github.com/getgrav/grav-plugin-admin/issues/712)
+ * Fixed Lists issue when reindexing, causing Radio fields to potentially lose their `checked` status
+ * Avoid overwriting a file when uploaded with the same filename through the Admin blueprint `file` field type if `avoid_overwriting` is enabled on the field
+ * Fixed issue with Array field in `value_only` mode, improperly displaying the key when no value was set
+ * Translate the description of a blueprint field [#729](https://github.com/getgrav/grav-plugin-admin/issues/729)
+
+# v1.1.2
+## 07/16/2016
+
+1. [](#improved)
+ * Forcing limit of upload files based on System settings
+1. [](#bugfix)
+ * Definitive fix for multi form submission in Microsoft Edge causing the Save to not work [#694](https://github.com/getgrav/grav-plugin-admin/issues/694)
+ * Fix issue with calculating the `theme_url` with `open_basedir` restrictions [#699](https://github.com/getgrav/grav-plugin-admin/issues/699)
+ * Check for null payload before going on [#526](https://github.com/getgrav/grav-plugin-admin/issues/526)
+ * Redraw Dashboard Charts when collapsing/expanding the sidebar
+ * Fix for `cache/compiled` errors resulting from page media uploads [getgrav/grav#938](https://github.com/getgrav/grav/issues/938)
+
+# v1.1.1
+## 07/14/2016
+
+1. [](#bugfix)
+ * Fixed issue with forms causing creation of new pages not to work [#698](https://github.com/getgrav/grav-plugin-admin/issues/698) and [getgrav/grav#934](https://github.com/getgrav/grav/issues/934)
+
+# v1.1.0
+## 07/14/2016
+
+1. [](#improved)
+ * Added the ability to login with the email in addition to the username. [#674](https://github.com/getgrav/grav-plugin-admin/issues/674)
+ * It is now possible to sort the Plugins and Themes views by 'Name', 'Author', 'GravTeam', 'Release Date', 'Updates Available' and 'Testing' releases (if in Testing Channel), both Ascending and Descending. [#583](https://github.com/getgrav/grav-plugin-admin/issues/583)
+ * Prevent external links (like the Preview button) to trigger the "Changes Detected" notice [#689](https://github.com/getgrav/grav-plugin-admin/issues/689)
+ * Added a filter field in Plugins and Themes list views, to allow for quick search of a particular resource
+ * Added new `Enabled` sorting option for Plugins list view
+1. [](#bugfix)
+ * Fixed an issue that prevented removing more than one page, in the pages listng [#672](https://github.com/getgrav/grav-plugin-admin/issues/672)
+ * Fixed toggleables in lists that were always loading as checked even when not stored [#688](https://github.com/getgrav/grav-plugin-admin/issues/688)
+ * Fixed Fullscreen tooltip in Editor displaying off screen (when in fullscreen mode) [#677](https://github.com/getgrav/grav-plugin-admin/issues/677)
+ * Fixed inconsistency in the way selectized fields would be rendered [#692](https://github.com/getgrav/grav-plugin-admin/issues/692)
+ * Fixed issue with Save in Microsoft Edge [#694](https://github.com/getgrav/grav-plugin-admin/issues/694)
+
+# v1.1.0-rc.4
+## 06/21/2016
+
+1. [](#bugfix)
+ * Fix for 'front-end' shortcut showing in mobile sidebar incorrectly.
+ * Append progressive number to the copied page title. [#394](https://github.com/getgrav/grav-plugin-admin/issues/394)
+ * Add field description to forms [#667](https://github.com/getgrav/grav-plugin-admin/pull/667)
+ * Fix clearing all cache [#658](https://github.com/getgrav/grav-plugin-admin/issues/658)
+ * Assign the correct ordering when saving a page that didn't have ordering set before [#628](https://github.com/getgrav/grav-plugin-admin/issues/628)
+ * Fix issue when saving a modular child folder as 05.somethin and being reset to 01.something upon save [#628](https://github.com/getgrav/grav-plugin-admin/issues/628)
+
+# v1.1.0-rc.3
+## 06/14/2016
+
+1. [](#bugfix)
+ * Fix for Gemini Scrollbar CSS breaking layout in IE 9+ [#644](https://github.com/getgrav/grav-plugin-admin/issues/644)
+ * Fall back to english for UI language if admin's language is not set [#641](https://github.com/getgrav/grav-plugin-admin/issues/641)
+ * List field has the wrong label/field width. Switched to "1/3 | 2/3" like all other fields.
+ * Correctly set the page slug on page copy. Avoids having two pages with the same slug [#394](https://github.com/getgrav/grav-plugin-admin/issues/394)
+ * When copying a page, if there's a page prefix (used for ordering), update the value to avoid having two pages with the same order number [#429](https://github.com/getgrav/grav-plugin-admin/issues/429)
+ * Fixed size of dropdown text in responsive views to be readable [#647](https://github.com/getgrav/grav-plugin-admin/issues/647)
+ * Fixed issue with checkbox in toggleables getting submitted with the form even when disabled (fixes #646)
+
+# v1.1.0-rc.2
+## 06/02/2016
+
+1. [](#improved)
+ * Cleaned up the Page Preview CSS to make it more 'standard' [#634](https://github.com/getgrav/grav-plugin-admin/issues/634)
+ * Added a legend with the Page colors explained [#637](https://github.com/getgrav/grav-plugin-admin/issues/637)
+ * Hide email output when sending forgot password instructions [#571](https://github.com/getgrav/grav-plugin-admin/issues/571)
+1. [](#bugfix)
+ * Fixed "Data type `System` doesn't exist!" error when activating a theme [#635](https://github.com/getgrav/grav-plugin-admin/issues/635)
+ * Fixed issue with custom media types not deleting on save [#633](https://github.com/getgrav/grav-plugin-admin/issues/633)
+ * Fixed issue when saving `List` field type in plugins + pages
+ * Fixed JS error on login/logout page due to jQuery not being loaded
+
+
+# v1.1.0-rc.1
+## 06/01/2016
+
+1. [](#new)
+ * Major improvements with the **File Upload** (`file`) field type. Now fully supports themes, plugins, configuration + pages
+1. [](#improved)
+ * Updated with latest languages via [Crowdin](https://crowdin.com/project/grav-admin/)
+ * Provide security options for single tabs [#615](https://github.com/getgrav/grav-plugin-admin/issues/615)
+ * Disable double clicking on Save/Delete/Copy page actions [#611](https://github.com/getgrav/grav-plugin-admin/issues/611)
+ * Tweaked the avatar alignment in sidebar [#592](https://github.com/getgrav/grav-plugin-admin/issues/592)
+ * Added page name to delete dialog [#511](https://github.com/getgrav/grav-plugin-admin/issues/511)
+ * Enabling / Disabling a Plugin doesn't trigger the expand / collapse details anymore [#614](https://github.com/getgrav/grav-plugin-admin/issues/614)
+ * Added hover on plugins list rows to match pages [#619](https://github.com/getgrav/grav-plugin-admin/issues/619)
+ * Translate media configuration [#608](https://github.com/getgrav/grav-plugin-admin/issues/608)
+ * Use raw routes in blueprints to better support multi-language [#798](https://github.com/getgrav/grav-plugin-admin/issues/798)
+ * Updated NPM modules dependencies
+1. [](#bugfix)
+ * Fix double "Removed successfully" appearing when removing a package [#609](https://github.com/getgrav/grav-plugin-admin/issues/609)
+ * Prevent removing required plugins dependencies when removing a package [#613](https://github.com/getgrav/grav-plugin-admin/issues/613)
+ * Show page title in Delete Confirmation modal if this information is available
+ * Don't try to uninstall admin/form/login/email plugins
+ * Only check for updates if not `admin.maintenance` or `admin.super` [#557](https://github.com/getgrav/grav-plugin-admin/issues/557)
+ * Always submit checkboxes that are not checked and force a 0 value [#616](https://github.com/getgrav/grav-plugin-admin/issues/616)
+ * Fix encoding in tooltips again [#622](https://github.com/getgrav/grav-plugin-admin/issues/622)
+ * Do not show `move` cursor for Collections that aren't sortable [#624](https://github.com/getgrav/grav-plugin-admin/issues/624)
+ * Properly handle Collections that specify a custom key, rather than falling back to indexed list [#632](https://github.com/getgrav/grav-plugin-admin/issues/632)
+
+# v1.1.0-beta.5
+## 05/23/2016
+
+1. [](#improved)
+ * Set sidebar navigation defaults back to "Tab Activation" and "Auto Width"
+ * Custom logo text is displayed as first letter in small sidebar view [#829](https://github.com/getgrav/grav/issues/829)
+ * Copied admin-only blueprints from Grav core to the Admin plugin
+ * Allow `field.label` to have HTML in it [#601](https://github.com/getgrav/grav-plugin-admin/issues/601)
+1. [](#bugfix)
+ * Fixed Togggle field with doubled `checked="checked"` when `toggleable: true` [#579](https://github.com/getgrav/grav-plugin-admin/issues/579)
+ * Strip HTML tags and lowercase username from login/reset forms [#577](https://github.com/getgrav/grav-plugin-admin/issues/577)
+ * Fixed issue with version numbers not showing up for dependencies [#581](https://github.com/getgrav/grav-plugin-admin/issues/581)
+ * Fixed editor tooltips in fullscreen mode and tablet devices rendering [#566](https://github.com/getgrav/grav-plugin-admin/issues/566)
+ * Fixed issue with `file` form field not functioning [#838](https://github.com/getgrav/grav/issues/838)
+ * Fixed issue with creating pages [#595](https://github.com/getgrav/grav-plugin-admin/issues/595)
+
+# v1.1.0-beta.4
+## 05/09/2016
+
+1. [](#new)
+ * Implemented Quickopen functionality to automatically open / close the Sidebar when mouseover
+1. [](#improved)
+ * Better error handling when `obj->validate()` fails with exception [#594](https://github.com/getgrav/grav-plugin-admin/issues/564)
+ * Improve markup of update and add package dependencies in update modal [#560](https://github.com/getgrav/grav-plugin-admin/issues/560)
+1. [](#bugfix)
+ * Fix for admin translation filter (`|tu`) not substituting text - [#567](https://github.com/getgrav/grav-plugin-admin/issues/567)
+ * Translated "Publishing" tab text [#561](https://github.com/getgrav/grav-plugin-admin/issues/561)
+ * Fix invalid argument supplied in foreach [#563](https://github.com/getgrav/grav-plugin-admin/issues/563)
+ * CSS fixes for editor button alignment
+ * Fix for forgot password not finding anyone
+ * Fix UI issue with update button on a package page in Firefox
+ * Fix issue with update button when automatic check for updates is disabled
+ * Fix issue caused by clicking "Check for updates" multiple times
+ * Added missing translations
+ * Fix for Themes with an array of keywords [#823](https://github.com/getgrav/grav/issues/823)
+
+# v1.1.0-beta.3
+## 05/04/2016
+
+1. [](#new)
+ * Added a `|adminNicetime` Twig filter to show 'nicetime' in admin user's language
+ * Added a `prepend` and `append` field option for text input type
+ * Added a WIP `onAdminRegisterPermissions` event
+ * Added several new languages: Arabic, Danish, Greek, Farsi, Korean, Romanian, Thai. Huge thanks to the [translation teams](https://crowdin.com/project/grav-admin)
+1. [](#improved)
+ * Fixed UI issue with Backup / Update buttons positioning
+ * Tweaked placeholders color in login/new user panels [#542](https://github.com/getgrav/grav-plugin-admin/issues/542)
+1. [](#bugfix)
+ * Fixed several untranslated strings
+ * Fix the version information after updating Grav from Admin
+ * Fix a Twig autoescape issue on Plugins descriptions
+ * Fix for showing empty drop-down with only one supported language [#522](https://github.com/getgrav/grav-plugin-admin/issues/522)
+ * Fix for visibility toggle on new page not working [#551](https://github.com/getgrav/grav-plugin-admin/issues/551)
+ * Page tooltips usability issue [#496](https://github.com/getgrav/grav-plugin-admin/issues/496)
+ * Fix removed title attribute from editor toolbar buttons [#539](https://github.com/getgrav/grav-plugin-admin/issues/539)
+ * Allow Incognito / Private browsing to still function in Safari [#527](https://github.com/getgrav/grav-plugin-admin/issues/527)
+
+# v1.1.0-beta.2
+## 04/27/2016
+
+1. [](#new)
+ * Added `grav ~1.1` to dependencies
+ * Added a persistent message if you try to run Admin 1.1 on Grav 1.0
+1. [](#improved)
+ * Used locator instead of `CACHE_DIR`
+ * Added a better way to get Admin version
+ * Show account page for users with certain ACL [#524](https://github.com/getgrav/grav-plugin-admin/pull/524)
+1. [](#bugfix)
+ * Fixed Editor Preview using wrong parameters for the ajax call
+ * Fixed toggle for stable/testing channel
+ * Fixed blueprint JSON fields
+ * If not logged in redirect to base path [#445](https://github.com/getgrav/grav-plugin-admin/pull/445)
+ * Various autoescape fixes
+ * ColorPicker CSS fixes
+ * Fix for translation of admin login [#500](https://github.com/getgrav/grav-plugin-admin/issues/500)
+ * Fix list not applying `toggleable: true` and `style: vertical` [#518](https://github.com/getgrav/grav-plugin-admin/pull/518)
+ * Fixed issue with update for wrong plugin displaying on plugin details pages
+ * Fixed error with the **close sidebar** toggle in some browsers (Firefox, iOS Safari)
+
+# v1.1.0-beta.1
+## 04/20/2016
+
+1. [](#new)
+ * JavaScript Rewrite. Admin is now built in ES6
+ * Lists can now be nested and 'fancy fields' (such as editor, datetime picker, selectize, other lists) get automatically initialized so they are always available no matter if you add or remove items from the lists
+ * The Editor has been reworked to be more flexible. In fact you can now pass any CodeMirror setting via blueprints, through the codemirror: attribute. The buttons have also a new API that allow to add or ignore buttons and behaviors into the toolbar from any plugin (see grav-plugin-editor-buttons). We also added the headers buttons (H1-H6) and Undo / Redo buttons, due to popular demand
+ * We introduced a new colorpicker field. You can now add more colors to your admin plugins :)
+ * Along with the versioning support added in the Grav Core for 1.1, the admin plugin can now install dependencies with the same versioning requirements as the GPM CLI commands.
+ * New System configuration field for toggling GPM release version (testing/stable)
+ * Several new system configuration options for new functionality such as `Process frontmatter Twig`
+ * Ability to collapse the sidebar to a smaller icon view if you need more room.
+1. [](#improved)
+ * The default Grav theme has been tweaked and in many places completely rewritten to ensure that it's as flexible as possible. The primary reason for this was to ensure theming and customization compatibility for the upcoming Admin Pro plugin, but a key benefit includes greatly improved mobile compatibility.
+ * We reworked the Datetimepicker, you will notice a new refreshed UI with a much better support for translations
+ * Tabs are now persistent. In views such as Page editing, when switching tab and saving or refreshing, would cause the tab to be reset to the initial one.
+ * When editing a page in Expert mode, the frontmatter editor is now more friendly. You will now get line numbers, undo/redo and YAML linter.
+ * Behind the scenes we have reworked how the form and toggleables work. This added a lot more reliability and consistency across the whole admin.
+ * The Pages view has more persistent states. It will now remember your expanded/collapsed states as well as filtering.
+ * Lists can now accept a custom button label with the 'btnLabel' property
+ * After login to Admin, redirect to the original URL called
+ * Admin now has an unique cache key compared to the 'site' so pages can be cached independently
+ * Improved the layout of the User Profile page.
+ * Set cache key uniquely for admin so cache does not colide with site
+1. [](#bugfix)
+ * Fix for modular preview - [#254](https://github.com/getgrav/grav-plugin-admin/issues/254)
+ * Fix for long content and page tabs - [#441](https://github.com/getgrav/grav-plugin-admin/issues/441)
+ * Fix for clear cache after adding new folder - [#393](https://github.com/getgrav/grav-plugin-admin/issues/393)
+
+# v1.0.9
+## 02/11/2016
+
+1. [](#bugfix)
+ * Fix language translation files
+
+# v1.0.8
+## 02/05/2016
+
+1. [](#new)
+ * Added a logout button when not authorized to access a page in Admin
+ * Added the option to hide a tab from an extended blueprint (https://github.com/getgrav/grav/issues/620)
+ * Many new languages and updates to existing languages from the Translation team.
+1. [](#improved)
+ * Check frontmatter for validity prior to saving
+ * Add noindex, nofollow across the entire admin theme if no other robots headers are set on a page
+ * Allow to hide a configuration blueprint section / tab and still save its values
+ * Allow to show user defined blueprints in configuration
+ * Updated FontAwesome to latest 4.5.0 version
+1. [](#bugfix)
+ * Fixed an issue with user registration on Linux caused by `glob()` possibly returning false.
+ * Fixed an issue preventing Admin to work correctly in a multisite configuration
+ * Fixed preview and insertion of images with non-lowercase extension
+ * Fixed an incorrect number of pages being displayed in the sidebar in some cases
+ * [Security] Don't reveal Grav filesystem path when trying to delete non-existing images
+ * [Security] Fix PHP error happening when uploading file without extension if the JS dropzone uploader is configured to allow empty file extensions
+ * [Security] Ensure correct escaping in various Twig files
+
+# v1.0.7
+## 01/15/2016
+
+1. [](#new)
+ * Added onAdminDashboard event
+ * Added onAdminSave event
+ * New lang strings for reverse proxy toggle
+1. [](#improved)
+ * More robust YAML file checking in config folders
+ * Removed deprecated menu event
+ * Removed old logs code
+ * Used new onAdminDashboard event for current dashboard widgets
+1. [](#bugfix)
+ * Fix for missing access checks on config pages #397
+ * Fix parent not loaded on admin form save #587
+ * When no route field is added to a page blueprint, add it as page root
+ * Fix for wrong page count (will show dynamic added pages in count too - Need to fix this)
+ * Fix for IE/Edge saving forms #391
+
+# v1.0.6
+## 01/07/2016
+
+1. [](#bugfix)
+ * Fix for forms appending `_json` fields on every save
+
+# v1.0.5
+## 01/07/2016
+
+1. [](#new)
+ * Added a pointer to Grav's contributing guide
+ * Handle the optional logic to strip home from Page routes and urls
+ * The Configuration page now shows any blueprint found in the user/blueprints/config/ folder, thus allowing to add custom configurations
+1. [](#improved)
+ * Allow the nonce for a POST action to be set in the query url
+ * Add a fallback twig template to use in case Twig cannot find a template file
+ * Modified update Theme and Plugin buttons to use more reliably markup
+1. [](#bugfix)
+ * Fix additional `on` parameter when saving plugins configs that contain tabs in their blueprint
+ * Fixes for the `pagemediaselect` form field
+ * Fix an untranslated message in the logout form when `system.languages.translations` is disabled
+ * Fixed a hardcoded `http://` reference throwing warnings under HTTPS
+ * Ensure download package has `.zip` extension, just in case
+
+# v1.0.4
+## 12/22/2015
+
+1. [](#improved)
+ * Improved File input field for admin
+ * Restore file inputs functionality and process form via JS if no inputs found
+1. [](#bugfix)
+ * Fix for the image preview in the file field on multi-lang sites
+ * Fix problem in form code introduced by fix to allow file uploads
+ * Fix redirect in deleting page media
+
+# v1.0.3
+## 12/20/2015
+
+1. [](#new)
+ * Added `pagemediaselect` field for use in pages
+1. [](#improved)
+ * Updated various languages
+ * Check for method `meetsRequirements()` prior to using
+ * Enable `file` form field to be used in plugins and theme blueprints
+
+# v1.0.2
+## 12/18/2015
+
+1. [](#bugfix)
+ * Fixed issue with user edit page causing error due to individual language files
+
+# v1.0.1
+## 12/18/2015
+
+1. [](#new)
+ * Moved languages into individual files under `languages/` folder
+ * Added a check for PHP version
+ * Dutch translation added
+1. [](#improved)
+ * Let forms work with file inputs
+ * Various file input improvements
+ * Language updates
+ * Better checks for existence of Popularity JSON data
+ * Add file processing to admin forms
+ * More Admin Pro integration fixes
+1. [](#bugfix)
+ * Set form to multipart if it contains a file field
+ * `cleanFilesData()` now returns just the filename
+
+# v1.0.0
+## 12/11/2015
+
+1. [](#new)
+ * New built-in admin registration process
+ * Added security check to `section` form field
+ * Added new RocketTheme font with various icons
+ * Add `onAdminThemeInitialized()` event to admin `Themes::init()`
+ * Force timestamp on CSS/JS assets based on `GRAV_VERSION`
+ * Additions for Gantry5 support
+1. [](#improved)
+ * Force lowercase `username` when logging in
+ * Hide markdown preview except for pages
+ * Added a notice if you don't have permission to see dashboard
+ * Updated admin login page logic
+ * Return "Invalid Security Token" instead of "Unauthorized"
+ * Throw exception if you used with built-in PHP web server
+ * Updated languages
+ * Removed `noreply@getgrav.org` default email address
+ * Use new methods to disable CSS/JS pipeline if available
+ * Various code cleanups
+1. [](#bugfix)
+ * Handle case when email `from` is not configured
+ * Fix tabs support in plugin/themes settings
+ * Fix param separator in page media Ajax call
+ * Fix favicon base URL
+
+# v1.0.0-rc.7
+## 12/01/2015
+
+1. [](#new)
+ * Display error page if page does not exist in admin
+ * Removed Beta message option and added toggle for GitHub message
+ * Added functionality to support Admin Pro plugin (in development)
+1. [](#improved)
+ * Added support for Markdown editor in lists #239
+ * Better Markdown Editor API with dynamic initialization
+ * Various language updates
+ * Removed some unused variables
+ * Added admin check for pages existence
+ * Prevent the admin to cause an error when an Ajax action is in progress
+ * Force translations to be active even when disabled in site #299
+ * Do not reinitialize `Selectize` if already available
+1. [](#bugfix)
+ * Fixed full-screen markdown Editor
+ * Fix modular preview not working reliably #254
+ * **Nonce fixes** (hopefully the last of them!)
+ * Fix broken plugin enable/disable
+ * Fix issue where `_redirect: /plugins` was getting stored in the plugin configuration
+ * Replace default them service with admin one
+ * Fix saving array fields #304
+ * Fix missing translations when default language is not english
+ * Fix title variables not translated #310
+
+# v1.0.0-rc.6
+## 11/21/2015
+
+1. [](#improved)
+ * Implemented logic to detect when offline and suppress Ajax calls
+ * Added nonce logic to be used by JS
+1. [](#bugfix)
+ * Nonce fix for updating themes
+ * Nonce fix for deleting pages
+
+# v1.0.0-rc.5
+## 11/20/2015
+
+1. [](#new)
+ * Use **Nonce** mechanism for form security
+ * Added Hungarian translation
+ * Add support for Markdown labels #271
+ * Added support for Markdown Editor in all the things
+ * Implemented save keyboard shortcut (Ctrl + S / CMD + S)
+1. [](#improved)
+ * Better error for "Internal Server Error" when accessing GPM
+ * Updated French translation
+ * Updated Russian translation
+ * Load Gravatar image with protocol-less `//:` syntax
+ * Improved header UI in mobile browsers #265
+ * Dropped unused version of JQuery
+ * More visible Preview link icon
+ * Hide **Latest pages** if there are none
+ * Improved toggle to better support different length strings
+1. [](#bugfix)
+ * Force rescanning fields when submitting a form #243
+ * Set default lang for pages on fresh session
+ * Escaped values in `array.html.twig`
+ * Fix saving in IE Edge
+ * Fixed various typos
+ * Fixed JS button issues #370
+ * Fixed JS error in private browsing #272
+ * Fixed date field border
+ * Fixed multiple instance of Markdown Editor #285
+ * Fixed Spacer CSS #267
+
+# v1.0.0-rc.4
+## 10/29/2015
+
+1. [](#improved)
+ * Changed admin menu event hook to `onAdminMenu()`
+ * Minor improvements for admin page location
+ * Additional lang strings for Grav 1.0.0-rc.3
+
+# v1.0.0-rc.3
+## 10/27/2015
+
+1. [](#improved)
+ * Rely on context-language for active language
+ * Improved some Russian translations
+ * Only show login if not already logged in
+1. [](#bugfix)
+ * Disable asset pipeline in admin only
+ * Fix Editor cursor insertion point when text is selected in some actions
+
+# v1.0.0-rc.2
+## 10/23/2015
+
+1. [](#bugfix)
+ * Reverted lang redirect code. Needs to be reworked to be more reliable
+
+# v1.0.0-rc.1
+## 10/23/2015
+
+1. [](#new)
+ * Redirect to non-language URL except for `pages/`
+1. [](#improved)
+ * New language strings for new `system.yaml` fields
+ * Improved Russian translations
+ * Improved compatibility with PECL Yaml parser
+1. [](#bugfix)
+ * Redirect to correct page if you change folder/slug
+ * Fix issue with Asset pipeline not being disabled in admin
+ * Fix for HTML in text input fields
+ * Fixed various icons in headers
+
+# v0.6.2
+## 10/15/2015
+
+1. [](#improved)
+ * Use `title` rather than `menu` in Page listing
+ * Wrapped language strings in double-quotes
+ * New language strings for new fields
+1. [](#bugfix)
+ * Fixed issue with IE not able to save pages
+
+# v0.6.1
+## 10/07/2015
+
+1. [](#new)
+ * Added the ability to render front-end templates in markdown preview
+ * Option to disable Google-based fonts. Useful for Cyrillic languages.
+ * Couple of new static helper methods used by new page blueprints
+ * New `fieldset` form field (thanks @Sommerregen!)
+1. [](#improved)
+ * Hide editor buttons in preview mode
+ * Improved support for admin when offline
+ * Use relative URL in Login form
+ * Added some more missing lang strings
+ * Improved German translation
+ * Compressed CSS files for improved performance
+ * Only get last 7 days in week count calculation
+1. [](#bugfix)
+ * Fix saving pages in local-specific languages
+ * Only track 'human' page hits in statistics
+ * Responsive fixes for 'wordy' languages
+ * Fixed delete issue with array field type
+ * Fixed some hardcoded `admin` references to allow admin path change
+ * Fix for issue with lang code being added twice
+ * Fix language name in admin buttons
+
+# v0.6.0
+## 09/16/2015
+
+1. [](#new)
+ * Support for custom markdown editor buttons!
+ * Added Russian translations
+ * Added Japanese translations
+ * Ajax session keep-alive when editing forms
+1. [](#improved)
+ * Added missing Italian translations
+ * Added additional options field into the pages form field
+1. [](#bugfix)
+ * Fix GPM errors in offline mode
+ * Fix for duplicate status messages
+
+# v0.5.0
+## 09/11/2015
+
+1. [](#new)
+ * Responsive layout for mobile compatibility (thanks @Vivalldi!)
+ * Added page type and many other new filters to Page list view
+ * Added granular ACL requirements to admin pages
+ * Ability to define page date format
+ * Added `onAdminTemplateNavPluginHook` to allow for plugins to hook into sidebar
+ * Added YAML Twig filters (to and from)
+ * Support for nested metadata
+ * Added ability to disable automatic update checks via admin plugin configuration
+ * Initial Spanish translation
+1. [](#improved)
+ * Check for existence of a user account
+ * Various language additions
+ * Refactored form fields to remove duplicates from form plugin
+ * Improved date picker
+ * Improved display field
+ * Add page template type to page list view
+ * Various UI fixes
+ * Added some default field 'focus' to save clicking
+ * Only allow "Add Modular" if the theme has modular templates
+ * Updated `chartist.js` library
+ * Updated 'fontawesome' fonts to the latest v4.4
+1. [](#bugfix)
+ * Fix for "drag-n-drop" of non-image media
+ * Fix a fatal error in GPM when offline
+ * Fix a z-index bug with tooltips
+ * Fix a z-index bug in lang dropdowns
+ * Don't allow deleting of last empty array field
+ * Fix for images with parenthesis in filenames
+ * Fix for page title visualization when not set
+ * Fix for cursor position in folder/array fields
+
+# v0.4.3
+## 08/31/2015
+
+1. [](#new)
+ * Added Japanese translation
+ * Support for independent file name and template override
+1. [](#improved)
+ * Improved slug generation using `slugify.js`
+ * Allow the `title` twig variables to set the page title
+ * Improved Page media handling with several bugfixes
+ * Prevent error when there are no pages on a site
+ * If all updates are applied, show "Fully Updated" text in dashboard
+ * Better preview link (requires `rtrim` filter from Grav 0.9.40)
+ * Order all plugins and themes alphabetically
+ * Removed duplicate language entries
+1. [](#bugfix)
+ * Fix for redirect after saving when multilang not enabled
+ * Fix for deleting responsive media
+ * Fix for HTML encoding in markdown field
+
+# v0.4.2
+## 08/25/2015
+
+1. [](#bugfix)
+ * Fix for current admin lang not showing up in page lang dropdown
+ * Fix for incorrect NAME/CONTENT lang keys
+ * Fix for incorrect site link
+
+# v0.4.1
+## 08/24/2015
+
+1. [](#bugfix)
+ * Fix for broken **Add Page** - Doh!
+ * Fix for empty site link when at root
+
+# v0.4.0
+## 08/24/2015
+
+1. [](#new)
+ * Multi-language Page support!!!
+ * Admin languages configurable per user
+ * Toastr messages for `check updates`
+ * new `tu` filter for admin translations
+ * Italian and German admin translations
+ * Added a save location in system and site configuration
+ * Page metadata now uses flexible array field
+1. [](#improved)
+ * Allow subpages of modular pages to display in pages list
+ * Open external pages in new tabs
+ * Reworked `visibility` of pages
+ * Use `PLUGIN_ADMIN` prefix for translations
+ * Added link to gravatar.com to avoid confusion on avatar
+ * Limit page count to 200 in ordering field
+ * Fixed various Safari _flex_ issues
+ * Use `rawRoute()` for page links
+ * Minor `param separator` fixes
+ * Various CSS fixes
+ * Improved CodeMirror to force spaces
+ * Added **Selectize** dropdowns to various forms and modals
+1. [](#bugfix)
+ * Fix for `Call to a member function path() on non-object` error
+ * Fixed dropdown z-index issues
+ * Correctly set the filename including language if set
+ * Fix for empty taxonomies on page save
+ * Fix for page not redirecting properly on folder change
+ * Fix for table headers styling
+ * Added missing translation strings
+ * Unique page counting in total page counts
+ * Fixed JS warning with page filtering and deleting
+
+
+# v0.3.0
+## 08/11/2015
+
+1. [](#new)
+ * Show current date in form date format fields
+ * Added a new **check for updates** button to flush GPM
+ * Added session timeout configuration for admin
+ * Added `isSymlink` logic for Grav
+ * Added new `phpinfo` page
+1. [](#improved)
+ * Improved toggleables
+ * Support `param_separator` for Apache on windows
+ * Logout now goes to interstitial to provide session messages
+ * Updated hints and improved formatting
+ * Encoding URI for images in editor preview
+ * Create user `system.yaml` and `site.yaml` if they are missing
+ * Open external links in new tab by default
+ * Set edit mode to `normal` by default
+ * Disable CSS/JS pipelining in the admin
+1. [](#bugfix)
+ * Fixed form submission not working in IE
+ * Fix fatal error when deleting homepage
+ * Prevent admin plugin activating when the URL of a page contains partial route
+
+# v0.2.0
+## 08/06/2015
+
+1. [](#new)
+ * Added multiple **clear cache** types
+ * Added back to themes link when adding new themes
+ * Properly handles visibility and ordering and guesses best option on new
+ * Added new templates field with support for custom (unsupported) template type
+ * Added new display field for displaying simple text value
+ * **Update Grav** button now works
+ * Added spanish translation
+ * Added german translation
+1. [](#improved)
+ * Improved page order handling logic
+ * Implemented 2-step theme switching logic with warning
+ * Force `modular` page class for modular template
+ * Clear page cache on page delete (ghost pages still showing)
+ * Clears route on page save so changes such as `slug` are picked up
+ * Fix dashboard layout in Safari
+ * Added tooltips for official 'Team Grav' themes/plugins
+1. [](#bugfix)
+ * Handle modular page templates on create
+ * Fixed Firefox JS error for arrays
+ * Ensure we don't change page type to empty and save (causing page to be deleted)
+ * Fixed some minor CSS issues with editor
+ * Fixed link to RocketTheme.com
+ * Disabled fields now stay properly disabled
+
+# v0.1.1
+## 08/04/2015
+
+1. [](#bugfix)
+ * Fixed GitHub URLs
+ * Hiding toggle for disabling Admin plugin
+ * Removed extra text not needed
+
+# v0.1.0
+## 08/04/2015
+
+1. [](#new)
+ * ChangeLog started...
diff --git a/user/plugins/admin/CONTRIBUTING.md b/user/plugins/admin/CONTRIBUTING.md
new file mode 100644
index 0000000..ef2bc6f
--- /dev/null
+++ b/user/plugins/admin/CONTRIBUTING.md
@@ -0,0 +1 @@
+Please read the Contributing Guidelines of the Grav Project
\ No newline at end of file
diff --git a/user/plugins/admin/LICENSE b/user/plugins/admin/LICENSE
new file mode 100644
index 0000000..f7d4bb6
--- /dev/null
+++ b/user/plugins/admin/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2017 Grav
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/user/plugins/admin/README.md b/user/plugins/admin/README.md
new file mode 100644
index 0000000..430509d
--- /dev/null
+++ b/user/plugins/admin/README.md
@@ -0,0 +1,111 @@
+# Grav Standard Administration Panel Plugin
+
+This **admin plugin** for [Grav](http://github.com/getgrav/grav) is an HTML user interface that provides a convenient way to configure Grav and easily create and modify pages. This will remain a totally optional plugin, and is not in any way required or needed to use Grav effectively. In fact, the admin provides an intentionally limited view to ensure it remains easy to use and not overwhelming. I'm sure power users will still prefer to work with the configuration files directly.
+
+
+
+# Features
+
+* User login with automatic password encryption
+* Forgot password functionality
+* Logged-in-user management
+* One click Grav core updates
+* Dashboard with maintenance status, site activity and latest page updates
+* Notifications system for latest news, blogs, and announcements
+* Ajax-powered backup capability
+* Ajax-powered clear-cache capability
+* System configuration management
+* Site configuration management
+* Normal and Expert modes which allow editing via forms or YAML
+* Page listing with filtering and search
+* Page creation, editing, moving, copying, and deleting
+* Powerful syntax highlighting code editor with instant Grav-powered preview
+* Editor features, hot keys, toolbar, and distraction-free fullscreen mode
+* Drag-n-drop upload of page media files including drag-n-drop placement in the editor
+* One click theme and plugin updates
+* Plugin manager that allows listing and configuration of installed plugins
+* Theme manager that allows listing and configuration of installed themes
+* GPM-powered installation of new plugins and themes
+
+# Support
+
+#### Support
+
+We have tested internally, but we hope to use this public beta phase to identify, isolate, and fix issues related to the plugin to ensure it is as solid and reliable as possible.
+
+For **live chatting**, please use the dedicated [Slack Chat Room](https://getgrav.org/slack) for discussions directly related to Grav.
+
+For **bugs, features, improvements**, please ensure you [create issues in the admin plugin GitHub repository](https://github.com/getgrav/grav-plugin-admin).
+
+# Installation
+
+First ensure you are running the latest **Grav 1.6.7 or later**. This is required for the admin plugin to run properly (`-f` forces a refresh of the GPM index).
+
+```
+$ bin/gpm selfupgrade -f
+```
+
+The admin plugin actually requires the help of 3 other plugins, so to get the admin plugin to work you first need to install **admin**, **login**, **forms**, and **email** plugins. These are available via GPM, and because the plugin has dependencies you just need to proceed and install the admin plugin, and agree when prompted to install the others:
+
+```
+$ bin/gpm install admin
+```
+
+### Manual Installation
+
+Manual installation is not the recommended method of installation, however, it is still possible to install the admin plugin manually. Basically, you need to download each of the following plugins individually:
+
+* [admin](https://github.com/getgrav/grav-plugin-admin/archive/develop.zip)
+* [login](https://github.com/getgrav/grav-plugin-login/archive/develop.zip)
+* [form](https://github.com/getgrav/grav-plugin-form/archive/develop.zip)
+* [email](https://github.com/getgrav/grav-plugin-email/archive/develop.zip)
+
+Extract each archive file into your `user/plugins` folder, then ensure the folders are renamed to just `admin/`, `login/`, `form/`, and `email/`. Then proceed with the **Usage instructions below**.
+
+# Usage
+
+### Create User with CLI
+
+After this you need to create a user account with admin privileges:
+
+```
+$ bin/plugin login new-user
+```
+
+### Create User Manually
+
+Alternatively, you can create a user account manually, in a file called `user/accounts/admin.yaml`. This **filename** is actually the **username** that you will use to login. The contents will contain the other information for the user.
+
+```
+password: 'password'
+email: 'youremail@mail.com'
+fullname: 'Johnny Appleseed'
+title: 'Site Administrator'
+access:
+ admin:
+ login: true
+ super: true
+```
+
+Of course you should edit your `email`, `password`, `fullname`, and `title` to suit your needs.
+
+> You can use any password when you manually put it in this `.yaml` file. However, when you change your password in the admin, it must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters.
+
+# Accessing the Admin
+
+By default, you can access the admin by pointing your browser to `http://yoursite.com/admin`. You can simply log in with the `username` and `password` set in the YAML file you configured earlier.
+
+> After logging in, your **plaintext password** will be removed and replaced by an **encrypted** one.
+
+# Standard Free & Paid Pro Versions
+
+If you have been following the [blog](http://getgrav.org/blog), [Twitter](https://twitter.com/getgrav), [Slack chat](https://getgrav.org/slack), etc., you probably already know now that our intention is to provide two versions of this plugin.
+
+The **standard free version**, is very powerful, and has more functionality than most commercial flat-file CMS systems.
+
+We also intend to release in the near future a more feature-rich **pro version** that will include enhanced functionality, as well as some additional nice-to-have capabilities. This pro version will be a **paid** plugin the price of which is not yet 100% finalized.
+
+# Running Tests
+
+First install the dev dependencies by running `composer update` from the Grav root.
+Then `composer test` will run the Unit Tests, which should be always executed successfully on any site.
diff --git a/user/plugins/admin/admin.php b/user/plugins/admin/admin.php
new file mode 100644
index 0000000..a6c4285
--- /dev/null
+++ b/user/plugins/admin/admin.php
@@ -0,0 +1,1017 @@
+ 1000,
+ ];
+
+ /** @var bool */
+ protected $active = false;
+
+ /** @var string */
+ protected $template;
+
+ /** @var string */
+ protected $theme;
+
+ /** @var string */
+ protected $route;
+
+ /** @var string */
+ protected $admin_route;
+
+ /** @var Uri */
+ protected $uri;
+
+ /** @var Admin */
+ protected $admin;
+
+ /** @var Session */
+ protected $session;
+
+ /** @var Popularity */
+ protected $popularity;
+
+ /** @var string */
+ protected $base;
+
+ /** @var string */
+ protected $version;
+
+ /**
+ * @return array
+ */
+ public static function getSubscribedEvents()
+ {
+ return [
+ 'onPluginsInitialized' => [
+ ['autoload', 100001],
+ ['setup', 100000],
+ ['onPluginsInitialized', 1001]
+ ],
+ 'onPageInitialized' => ['onPageInitialized', 0],
+ 'onFormProcessed' => ['onFormProcessed', 0],
+ 'onShutdown' => ['onShutdown', 1000],
+ 'onAdminDashboard' => ['onAdminDashboard', 0],
+ 'onAdminTools' => ['onAdminTools', 0],
+ ];
+ }
+
+ /**
+ * Get list of form field types specified in this plugin. Only special types needs to be listed.
+ *
+ * @return array
+ */
+ public function getFormFieldTypes()
+ {
+ return [
+ 'column' => [
+ 'input@' => false
+ ],
+ 'columns' => [
+ 'input@' => false
+ ],
+ 'fieldset' => [
+ 'input@' => false
+ ],
+ 'section' => [
+ 'input@' => false
+ ],
+ 'list' => [
+ 'array' => true
+ ],
+ 'file' => [
+ 'array' => true
+ ]
+ ];
+ }
+
+ /**
+ * [onPluginsInitialized:100000] Composer autoload.
+ *
+ * @return ClassLoader
+ */
+ public function autoload()
+ {
+ return require __DIR__ . '/vendor/autoload.php';
+ }
+
+ /**
+ * [onPluginsInitialized:100000]
+ *
+ * If the admin path matches, initialize the Login plugin configuration and set the admin
+ * as active.
+ */
+ public function setup()
+ {
+ $route = $this->config->get('plugins.admin.route');
+ if (!$route) {
+ return;
+ }
+
+ $this->base = '/' . trim($route, '/');
+ $this->admin_route = rtrim($this->grav['pages']->base(), '/') . $this->base;
+ $this->uri = $this->grav['uri'];
+
+ $users_exist = Admin::doAnyUsersExist();
+
+ // If no users found, go to register
+ if (!$users_exist) {
+ if (!$this->isAdminPath()) {
+ $this->grav->redirect($this->admin_route);
+ }
+ $this->template = 'register';
+ }
+
+ // Only activate admin if we're inside the admin path.
+ if ($this->isAdminPath()) {
+ try {
+ $this->grav['session']->init();
+ } catch (SessionException $e) {
+ $this->grav['session']->init();
+ $message = 'Session corruption detected, restarting session...';
+
+ /** @var Debugger $debugger */
+ $debugger = $this->grav['debugger'];
+ $debugger->addMessage($message);
+
+ $this->grav['messages']->add($message, 'error');
+ }
+ $this->active = true;
+
+ // Set cache based on admin_cache option
+ $this->grav['cache']->setEnabled($this->config->get('plugins.admin.cache_enabled'));
+ $pages = $this->grav['pages'];
+ if (method_exists($pages, 'setCheckMethod')) {
+ // Force file hash checks to fix caching on moved and deleted pages.
+ $pages->setCheckMethod('hash');
+ }
+ }
+ }
+
+ /**
+ * [onPluginsInitialized:1001]
+ *
+ * If the admin plugin is set as active, initialize the admin
+ */
+ public function onPluginsInitialized()
+ {
+ // Only activate admin if we're inside the admin path.
+ if ($this->active) {
+ // Store this version.
+ $this->version = $this->getBlueprint()->get('version');
+
+ // Have a unique Admin-only Cache key
+ if (method_exists($this->grav['cache'], 'setKey')) {
+ /** @var Cache $cache */
+ $cache = $this->grav['cache'];
+ $cache_key = $cache->getKey();
+ $cache->setKey($cache_key . '$');
+ }
+
+ // Turn on Twig autoescaping
+ if (method_exists($this->grav['twig'], 'setAutoescape') && $this->grav['uri']->param('task') !== 'processmarkdown') {
+ $this->grav['twig']->setAutoescape(true);
+ }
+
+ $this->grav['debugger']->addMessage('Admin v' . $this->version);
+ $this->initializeAdmin();
+
+ // Disable Asset pipelining (old method - remove this after Grav is updated)
+ if (!method_exists($this->grav['assets'], 'setJsPipeline')) {
+ $this->config->set('system.assets.css_pipeline', false);
+ $this->config->set('system.assets.js_pipeline', false);
+ }
+
+ // Replace themes service with admin.
+ $this->grav['themes'] = function () {
+ return new Themes($this->grav);
+ };
+ }
+
+ // We need popularity no matter what
+ $this->popularity = new Popularity();
+
+ // Fire even to register permissions from other plugins
+ $this->grav->fireEvent('onAdminRegisterPermissions', new Event(['admin' => $this->admin]));
+ }
+
+ /**
+ * [onPageInitialized:0]
+ */
+ public function onPageInitialized()
+ {
+ $page = $this->grav['page'];
+
+ $template = $this->grav['uri']->param('tmpl');
+
+ if ($template) {
+ $page->template($template);
+ }
+ }
+
+ /**
+ * [onFormProcessed:0]
+ *
+ * Process the admin registration form.
+ *
+ * @param Event $event
+ */
+ public function onFormProcessed(Event $event)
+ {
+ $form = $event['form'];
+ $action = $event['action'];
+
+ switch ($action) {
+ case 'register_admin_user':
+
+ if (Admin::doAnyUsersExist()) {
+ throw new \RuntimeException('A user account already exists, please create an admin account manually.');
+ }
+
+ if (!$this->config->get('plugins.login.enabled')) {
+ throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.PLUGIN_LOGIN_DISABLED'));
+ }
+
+ $data = [];
+ $username = $form->value('username');
+
+ if ($form->value('password1') !== $form->value('password2')) {
+ $this->grav->fireEvent('onFormValidationError', new Event([
+ 'form' => $form,
+ 'message' => $this->grav['language']->translate('PLUGIN_LOGIN.PASSWORDS_DO_NOT_MATCH')
+ ]));
+ $event->stopPropagation();
+
+ return;
+ }
+
+ $data['password'] = $form->value('password1');
+
+ $fields = [
+ 'email',
+ 'fullname',
+ 'title'
+ ];
+
+ foreach ($fields as $field) {
+ // Process value of field if set in the page process.register_user
+ if (!isset($data[$field]) && $form->value($field)) {
+ $data[$field] = $form->value($field);
+ }
+ }
+
+ // Don't store plain text password or username (part of the filename).
+ unset($data['password1'], $data['password2'], $data['username']);
+
+ // Extra lowercase to ensure file is saved lowercase
+ $username = strtolower($username);
+
+ $inflector = new Inflector();
+
+ $data['fullname'] = $data['fullname'] ?? $inflector->titleize($username);
+ $data['title'] = $data['title'] ?? 'Administrator';
+ $data['state'] = 'enabled';
+ $data['access'] = ['admin' => ['login' => true, 'super' => true], 'site' => ['login' => true]];
+
+ /** @var UserCollectionInterface $users */
+ $users = $this->grav['accounts'];
+
+ // Create user object and save it
+ $user = $users->load($username);
+ $user->update($data);
+ $user->save();
+
+ //Login user
+ $this->grav['session']->user = $user;
+ unset($this->grav['user']);
+ $this->grav['user'] = $user;
+ $user->authenticated = true;
+ $user->authorized = $user->authorize('admin.login');
+
+ $messages = $this->grav['messages'];
+ $messages->add($this->grav['language']->translate('PLUGIN_ADMIN.LOGIN_LOGGED_IN'), 'info');
+ $this->grav->redirect($this->admin_route);
+
+ break;
+ }
+ }
+
+ /**
+ * [onShutdown:1000]
+ *
+ * Handles the shutdown
+ */
+ public function onShutdown()
+ {
+ if ($this->active) {
+ //only activate when Admin is active
+ if ($this->admin->shouldLoadAdditionalFilesInBackground()) {
+ $this->admin->loadAdditionalFilesInBackground();
+ }
+ } else {
+ //if popularity is enabled, track non-admin hits
+ if ($this->config->get('plugins.admin.popularity.enabled')) {
+ $this->popularity->trackHit();
+ }
+ }
+ }
+
+ /**
+ * [onAdminDashboard:0]
+ */
+ public function onAdminDashboard()
+ {
+ $this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = ['template' => 'dashboard-maintenance'];
+ $this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = ['template' => 'dashboard-statistics'];
+ $this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = ['template' => 'dashboard-notifications'];
+ $this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = ['template' => 'dashboard-feed'];
+ $this->grav['twig']->plugins_hooked_dashboard_widgets_main[] = ['template' => 'dashboard-pages'];
+ }
+
+
+ /**
+ * [onAdminTools:0]
+ *
+ * Provide the tools for the Tools page, currently only direct install
+ *
+ * @return Event
+ */
+ public function onAdminTools(Event $event)
+ {
+ $event['tools'] = array_merge($event['tools'], [
+ 'backups' => [['admin.maintenance', 'admin.super'], $this->grav['language']->translate('PLUGIN_ADMIN.BACKUPS')],
+ 'scheduler' => [['admin.super'], $this->grav['language']->translate('PLUGIN_ADMIN.SCHEDULER')],
+ 'logs' => [['admin.super'], $this->grav['language']->translate('PLUGIN_ADMIN.LOGS')],
+ 'reports' => [['admin.super'], $this->grav['language']->translate('PLUGIN_ADMIN.REPORTS')],
+ 'direct-install' => [['admin.super'], $this->grav['language']->translate('PLUGIN_ADMIN.DIRECT_INSTALL')],
+ ]);
+
+ return $event;
+ }
+
+ /**
+ * Sets longer path to the home page allowing us to have list of pages when we enter to pages section.
+ */
+ public function onPagesInitialized()
+ {
+ $config = $this->config;
+
+ // Force SSL with redirect if required
+ if ($config->get('system.force_ssl')) {
+ if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
+ $url = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+ $this->grav->redirect($url);
+ }
+ }
+
+ $this->session = $this->grav['session'];
+
+ // Set original route for the home page.
+ $home = '/' . trim($this->config->get('system.home.alias'), '/');
+
+ // set session variable if it's passed via the url
+ if ($this->uri->param('mode') === 'expert') {
+ $this->session->expert = true;
+ } elseif ($this->uri->param('mode') === 'normal') {
+ $this->session->expert = false;
+ } else {
+ // set the default if not set before
+ $this->session->expert = $this->session->expert ?? false;
+ }
+
+ /** @var Pages $pages */
+ $pages = $this->grav['pages'];
+
+ $this->grav['admin']->routes = $pages->routes();
+
+ // Remove default route from routes.
+ if (isset($this->grav['admin']->routes['/'])) {
+ unset($this->grav['admin']->routes['/']);
+ }
+
+ $page = $pages->dispatch('/', true);
+
+ // If page is null, the default page does not exist, and we cannot route to it
+ if ($page) {
+ $page->route($home);
+ }
+
+ // Make local copy of POST.
+ $post = $this->grav['uri']->post();
+
+ // Initialize Page Types
+ Pages::types();
+
+ // Handle tasks.
+ $this->admin->task = $task = $this->grav['task'] ?? $this->grav['action'];
+ if ($task) {
+ $this->initializeController($task, $post);
+ } elseif ($this->template === 'logs' && $this->route) {
+ // Display RAW error message.
+ echo $this->admin->logEntry();
+ exit();
+ }
+
+ $self = $this;
+
+ // make sure page is not frozen!
+ unset($this->grav['page']);
+
+ $this->admin->pagesCount();
+
+ // Replace page service with admin.
+ $this->grav['page'] = function () use ($self) {
+ $page = new Page();
+
+ // Plugins may not have the correct Cache-Control header set, force no-store for the proxies.
+ $page->expires(0);
+
+ if ($this->grav['user']->authorize('admin.login')) {
+ $event = new Event(['page' => $page]);
+ $event = $this->grav->fireEvent('onAdminPage', $event);
+ $page = $event['page'];
+
+ if ($page->slug()) {
+ return $page;
+ }
+ }
+
+ // Look in the pages provided by the Admin plugin itself
+ if (file_exists(__DIR__ . "/pages/admin/{$self->template}.md")) {
+ $page->init(new \SplFileInfo(__DIR__ . "/pages/admin/{$self->template}.md"));
+ $page->slug(basename($self->template));
+
+ return $page;
+ }
+
+ // If not provided by Admin, lookup pages added by other plugins
+ $plugins = $this->grav['plugins'];
+ $locator = $this->grav['locator'];
+
+ foreach ($plugins as $plugin) {
+ if ($this->config->get("plugins.{$plugin->name}.enabled") !== true) {
+ continue;
+ }
+
+ $path = $locator->findResource("plugins://{$plugin->name}/admin/pages/{$self->template}.md");
+
+ if ($path) {
+ $page->init(new \SplFileInfo($path));
+ $page->slug(basename($self->template));
+
+ return $page;
+ }
+ }
+
+ return null;
+ };
+
+ if (empty($this->grav['page'])) {
+ if ($this->grav['user']->authenticated) {
+ $event = new Event(['page' => null]);
+ $event->page = null;
+ $event = $this->grav->fireEvent('onPageNotFound', $event);
+ /** @var PageInterface $page */
+ $page = $event->page;
+
+ if (!$page || !$page->routable()) {
+ $error_file = $this->grav['locator']->findResource('plugins://admin/pages/admin/error.md');
+ $page = new Page();
+ $page->init(new \SplFileInfo($error_file));
+ $page->slug(basename($this->route));
+ $page->routable(true);
+ }
+
+ unset($this->grav['page']);
+ $this->grav['page'] = $page;
+ } else {
+ // Not Found and not logged in: Display login page.
+ $login_file = $this->grav['locator']->findResource('plugins://admin/pages/admin/login.md');
+ $page = new Page();
+ $page->init(new \SplFileInfo($login_file));
+ $page->slug(basename($this->route));
+ unset($this->grav['page']);
+ $this->grav['page'] = $page;
+ }
+ }
+
+ // Explicitly set a timestamp on assets
+ $this->grav['assets']->setTimestamp(substr(md5(GRAV_VERSION . $this->grav['config']->checksum()), 0, 10));
+ }
+
+ /**
+ * Handles initializing the assets
+ */
+ public function onAssetsInitialized()
+ {
+ // Disable Asset pipelining
+ $assets = $this->grav['assets'];
+ $assets->setJsPipeline(false);
+ $assets->setCssPipeline(false);
+ }
+
+ /**
+ * Add twig paths to plugin templates.
+ */
+ public function onTwigTemplatePaths()
+ {
+ $twig_paths = [];
+ $this->grav->fireEvent('onAdminTwigTemplatePaths', new Event(['paths' => &$twig_paths]));
+
+ $twig_paths[] = __DIR__ . '/themes/' . $this->theme . '/templates';
+
+ $this->grav['twig']->twig_paths = $twig_paths;
+ }
+
+ /**
+ * Set all twig variables for generating output.
+ */
+ public function onTwigSiteVariables()
+ {
+ $twig = $this->grav['twig'];
+ $page = $this->grav['page'];
+
+ $twig->twig_vars['location'] = $this->template;
+ $twig->twig_vars['base_url_relative_frontend'] = $twig->twig_vars['base_url_relative'] ?: '/';
+ $twig->twig_vars['admin_route'] = trim($this->admin_route, '/');
+ $twig->twig_vars['template_route'] = $this->template;
+ $twig->twig_vars['current_route'] = '/' . $twig->twig_vars['admin_route'] . '/' . $this->template . '/' . $this->route;
+ $twig->twig_vars['base_url_relative'] = $twig->twig_vars['base_url_simple'] . '/' . $twig->twig_vars['admin_route'];
+ $twig->twig_vars['current_url'] = rtrim($twig->twig_vars['base_url_relative'] . '/' . $this->template . '/' . $this->route, '/');
+ $theme_url = '/' . ltrim($this->grav['locator']->findResource('plugin://admin/themes/' . $this->theme,
+ false), '/');
+ $twig->twig_vars['theme_url'] = $theme_url;
+ $twig->twig_vars['preset_url'] = $twig->twig_vars['preset_url'] ?? $theme_url;
+ $twig->twig_vars['base_url'] = $twig->twig_vars['base_url_relative'];
+ $twig->twig_vars['base_path'] = GRAV_ROOT;
+ $twig->twig_vars['admin'] = $this->admin;
+ $twig->twig_vars['admin_version'] = $this->version;
+ $twig->twig_vars['logviewer'] = new LogViewer();
+ $twig->twig_vars['form_max_filesize'] = Utils::getUploadLimit() / 1024 / 1024;
+
+ $fa_icons_file = CompiledYamlFile::instance($this->grav['locator']->findResource('plugin://admin/themes/grav/templates/forms/fields/iconpicker/icons' . YAML_EXT));
+ $fa_icons = $fa_icons_file->content();
+ $fa_icons = array_map(function ($icon) {
+ //only pick used values
+ return ['id' => $icon['id'], 'unicode' => $icon['unicode']];
+ }, $fa_icons['icons']);
+
+ $twig->twig_vars['fa_icons'] = $fa_icons;
+
+ // add form if it exists in the page
+ $header = $page->header();
+
+ $forms = [];
+ if (isset($header->forms)) foreach ($header->forms as $key => $form) {
+ $forms[$key] = new Form($page, null, $form);
+ }
+ $twig->twig_vars['forms'] = $forms;
+
+ // preserve form validation
+ if (!isset($twig->twig_vars['form'])) {
+ if (isset($header->form)) {
+ $twig->twig_vars['form'] = new Form($page);
+ } elseif (isset($header->forms)) {
+ $twig->twig_vars['form'] = new Form($page, null, reset($header->forms));
+ }
+ }
+
+ // Gather Plugin-hooked nav items
+ $this->grav->fireEvent('onAdminMenu');
+
+ switch ($this->template) {
+ case 'dashboard':
+ $twig->twig_vars['popularity'] = $this->popularity;
+
+ // Gather Plugin-hooked dashboard items
+ $this->grav->fireEvent('onAdminDashboard');
+
+ break;
+ }
+
+ $flashData = $this->grav['session']->getFlashCookieObject(Admin::TMP_COOKIE_NAME);
+
+ if (isset($flashData->message)) {
+ $this->grav['messages']->add($flashData->message, $flashData->status);
+ }
+ }
+
+ // Add images to twig template paths to allow inclusion of SVG files
+ public function onTwigLoader()
+ {
+ $theme_paths = Grav::instance()['locator']->findResources('plugins://admin/themes/' . $this->theme . '/images');
+ foreach($theme_paths as $images_path) {
+ $this->grav['twig']->addPath($images_path, 'admin-images');
+ }
+ }
+
+ /**
+ * Add the Admin Twig Extensions
+ */
+ public function onTwigExtensions()
+ {
+ require_once __DIR__ . '/classes/Twig/AdminTwigExtension.php';
+
+ $this->grav['twig']->twig->addExtension(new AdminTwigExtension);
+ }
+
+ public function onAdminAfterSave(Event $event)
+ {
+ // Special case to redirect after changing the admin route to avoid 'breaking'
+ $obj = $event['object'];
+
+ if (null !== $obj && method_exists($obj, 'blueprints')) {
+ $blueprint = $obj->blueprints()->getFilename();
+
+ if ($blueprint === 'admin/blueprints' && isset($obj->route) && $this->admin_route !== $obj->route) {
+ $redirect = preg_replace('/^' . str_replace('/','\/',$this->admin_route) . '/',$obj->route,$this->uri->path());
+ $this->grav->redirect($redirect);
+ }
+ }
+ }
+
+ /**
+ * Convert some types where we want to process out of the standard config path
+ *
+ * @param Event $e
+ */
+ public function onAdminData(Event $e)
+ {
+ $type = $e['type'] ?? null;
+ switch ($type) {
+ case 'tools/scheduler':
+ $e['type'] = 'config/scheduler';
+ break;
+ case 'tools':
+ case 'tools/backups':
+ $e['type'] = 'config/backups';
+ break;
+ }
+ }
+
+ public function onOutputGenerated()
+ {
+ // Clear flash objects for previously uploaded files whenever the user switches page or reloads
+ // ignoring any JSON / extension call
+ if ($this->admin->task !== 'save' && empty($this->uri->extension())) {
+ // Discard any previously uploaded files session and remove all uploaded files.
+ if ($flash = $this->session->getFlashObject('files-upload')) {
+ $flash = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($flash));
+ foreach ($flash as $key => $value) {
+ if ($key !== 'tmp_name') {
+ continue;
+ }
+ @unlink($value);
+ }
+ }
+ }
+ }
+
+ /**
+ * Initial stab at registering permissions (WIP)
+ *
+ * @param Event $e
+ */
+ public function onAdminRegisterPermissions(Event $e)
+ {
+ $admin = $e['admin'];
+ $permissions = [
+ 'admin.super' => 'boolean',
+ 'admin.login' => 'boolean',
+ 'admin.cache' => 'boolean',
+ 'admin.configuration' => 'boolean',
+ 'admin.configuration_system' => 'boolean',
+ 'admin.configuration_site' => 'boolean',
+ 'admin.configuration_media' => 'boolean',
+ 'admin.configuration_info' => 'boolean',
+ 'admin.settings' => 'boolean',
+ 'admin.pages' => 'boolean',
+ 'admin.maintenance' => 'boolean',
+ 'admin.statistics' => 'boolean',
+ 'admin.plugins' => 'boolean',
+ 'admin.themes' => 'boolean',
+ 'admin.tools' => 'boolean',
+ 'admin.users' => 'boolean',
+ ];
+ $admin->addPermissions($permissions);
+ }
+
+ /**
+ * Check if the current route is under the admin path
+ *
+ * @return bool
+ */
+ public function isAdminPath()
+ {
+ $route = $this->uri->route();
+
+ return $route === $this->base || 0 === strpos($route, $this->base . '/');
+ }
+
+ /**
+ * Helper function to replace Pages::Types()
+ * and to provide an event to manipulate the data
+ *
+ * Dispatches 'onAdminPageTypes' event
+ * with 'types' data member which is a
+ * reference to the data
+ */
+ public static function pagesTypes()
+ {
+ $types = Pages::types();
+
+ // First filter by configuration
+ $hideTypes = Grav::instance()['config']->get('plugins.admin.hide_page_types', []);
+ foreach ((array) $hideTypes as $type) {
+ unset($types[$type]);
+ }
+
+ // Allow manipulating of the data by event
+ $e = new Event(['types' => &$types]);
+ Grav::instance()->fireEvent('onAdminPageTypes', $e);
+
+ return $types;
+ }
+
+ /**
+ * Helper function to replace Pages::modularTypes()
+ * and to provide an event to manipulate the data
+ *
+ * Dispatches 'onAdminModularPageTypes' event
+ * with 'types' data member which is a
+ * reference to the data
+ */
+ public static function pagesModularTypes()
+ {
+ $types = Pages::modularTypes();
+
+ // First filter by configuration
+ $hideTypes = (array) Grav::instance()['config']->get('plugins.admin.hide_modular_page_types', []);
+ foreach ($hideTypes as $type) {
+ unset($types[$type]);
+ }
+
+ // Allow manipulating of the data by event
+ $e = new Event(['types' => &$types]);
+ Grav::instance()->fireEvent('onAdminModularPageTypes', $e);
+
+ return $types;
+ }
+
+ /**
+ * Validate a value. Currently validates
+ *
+ * - 'user' for username format and username availability.
+ * - 'password1' for password format
+ * - 'password2' for equality to password1
+ *
+ * @param string $type The field type
+ * @param string $value The field value
+ * @param string $extra Any extra value required
+ *
+ * @return bool
+ */
+ protected function validate($type, $value, $extra = '')
+ {
+ /** @var Login $login */
+ $login = $this->grav['login'];
+
+ return $login->validateField($type, $value, $extra);
+ }
+
+ protected function initializeController($task, $post)
+ {
+ $controller = new AdminController();
+ $controller->initialize($this->grav, $this->template, $task, $this->route, $post);
+ $controller->execute();
+ $controller->redirect();
+ }
+
+ /**
+ * Initialize the admin.
+ *
+ * @throws \RuntimeException
+ */
+ protected function initializeAdmin()
+ {
+ $this->enable([
+ 'onTwigExtensions' => ['onTwigExtensions', 1000],
+ 'onPagesInitialized' => ['onPagesInitialized', 1000],
+ 'onTwigLoader' => ['onTwigLoader', 1000],
+ 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 1000],
+ 'onTwigSiteVariables' => ['onTwigSiteVariables', 1000],
+ 'onAssetsInitialized' => ['onAssetsInitialized', 1000],
+ 'onAdminRegisterPermissions' => ['onAdminRegisterPermissions', 0],
+ 'onOutputGenerated' => ['onOutputGenerated', 0],
+ 'onAdminAfterSave' => ['onAdminAfterSave', 0],
+ 'onAdminData' => ['onAdminData', 0],
+ ]);
+
+ // Autoload classes
+ require_once __DIR__ . '/vendor/autoload.php';
+
+
+ // Check for required plugins
+ if (!$this->grav['config']->get('plugins.login.enabled') || !$this->grav['config']->get('plugins.form.enabled') || !$this->grav['config']->get('plugins.email.enabled')) {
+ throw new \RuntimeException('One of the required plugins is missing or not enabled');
+ }
+
+ // Initialize Admin Language if needed
+ /** @var Language $language */
+ $language = $this->grav['language'];
+ if ($language->enabled() && empty($this->grav['session']->admin_lang)) {
+ $this->grav['session']->admin_lang = $language->getLanguage();
+ }
+
+ // Decide admin template and route.
+ $path = trim(substr($this->uri->route(), strlen($this->base)), '/');
+
+ if (empty($this->template)) {
+ $this->template = 'dashboard';
+ }
+
+ // Can't access path directly...
+ if ($path && $path !== 'register') {
+ $array = explode('/', $path, 2);
+ $this->template = array_shift($array);
+ $this->route = array_shift($array);
+ }
+
+ // Initialize admin class (also registers it to Grav services).
+ $this->admin = new Admin($this->grav, $this->admin_route, $this->template, $this->route);
+
+ // Double check we have system.yaml, site.yaml etc
+ $config_path = $this->grav['locator']->findResource('user://config');
+ foreach ($this->admin::configurations() as $config_file) {
+ $config_file = "{$config_path}/{$config_file}.yaml";
+ if (!file_exists($config_file)) {
+ touch($config_file);
+ }
+ }
+
+ // Get theme for admin
+ $this->theme = $this->config->get('plugins.admin.theme', 'grav');
+
+ $assets = $this->grav['assets'];
+ $translations = 'this.GravAdmin = this.GravAdmin || {}; if (!this.GravAdmin.translations) this.GravAdmin.translations = {}; ' . PHP_EOL . 'this.GravAdmin.translations.PLUGIN_ADMIN = {';
+
+ // Enable language translations
+ $translations_actual_state = $this->config->get('system.languages.translations');
+ $this->config->set('system.languages.translations', true);
+
+ $strings = [
+ 'EVERYTHING_UP_TO_DATE',
+ 'UPDATES_ARE_AVAILABLE',
+ 'IS_AVAILABLE_FOR_UPDATE',
+ 'AND',
+ 'IS_NOW_AVAILABLE',
+ 'CURRENT',
+ 'UPDATE_GRAV_NOW',
+ 'TASK_COMPLETED',
+ 'UPDATE',
+ 'UPDATING_PLEASE_WAIT',
+ 'GRAV_SYMBOLICALLY_LINKED',
+ 'OF_YOUR',
+ 'OF_THIS',
+ 'HAVE_AN_UPDATE_AVAILABLE',
+ 'UPDATE_AVAILABLE',
+ 'UPDATES_AVAILABLE',
+ 'FULLY_UPDATED',
+ 'DAYS',
+ 'PAGE_MODES',
+ 'PAGE_TYPES',
+ 'ACCESS_LEVELS',
+ 'NOTHING_TO_SAVE',
+ 'FILE_UNSUPPORTED',
+ 'FILE_ERROR_ADD',
+ 'FILE_ERROR_UPLOAD',
+ 'DROP_FILES_HERE_TO_UPLOAD',
+ 'DELETE',
+ 'UNSET',
+ 'INSERT',
+ 'METADATA',
+ 'VIEW',
+ 'UNDO',
+ 'REDO',
+ 'HEADERS',
+ 'BOLD',
+ 'ITALIC',
+ 'STRIKETHROUGH',
+ 'SUMMARY_DELIMITER',
+ 'LINK',
+ 'IMAGE',
+ 'BLOCKQUOTE',
+ 'UNORDERED_LIST',
+ 'ORDERED_LIST',
+ 'EDITOR',
+ 'PREVIEW',
+ 'FULLSCREEN',
+ 'MODULAR',
+ 'NON_MODULAR',
+ 'VISIBLE',
+ 'NON_VISIBLE',
+ 'ROUTABLE',
+ 'NON_ROUTABLE',
+ 'PUBLISHED',
+ 'NON_PUBLISHED',
+ 'PLUGINS',
+ 'THEMES',
+ 'ALL',
+ 'FROM',
+ 'TO',
+ 'DROPZONE_CANCEL_UPLOAD',
+ 'DROPZONE_CANCEL_UPLOAD_CONFIRMATION',
+ 'DROPZONE_DEFAULT_MESSAGE',
+ 'DROPZONE_FALLBACK_MESSAGE',
+ 'DROPZONE_FALLBACK_TEXT',
+ 'DROPZONE_FILE_TOO_BIG',
+ 'DROPZONE_INVALID_FILE_TYPE',
+ 'DROPZONE_MAX_FILES_EXCEEDED',
+ 'DROPZONE_REMOVE_FILE',
+ 'DROPZONE_RESPONSE_ERROR'
+ ];
+
+ foreach ($strings as $string) {
+ $separator = (end($strings) === $string) ? '' : ',';
+ $translations .= '"' . $string . '": "' . htmlspecialchars($this->admin::translate('PLUGIN_ADMIN.' . $string)) . '"' . $separator;
+ }
+
+ $translations .= '};';
+
+ $translations .= 'this.GravAdmin.translations.PLUGIN_FORM = {';
+ $strings = ['RESOLUTION_MIN', 'RESOLUTION_MAX'];
+ foreach ($strings as $string) {
+ $separator = (end($strings) === $string) ? '' : ',';
+ $translations .= '"' . $string . '": "' . $this->admin::translate('PLUGIN_FORM.' . $string) . '"' . $separator;
+ }
+ $translations .= '};';
+
+ $translations .= 'this.GravAdmin.translations.GRAV_CORE = {';
+ $strings = [
+ 'NICETIME.SECOND',
+ 'NICETIME.MINUTE',
+ 'NICETIME.HOUR',
+ 'NICETIME.DAY',
+ 'NICETIME.WEEK',
+ 'NICETIME.MONTH',
+ 'NICETIME.YEAR',
+ 'CRON.EVERY',
+ 'CRON.EVERY_HOUR',
+ 'CRON.EVERY_MINUTE',
+ 'CRON.EVERY_DAY_OF_WEEK',
+ 'CRON.EVERY_DAY_OF_MONTH',
+ 'CRON.EVERY_MONTH',
+ 'CRON.TEXT_PERIOD',
+ 'CRON.TEXT_MINS',
+ 'CRON.TEXT_TIME',
+ 'CRON.TEXT_DOW',
+ 'CRON.TEXT_MONTH',
+ 'CRON.TEXT_DOM',
+ 'CRON.ERROR1',
+ 'CRON.ERROR2',
+ 'CRON.ERROR3',
+ 'CRON.ERROR4',
+ 'MONTHS_OF_THE_YEAR',
+ 'DAYS_OF_THE_WEEK'
+ ];
+ foreach ($strings as $string) {
+ $separator = (end($strings) === $string) ? '' : ',';
+ $translations .= '"' . $string . '": ' . json_encode($this->admin::translate('GRAV.'.$string)) . $separator;
+ }
+ $translations .= '};';
+
+ // set the actual translations state back
+ $this->config->set('system.languages.translations', $translations_actual_state);
+
+ $assets->addInlineJs($translations);
+ }
+}
diff --git a/user/plugins/admin/admin.yaml b/user/plugins/admin/admin.yaml
new file mode 100644
index 0000000..56b3977
--- /dev/null
+++ b/user/plugins/admin/admin.yaml
@@ -0,0 +1,48 @@
+enabled: true
+route: '/admin'
+cache_enabled: false
+theme: grav
+logo_text: ''
+body_classes: ''
+content_padding: true
+twofa_enabled: true
+log_viewer_files: ['grav', 'email']
+sidebar:
+ activate: tab
+ hover_delay: 100
+ size: auto
+dashboard:
+ days_of_stats: 7
+widgets:
+ dashboard-maintenance: true
+ dashboard-statistics: true
+ dashboard-notifications: true
+ dashboard-feed: true
+ dashboard-pages: true
+pages:
+ show_parents: both
+ show_modular: true
+session:
+ timeout: 1800
+warnings:
+ delete_page: true
+ secure_delete: false
+edit_mode: normal
+frontend_preview_target: inline
+show_github_msg: true
+pages_list_display_field: title
+google_fonts: false
+admin_icons: line-awesome
+enable_auto_updates_check: true
+notifications:
+ feed: true
+ dashboard: true
+ plugins: true
+ themes: true
+popularity:
+ enabled: true
+ ignore: ['/test*','/modular']
+ history:
+ daily: 30
+ monthly: 12
+ visitors: 20
diff --git a/user/plugins/admin/assets/admin-dashboard.png b/user/plugins/admin/assets/admin-dashboard.png
new file mode 100644
index 0000000..599743e
Binary files /dev/null and b/user/plugins/admin/assets/admin-dashboard.png differ
diff --git a/user/plugins/admin/blueprints.yaml b/user/plugins/admin/blueprints.yaml
new file mode 100644
index 0000000..d54d189
--- /dev/null
+++ b/user/plugins/admin/blueprints.yaml
@@ -0,0 +1,528 @@
+name: Admin Panel
+slug: admin
+type: plugin
+version: 1.9.17
+testing: false
+description: Adds an advanced administration panel to manage your site
+icon: empire
+author:
+ name: Team Grav
+ email: devs@getgrav.org
+ url: http://getgrav.org
+homepage: https://github.com/getgrav/grav-plugin-admin
+keywords: admin, plugin, manager, panel
+bugs: https://github.com/getgrav/grav-plugin-admin/issues
+docs: https://github.com/getgrav/grav-plugin-admin/blob/develop/README.md
+license: MIT
+
+dependencies:
+ - { name: grav, version: '>=1.6.28' }
+ - { name: form, version: '>=3.0.0' }
+ - { name: login, version: '>=3.0.0' }
+ - { name: email, version: '>=3.0.0' }
+
+form:
+ validation: loose
+ fields:
+ Basics:
+ type: section
+ title: Basics
+ underline: false
+
+ enabled:
+ type: hidden
+ label: PLUGIN_ADMIN.PLUGIN_STATUS
+ highlight: 1
+ default: 0
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+
+ cache_enabled:
+ type: toggle
+ label: PLUGIN_ADMIN.ADMIN_CACHING
+ help: PLUGIN_ADMIN.ADMIN_CACHING_HELP
+ highlight: 0
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ twofa_enabled:
+ type: toggle
+ label: PLUGIN_LOGIN.2FA_TITLE
+ help: PLUGIN_LOGIN.2FA_ENABLED_HELP
+ default: 1
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ route:
+ type: text
+ label: Administrator path
+ size: medium
+ placeholder: "Default route for administrator (relative to base)"
+ help: If you want to change the URL for the administrator, you can provide a path here
+
+ logo_text:
+ type: text
+ label: Logo text
+ size: medium
+ placeholder: "Grav"
+ help: Text to display in place of the default Grav logo
+
+ content_padding:
+ type: toggle
+ label: PLUGIN_ADMIN.CONTENT_PADDING
+ help: PLUGIN_ADMIN.CONTENT_PADDING_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+
+ body_classes:
+ type: text
+ label: Body classes
+ size: medium
+ help: Add a space separated name of custom body classes
+
+ sidebar.activate:
+ type: select
+ label: Sidebar Activation
+ help: Control how the sidebar is activated
+ size: small
+ default: tab
+ options:
+ tab: Tab
+ hover: Hover
+
+ sidebar.hover_delay:
+ type: text
+ size: x-small
+ append: millseconds
+ label: Hover delay
+ default: 500
+ validate:
+ type: number
+ min: 1
+
+
+ sidebar.size:
+ type: select
+ label: Sidebar Size
+ help: Control the width of the sidebar
+ size: medium
+ default: auto
+ options:
+ auto: Automatic width
+ small: Small width
+
+ theme:
+ type: hidden
+ label: Theme
+ default: grav
+
+ edit_mode:
+ type: select
+ label: Edit mode
+ size: small
+ default: normal
+ options:
+ normal: Normal
+ expert: Expert
+ help: Auto will use blueprint if available, if none found, it will use "Expert" mode.
+
+ frontend_preview_target:
+ type: select
+ label: Preview pages target
+ size: medium
+ default: inline
+ options:
+ inline: Inline in Admin
+ _blank: New tab
+ _self: Current tab
+
+ pages.show_parents:
+ type: select
+ size: medium
+ label: Parent dropdown
+ highlight: 1
+ options:
+ both: Show slug and folder
+ folder: Show folder
+ fullpath: Show fullpath
+
+ pages.parents_levels:
+ type: text
+ label: Parents Levels
+ size: small
+ help: The number of levels to show in parent select list
+
+ pages.show_modular:
+ type: toggle
+ label: Modular parents
+ highlight: 1
+ default: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ help: Show modular pages in the parent select list
+
+ google_fonts:
+ type: toggle
+ label: Use Google Fonts
+ highlight: 0
+ default: 0
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ help: Use Google custom fonts. Disable this to use Helvetica. Useful when using Cyrillic and other languages with unsupported characters.
+
+ show_beta_msg:
+ type: hidden
+
+ show_github_msg:
+ type: toggle
+ label: Show GitHub Link
+ highlight: 1
+ default: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ help: Show the "Found an issue? Please report it on GitHub." message.
+
+ pages_list_display_field:
+ type: text
+ size: small
+ label: Pages List Display Field
+ help: "Field of the page to use in the list of pages if present. Defaults/Fallback to title."
+
+ enable_auto_updates_check:
+ type: toggle
+ label: Automatically check for updates
+ highlight: 1
+ default: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ help: Shows an informative message, in the admin panel, when an update is available.
+
+ session.timeout:
+ type: text
+ size: small
+ label: Session Timeout
+ append: secs
+ help: "Sets the session timeout in seconds"
+ validate:
+ type: number
+ min: 1
+
+ warnings.delete_page:
+ type: toggle
+ label: Warn on page delete
+ highlight: 1
+ default: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ help: Ask the user confirmation when deleting a page
+
+ warnings.secure_delete:
+ type: toggle
+ label: Secure Delete
+ highlight: 1
+ default: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ help: Shows the user a field to enter the word DELETE and enable the confirm delete button.
+
+ hide_page_types:
+ type: array
+ label: Hide page types in Admin
+ value_only: true
+
+ hide_modular_page_types:
+ type: array
+ label: Hide modular page types in Admin
+ value_only: true
+
+ log_viewer_files:
+ type: selectize
+ size: medium
+ label: PLUGIN_ADMIN.LOG_VIEWER_FILES
+ help: PLUGIN_ADMIN.LOG_VIEWER_FILES_HELP
+ classes: fancy
+ validate:
+ type: commalist
+
+ MediaResize:
+ type: section
+ title: Page Media Image Resizer
+ underline: true
+
+ MediaResizeNote:
+ type: spacer
+ text: PLUGIN_ADMIN.PAGEMEDIA_RESIZER
+ markdown: true
+
+ pagemedia.resize_width:
+ type: number
+ size: x-small
+ append: pixels
+ label: Resize Width
+ default: 0
+ validate:
+ type: number
+ help: Resize wide images down to the set value
+
+ pagemedia.resize_height:
+ type: number
+ size: x-small
+ append: pixels
+ label: Resize Height
+ default: 0
+ validate:
+ type: number
+ help: Resize tall images down to the set value
+
+ pagemedia.res_min_width:
+ type: number
+ size: x-small
+ append: pixels
+ label: Resolution Min Width
+ default: 0
+ validate:
+ type: number
+ help: The minimum width allowed for an image to be added
+
+ pagemedia.res_min_height:
+ type: number
+ size: x-small
+ append: pixels
+ label: Resolution Min Height
+ default: 0
+ validate:
+ type: number
+ help: The minimum height allowed for an image to be added
+
+ pagemedia.res_max_width:
+ type: number
+ size: x-small
+ append: pixels
+ label: Resolution Max Width
+ default: 0
+ validate:
+ type: number
+ help: The maximum width allowed for an image to be added
+
+ pagemedia.res_max_height:
+ type: number
+ size: x-small
+ append: pixels
+ label: Resolution Max Height
+ default: 0
+ validate:
+ type: number
+ help: The maximum height allowed for an image to be added
+
+
+ pagemedia.resize_quality:
+ type: number
+ size: x-small
+ append: 0...1
+ label: Resize Quality
+ default: 0.8
+ validate:
+ type: number
+ step: 0.01
+ help: The quality to use when resizing an image. Between 0 and 1 value.
+
+ Dashboard:
+ type: section
+ title: Dashboard
+ underline: true
+
+ widgets.dashboard-maintenance:
+ type: toggle
+ label: Maintenance Widget
+ highlight: 1
+ default: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ help: Display dashboard maintenance widget
+
+ widgets.dashboard-statistics:
+ type: toggle
+ label: Statistics Widget
+ highlight: 1
+ default: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ help: Display dashboard statistics widget
+
+ widgets.dashboard-notifications:
+ type: toggle
+ label: Notifications Feed Widget
+ highlight: 1
+ default: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ help: Display dashboard notifications feed widget
+
+ widgets.dashboard-feed:
+ type: toggle
+ label: News Feed Widget
+ highlight: 1
+ default: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ help: Display dashboard news feed widget
+
+ widgets.dashboard-pages:
+ type: toggle
+ label: Latest Pages Widget
+ highlight: 1
+ default: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ help: Display dashboard latest pages widget
+
+ Notifications:
+ type: section
+ title: Notifications
+ underline: true
+
+ notifications.feed:
+ type: toggle
+ label: Feed Notifications
+ highlight: 1
+ default: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ help: Display feed-based notifications
+
+ notifications.dashboard:
+ type: toggle
+ label: Dashboard Notifications
+ highlight: 1
+ default: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ help: Display dashboard-based notifications
+
+ notifications.plugins:
+ type: toggle
+ label: Plugins Notifications
+ highlight: 1
+ default: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ help: Display plugins-targeted notifications
+
+ notifications.themes:
+ type: toggle
+ label: Themes Notifications
+ highlight: 1
+ default: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ help: Display themes-targeted notifications
+
+ Popularity:
+ type: section
+ title: Popularity
+ underline: true
+
+ popularity.enabled:
+ type: toggle
+ label: Visitor tracking
+ highlight: 1
+ default: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ help: Enable the visitors stats collecting feature
+
+ dashboard.days_of_stats:
+ type: text
+ label: Days of stats
+ append: days
+ size: x-small
+ default: 7
+ help: Keep stats for the specified number of days, then drop them
+ validate:
+ type: int
+
+ popularity.ignore:
+ type: array
+ label: Ignore
+ size: large
+ help: "URLs to ignore"
+ default: ['/test*','/modular']
+ value_only: true
+ placeholder_value: /ignore-this-route
+
+ popularity.history.daily:
+ type: hidden
+ label: Daily history
+ default: 30
+
+ popularity.history.monthly:
+ type: hidden
+ label: Monthly history
+ default: 12
+
+ popularity.history.visitors:
+ type: hidden
+ label: Visitors history
+ default: 20
diff --git a/user/plugins/admin/blueprints/admin/pages/modular_new.yaml b/user/plugins/admin/blueprints/admin/pages/modular_new.yaml
new file mode 100644
index 0000000..85aa7b0
--- /dev/null
+++ b/user/plugins/admin/blueprints/admin/pages/modular_new.yaml
@@ -0,0 +1,52 @@
+rules:
+ slug:
+ pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
+ min: 1
+ max: 200
+
+form:
+ validation: loose
+ fields:
+
+ section:
+ type: section
+ title: PLUGIN_ADMIN.ADD_MODULAR_CONTENT
+
+ title:
+ type: text
+ label: PLUGIN_ADMIN.PAGE_TITLE
+ validate:
+ required: true
+
+ folder:
+ type: text
+ label: PLUGIN_ADMIN.FOLDER_NAME
+ validate:
+ rule: slug
+ required: true
+
+ route:
+ type: parents
+ label: PLUGIN_ADMIN.PAGE
+ classes: fancy
+ validate:
+ required: true
+
+ name:
+ type: select
+ classes: fancy
+ label: PLUGIN_ADMIN.MODULAR_TEMPLATE
+ help: PLUGIN_ADMIN.PAGE_FILE_HELP
+ default: default
+ data-options@: '\Grav\Plugin\AdminPlugin::pagesModularTypes'
+ validate:
+ required: true
+
+ modular:
+ type: hidden
+ default: 1
+ validate:
+ type: bool
+
+ blueprint:
+ type: blueprint
diff --git a/user/plugins/admin/blueprints/admin/pages/modular_raw.yaml b/user/plugins/admin/blueprints/admin/pages/modular_raw.yaml
new file mode 100644
index 0000000..abd2193
--- /dev/null
+++ b/user/plugins/admin/blueprints/admin/pages/modular_raw.yaml
@@ -0,0 +1,101 @@
+rules:
+ slug:
+ pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
+ min: 1
+ max: 200
+
+form:
+ validation: loose
+ fields:
+
+ tabs:
+ type: tabs
+ active: 1
+
+ fields:
+ content:
+ type: tab
+ title: PLUGIN_ADMIN.CONTENT
+
+ fields:
+ frontmatter:
+ classes: frontmatter
+ type: editor
+ label: PLUGIN_ADMIN.FRONTMATTER
+ autofocus: true
+ codemirror:
+ mode: 'yaml'
+ indentUnit: 4
+ autofocus: true
+ indentWithTabs: false
+ lineNumbers: true
+ styleActiveLine: true
+ gutters: ['CodeMirror-lint-markers']
+ lint: true
+
+ content:
+ type: markdown
+
+ header.media_order:
+ type: pagemedia
+ label: PLUGIN_ADMIN.PAGE_MEDIA
+
+ options:
+ type: tab
+ title: PLUGIN_ADMIN.OPTIONS
+
+ fields:
+
+ columns:
+ type: columns
+
+ fields:
+ column1:
+ type: column
+
+ fields:
+
+ ordering:
+ type: toggle
+ label: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX
+ help: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+
+ folder:
+ type: text
+ label: PLUGIN_ADMIN.FILENAME
+ validate:
+ rule: slug
+ required: true
+
+ route:
+ type: parents
+ label: PLUGIN_ADMIN.PARENT
+ classes: fancy
+ validate:
+ required: true
+
+ name:
+ type: select
+ classes: fancy
+ label: PLUGIN_ADMIN.MODULAR_TEMPLATE
+ default: default
+ data-options@: '\Grav\Plugin\AdminPlugin::pagesModularTypes'
+ validate:
+ required: true
+
+ column2:
+ type: column
+
+ fields:
+ order:
+ type: order
+ label: PLUGIN_ADMIN.ORDERING
+
+ blueprint:
+ type: blueprint
diff --git a/user/plugins/admin/blueprints/admin/pages/move.yaml b/user/plugins/admin/blueprints/admin/pages/move.yaml
new file mode 100644
index 0000000..89fa414
--- /dev/null
+++ b/user/plugins/admin/blueprints/admin/pages/move.yaml
@@ -0,0 +1,7 @@
+form:
+ validation: loose
+ fields:
+ route:
+ type: parents
+ label: PLUGIN_ADMIN.PARENT
+ classes: fancy
diff --git a/user/plugins/admin/blueprints/admin/pages/new.yaml b/user/plugins/admin/blueprints/admin/pages/new.yaml
new file mode 100644
index 0000000..d62c6ae
--- /dev/null
+++ b/user/plugins/admin/blueprints/admin/pages/new.yaml
@@ -0,0 +1,62 @@
+rules:
+ slug:
+ pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
+ min: 1
+ max: 200
+
+form:
+ validation: loose
+ fields:
+
+ section:
+ type: section
+ title: PLUGIN_ADMIN.ADD_PAGE
+
+ title:
+ type: text
+ label: PLUGIN_ADMIN.PAGE_TITLE
+ help: PLUGIN_ADMIN.PAGE_TITLE_HELP
+ validate:
+ required: true
+
+ folder:
+ type: text
+ label: PLUGIN_ADMIN.FOLDER_NAME
+ help: PLUGIN_ADMIN.FOLDER_NAME_HELP
+ validate:
+ rule: slug
+ required: true
+
+ route:
+ type: parents
+ label: PLUGIN_ADMIN.PARENT_PAGE
+ classes: fancy
+ validate:
+ required: true
+
+ name:
+ type: select
+ classes: fancy
+ label: PLUGIN_ADMIN.PAGE_FILE
+ help: PLUGIN_ADMIN.PAGE_FILE_HELP
+ data-options@: '\Grav\Plugin\AdminPlugin::pagesTypes'
+ data-default@: '\Grav\Plugin\Admin\Admin::getLastPageName'
+ validate:
+ required: true
+
+ visible:
+ type: toggle
+ label: PLUGIN_ADMIN.VISIBLE
+ help: PLUGIN_ADMIN.VISIBLE_HELP
+ highlight: ''
+ default: ''
+ options:
+ '': Auto
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ validate:
+ type: bool
+ required: true
+
+ blueprint:
+ type: blueprint
diff --git a/user/plugins/admin/blueprints/admin/pages/new_folder.yaml b/user/plugins/admin/blueprints/admin/pages/new_folder.yaml
new file mode 100644
index 0000000..9db58f2
--- /dev/null
+++ b/user/plugins/admin/blueprints/admin/pages/new_folder.yaml
@@ -0,0 +1,31 @@
+rules:
+ slug:
+ pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
+ min: 1
+ max: 200
+
+form:
+ validation: loose
+ fields:
+
+ section:
+ type: section
+ title: PLUGIN_ADMIN.ADD_FOLDER
+
+ folder:
+ type: text
+ label: PLUGIN_ADMIN.FOLDER_NAME
+ help: PLUGIN_ADMIN.FOLDER_NAME_HELP
+ validate:
+ rule: slug
+ required: true
+
+ route:
+ type: parents
+ label: PLUGIN_ADMIN.PARENT_PAGE
+ classes: fancy
+ validate:
+ required: true
+
+ blueprint:
+ type: blueprint
diff --git a/user/plugins/admin/blueprints/admin/pages/raw.yaml b/user/plugins/admin/blueprints/admin/pages/raw.yaml
new file mode 100644
index 0000000..788e9e2
--- /dev/null
+++ b/user/plugins/admin/blueprints/admin/pages/raw.yaml
@@ -0,0 +1,106 @@
+rules:
+ slug:
+ pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
+ min: 1
+ max: 200
+
+form:
+ validation: loose
+ fields:
+
+ tabs:
+ type: tabs
+ active: 1
+
+ fields:
+ content:
+ type: tab
+ title: PLUGIN_ADMIN.CONTENT
+
+ fields:
+ xss_check:
+ type: xss
+
+ frontmatter:
+ classes: frontmatter
+ type: editor
+ label: PLUGIN_ADMIN.FRONTMATTER
+ autofocus: true
+ codemirror:
+ mode: 'yaml'
+ indentUnit: 4
+ autofocus: true
+ indentWithTabs: false
+ lineNumbers: true
+ styleActiveLine: true
+ gutters: ['CodeMirror-lint-markers']
+ lint: true
+
+ content:
+ type: markdown
+
+ header.media_order:
+ type: pagemedia
+ label: PLUGIN_ADMIN.PAGE_MEDIA
+
+ options:
+ type: tab
+ title: PLUGIN_ADMIN.OPTIONS
+
+ fields:
+
+ columns:
+ type: columns
+
+ fields:
+ column1:
+ type: column
+
+ fields:
+
+ ordering:
+ type: toggle
+ label: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX
+ help: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+
+ folder:
+ type: text
+ label: PLUGIN_ADMIN.FOLDER_NAME
+ help: PLUGIN_ADMIN.FOLDER_NAME_HELP
+ validate:
+ rule: slug
+ required: true
+
+ route:
+ type: parents
+ label: PLUGIN_ADMIN.PARENT
+ classes: fancy
+ validate:
+ required: true
+
+ name:
+ type: select
+ classes: fancy
+ label: PLUGIN_ADMIN.DISPLAY_TEMPLATE
+ help: PLUGIN_ADMIN.DISPLAY_TEMPLATE_HELP
+ default: default
+ data-options@: '\Grav\Plugin\AdminPlugin::pagesTypes'
+ validate:
+ required: true
+
+ column2:
+ type: column
+
+ fields:
+ order:
+ type: order
+ label: PLUGIN_ADMIN.ORDERING
+
+ blueprint:
+ type: blueprint
diff --git a/user/plugins/admin/blueprints/config/media.yaml b/user/plugins/admin/blueprints/config/media.yaml
new file mode 100644
index 0000000..ba55868
--- /dev/null
+++ b/user/plugins/admin/blueprints/config/media.yaml
@@ -0,0 +1,36 @@
+title: PLUGIN_ADMIN.MEDIA
+form:
+ validation: loose
+ fields:
+ 'types':
+ name: medias
+ type: list
+ label: PLUGIN_ADMIN.MEDIA_TYPES
+ style: vertical
+ key: extension
+ controls: both
+ collapsed: true
+
+ fields:
+ .extension:
+ type: key
+ label: PLUGIN_ADMIN.FILE_EXTENSION
+ .type:
+ type: text
+ label: PLUGIN_ADMIN.TYPE
+ .thumb:
+ type: text
+ label: PLUGIN_ADMIN.THUMB
+ .mime:
+ type: text
+ label: PLUGIN_ADMIN.MIME_TYPE
+ validate:
+ type: lower
+ .image:
+ type: textarea
+ yaml: true
+ label: PLUGIN_ADMIN.IMAGE_OPTIONS
+ validate:
+ type: yaml
+
+
diff --git a/user/plugins/admin/classes/Twig/AdminTwigExtension.php b/user/plugins/admin/classes/Twig/AdminTwigExtension.php
new file mode 100644
index 0000000..073aef2
--- /dev/null
+++ b/user/plugins/admin/classes/Twig/AdminTwigExtension.php
@@ -0,0 +1,118 @@
+grav = Grav::instance();
+ $this->lang = $this->grav['user']->language;
+ }
+
+ public function getFilters()
+ {
+ return [
+ new \Twig_SimpleFilter('tu', [$this, 'tuFilter']),
+ new \Twig_SimpleFilter('toYaml', [$this, 'toYamlFilter']),
+ new \Twig_SimpleFilter('fromYaml', [$this, 'fromYamlFilter']),
+ new \Twig_SimpleFilter('adminNicetime', [$this, 'adminNicetimeFilter']),
+ new \Twig_SimpleFilter('nested', [$this, 'nestedFilter']),
+ ];
+ }
+
+ public function getFunctions()
+ {
+ return [
+ new \Twig_SimpleFunction('getPageUrl', [$this, 'getPageUrl'], ['needs_context' => true]),
+ new \Twig_SimpleFunction('clone', [$this, 'cloneFunc']),
+ ];
+ }
+
+ public function nestedFilter($current, $name)
+ {
+ $path = explode('.', trim($name, '.'));
+
+ foreach ($path as $field) {
+ if (is_object($current) && isset($current->{$field})) {
+ $current = $current->{$field};
+ } elseif (is_array($current) && isset($current[$field])) {
+ $current = $current[$field];
+ } else {
+ return null;
+ }
+ }
+
+ return $current;
+ }
+
+ public function cloneFunc($obj)
+ {
+ return clone $obj;
+ }
+
+ public function getPageUrl($context, PageInterface $page)
+ {
+ $page_route = trim($page->rawRoute(), '/');
+ $page_lang = $page->language();
+ $base_url = $context['base_url'];
+ $base_url_simple = $context['base_url_simple'];
+ $admin_lang = Grav::instance()['session']->admin_lang ?: 'en';
+
+ if ($page_lang && $page_lang !== $admin_lang) {
+ $page_url = $base_url_simple . '/' . $page_lang . '/' . $context['admin_route'] . '/pages/' . $page_route;
+ } else {
+ $page_url = $base_url . '/pages/' . $page_route;
+ }
+
+ return $page_url;
+ }
+
+ public static function tuFilter()
+ {
+ $args = func_get_args();
+ $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 Grav::instance()['admin']->translate($args, $lang);
+ }
+
+ public function toYamlFilter($value, $inline = null)
+ {
+ return Yaml::dump($value, $inline);
+
+ }
+
+ public function fromYamlFilter($value)
+ {
+ return Yaml::parse($value);
+ }
+
+ public function adminNicetimeFilter($date, $long_strings = true)
+ {
+ return Grav::instance()['admin']->adminNiceTime($date, $long_strings);
+ }
+
+}
diff --git a/user/plugins/admin/classes/admin.php b/user/plugins/admin/classes/admin.php
new file mode 100644
index 0000000..be4f679
--- /dev/null
+++ b/user/plugins/admin/classes/admin.php
@@ -0,0 +1,1976 @@
+grav = $grav;
+ $this->base = $base;
+ $this->location = $location;
+ $this->route = $route;
+ $this->uri = $grav['uri'];
+ $this->session = $grav['session'];
+ $this->user = $grav['user'];
+ $this->permissions = [];
+ $language = $grav['language'];
+
+ // Load utility class
+ if ($language->enabled()) {
+ $this->multilang = true;
+ $this->languages_enabled = (array)$this->grav['config']->get('system.languages.supported', []);
+
+ //Set the currently active language for the admin
+ $language = $this->grav['uri']->param('lang');
+ if (!$language) {
+ if (!$this->session->admin_lang) {
+ $this->session->admin_lang = $this->grav['language']->getLanguage();
+ }
+ $language = $this->session->admin_lang;
+ }
+ $this->grav['language']->setActive($language ?: 'en');
+ } else {
+ $this->grav['language']->setActive('en');
+ $this->multilang = false;
+ }
+
+ }
+
+ /**
+ * Return the languages available in the admin
+ *
+ * @return array
+ */
+ public static function adminLanguages()
+ {
+ $languages = [];
+
+ $path = Grav::instance()['locator']->findResource('plugins://admin/languages');
+
+ /** @var \DirectoryIterator $directory */
+ foreach (new \DirectoryIterator($path) as $file) {
+ if ($file->isDir() || $file->isDot() || Utils::startsWith($file->getFilename(), '.')) {
+ continue;
+ }
+
+ $lang = $file->getBasename('.yaml');
+
+ $languages[$lang] = LanguageCodes::getNativeName($lang);
+
+ }
+
+ // sort languages
+ asort($languages);
+
+ return $languages;
+ }
+
+ /**
+ * Return the found configuration blueprints
+ *
+ * @return array
+ */
+ public static function configurations()
+ {
+ $configurations = [];
+
+ /** @var UniformResourceIterator $iterator */
+ $iterator = Grav::instance()['locator']->getIterator('blueprints://config');
+
+ foreach ($iterator as $file) {
+ if ($file->isDir() || !preg_match('/^[^.].*.yaml$/', $file->getFilename())) {
+ continue;
+ }
+ $configurations[] = $file->getBasename('.yaml');
+ }
+
+ return $configurations;
+ }
+
+ /**
+ * Return the tools found
+ *
+ * @return array
+ */
+ public static function tools()
+ {
+ $tools = [];
+ Grav::instance()->fireEvent('onAdminTools', new Event(['tools' => &$tools]));
+
+ return $tools;
+ }
+
+ public static function toolsPermissions()
+ {
+ $tools = static::tools();
+ $perms = [];
+
+ foreach ($tools as $tool) {
+ $perms = array_merge($perms, $tool[0]);
+ }
+
+ return array_unique($perms);
+ }
+
+ /**
+ * Return the languages available in the site
+ *
+ * @return array
+ */
+ public static function siteLanguages()
+ {
+ $languages = [];
+ $lang_data = (array) Grav::instance()['config']->get('system.languages.supported', []);
+
+ foreach ($lang_data as $index => $lang) {
+ $languages[$lang] = LanguageCodes::getNativeName($lang);
+ }
+
+ return $languages;
+ }
+
+ /**
+ * Static helper method to return the admin form nonce
+ *
+ * @return string
+ */
+ public static function getNonce()
+ {
+ $action = 'admin-form';
+
+ return Utils::getNonce($action);
+ }
+
+ /**
+ * Static helper method to return the last used page name
+ *
+ * @return string
+ */
+ public static function getLastPageName()
+ {
+ return Grav::instance()['session']->lastPageName ?: 'default';
+ }
+
+ /**
+ * Static helper method to return the last used page route
+ *
+ * @return string
+ */
+ public static function getLastPageRoute()
+ {
+ return Grav::instance()['session']->lastPageRoute ?: self::route();
+ }
+
+ /**
+ * Static helper method to return current route.
+ *
+ * @return string
+ */
+ public static function route()
+ {
+ $pages = Grav::instance()['pages'];
+ $route = '/' . ltrim(Grav::instance()['admin']->route, '/');
+
+ /** @var PageInterface $page */
+ $page = $pages->dispatch($route);
+ $parent_route = null;
+ if ($page) {
+ /** @var PageInterface $parent */
+ $parent = $page->parent();
+ $parent_route = $parent->rawRoute();
+ }
+
+ return $parent_route;
+ }
+
+ public static function getTempDir()
+ {
+ try {
+ $tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
+ } catch (\Exception $e) {
+ $tmp_dir = Grav::instance()['locator']->findResource('cache://', true, true) . '/tmp';
+ }
+
+ return $tmp_dir;
+ }
+
+ public static function getPageMedia()
+ {
+ $files = [];
+ $grav = Grav::instance();
+
+ $pages = $grav['pages'];
+ $route = '/' . ltrim($grav['admin']->route, '/');
+
+ /** @var PageInterface $page */
+ $page = $pages->dispatch($route);
+ $parent_route = null;
+ if ($page) {
+ $media = $page->media()->all();
+ $files = array_keys($media);
+ }
+ return $files;
+
+ }
+
+ /**
+ * Get current session.
+ *
+ * @return Session
+ */
+ public function session()
+ {
+ return $this->session;
+ }
+
+ /**
+ * Fetch and delete messages from the session queue.
+ *
+ * @param string $type
+ *
+ * @return array
+ */
+ public function messages($type = null)
+ {
+ /** @var Message $messages */
+ $messages = $this->grav['messages'];
+
+ return $messages->fetch($type);
+ }
+
+ /**
+ * Authenticate user.
+ *
+ * @param array $credentials User credentials.
+ */
+ public function authenticate($credentials, $post)
+ {
+ /** @var Login $login */
+ $login = $this->grav['login'];
+
+ // Remove login nonce from the form.
+ $credentials = array_diff_key($credentials, ['admin-nonce' => true]);
+ $twofa = $this->grav['config']->get('plugins.admin.twofa_enabled', false);
+
+ $rateLimiter = $login->getRateLimiter('login_attempts');
+
+ $userKey = (string)($credentials['username'] ?? '');
+ $ipKey = Uri::ip();
+ $redirect = $post['redirect'] ?? $this->base . $this->route;
+
+ // Pseudonymization of the IP
+ $ipKey = sha1($ipKey . $this->grav['config']->get('security.salt'));
+
+ // Check if the current IP has been used in failed login attempts.
+ $attempts = count($rateLimiter->getAttempts($ipKey, 'ip'));
+
+ $rateLimiter->registerRateLimitedAction($ipKey, 'ip')->registerRateLimitedAction($userKey);
+
+ // Check rate limit for both IP and user, but allow each IP a single try even if user is already rate limited.
+ if ($rateLimiter->isRateLimited($ipKey, 'ip') || ($attempts && $rateLimiter->isRateLimited($userKey))) {
+ $this->setMessage(static::translate(['PLUGIN_LOGIN.TOO_MANY_LOGIN_ATTEMPTS', $rateLimiter->getInterval()]), 'error');
+
+ $this->grav->redirect('/');
+ }
+
+ // Fire Login process.
+ $event = $login->login(
+ $credentials,
+ ['admin' => true, 'twofa' => $twofa],
+ ['authorize' => 'admin.login', 'return_event' => true]
+ );
+ $user = $event->getUser();
+
+ if ($user->authenticated) {
+ $rateLimiter->resetRateLimit($ipKey, 'ip')->resetRateLimit($userKey);
+ if ($user->authorized) {
+ $event->defMessage('PLUGIN_ADMIN.LOGIN_LOGGED_IN', 'info');
+
+ $event->defRedirect($post['redirect'] ?? $redirect);
+ } else {
+ $this->session->redirect = $redirect;
+ }
+ } else {
+ if ($user->authorized) {
+ $event->defMessage('PLUGIN_LOGIN.ACCESS_DENIED', 'error');
+ } else {
+ $event->defMessage('PLUGIN_LOGIN.LOGIN_FAILED', 'error');
+ }
+ }
+
+ $event->defRedirect($redirect);
+
+ $message = $event->getMessage();
+ if ($message) {
+ $this->setMessage(static::translate($message), $event->getMessageType());
+ }
+
+ $redirect = $event->getRedirect();
+
+ $this->grav->redirect($redirect, $event->getRedirectCode());
+ }
+
+ /**
+ * Check Two-Factor Authentication.
+ */
+ public function twoFa($data, $post)
+ {
+ /** @var Login $login */
+ $login = $this->grav['login'];
+
+ /** @var TwoFactorAuth $twoFa */
+ $twoFa = $login->twoFactorAuth();
+ $user = $this->grav['user'];
+
+ $code = $data['2fa_code'] ?? null;
+
+ $secret = $user->twofa_secret ?? null;
+
+ if (!$code || !$secret || !$twoFa->verifyCode($secret, $code)) {
+ $login->logout(['admin' => true]);
+
+ $this->grav['session']->setFlashCookieObject(Admin::TMP_COOKIE_NAME, ['message' => $this->translate('PLUGIN_ADMIN.2FA_FAILED'), 'status' => 'error']);
+
+ $this->grav->redirect($this->uri->route(), 303);
+ }
+
+ $this->setMessage($this->translate('PLUGIN_ADMIN.LOGIN_LOGGED_IN'), 'info');
+
+ $user->authorized = true;
+
+ $this->grav->redirect($post['redirect']);
+ }
+
+ /**
+ * Logout from admin.
+ */
+ public function logout($data, $post)
+ {
+ /** @var Login $login */
+ $login = $this->grav['login'];
+
+ $event = $login->logout(['admin' => true], ['return_event' => true]);
+
+ $event->defMessage('PLUGIN_ADMIN.LOGGED_OUT', 'info');
+ $message = $event->getMessage();
+ if ($message) {
+ $this->grav['session']->setFlashCookieObject(Admin::TMP_COOKIE_NAME, ['message' => $this->translate($message), 'status' => $event->getMessageType()]);
+ }
+
+ $this->grav->redirect($this->base);
+ }
+
+ /**
+ * @return bool
+ */
+ public static function doAnyUsersExist()
+ {
+ $accounts = Grav::instance()['accounts'] ?? null;
+ if ($accounts instanceof \Countable) {
+ return $accounts->count() > 0;
+ }
+
+ // TODO: remove old way to check for existence of a user account (Grav < v1.6.9)
+ $account_dir = $file_path = Grav::instance()['locator']->findResource('account://');
+ $user_check = glob($account_dir . '/*.yaml');
+
+ return $user_check;
+ }
+
+ /**
+ * Add message into the session queue.
+ *
+ * @param string $msg
+ * @param string $type
+ */
+ public function setMessage($msg, $type = 'info')
+ {
+ /** @var Message $messages */
+ $messages = $this->grav['messages'];
+ $messages->add($msg, $type);
+ }
+
+ public function addTempMessage($msg, $type)
+ {
+ $this->temp_messages[] = ['message' => $msg, 'scope' => $type];
+ }
+
+ public function getTempMessages()
+ {
+ return $this->temp_messages;
+ }
+
+ /**
+ * Translate a string to the user-defined language
+ *
+ * @param array|mixed $args
+ *
+ * @param mixed $languages
+ *
+ * @return string
+ */
+ public static function translate($args, $languages = null)
+ {
+ $grav = Grav::instance();
+
+ if (is_array($args)) {
+ $lookup = array_shift($args);
+ } else {
+ $lookup = $args;
+ $args = [];
+ }
+
+ if (!$languages) {
+ if ($grav['config']->get('system.languages.translations_fallback', true)) {
+ $languages = $grav['language']->getFallbackLanguages();
+ } else {
+ $languages = (array)$grav['language']->getDefault();
+ }
+ $languages = $grav['user']->authenticated ? [ $grav['user']->language ] : $languages;
+ } else {
+ $languages = (array)$languages;
+ }
+
+ foreach ((array)$languages as $lang) {
+ $translation = $grav['language']->getTranslation($lang, $lookup, true);
+
+ if (!$translation) {
+ $language = $grav['language']->getDefault() ?: 'en';
+ $translation = $grav['language']->getTranslation($language, $lookup, true);
+ }
+
+ if (!$translation) {
+ $language = 'en';
+ $translation = $grav['language']->getTranslation($language, $lookup, true);
+ }
+
+ if ($translation) {
+ if (count($args) >= 1) {
+ return vsprintf($translation, $args);
+ }
+
+ return $translation;
+ }
+ }
+
+ return $lookup;
+ }
+
+ /**
+ * Checks user authorisation to the action.
+ *
+ * @param string|string[] $action
+ *
+ * @return bool
+ */
+ public function authorize($action = 'admin.login')
+ {
+ $action = (array)$action;
+
+ foreach ($action as $a) {
+ if ($this->user->authorize($a)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets configuration data.
+ *
+ * @param string $type
+ * @param array $post
+ *
+ * @return mixed
+ * @throws \RuntimeException
+ */
+ public function data($type, array $post = [])
+ {
+ static $data = [];
+
+ if (isset($data[$type])) {
+ return $data[$type];
+ }
+
+ if (!$post) {
+ $post = $this->grav['uri']->post();
+ $post = $post['data'] ?? [];
+ }
+
+ // Check to see if a data type is plugin-provided, before looking into core ones
+ $event = $this->grav->fireEvent('onAdminData', new Event(['type' => &$type]));
+ if ($event) {
+ if (isset($event['data_type'])) {
+ return $event['data_type'];
+ }
+
+ if (is_string($event['type'])) {
+ $type = $event['type'];
+ }
+ }
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $this->grav['locator'];
+ $filename = $locator->findResource("config://{$type}.yaml", true, true);
+ $file = CompiledYamlFile::instance($filename);
+
+ if (preg_match('|plugins/|', $type)) {
+ /** @var Plugins $plugins */
+ $plugins = $this->grav['plugins'];
+ $obj = $plugins->get(preg_replace('|plugins/|', '', $type));
+
+ if (!$obj) {
+ return [];
+ }
+
+ $obj->merge($post);
+ $obj->file($file);
+
+ $data[$type] = $obj;
+ } elseif (preg_match('|themes/|', $type)) {
+ /** @var Themes $themes */
+ $themes = $this->grav['themes'];
+ $obj = $themes->get(preg_replace('|themes/|', '', $type));
+
+ if (!$obj) {
+ return [];
+ }
+
+ $obj->merge($post);
+ $obj->file($file);
+
+ $data[$type] = $obj;
+ } elseif (preg_match('|users?/|', $type)) {
+ /** @var UserCollectionInterface $users */
+ $users = $this->grav['accounts'];
+
+ $obj = $users->load(preg_replace('|users?/|', '', $type));
+ $obj->update($this->cleanUserPost($post));
+
+ $data[$type] = $obj;
+ } elseif (preg_match('|config/|', $type)) {
+ $type = preg_replace('|config/|', '', $type);
+ $blueprints = $this->blueprints("config/{$type}");
+ $config = $this->grav['config'];
+ $obj = new Data\Data($config->get($type, []), $blueprints);
+ $obj->merge($post);
+
+ // FIXME: We shouldn't allow user to change configuration files in system folder!
+ $filename = $this->grav['locator']->findResource("config://{$type}.yaml")
+ ?: $this->grav['locator']->findResource("config://{$type}.yaml", true, true);
+ $file = CompiledYamlFile::instance($filename);
+ $obj->file($file);
+ $data[$type] = $obj;
+ } elseif (preg_match('|media-manager/|', $type)) {
+ $filename = base64_decode(preg_replace('|media-manager/|', '', $type));
+
+ $file = File::instance($filename);
+
+ $obj = new \stdClass();
+ $obj->title = $file->basename();
+ $obj->path = $file->filename();
+ $obj->file = $file;
+ $obj->page = $this->grav['pages']->get(dirname($obj->path));
+
+ $fileInfo = pathinfo($obj->title);
+ $filename = str_replace(['@3x', '@2x'], '', $fileInfo['filename']);
+ if (isset($fileInfo['extension'])) {
+ $filename .= '.' . $fileInfo['extension'];
+ }
+
+ if ($obj->page && isset($obj->page->media()[$filename])) {
+ $obj->metadata = new Data\Data($obj->page->media()[$filename]->metadata());
+ }
+
+ $data[$type] = $obj;
+ } else {
+ throw new \RuntimeException("Data type '{$type}' doesn't exist!");
+ }
+
+ return $data[$type];
+ }
+
+ /**
+ * Clean user form post and remove extra stuff that may be passed along
+ *
+ * @param array $post
+ * @return array
+ */
+ public function cleanUserPost($post)
+ {
+ // Clean fields for all users
+ unset($post['hashed_password']);
+
+ // Clean field for users who shouldn't be able to modify these fields
+ if (!$this->authorize(['admin.user', 'admin.super'])) {
+ unset($post['access'], $post['state']);
+ }
+
+ return $post;
+ }
+
+ protected function hasErrorMessage()
+ {
+ $msgs = $this->grav['messages']->all();
+ foreach ($msgs as $msg) {
+ if (isset($msg['scope']) && $msg['scope'] === 'error') {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns blueprints for the given type.
+ *
+ * @param string $type
+ *
+ * @return Data\Blueprint
+ */
+ public function blueprints($type)
+ {
+ if ($this->blueprints === null) {
+ $this->blueprints = new Data\Blueprints('blueprints://');
+ }
+
+ return $this->blueprints->get($type);
+ }
+
+ /**
+ * Converts dot notation to array notation.
+ *
+ * @param string $name
+ *
+ * @return string
+ */
+ public function field($name)
+ {
+ $path = explode('.', $name);
+
+ return array_shift($path) . ($path ? '[' . implode('][', $path) . ']' : '');
+ }
+
+ /**
+ * Get all routes.
+ *
+ * @param bool $unique
+ *
+ * @return array
+ */
+ public function routes($unique = false)
+ {
+ /** @var Pages $pages */
+ $pages = $this->grav['pages'];
+
+ if ($unique) {
+ $routes = array_unique($pages->routes());
+ } else {
+ $routes = $pages->routes();
+ }
+
+ return $routes;
+ }
+
+ /**
+ * Count the pages
+ *
+ * @return int
+ */
+ public function pagesCount()
+ {
+ if (!$this->pages_count) {
+ $this->pages_count = count($this->grav['pages']->all());
+ }
+
+ return $this->pages_count;
+ }
+
+ /**
+ * Get all template types
+ *
+ * @return array
+ */
+ public function types()
+ {
+ return Pages::types();
+ }
+
+ /**
+ * Get all modular template types
+ *
+ * @return array
+ */
+ public function modularTypes()
+ {
+ return Pages::modularTypes();
+ }
+
+ /**
+ * Get all access levels
+ *
+ * @return array
+ */
+ public function accessLevels()
+ {
+ if (method_exists($this->grav['pages'], 'accessLevels')) {
+ return $this->grav['pages']->accessLevels();
+ }
+
+ return [];
+ }
+
+ public function license($package_slug)
+ {
+ return Licenses::get($package_slug);
+ }
+
+ /**
+ * Generate an array of dependencies for a package, used to generate a list of
+ * packages that can be removed when removing a package.
+ *
+ * @param string $slug The package slug
+ *
+ * @return array|bool
+ */
+ public function dependenciesThatCanBeRemovedWhenRemoving($slug)
+ {
+ $gpm = $this->gpm();
+ if (!$gpm) {
+ return false;
+ }
+
+ $dependencies = [];
+
+ $package = $this->getPackageFromGPM($slug);
+
+ if ($package) {
+ if ($package->dependencies) {
+ foreach ($package->dependencies as $dependency) {
+// if (count($gpm->getPackagesThatDependOnPackage($dependency)) > 1) {
+// continue;
+// }
+ if (isset($dependency['name'])) {
+ $dependency = $dependency['name'];
+ }
+
+ if (!in_array($dependency, $dependencies, true)) {
+ if (!in_array($dependency, ['admin', 'form', 'login', 'email', 'php'])) {
+ $dependencies[] = $dependency;
+ }
+ }
+ }
+ }
+ }
+
+ return $dependencies;
+ }
+
+ /**
+ * Get the GPM instance
+ *
+ * @return GPM The GPM instance
+ */
+ public function gpm()
+ {
+ if (!$this->gpm) {
+ try {
+ $this->gpm = new GPM();
+ } catch (\Exception $e) {
+ $this->setMessage($e->getMessage(), 'error');
+ }
+ }
+
+ return $this->gpm;
+ }
+
+ public function getPackageFromGPM($package_slug)
+ {
+ $package = $this->plugins(true)[$package_slug];
+ if (!$package) {
+ $package = $this->themes(true)[$package_slug];
+ }
+
+ return $package;
+ }
+
+ /**
+ * Get all plugins.
+ *
+ * @param bool $local
+ *
+ * @return mixed
+ */
+ public function plugins($local = true)
+ {
+ $gpm = $this->gpm();
+
+ if (!$gpm) {
+ return false;
+ }
+
+ if ($local) {
+ return $gpm->getInstalledPlugins();
+ }
+
+ $plugins = $gpm->getRepositoryPlugins();
+ if ($plugins) {
+ return $plugins->filter(function ($package, $slug) use ($gpm) {
+ return !$gpm->isPluginInstalled($slug);
+ });
+ }
+
+ return [];
+ }
+
+ /**
+ * Get all themes.
+ *
+ * @param bool $local
+ *
+ * @return mixed
+ */
+ public function themes($local = true)
+ {
+ $gpm = $this->gpm();
+
+ if (!$gpm) {
+ return false;
+ }
+
+ if ($local) {
+ return $gpm->getInstalledThemes();
+ }
+
+ $themes = $gpm->getRepositoryThemes();
+ if ($themes) {
+ return $themes->filter(function ($package, $slug) use ($gpm) {
+ return !$gpm->isThemeInstalled($slug);
+ });
+ }
+
+ return [];
+ }
+
+ /**
+ * Get list of packages that depend on the passed package slug
+ *
+ * @param string $slug The package slug
+ *
+ * @return array|bool
+ */
+ public function getPackagesThatDependOnPackage($slug)
+ {
+ $gpm = $this->gpm();
+ if (!$gpm) {
+ return false;
+ }
+
+ return $gpm->getPackagesThatDependOnPackage($slug);
+ }
+
+ /**
+ * Check the passed packages list can be updated
+ *
+ * @param array $packages
+ *
+ * @throws \Exception
+ * @return bool
+ */
+ public function checkPackagesCanBeInstalled($packages)
+ {
+ $gpm = $this->gpm();
+ if (!$gpm) {
+ return false;
+ }
+
+ $this->gpm->checkPackagesCanBeInstalled($packages);
+
+ return true;
+ }
+
+ /**
+ * Get an array of dependencies needed to be installed or updated for a list of packages
+ * to be installed.
+ *
+ * @param array $packages The packages slugs
+ *
+ * @return array|bool
+ */
+ public function getDependenciesNeededToInstall($packages)
+ {
+ $gpm = $this->gpm();
+ if (!$gpm) {
+ return false;
+ }
+
+ return $this->gpm->getDependencies($packages);
+ }
+
+ /**
+ * Used by the Dashboard in the admin to display the X latest pages
+ * that have been modified
+ *
+ * @param integer $count number of pages to pull back
+ *
+ * @return array|null
+ */
+ public function latestPages($count = 10)
+ {
+ /** @var Pages $pages */
+ $pages = $this->grav['pages'];
+
+ $latest = [];
+
+ if (null === $pages->routes()) {
+ return null;
+ }
+
+ foreach ($pages->routes() as $url => $path) {
+ $page = $pages->dispatch($url, true);
+ if ($page && $page->routable()) {
+ $latest[$page->route()] = ['modified' => $page->modified(), 'page' => $page];
+ }
+ }
+
+ // sort based on modified
+ uasort($latest, function ($a, $b) {
+ if ($a['modified'] == $b['modified']) {
+ return 0;
+ }
+
+ return ($a['modified'] > $b['modified']) ? -1 : 1;
+ });
+
+ // build new array with just pages in it
+ $list = [];
+ foreach ($latest as $item) {
+ $list[] = $item['page'];
+ }
+
+ return array_slice($list, 0, $count);
+ }
+
+ /**
+ * Get log file for fatal errors.
+ *
+ * @return string
+ */
+ public function logEntry()
+ {
+ $file = File::instance($this->grav['locator']->findResource("log://{$this->route}.html"));
+ $content = $file->content();
+ $file->free();
+
+ return $content;
+ }
+
+ /**
+ * Search in the logs when was the latest backup made
+ *
+ * @return array Array containing the latest backup information
+ */
+ public function lastBackup()
+ {
+ $file = JsonFile::instance($this->grav['locator']->findResource('log://backup.log'));
+ $content = $file->content();
+ if (empty($content)) {
+ return [
+ 'days' => '∞',
+ 'chart_fill' => 100,
+ 'chart_empty' => 0
+ ];
+ }
+
+ $backup = new \DateTime();
+ $backup->setTimestamp($content['time']);
+ $diff = $backup->diff(new \DateTime());
+
+ $days = $diff->days;
+ $chart_fill = $days > 30 ? 100 : round($days / 30 * 100);
+
+ return [
+ 'days' => $days,
+ 'chart_fill' => $chart_fill,
+ 'chart_empty' => 100 - $chart_fill
+ ];
+ }
+
+ /**
+ * Determine if the plugin or theme info passed is from Team Grav
+ *
+ * @param object $info Plugin or Theme info object
+ *
+ * @return bool
+ */
+ public function isTeamGrav($info)
+ {
+ return isset($info['author']['name']) && ($info['author']['name'] === 'Team Grav' || Utils::contains($info['author']['name'], 'Trilby Media'));
+ }
+
+ /**
+ * Determine if the plugin or theme info passed is premium
+ *
+ * @param object $info Plugin or Theme info object
+ *
+ * @return bool
+ */
+ public function isPremiumProduct($info)
+ {
+ return isset($info['premium']);
+ }
+
+ /**
+ * Renders phpinfo
+ *
+ * @return string The phpinfo() output
+ */
+ function phpinfo()
+ {
+ if (function_exists('phpinfo')) {
+ ob_start();
+ phpinfo();
+ $pinfo = ob_get_contents();
+ ob_end_clean();
+
+ $pinfo = preg_replace('%^.*(.*).*$%ms', '$1', $pinfo);
+
+ return $pinfo;
+ }
+
+ return 'phpinfo() method is not available on this server.';
+ }
+
+ /**
+ * Guest date format based on euro/US
+ *
+ * @param string $date
+ *
+ * @return string
+ */
+ public function guessDateFormat($date)
+ {
+ static $guess;
+
+ $date_formats = [
+ 'm/d/y',
+ 'm/d/Y',
+ 'n/d/y',
+ 'n/d/Y',
+ 'd-m-Y',
+ 'd-m-y',
+ ];
+
+ $time_formats = [
+ 'H:i',
+ 'G:i',
+ 'h:ia',
+ 'g:ia'
+ ];
+
+ if (!isset($guess[$date])) {
+ foreach ($date_formats as $date_format) {
+ foreach ($time_formats as $time_format) {
+ if ($this->validateDate($date, "$date_format $time_format")) {
+ $guess[$date] = "$date_format $time_format";
+ break 2;
+ }
+ if ($this->validateDate($date, "$time_format $date_format")) {
+ $guess[$date] = "$time_format $date_format";
+ break 2;
+ }
+ }
+ }
+
+ if (!isset($guess[$date])) {
+ $guess[$date] = 'd-m-Y H:i';
+ }
+ }
+
+ return $guess[$date];
+ }
+
+ public function validateDate($date, $format)
+ {
+ $d = DateTime::createFromFormat($format, $date);
+
+ return $d && $d->format($format) == $date;
+ }
+
+ /**
+ * @param string $php_format
+ *
+ * @return string
+ */
+ public function dateformatToMomentJS($php_format)
+ {
+ $SYMBOLS_MATCHING = [
+ // Day
+ 'd' => 'DD',
+ 'D' => 'ddd',
+ 'j' => 'D',
+ 'l' => 'dddd',
+ 'N' => 'E',
+ 'S' => 'Do',
+ 'w' => 'd',
+ 'z' => 'DDD',
+ // Week
+ 'W' => 'W',
+ // Month
+ 'F' => 'MMMM',
+ 'm' => 'MM',
+ 'M' => 'MMM',
+ 'n' => 'M',
+ 't' => '',
+ // Year
+ 'L' => '',
+ 'o' => 'GGGG',
+ 'Y' => 'YYYY',
+ 'y' => 'yy',
+ // Time
+ 'a' => 'a',
+ 'A' => 'A',
+ 'B' => 'SSS',
+ 'g' => 'h',
+ 'G' => 'H',
+ 'h' => 'hh',
+ 'H' => 'HH',
+ 'i' => 'mm',
+ 's' => 'ss',
+ 'u' => '',
+ // Timezone
+ 'e' => '',
+ 'I' => '',
+ 'O' => 'ZZ',
+ 'P' => 'Z',
+ 'T' => 'z',
+ 'Z' => '',
+ // Full Date/Time
+ 'c' => '',
+ 'r' => 'llll ZZ',
+ 'U' => 'X'
+ ];
+ $js_format = '';
+ $escaping = false;
+ $len = strlen($php_format);
+ for ($i = 0; $i < $len; $i++) {
+ $char = $php_format[$i];
+ if ($char === '\\') // PHP date format escaping character
+ {
+ $i++;
+ if ($escaping) {
+ $js_format .= $php_format[$i];
+ } else {
+ $js_format .= '\'' . $php_format[$i];
+ }
+ $escaping = true;
+ } else {
+ if ($escaping) {
+ $js_format .= "'";
+ $escaping = false;
+ }
+ if (isset($SYMBOLS_MATCHING[$char])) {
+ $js_format .= $SYMBOLS_MATCHING[$char];
+ } else {
+ $js_format .= $char;
+ }
+ }
+ }
+
+ return $js_format;
+ }
+
+ /**
+ * Gets the entire permissions array
+ *
+ * @return array
+ */
+ public function getPermissions()
+ {
+ return $this->permissions;
+ }
+
+ /**
+ * Sets the entire permissions array
+ *
+ * @param array $permissions
+ */
+ public function setPermissions($permissions)
+ {
+ $this->permissions = $permissions;
+ }
+
+ /**
+ * Adds a permission to the permissions array
+ *
+ * @param array $permissions
+ */
+ public function addPermissions($permissions)
+ {
+ $this->permissions = array_merge($this->permissions, $permissions);
+ }
+
+ public function getNotifications($force = false)
+ {
+ $last_checked = null;
+ $filename = $this->grav['locator']->findResource('user://data/notifications/' . md5($this->grav['user']->username) . YAML_EXT, true, true);
+
+ $notifications_file = CompiledYamlFile::instance($filename);
+ $notifications_content = (array)$notifications_file->content();
+
+ $last_checked = $notifications_content['last_checked'] ?? null;
+ $notifications = $notifications_content['data'] ?? array();
+ $timeout = $this->grav['config']->get('system.session.timeout', 1800);
+
+ if ($force || !$last_checked || empty($notifications) || (time() - $last_checked > $timeout)) {
+ $body = Response::get('https://getgrav.org/notifications.json?' . time());
+// $body = Response::get('http://localhost/notifications.json?' . time());
+ $notifications = json_decode($body, true);
+
+ // Sort by date
+ usort($notifications, function ($a, $b) {
+ return strcmp($a['date'], $b['date']);
+ });
+
+ // Reverse order and create a new array
+ $notifications = array_reverse($notifications);
+ $cleaned_notifications = [];
+
+ foreach ($notifications as $key => $notification) {
+
+ if (isset($notification['permissions']) && !$this->authorize($notification['permissions'])) {
+ continue;
+ }
+
+ if (isset($notification['dependencies'])) {
+ foreach ($notification['dependencies'] as $dependency => $constraints) {
+ if ($dependency === 'grav') {
+ if (!Semver::satisfies(GRAV_VERSION, $constraints)) {
+ continue;
+ }
+ } else {
+ $packages = array_merge($this->plugins()->toArray(), $this->themes()->toArray());
+ if (!isset($packages[$dependency])) {
+ continue;
+ } else {
+ $version = $packages[$dependency]['version'];
+ if (!Semver::satisfies($version, $constraints)) {
+ continue;
+ }
+ }
+ }
+ }
+ }
+
+ $cleaned_notifications[] = $notification;
+
+ }
+
+ // reset notifications
+ $notifications = [];
+
+ foreach($cleaned_notifications as $notification) {
+ foreach ($notification['location'] as $location) {
+ $notifications = array_merge_recursive($notifications, [$location => [$notification]]);
+ }
+ }
+
+
+ $notifications_file->content(['last_checked' => time(), 'data' => $notifications]);
+ $notifications_file->save();
+ }
+
+
+ return $notifications;
+ }
+
+ /**
+ * Get https://getgrav.org news feed
+ *
+ * @return mixed
+ * @throws MalformedXmlException
+ */
+ public function getFeed($force = false)
+ {
+ $last_checked = null;
+ $filename = $this->grav['locator']->findResource('user://data/feed/' . md5($this->grav['user']->username) . YAML_EXT, true, true);
+
+ $feed_file = CompiledYamlFile::instance($filename);
+ $feed_content = (array)$feed_file->content();
+
+ $last_checked = $feed_content['last_checked'] ?? null;
+ $feed = $feed_content['data'] ?? array();
+ $timeout = $this->grav['config']->get('system.session.timeout', 1800);
+
+ if ($force || !$last_checked || empty($feed) || ($last_checked && (time() - $last_checked > $timeout))) {
+ $feed_url = 'https://getgrav.org/blog.atom';
+ $body = Response::get($feed_url);
+
+ $reader = new Reader();
+ $parser = $reader->getParser($feed_url, $body, 'utf-8');
+ $data = $parser->execute()->getItems();
+
+ // Get top 10
+ $data = array_slice($data, 0, 10);
+
+ $feed = array_map(function ($entry) {
+ $simple_entry['title'] = $entry->getTitle();
+ $simple_entry['url'] = $entry->getUrl();
+ $simple_entry['date'] = $entry->getDate()->getTimestamp();
+ $simple_entry['nicetime'] = $this->adminNiceTime($simple_entry['date']);
+ return $simple_entry;
+ }, $data);
+
+ $feed_file->content(['last_checked' => time(), 'data' => $feed]);
+ $feed_file->save();
+ }
+
+ return $feed;
+
+ }
+
+ public function adminNiceTime($date, $long_strings = true)
+ {
+ if (empty($date)) {
+ return $this->translate('GRAV.NICETIME.NO_DATE_PROVIDED', null);
+ }
+
+ if ($long_strings) {
+ $periods = [
+ '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'
+ ];
+ }
+
+ $lengths = ['60', '60', '24', '7', '4.35', '12', '10'];
+
+ $now = time();
+
+ // check if unix timestamp
+ if ((string)(int)$date === (string)$date) {
+ $unix_date = $date;
+ } else {
+ $unix_date = strtotime($date);
+ }
+
+ // check validity of date
+ if (empty($unix_date)) {
+ return $this->translate('GRAV.NICETIME.BAD_DATE', null);
+ }
+
+ // is it future date or past date
+ if ($now > $unix_date) {
+ $difference = $now - $unix_date;
+ $tense = $this->translate('GRAV.NICETIME.AGO', null);
+
+ } else {
+ $difference = $unix_date - $now;
+ $tense = $this->translate('GRAV.NICETIME.FROM_NOW', null);
+ }
+
+ $len = count($lengths) - 1;
+ for ($j = 0; $difference >= $lengths[$j] && $j < $len; $j++) {
+ $difference /= $lengths[$j];
+ }
+
+ $difference = round($difference);
+
+ if ($difference !== 1) {
+ $periods[$j] .= '_PLURAL';
+ }
+
+ if ($this->grav['language']->getTranslation($this->grav['user']->language,
+ $periods[$j] . '_MORE_THAN_TWO')
+ ) {
+ if ($difference > 2) {
+ $periods[$j] .= '_MORE_THAN_TWO';
+ }
+ }
+
+ $periods[$j] = $this->translate('GRAV.'.$periods[$j], null);
+
+ return "{$difference} {$periods[$j]} {$tense}";
+ }
+
+ public function findFormFields($type, $fields, $found_fields = [])
+ {
+ foreach ($fields as $key => $field) {
+
+ if (isset($field['type']) && $field['type'] == $type) {
+ $found_fields[$key] = $field;
+ } elseif (isset($field['fields'])) {
+ $result = $this->findFormFields($type, $field['fields'], $found_fields);
+ if (!empty($result)) {
+ $found_fields = array_merge($found_fields, $result);
+ }
+ }
+ }
+
+ return $found_fields;
+ }
+
+ public function getPagePathFromToken($path, $page = null)
+ {
+ return Utils::getPagePathFromToken($path, $page ?: $this->page(true));
+ }
+
+ /**
+ * Returns edited page.
+ *
+ * @param bool $route
+ *
+ * @param null $path
+ *
+ * @return PageInterface
+ */
+ public function page($route = false, $path = null)
+ {
+ if (!$path) {
+ $path = $this->route;
+ }
+
+ if ($route && !$path) {
+ $path = '/';
+ }
+
+ if (!isset($this->pages[$path])) {
+ $this->pages[$path] = $this->getPage($path);
+ }
+
+ return $this->pages[$path];
+ }
+
+ /**
+ * Returns the page creating it if it does not exist.
+ *
+ * @param string $path
+ *
+ * @return PageInterface|null
+ */
+ public function getPage($path)
+ {
+ /** @var Pages $pages */
+ $pages = $this->grav['pages'];
+
+ if ($path && $path[0] !== '/') {
+ $path = "/{$path}";
+ }
+
+ // Fix for entities in path causing looping...
+ $path = urldecode($path);
+
+ $page = $path ? $pages->dispatch($path, true) : $pages->root();
+
+ if (!$page) {
+ $slug = basename($path);
+
+ if ($slug === '') {
+ return null;
+ }
+
+ $ppath = str_replace('\\', '/', dirname($path));
+
+ // Find or create parent(s).
+ $parent = $this->getPage($ppath !== '/' ? $ppath : '');
+
+ // Create page.
+ $page = new Page();
+ $page->parent($parent);
+ $page->filePath($parent->path() . '/' . $slug . '/' . $page->name());
+
+ // Add routing information.
+ $pages->addPage($page, $path);
+
+ // Set if Modular
+ $page->modularTwig($slug[0] === '_');
+
+ // Determine page type.
+ if (isset($this->session->{$page->route()})) {
+ // Found the type and header from the session.
+ $data = $this->session->{$page->route()};
+
+ // Set the key header value
+ $header = ['title' => $data['title']];
+
+ if (isset($data['visible'])) {
+ if ($data['visible'] === '' || $data['visible']) {
+ // if auto (ie '')
+ $pageParent = $page->parent();
+ $children = $pageParent ? $pageParent->children() : [];
+ foreach ($children as $child) {
+ if ($child->order()) {
+ // set page order
+ $page->order(AdminController::getNextOrderInFolder($pageParent->path()));
+ break;
+ }
+ }
+ }
+ if ((int)$data['visible'] === 1 && !$page->order()) {
+ $header['visible'] = $data['visible'];
+ }
+
+ }
+
+ if ($data['name'] === 'modular') {
+ $header['body_classes'] = 'modular';
+ }
+
+ $name = $page->modular() ? str_replace('modular/', '', $data['name']) : $data['name'];
+ $page->name($name . '.md');
+
+ // Fire new event to allow plugins to manipulate page frontmatter
+ $this->grav->fireEvent('onAdminCreatePageFrontmatter', new Event(['header' => &$header,
+ 'data' => $data]));
+
+ $page->header($header);
+ $page->frontmatter(Yaml::dump((array)$page->header(), 20));
+ } else {
+ // Find out the type by looking at the parent.
+ $type = $parent->childType() ?: $parent->blueprints()->get('child_type', 'default');
+ $page->name($type . CONTENT_EXT);
+ $page->header();
+ }
+ }
+
+ return $page;
+ }
+
+ public function generateReports()
+ {
+ $reports = new ArrayCollection();
+
+ /** @var Pages $pages */
+ $pages = $this->grav['pages'];
+
+ // Default to XSS Security Report
+ $result = Security::detectXssFromPages($pages, true);
+
+ $reports['Grav Security Check'] = $this->grav['twig']->processTemplate('reports/security.html.twig', [
+ 'result' => $result,
+ ]);
+
+ // Linting Issues
+
+ $result = YamlLinter::lint();
+
+ $reports['Grav Yaml Linter'] = $this->grav['twig']->processTemplate('reports/yamllinter.html.twig', [
+ 'result' => $result,
+ ]);
+
+ // Fire new event to allow plugins to manipulate page frontmatter
+ $this->grav->fireEvent('onAdminGenerateReports', new Event(['reports' => $reports]));
+
+ return $reports;
+ }
+
+ public function getRouteDetails()
+ {
+ return [$this->base, $this->location, $this->route];
+ }
+
+ /**
+ * Get the files list
+ *
+ * @param bool $filtered
+ * @param int $page_index
+ * @return array|null
+ * @todo allow pagination
+ */
+ public function files($filtered = true, $page_index = 0)
+ {
+ $param_type = $this->grav['uri']->param('type');
+ $param_date = $this->grav['uri']->param('date');
+ $param_page = $this->grav['uri']->param('page');
+ $param_page = str_replace('\\', '/', $param_page);
+
+ $files_cache_key = 'media-manager-files';
+
+ if ($param_type) {
+ $files_cache_key .= "-{$param_type}";
+ }
+ if ($param_date) {
+ $files_cache_key .= "-{$param_date}";
+ }
+ if ($param_page) {
+ $files_cache_key .= "-{$param_page}";
+ }
+
+ $page_files = null;
+
+ $cache_enabled = $this->grav['config']->get('plugins.admin.cache_enabled');
+ if (!$cache_enabled) {
+ $this->grav['cache']->setEnabled(true);
+ }
+
+ $page_files = $this->grav['cache']->fetch(md5($files_cache_key));
+
+ if (!$cache_enabled) {
+ $this->grav['cache']->setEnabled(false);
+ }
+
+ if (!$page_files) {
+ $page_files = [];
+ $pages = $this->grav['pages'];
+
+ if ($param_page) {
+ $page = $pages->dispatch($param_page);
+
+ $page_files = $this->getFiles('images', $page, $page_files, $filtered);
+ $page_files = $this->getFiles('videos', $page, $page_files, $filtered);
+ $page_files = $this->getFiles('audios', $page, $page_files, $filtered);
+ $page_files = $this->getFiles('files', $page, $page_files, $filtered);
+ } else {
+ $allPages = $pages->all();
+
+ if ($allPages) foreach ($allPages as $page) {
+ $page_files = $this->getFiles('images', $page, $page_files, $filtered);
+ $page_files = $this->getFiles('videos', $page, $page_files, $filtered);
+ $page_files = $this->getFiles('audios', $page, $page_files, $filtered);
+ $page_files = $this->getFiles('files', $page, $page_files, $filtered);
+ }
+ }
+
+ if (count($page_files) >= self::MEDIA_PAGINATION_INTERVAL) {
+ $this->shouldLoadAdditionalFilesInBackground(true);
+ }
+
+ if (!$cache_enabled) {
+ $this->grav['cache']->setEnabled(true);
+ }
+ $this->grav['cache']->save(md5($files_cache_key), $page_files, 600); //cache for 10 minutes
+ if (!$cache_enabled) {
+ $this->grav['cache']->setEnabled(false);
+ }
+
+ }
+
+ if (count($page_files) >= self::MEDIA_PAGINATION_INTERVAL) {
+ $page_files = array_slice($page_files, $page_index * self::MEDIA_PAGINATION_INTERVAL, self::MEDIA_PAGINATION_INTERVAL);
+ }
+
+ return $page_files;
+ }
+
+ public function shouldLoadAdditionalFilesInBackground($status = null)
+ {
+ if ($status) {
+ $this->load_additional_files_in_background = true;
+ }
+
+ return $this->load_additional_files_in_background;
+ }
+
+ public function loadAdditionalFilesInBackground($status = null)
+ {
+ if (!$this->loading_additional_files_in_background) {
+ $this->loading_additional_files_in_background = true;
+ $this->files(false, false);
+ $this->shouldLoadAdditionalFilesInBackground(false);
+ $this->loading_additional_files_in_background = false;
+ }
+ }
+
+ private function getFiles($type, $page, $page_files, $filtered)
+ {
+ $page_files = $this->getMediaOfType($type, $page, $page_files);
+
+ if ($filtered) {
+ $page_files = $this->filterByType($page_files);
+ $page_files = $this->filterByDate($page_files);
+ }
+
+ return $page_files;
+ }
+
+ /**
+ * Get all the media of a type ('images' | 'audios' | 'videos' | 'files')
+ *
+ * @param string $type
+ * @param PageInterface|null $page
+ * @param array $files
+ *
+ * @return array
+ */
+ private function getMediaOfType($type, ?PageInterface $page, array $files)
+ {
+ if ($page) {
+ $media = $page->media();
+ $mediaOfType = $media->$type();
+
+ foreach($mediaOfType as $title => $file) {
+ $files[] = [
+ 'title' => $title,
+ 'type' => $type,
+ 'page_route' => $page->route(),
+ 'file' => $file->higherQualityAlternative()
+ ];
+ }
+
+ return $files;
+ }
+
+ return [];
+ }
+
+ /**
+ * Filter media by type
+ *
+ * @param array $filesFiltered
+ *
+ * @return array
+ */
+ private function filterByType($filesFiltered)
+ {
+ $filter_type = $this->grav['uri']->param('type');
+ if (!$filter_type) {
+ return $filesFiltered;
+ }
+
+ $filesFiltered = array_filter($filesFiltered, function ($file) use ($filter_type) {
+ return $file['type'] == $filter_type;
+ });
+
+ return $filesFiltered;
+ }
+
+ /**
+ * Filter media by date
+ *
+ * @param array $filesFiltered
+ *
+ * @return array
+ */
+ private function filterByDate($filesFiltered)
+ {
+ $filter_date = $this->grav['uri']->param('date');
+ if (!$filter_date) {
+ return $filesFiltered;
+ }
+
+ $year = substr($filter_date, 0, 4);
+ $month = substr($filter_date, 5, 2);
+
+ $filesFilteredByDate = [];
+
+ foreach($filesFiltered as $file) {
+ $filedate = $this->fileDate($file['file']);
+ $fileYear = $filedate->format('Y');
+ $fileMonth = $filedate->format('m');
+
+ if ($fileYear == $year && $fileMonth == $month) {
+ $filesFilteredByDate[] = $file;
+ }
+ }
+
+ return $filesFilteredByDate;
+ }
+
+ /**
+ * Return the DateTime object representation of a file modified date
+ *
+ * @param File $file
+ *
+ * @return DateTime
+ */
+ private function fileDate($file) {
+ $datetime = new \DateTime();
+ $datetime->setTimestamp($file->toArray()['modified']);
+ return $datetime;
+ }
+
+ /**
+ * Get the files dates list to be used in the Media Files filter
+ *
+ * @return array
+ */
+ public function filesDates()
+ {
+ $files = $this->files(false);
+ $dates = [];
+
+ foreach ($files as $file) {
+ $datetime = $this->fileDate($file['file']);
+ $year = $datetime->format('Y');
+ $month = $datetime->format('m');
+
+ if (!isset($dates[$year])) {
+ $dates[$year] = [];
+ }
+
+ if (!isset($dates[$year][$month])) {
+ $dates[$year][$month] = 1;
+ } else {
+ $dates[$year][$month]++;
+ }
+ }
+
+ return $dates;
+ }
+
+ /**
+ * Get the pages list to be used in the Media Files filter
+ *
+ * @return array
+ */
+ public function pages()
+ {
+ /** @var Collection $pages */
+ $pages = $this->grav['pages']->all();
+
+ $pagesWithFiles = [];
+ foreach ($pages as $page) {
+ if (count($page->media()->all())) {
+ $pagesWithFiles[] = $page;
+ }
+ }
+
+ return $pagesWithFiles;
+ }
+
+ /**
+ * Return HTTP_REFERRER if set
+ *
+ * @return null
+ */
+ public function getReferrer()
+ {
+ return $_SERVER['HTTP_REFERER'] ?? null;
+ }
+
+ public static function enablePages()
+ {
+ return Grav::instance()['pages'];
+ }
+}
diff --git a/user/plugins/admin/classes/adminbasecontroller.php b/user/plugins/admin/classes/adminbasecontroller.php
new file mode 100644
index 0000000..e3828a5
--- /dev/null
+++ b/user/plugins/admin/classes/adminbasecontroller.php
@@ -0,0 +1,1122 @@
+ 'There is no error, the file uploaded with success',
+ 1 => 'The uploaded file exceeds the max upload size',
+ 2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML',
+ 3 => 'The uploaded file was only partially uploaded',
+ 4 => 'No file was uploaded',
+ 6 => 'Missing a temporary folder',
+ 7 => 'Failed to write file to disk',
+ 8 => 'A PHP extension stopped the file upload'
+ ];
+
+ /** @var array */
+ public $blacklist_views = [];
+
+ /**
+ * Performs a task.
+ *
+ * @return bool True if the action was performed successfully.
+ */
+ public function execute()
+ {
+ if (in_array($this->view, $this->blacklist_views, true)) {
+ return false;
+ }
+
+// if (!$this->validateNonce()) {
+// return false;
+// }
+
+ $method = 'task' . ucfirst($this->task);
+
+ if (method_exists($this, $method)) {
+ try {
+ $success = $this->{$method}();
+ } catch (\RuntimeException $e) {
+ $success = true;
+ $this->admin->setMessage($e->getMessage(), 'error');
+ }
+ } else {
+ $success = $this->grav->fireEvent('onAdminTaskExecute',
+ new Event(['controller' => $this, 'method' => $method]));
+ }
+
+ // Grab redirect parameter.
+ $redirect = $this->post['_redirect'] ?? null;
+ unset($this->post['_redirect']);
+
+ // Redirect if requested.
+ if ($redirect) {
+ $this->setRedirect($redirect);
+ }
+
+ return $success;
+ }
+
+ protected function validateNonce()
+ {
+ if (strtolower($_SERVER['REQUEST_METHOD']) === 'post') {
+ if (isset($this->post['admin-nonce'])) {
+ $nonce = $this->post['admin-nonce'];
+ } else {
+ $nonce = $this->grav['uri']->param('admin-nonce');
+ }
+
+ if (!$nonce || !Utils::verifyNonce($nonce, 'admin-form')) {
+ if ($this->task === 'addmedia') {
+
+ $message = sprintf($this->admin::translate('PLUGIN_ADMIN.FILE_TOO_LARGE', null),
+ ini_get('post_max_size'));
+
+ //In this case it's more likely that the image is too big than POST can handle. Show message
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $message
+ ];
+
+ return false;
+ }
+
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'), 'error');
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN')
+ ];
+
+ return false;
+ }
+ unset($this->post['admin-nonce']);
+ } else {
+ if ($this->task === 'logout') {
+ $nonce = $this->grav['uri']->param('logout-nonce');
+ if (null === $nonce || !Utils::verifyNonce($nonce, 'logout-form')) {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'),
+ 'error');
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN')
+ ];
+
+ return false;
+ }
+ } else {
+ $nonce = $this->grav['uri']->param('admin-nonce');
+ if (null === $nonce || !Utils::verifyNonce($nonce, 'admin-form')) {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'),
+ 'error');
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN')
+ ];
+
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Sets the page redirect.
+ *
+ * @param string $path The path to redirect to
+ * @param int $code The HTTP redirect code
+ */
+ public function setRedirect($path, $code = 303)
+ {
+ $this->redirect = $path;
+ $this->redirectCode = $code;
+ }
+
+ /**
+ * Sends JSON response and terminates the call.
+ *
+ * @param array $response
+ * @param int $code
+ * @return bool
+ */
+ protected function sendJsonResponse(array $response, $code = 200)
+ {
+ // Make sure nothing extra gets written to the response.
+ while (ob_get_level()) {
+ ob_end_clean();
+ }
+
+ // JSON response.
+ http_response_code($code);
+ header('Content-Type: application/json');
+ header('Cache-Control: no-cache, no-store, must-revalidate');
+
+ echo json_encode($response);
+ exit();
+ }
+
+ /**
+ * Handles ajax upload for files.
+ * Stores in a flash object the temporary file and deals with potential file errors.
+ *
+ * @return bool True if the action was performed.
+ */
+ public function taskFilesUpload()
+ {
+ if (null === $_FILES || !$this->authorizeTask('save', $this->dataPermissions())) {
+ return false;
+ }
+
+ /** @var Config $config */
+ $config = $this->grav['config'];
+ $data = $this->view === 'pages' ? $this->admin->page(true) : $this->prepareData([]);
+ $settings = $data->blueprints()->schema()->getProperty($this->post['name']);
+ $settings = (object)array_merge([
+ 'avoid_overwriting' => false,
+ 'random_name' => false,
+ 'accept' => ['image/*'],
+ 'limit' => 10,
+ 'filesize' => Utils::getUploadLimit()
+ ], (array)$settings, ['name' => $this->post['name']]);
+
+ $upload = $this->normalizeFiles($_FILES['data'], $settings->name);
+
+ $filename = $upload->file->name;
+
+ // Handle bad filenames.
+ if (!Utils::checkFilename($filename)) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD', null),
+ $filename, 'Bad filename')
+ ];
+
+ return false;
+ }
+
+ if (!isset($settings->destination)) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.DESTINATION_NOT_SPECIFIED', null)
+ ];
+
+ return false;
+ }
+
+ // Do not use self@ outside of pages
+ if ($this->view !== 'pages' && in_array($settings->destination, ['@self', 'self@', '@self@'])) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_PREVENT_SELF', null),
+ $settings->destination)
+ ];
+
+ return false;
+ }
+
+ // Handle errors and breaks without proceeding further
+ if ($upload->file->error !== UPLOAD_ERR_OK) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD', null),
+ $filename, $this->upload_errors[$upload->file->error])
+ ];
+
+ return false;
+ }
+
+ // Handle file size limits
+ $settings->filesize *= 1048576; // 2^20 [MB in Bytes]
+ if ($settings->filesize > 0 && $upload->file->size > $settings->filesize) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT')
+ ];
+
+ return false;
+ }
+
+ // Handle Accepted file types
+ // Accept can only be mime types (image/png | image/*) or file extensions (.pdf|.jpg)
+ $accepted = false;
+ $errors = [];
+
+ // Do not trust mimetype sent by the browser
+ $mime = Utils::getMimeByFilename($filename);
+
+ foreach ((array)$settings->accept as $type) {
+ // Force acceptance of any file when star notation
+ if ($type === '*') {
+ $accepted = true;
+ break;
+ }
+
+ $isMime = strstr($type, '/');
+ $find = str_replace(['.', '*', '+'], ['\.', '.*', '\+'], $type);
+
+ if ($isMime) {
+ $match = preg_match('#' . $find . '$#', $mime);
+ if (!$match) {
+ $errors[] = 'The MIME type "' . $mime . '" for the file "' . $filename . '" is not an accepted.';
+ } else {
+ $accepted = true;
+ break;
+ }
+ } else {
+ $match = preg_match('#' . $find . '$#', $filename);
+ if (!$match) {
+ $errors[] = 'The File Extension for the file "' . $filename . '" is not an accepted.';
+ } else {
+ $accepted = true;
+ break;
+ }
+ }
+ }
+
+ if (!$accepted) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => implode(' ', $errors)
+ ];
+
+ return false;
+ }
+
+ // Remove the error object to avoid storing it
+ unset($upload->file->error);
+
+ // we need to move the file at this stage or else
+ // it won't be available upon save later on
+ // since php removes it from the upload location
+ $tmp_dir = Admin::getTempDir();
+ $tmp_file = $upload->file->tmp_name;
+ $tmp = $tmp_dir . '/uploaded-files/' . basename($tmp_file);
+
+ Folder::create(dirname($tmp));
+ if (!move_uploaded_file($tmp_file, $tmp)) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_MOVE', null), '',
+ $tmp)
+ ];
+
+ return false;
+ }
+
+ $upload->file->tmp_name = $tmp;
+
+ // Retrieve the current session of the uploaded files for the field
+ // and initialize it if it doesn't exist
+ $sessionField = base64_encode($this->grav['uri']->url());
+ $flash = $this->admin->session()->getFlashObject('files-upload');
+ if (!$flash) {
+ $flash = [];
+ }
+ if (!isset($flash[$sessionField])) {
+ $flash[$sessionField] = [];
+ }
+ if (!isset($flash[$sessionField][$upload->field])) {
+ $flash[$sessionField][$upload->field] = [];
+ }
+
+ // Set destination
+ if ($this->grav['locator']->isStream($settings->destination)) {
+ $destination = $this->grav['locator']->findResource($settings->destination, false, true);
+ } else {
+ $destination = Folder::getRelativePath(rtrim($settings->destination, '/'));
+ $destination = $this->admin->getPagePathFromToken($destination);
+ }
+
+ // Create destination if needed
+ if (!is_dir($destination)) {
+ Folder::mkdir($destination);
+ }
+
+ // Generate random name if required
+ if ($settings->random_name) { // TODO: document
+ $extension = pathinfo($upload->file->name, PATHINFO_EXTENSION);
+ $upload->file->name = Utils::generateRandomString(15) . '.' . $extension;
+ }
+
+ // Handle conflicting name if needed
+ if ($settings->avoid_overwriting) { // TODO: document
+ if (file_exists($destination . '/' . $upload->file->name)) {
+ $upload->file->name = date('YmdHis') . '-' . $upload->file->name;
+ }
+ }
+
+ // Prepare object for later save
+ $path = $destination . '/' . $upload->file->name;
+ $upload->file->path = $path;
+ // $upload->file->route = $page ? $path : null;
+
+ // Prepare data to be saved later
+ $flash[$sessionField][$upload->field][$path] = (array)$upload->file;
+
+ // Finally store the new uploaded file in the field session
+ $this->admin->session()->setFlashObject('files-upload', $flash);
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'session' => \json_encode([
+ 'sessionField' => base64_encode($this->grav['uri']->url()),
+ 'path' => $upload->file->path,
+ 'field' => $settings->name
+ ])
+ ];
+
+ return true;
+ }
+
+ /**
+ * Checks if the user is allowed to perform the given task with its associated permissions
+ *
+ * @param string $task The task to execute
+ * @param array $permissions The permissions given
+ *
+ * @return bool True if authorized. False if not.
+ */
+ public function authorizeTask($task = '', $permissions = [])
+ {
+ if (!$this->admin->authorize($permissions)) {
+ if ($this->grav['uri']->extension() === 'json') {
+ $this->admin->json_response = [
+ 'status' => 'unauthorized',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' ' . $task . '.'
+ ];
+ } else {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' ' . $task . '.',
+ 'error');
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets the permissions needed to access a given view
+ *
+ * @return array An array of permissions
+ */
+ protected function dataPermissions()
+ {
+ $type = $this->view;
+ $permissions = ['admin.super'];
+
+ switch ($type) {
+ case 'configuration':
+ case 'config':
+ case 'system':
+ $permissions[] = 'admin.configuration';
+ break;
+ case 'settings':
+ case 'site':
+ $permissions[] = 'admin.settings';
+ break;
+ case 'plugins':
+ $permissions[] = 'admin.plugins';
+ break;
+ case 'themes':
+ $permissions[] = 'admin.themes';
+ break;
+ case 'users':
+ $permissions[] = 'admin.users';
+ break;
+ case 'user':
+ $permissions[] = 'admin.login';
+ $permissions[] = 'admin.users';
+ break;
+ case 'pages':
+ $permissions[] = 'admin.pages';
+ break;
+ }
+
+ return $permissions;
+ }
+
+ /**
+ * Gets the configuration data for a given view & post
+ *
+ * @param array $data
+ *
+ * @return array
+ */
+ protected function prepareData(array $data)
+ {
+ return $data;
+ }
+
+ /**
+ * Internal method to normalize the $_FILES array
+ *
+ * @param array $data $_FILES starting point data
+ * @param string $key
+ *
+ * @return object a new Object with a normalized list of files
+ */
+ protected function normalizeFiles($data, $key = '')
+ {
+ $files = new \stdClass();
+ $files->field = $key;
+ $files->file = new \stdClass();
+
+ foreach ($data as $fieldName => $fieldValue) {
+ // Since Files Upload are always happening via Ajax
+ // we are not interested in handling `multiple="true"`
+ // because they are always handled one at a time.
+ // For this reason we normalize the value to string,
+ // in case it is arriving as an array.
+ $value = (array)Utils::getDotNotation($fieldValue, $key);
+ $files->file->{$fieldName} = array_shift($value);
+ }
+
+ return $files;
+ }
+
+ /**
+ * Removes a file from the flash object session, before it gets saved
+ *
+ * @return bool True if the action was performed.
+ */
+ public function taskFilesSessionRemove()
+ {
+ if (!$this->authorizeTask('save', $this->dataPermissions())) {
+ return false;
+ }
+
+ // Retrieve the current session of the uploaded files for the field
+ // and initialize it if it doesn't exist
+ $sessionField = base64_encode($this->grav['uri']->url());
+ $request = \json_decode($this->post['session']);
+
+ // Ensure the URI requested matches the current one, otherwise fail
+ if ($request->sessionField !== $sessionField) {
+ return false;
+ }
+
+ // Retrieve the flash object and remove the requested file from it
+ $flash = $this->admin->session()->getFlashObject('files-upload');
+ $endpoint = $flash[$request->sessionField][$request->field][$request->path];
+
+ if (isset($endpoint)) {
+ if (file_exists($endpoint['tmp_name'])) {
+ unlink($endpoint['tmp_name']);
+ }
+
+ unset($endpoint);
+ }
+
+ // Walk backward to cleanup any empty field that's left
+ // Field
+ if (isset($flash[$request->sessionField][$request->field][$request->path])) {
+ unset($flash[$request->sessionField][$request->field][$request->path]);
+ }
+
+ // Field
+ if (isset($flash[$request->sessionField][$request->field]) && empty($flash[$request->sessionField][$request->field])) {
+ unset($flash[$request->sessionField][$request->field]);
+ }
+
+ // Session Field
+ if (isset($flash[$request->sessionField]) && empty($flash[$request->sessionField])) {
+ unset($flash[$request->sessionField]);
+ }
+
+
+ // If there's anything left to restore in the flash object, do so
+ if (count($flash)) {
+ $this->admin->session()->setFlashObject('files-upload', $flash);
+ }
+
+ $this->admin->json_response = ['status' => 'success'];
+
+ return true;
+ }
+
+ /**
+ * Redirect to the route stored in $this->redirect
+ */
+ public function redirect()
+ {
+ if (!$this->redirect) {
+ return;
+ }
+
+ $base = $this->admin->base;
+ $this->redirect = '/' . ltrim($this->redirect, '/');
+ $multilang = $this->isMultilang();
+
+ $redirect = '';
+ if ($multilang) {
+ // if base path does not already contain the lang code, add it
+ $langPrefix = '/' . $this->grav['session']->admin_lang;
+ if (!Utils::startsWith($base, $langPrefix . '/')) {
+ $base = $langPrefix . $base;
+ }
+
+ // now the first 4 chars of base contain the lang code.
+ // if redirect path already contains the lang code, and is != than the base lang code, then use redirect path as-is
+ if (Utils::pathPrefixedByLangCode($base) && Utils::pathPrefixedByLangCode($this->redirect)
+ && !Utils::startsWith($this->redirect, $base)
+ ) {
+ $redirect = $this->redirect;
+ } else {
+ if (!Utils::startsWith($this->redirect, $base)) {
+ $this->redirect = $base . $this->redirect;
+ }
+ }
+
+ } else {
+ if (!Utils::startsWith($this->redirect, $base)) {
+ $this->redirect = $base . $this->redirect;
+ }
+ }
+
+ if (!$redirect) {
+ $redirect = $this->redirect;
+ }
+
+ $this->grav->redirect($redirect, $this->redirectCode);
+ }
+
+ /**
+ * Prepare and return POST data.
+ *
+ * @param array $post
+ *
+ * @return array
+ */
+ protected function getPost($post)
+ {
+ if (!is_array($post)) {
+ return [];
+ }
+
+ unset($post['task']);
+
+ // Decode JSON encoded fields and merge them to data.
+ if (isset($post['_json'])) {
+ $post = array_replace_recursive($post, $this->jsonDecode($post['_json']));
+ unset($post['_json']);
+ }
+
+ $post = $this->cleanDataKeys($post);
+
+ return $post;
+ }
+
+ /**
+ * Recursively JSON decode data.
+ *
+ * @param array $data
+ *
+ * @return array
+ */
+ protected function jsonDecode(array $data)
+ {
+ foreach ($data as &$value) {
+ if (is_array($value)) {
+ $value = $this->jsonDecode($value);
+ } else {
+ $value = json_decode($value, true);
+ }
+ }
+
+ return $data;
+ }
+
+ protected function cleanDataKeys($source = [])
+ {
+ $out = [];
+
+ if (is_array($source)) {
+ foreach ($source as $key => $value) {
+ $key = str_replace(['%5B', '%5D'], ['[', ']'], $key);
+ if (is_array($value)) {
+ $out[$key] = $this->cleanDataKeys($value);
+ } else {
+ $out[$key] = $value;
+ }
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * Return true if multilang is active
+ *
+ * @return bool True if multilang is active
+ */
+ protected function isMultilang()
+ {
+ return count($this->grav['config']->get('system.languages.supported', [])) > 1;
+ }
+
+ /**
+ * @param PageInterface|UserInterface|Data $obj
+ *
+ * @return PageInterface|UserInterface|Data
+ */
+ protected function storeFiles($obj)
+ {
+ // Process previously uploaded files for the current URI
+ // and finally store them. Everything else will get discarded
+ $queue = $this->admin->session()->getFlashObject('files-upload');
+ if (is_array($queue)) {
+ $queue = $queue[base64_encode($this->grav['uri']->url())];
+ foreach ($queue as $key => $files) {
+ foreach ($files as $destination => $file) {
+ if (!rename($file['tmp_name'], $destination)) {
+ throw new \RuntimeException(sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_MOVE',
+ null), '"' . $file['tmp_name'] . '"', $destination));
+ }
+
+ unset($files[$destination]['tmp_name']);
+ }
+
+ if ($this->view === 'pages') {
+ $keys = explode('.', preg_replace('/^header./', '', $key));
+ $init_key = array_shift($keys);
+ if (count($keys) > 0) {
+ $new_data = $obj->header()->{$init_key} ?? [];
+ Utils::setDotNotation($new_data, implode('.', $keys), $files, true);
+ } else {
+ $new_data = $files;
+ }
+ if (isset($obj->header()->{$init_key})) {
+ $obj->modifyHeader($init_key,
+ array_replace_recursive([], $obj->header()->{$init_key}, $new_data));
+ } else {
+ $obj->modifyHeader($init_key, $new_data);
+ }
+ } elseif ($obj instanceof UserInterface and $key === 'avatar') {
+ $obj->set($key, $files);
+ } else {
+ // TODO: [this is JS handled] if it's single file, remove existing and use set, if it's multiple, use join
+ $obj->join($key, $files); // stores
+ }
+
+ }
+ }
+
+ return $obj;
+ }
+
+ /**
+ * Used by the filepicker field to get a list of files in a folder.
+ */
+ protected function taskGetFilesInFolder()
+ {
+ if (!$this->authorizeTask('save', $this->dataPermissions())) {
+ return false;
+ }
+
+ $data = $this->view === 'pages' ? $this->admin->page(true) : $this->prepareData([]);
+
+ if (null === $data) {
+ return false;
+ }
+
+ if (method_exists($data, 'blueprints')) {
+ $settings = $data->blueprints()->schema()->getProperty($this->post['name']);
+ } elseif (method_exists($data, 'getBlueprint')) {
+ $settings = $data->getBlueprint()->schema()->getProperty($this->post['name']);
+ }
+
+ if (isset($settings['folder'])) {
+ $folder = $settings['folder'];
+ } else {
+ $folder = 'self@';
+ }
+
+ // Do not use self@ outside of pages
+ if ($this->view !== 'pages' && in_array($folder, ['@self', 'self@', '@self@'])) {
+ if (!$data instanceof MediaInterface) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_PREVENT_SELF', null), $folder)
+ ];
+
+ return false;
+ }
+
+ $media = $data->getMedia();
+ } else {
+ // Set destination
+ $folder = Folder::getRelativePath(rtrim($folder, '/'));
+ $folder = $this->admin->getPagePathFromToken($folder);
+
+ $media = new Media($folder);
+ }
+
+ $available_files = [];
+ $metadata = [];
+ $thumbs = [];
+
+
+ foreach ($media->all() as $name => $medium) {
+
+ $available_files[] = $name;
+
+ if (isset($settings['include_metadata'])) {
+ $img_metadata = $medium->metadata();
+ if ($img_metadata) {
+ $metadata[$name] = $img_metadata;
+ }
+ }
+
+ }
+
+ // Peak in the flashObject for optimistic filepicker updates
+ $pending_files = [];
+ $sessionField = base64_encode($this->grav['uri']->url());
+ $flash = $this->admin->session()->getFlashObject('files-upload');
+
+ if ($flash && isset($flash[$sessionField])) {
+ foreach ($flash[$sessionField] as $field => $data) {
+ foreach ($data as $file) {
+ if (dirname($file['path']) === $folder) {
+ $pending_files[] = $file['name'];
+ }
+ }
+ }
+ }
+
+ $this->admin->session()->setFlashObject('files-upload', $flash);
+
+ // Handle Accepted file types
+ // Accept can only be file extensions (.pdf|.jpg)
+ if (isset($settings['accept'])) {
+ $available_files = array_filter($available_files, function ($file) use ($settings) {
+ return $this->filterAcceptedFiles($file, $settings);
+ });
+
+ $pending_files = array_filter($pending_files, function ($file) use ($settings) {
+ return $this->filterAcceptedFiles($file, $settings);
+ });
+ }
+
+ // Generate thumbs if needed
+ if (isset($settings['preview_images']) && $settings['preview_images'] === true) {
+ foreach ($available_files as $filename) {
+ $thumbs[$filename] = $media[$filename]->zoomCrop(100,100)->url();
+ }
+ }
+
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'files' => array_values($available_files),
+ 'pending' => array_values($pending_files),
+ 'folder' => $folder,
+ 'metadata' => $metadata,
+ 'thumbs' => $thumbs
+ ];
+
+ return true;
+ }
+
+ protected function filterAcceptedFiles($file, $settings)
+ {
+ $valid = false;
+
+ foreach ((array)$settings['accept'] as $type) {
+ $find = str_replace('*', '.*', $type);
+ $valid |= preg_match('#' . $find . '$#', $file);
+ }
+
+ return $valid;
+ }
+
+ /**
+ * Handle deleting a file from a blueprint
+ *
+ * @return bool True if the action was performed.
+ */
+ protected function taskRemoveFileFromBlueprint()
+ {
+ /** @var Uri $uri */
+ $uri = $this->grav['uri'];
+ $blueprint = base64_decode($uri->param('blueprint'));
+ $path = base64_decode($uri->param('path'));
+ $filename = basename($this->post['filename'] ?? '');
+ $proute = base64_decode($uri->param('proute'));
+ $type = $uri->param('type');
+ $field = $uri->param('field');
+
+ if ($filename === '') {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => 'Filename is empty'
+ ];
+
+ return false;
+ }
+
+ // Get Blueprint
+ if ($type === 'pages' || strpos($blueprint, 'pages/') === 0) {
+ $page = $this->admin->page(true, $proute);
+ if (!$page) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => 'Page not found'
+ ];
+
+ return false;
+ }
+ $blueprints = $page->blueprints();
+ $path = Folder::getRelativePath($page->path());
+ $settings = (object)$blueprints->schema()->getProperty($field);
+ } else {
+ $page = null;
+ if ($type === 'themes' || $type === 'plugins') {
+ $obj = $this->grav[$type]->get(Utils::substrToString($blueprint, '/')); //here
+ $settings = (object) $obj->blueprints()->schema()->getProperty($field);
+ } else {
+ $settings = (object)$this->admin->blueprints($blueprint)->schema()->getProperty($field);
+ }
+ }
+
+ // Get destination
+ if ($this->grav['locator']->isStream($settings->destination)) {
+ $destination = $this->grav['locator']->findResource($settings->destination, false, true);
+
+ } else {
+ $destination = Folder::getRelativePath(rtrim($settings->destination, '/'));
+ $destination = $this->admin->getPagePathFromToken($destination, $page);
+ }
+
+ // Not in path
+ if (!Utils::startsWith($path, $destination)) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => 'Path not valid for this data type'
+ ];
+
+ return false;
+ }
+
+ // Only remove files from correct destination...
+ $this->taskRemoveMedia($destination . '/' . $filename);
+
+ if ($page) {
+ $keys = explode('.', preg_replace('/^header./', '', $field));
+ $header = (array)$page->header();
+ $data_path = implode('.', $keys);
+ $data = Utils::getDotNotation($header, $data_path);
+
+ if (isset($data[$path])) {
+ unset($data[$path]);
+ Utils::setDotNotation($header, $data_path, $data);
+ $page->header($header);
+ }
+
+ $page->save();
+ } else {
+
+ $blueprint_prefix = $type === 'config' ? '' : $type . '.';
+ $blueprint_name = str_replace(['config/', '/blueprints'], '', $blueprint);
+ $blueprint_field = $blueprint_prefix . $blueprint_name . '.' . $field;
+
+ $files = $this->grav['config']->get($blueprint_field);
+
+ if ($files) {
+ foreach ($files as $key => $value) {
+ if ($key == $path) {
+ unset($files[$key]);
+ }
+ }
+ }
+
+ $this->grav['config']->set($blueprint_field, $files);
+
+ switch ($type) {
+ case 'config':
+ $data = $this->grav['config']->get($blueprint_name);
+ $config = $this->admin->data($blueprint, $data);
+ $config->save();
+ break;
+ case 'themes':
+ Theme::saveConfig($blueprint_name);
+ break;
+ case 'plugins':
+ Plugin::saveConfig($blueprint_name);
+ break;
+ }
+ }
+
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.REMOVE_SUCCESSFUL')
+ ];
+
+ return true;
+ }
+
+ /**
+ * Handles removing a media file
+ *
+ * @return bool True if the action was performed
+ */
+ public function taskRemoveMedia($filename = null)
+ {
+ if (!$this->canEditMedia()) {
+ return false;
+ }
+
+ if (null === $filename) {
+ $filename = base64_decode($this->grav['uri']->param('route'));
+ if (!$filename) {
+ $filename = base64_decode($this->route);
+ }
+ }
+
+ $file = File::instance($filename);
+ $resultRemoveMedia = false;
+
+ if ($file->exists()) {
+ $resultRemoveMedia = $file->delete();
+
+ $fileParts = pathinfo($filename);
+
+ foreach (scandir($fileParts['dirname']) as $file) {
+ $regex_pattern = '/' . preg_quote($fileParts['filename'], '/') . "@\d+x\." . $fileParts['extension'] . "(?:\.meta\.yaml)?$|" . preg_quote($fileParts['basename'], '/') . "\.meta\.yaml$/";
+ if (preg_match($regex_pattern, $file)) {
+ $path = $fileParts['dirname'] . '/' . $file;
+ @unlink($path);
+ }
+ }
+
+ }
+
+ if ($resultRemoveMedia) {
+ if ($this->grav['uri']->extension() === 'json') {
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.REMOVE_SUCCESSFUL')
+ ];
+ } else {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.REMOVE_SUCCESSFUL'), 'info');
+ $this->clearMediaCache();
+ $this->setRedirect('/media-manager');
+ }
+
+ return true;
+ }
+
+ if ($this->grav['uri']->extension() === 'json') {
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.REMOVE_FAILED')
+ ];
+ } else {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.REMOVE_FAILED'), 'error');
+ }
+
+ return false;
+ }
+
+ /**
+ * Handles clearing the media cache
+ *
+ * @return bool True if the action was performed
+ */
+ protected function clearMediaCache()
+ {
+ $key = 'media-manager-files';
+ $cache = $this->grav['cache'];
+ $cache->delete(md5($key));
+
+ return true;
+ }
+
+ /**
+ * Determine if the user can edit media
+ *
+ * @param string $type
+ *
+ * @return bool True if the media action is allowed
+ */
+ protected function canEditMedia($type = 'media')
+ {
+ if (!$this->authorizeTask('edit media', ['admin.' . $type, 'admin.super'])) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/user/plugins/admin/classes/admincontroller.php b/user/plugins/admin/classes/admincontroller.php
new file mode 100644
index 0000000..8329ea0
--- /dev/null
+++ b/user/plugins/admin/classes/admincontroller.php
@@ -0,0 +1,2668 @@
+grav = $grav;
+ $this->view = $view;
+ $this->task = $task ?: 'display';
+ if (isset($post['data'])) {
+ $this->data = $this->getPost($post['data']);
+ unset($post['data']);
+ } else {
+ // Backwards compatibility for Form plugin <= 1.2
+ $this->data = $this->getPost($post);
+ }
+ $this->post = $this->getPost($post);
+ $this->route = $route;
+ $this->admin = $this->grav['admin'];
+
+ $this->grav->fireEvent('onAdminControllerInit', new Event(['controller' => &$this]));
+ }
+
+ /**
+ * Handle login.
+ *
+ * @return bool True if the action was performed.
+ */
+ protected function taskLogin()
+ {
+ $this->admin->authenticate($this->data, $this->post);
+
+ return true;
+ }
+
+ /**
+ * @return bool True if the action was performed.
+ */
+ protected function taskTwofa()
+ {
+ $this->admin->twoFa($this->data, $this->post);
+
+ return true;
+ }
+
+ /**
+ * Handle logout.
+ *
+ * @return bool True if the action was performed.
+ */
+ protected function taskLogout()
+ {
+ $this->admin->logout($this->data, $this->post);
+
+ return true;
+ }
+
+ /**
+ * @return bool
+ */
+ public function taskRegenerate2FASecret()
+ {
+ if (!$this->authorizeTask('regenerate 2FA Secret', ['admin.login'])) {
+ return false;
+ }
+
+ try {
+ /** @var User $user */
+ $user = $this->grav['user'];
+
+ /** @var TwoFactorAuth $twoFa */
+ $twoFa = $this->grav['login']->twoFactorAuth();
+ $secret = $twoFa->createSecret();
+ $image = $twoFa->getQrImageData($user->username, $secret);
+
+ $user->set('twofa_secret', $secret);
+
+ // TODO: data user can also use save, but please test it before removing this code.
+ if ($user instanceof \Grav\Common\User\DataUser\User) {
+ // Save secret into the user file.
+ $file = $user->file();
+ if ($file->exists()) {
+ $content = (array)$file->content();
+ $content['twofa_secret'] = $secret;
+ $file->save($content);
+ $file->free();
+ }
+ } else {
+ $user->save();
+ }
+
+ $this->admin->json_response = ['status' => 'success', 'image' => $image, 'secret' => preg_replace('|(\w{4})|', '\\1 ', $secret)];
+ } catch (\Exception $e) {
+ $this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Handle the reset password action.
+ *
+ * @return bool True if the action was performed.
+ */
+ public function taskReset()
+ {
+ $data = $this->data;
+
+ if (isset($data['password'])) {
+ /** @var UserCollectionInterface $users */
+ $users = $this->grav['accounts'];
+
+ $username = isset($data['username']) ? strip_tags(strtolower($data['username'])) : null;
+ $user = $username ? $users->load($username) : null;
+ $password = $data['password'] ?? null;
+ $token = $data['token'] ?? null;
+
+ if ($user && $user->exists() && !empty($user->get('reset'))) {
+ list($good_token, $expire) = explode('::', $user->get('reset'));
+
+ if ($good_token === $token) {
+ if (time() > $expire) {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.RESET_LINK_EXPIRED'), 'error');
+ $this->setRedirect('/forgot');
+
+ return true;
+ }
+
+ $user->undef('hashed_password');
+ $user->undef('reset');
+ $user->set('password', $password);
+ $user->save();
+
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.RESET_PASSWORD_RESET'), 'info');
+ $this->setRedirect('/');
+
+ return true;
+ }
+ }
+
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.RESET_INVALID_LINK'), 'error');
+ $this->setRedirect('/forgot');
+
+ return true;
+
+ }
+
+ $user = $this->grav['uri']->param('user');
+ $token = $this->grav['uri']->param('token');
+
+ if (empty($user) || empty($token)) {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.RESET_INVALID_LINK'), 'error');
+ $this->setRedirect('/forgot');
+
+ return true;
+ }
+
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.RESET_NEW_PASSWORD'), 'info');
+
+ $this->admin->forgot = ['username' => $user, 'token' => $token];
+
+ return true;
+ }
+
+ /**
+ * Handle the email password recovery procedure.
+ *
+ * @return bool True if the action was performed.
+ */
+ protected function taskForgot()
+ {
+ $param_sep = $this->grav['config']->get('system.param_sep', ':');
+ $post = $this->post;
+ $data = $this->data;
+ $login = $this->grav['login'];
+
+ /** @var UserCollectionInterface $users */
+ $users = $this->grav['accounts'];
+
+ $username = isset($data['username']) ? strip_tags(strtolower($data['username'])) : '';
+ $user = !empty($username) ? $users->load($username) : null;
+
+ if (!isset($this->grav['Email'])) {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_EMAIL_NOT_CONFIGURED'), 'error');
+ $this->setRedirect($post['redirect']);
+
+ return true;
+ }
+
+ if (!$user || !$user->exists()) {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'),
+ 'info');
+ $this->setRedirect($post['redirect']);
+
+ return true;
+ }
+
+ if (empty($user->email)) {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'),
+ 'info');
+ $this->setRedirect($post['redirect']);
+
+ return true;
+ }
+
+ $count = $this->grav['config']->get('plugins.login.max_pw_resets_count', 0);
+ $interval =$this->grav['config']->get('plugins.login.max_pw_resets_interval', 2);
+
+ if ($login->isUserRateLimited($user, 'pw_resets', $count, $interval)) {
+ $this->admin->setMessage($this->admin::translate(['PLUGIN_LOGIN.FORGOT_CANNOT_RESET_IT_IS_BLOCKED', $user->email, $interval]), 'error');
+ $this->setRedirect($post['redirect']);
+
+ return true;
+ }
+
+ $token = md5(uniqid(mt_rand(), true));
+ $expire = time() + 604800; // next week
+
+ $user->set('reset', $token . '::' . $expire);
+ $user->save();
+
+ $author = $this->grav['config']->get('site.author.name', '');
+ $fullname = $user->fullname ?: $username;
+ $reset_link = rtrim($this->grav['uri']->rootUrl(true), '/') . '/' . trim($this->admin->base,
+ '/') . '/reset/task' . $param_sep . 'reset/user' . $param_sep . $username . '/token' . $param_sep . $token . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');
+
+ $sitename = $this->grav['config']->get('site.title', 'Website');
+ $from = $this->grav['config']->get('plugins.email.from');
+
+ if (empty($from)) {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_EMAIL_NOT_CONFIGURED'), 'error');
+ $this->setRedirect($post['redirect']);
+
+ return true;
+ }
+
+ $to = $user->email;
+
+ $subject = $this->admin::translate(['PLUGIN_ADMIN.FORGOT_EMAIL_SUBJECT', $sitename]);
+ $content = $this->admin::translate([
+ 'PLUGIN_ADMIN.FORGOT_EMAIL_BODY',
+ $fullname,
+ $reset_link,
+ $author,
+ $sitename
+ ]);
+
+ $body = $this->grav['twig']->processTemplate('email/base.html.twig', ['content' => $content]);
+
+ $message = $this->grav['Email']->message($subject, $body, 'text/html')->setFrom($from)->setTo($to);
+
+ $sent = $this->grav['Email']->send($message);
+
+ if ($sent < 1) {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_FAILED_TO_EMAIL'), 'error');
+ } else {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'),
+ 'info');
+ }
+
+ $this->setRedirect('/');
+
+ return true;
+ }
+
+ /**
+ * Enable a plugin.
+ *
+ * @return bool True if the action was performed.
+ */
+ public function taskEnable()
+ {
+ if (!$this->authorizeTask('enable plugin', ['admin.plugins', 'admin.super'])) {
+ return false;
+ }
+
+ if ($this->view !== 'plugins') {
+ return false;
+ }
+
+ // Filter value and save it.
+ $this->post = ['enabled' => true];
+ $obj = $this->prepareData($this->post);
+ $obj->save();
+
+ $this->post = ['_redirect' => 'plugins'];
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_ENABLED_PLUGIN'), 'info');
+
+ return true;
+ }
+
+ /**
+ * Gets the configuration data for a given view & post
+ *
+ * @param array $data
+ *
+ * @return object
+ */
+ protected function prepareData(array $data)
+ {
+ $type = trim("{$this->view}/{$this->admin->route}", '/');
+ $data = $this->admin->data($type, $data);
+
+ return $data;
+ }
+
+ /**
+ * Disable a plugin.
+ *
+ * @return bool True if the action was performed.
+ */
+ public function taskDisable()
+ {
+ if (!$this->authorizeTask('disable plugin', ['admin.plugins', 'admin.super'])) {
+ return false;
+ }
+
+ if ($this->view !== 'plugins') {
+ return false;
+ }
+
+ // Filter value and save it.
+ $this->post = ['enabled' => false];
+ $obj = $this->prepareData($this->post);
+ $obj->save();
+
+ $this->post = ['_redirect' => 'plugins'];
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_DISABLED_PLUGIN'), 'info');
+
+ return true;
+ }
+
+ /**
+ * Set the default theme.
+ *
+ * @return bool True if the action was performed.
+ */
+ public function taskActivate()
+ {
+ if (!$this->authorizeTask('activate theme', ['admin.themes', 'admin.super'])) {
+ return false;
+ }
+
+ if ($this->view !== 'themes') {
+ return false;
+ }
+
+ $this->post = ['_redirect' => 'themes'];
+
+ // Make sure theme exists (throws exception)
+ $name = $this->route;
+ $this->grav['themes']->get($name);
+
+ // Store system configuration.
+ $system = $this->admin->data('config/system');
+ $system->set('pages.theme', $name);
+ $system->save();
+
+ // Force configuration reload and save.
+ /** @var Config $config */
+ $config = $this->grav['config'];
+ $config->reload()->save();
+
+ $config->set('system.pages.theme', $name);
+
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_CHANGED_THEME'), 'info');
+
+ return true;
+ }
+
+ /**
+ * Handles updating Grav
+ *
+ * @return bool False if user has no permissions.
+ */
+ public function taskUpdategrav()
+ {
+ if (!$this->authorizeTask('install grav', ['admin.super'])) {
+ return false;
+ }
+
+ $gpm = Gpm::GPM();
+ $version = $gpm->grav->getVersion();
+ $result = Gpm::selfupgrade();
+
+ if ($result) {
+ $json_response = [
+ 'status' => 'success',
+ 'type' => 'updategrav',
+ 'version' => $version,
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.GRAV_WAS_SUCCESSFULLY_UPDATED_TO') . ' ' . $version
+ ];
+ } else {
+ $json_response = [
+ 'status' => 'error',
+ 'type' => 'updategrav',
+ 'version' => GRAV_VERSION,
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.GRAV_UPDATE_FAILED') . ' ' . Installer::lastErrorMsg()
+ ];
+ }
+
+ return $this->sendJsonResponse($json_response);
+ }
+
+ /**
+ * Handles uninstalling plugins and themes
+ *
+ * @deprecated
+ *
+ * @return bool True if the action was performed
+ */
+ public function taskUninstall()
+ {
+ $type = $this->view === 'plugins' ? 'plugins' : 'themes';
+ if (!$this->authorizeTask('uninstall ' . $type, ['admin.' . $type, 'admin.super'])) {
+ return false;
+ }
+
+ $package = $this->route;
+
+ $result = Gpm::uninstall($package, []);
+
+ if ($result) {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.UNINSTALL_SUCCESSFUL'), 'info');
+ } else {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.UNINSTALL_FAILED'), 'error');
+ }
+
+ $this->post = ['_redirect' => $this->view];
+
+ return true;
+ }
+
+ /**
+ * Handles creating an empty page folder (without markdown file)
+ *
+ * @return bool True if the action was performed.
+ */
+ public function taskSaveNewFolder()
+ {
+ if (!$this->authorizeTask('save', $this->dataPermissions())) {
+ return false;
+ }
+
+ $data = (array)$this->data;
+
+ $folder = $data['folder'] ?? '';
+ if ($folder === '' || mb_strpos($folder, '/') !== false) {
+ throw new \RuntimeException('Creating folder failed, bad folder name', 400);
+ }
+
+ if ($data['route'] === '/') {
+ $path = $this->grav['locator']->findResource('page://');
+ } else {
+ $path = $this->grav['page']->find($data['route'])->path();
+ }
+
+ $orderOfNewFolder = static::getNextOrderInFolder($path);
+ $new_path = $path . '/' . $orderOfNewFolder . '.' . $folder;
+
+ Folder::create($new_path);
+ Cache::clearCache('invalidate');
+
+ $this->grav->fireEvent('onAdminAfterSaveAs', new Event(['path' => $new_path]));
+
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_SAVED'), 'info');
+
+ $multilang = $this->isMultilang();
+ $admin_route = $this->admin->base;
+ $redirect_url = '/' . ($multilang ? ($this->grav['session']->admin_lang) : '') . $admin_route . '/' . $this->view;
+ $this->setRedirect($redirect_url);
+
+ return true;
+ }
+
+ /**
+ * Get the next available ordering number in a folder
+ *
+ * @param string $path
+ *
+ * @return string the correct order string to prepend
+ */
+ public static function getNextOrderInFolder($path)
+ {
+ $files = Folder::all($path, ['recursive' => false]);
+
+ $highestOrder = 0;
+ foreach ($files as $file) {
+ preg_match(PAGE_ORDER_PREFIX_REGEX, $file, $order);
+
+ if (isset($order[0])) {
+ $theOrder = (int)trim($order[0], '.');
+ } else {
+ $theOrder = 0;
+ }
+
+ if ($theOrder >= $highestOrder) {
+ $highestOrder = $theOrder;
+ }
+ }
+
+ $orderOfNewFolder = $highestOrder + 1;
+
+ if ($orderOfNewFolder < 10) {
+ $orderOfNewFolder = '0' . $orderOfNewFolder;
+ }
+
+ return $orderOfNewFolder;
+ }
+
+ /**
+ * Handles form and saves the input data if its valid.
+ *
+ * @return bool True if the action was performed.
+ */
+ public function taskSave()
+ {
+ if (!$this->authorizeTask('save', $this->dataPermissions())) {
+ return false;
+ }
+
+ $this->grav['twig']->twig_vars['current_form_data'] = (array)$this->data;
+
+ switch ($this->view) {
+ case 'pages':
+ return $this->taskSavePage();
+ case 'user':
+ return $this->taskSaveUser();
+ default:
+ return $this->taskSaveDefault();
+ }
+ }
+
+ protected function taskSavePage()
+ {
+ $reorder = true;
+
+ $data = (array)$this->data;
+ $this->grav['twig']->twig_vars['current_form_data'] = $data;
+
+ /** @var Pages $pages */
+ $pages = $this->grav['pages'];
+
+ // Find new parent page in order to build the path.
+ $route = $data['route'] ?? dirname($this->admin->route);
+
+ /** @var PageInterface $obj */
+ $obj = $this->admin->page(true);
+
+ $folder = $data['folder'] ?? null;
+ if ($folder === '' || mb_strpos($folder, '/') !== false) {
+ throw new \RuntimeException('Saving page failed: bad folder name', 400);
+ }
+
+ if (!isset($data['folder']) || !$data['folder']) {
+ $data['folder'] = $obj->slug();
+ $this->data['folder'] = $obj->slug();
+ }
+
+ // Ensure route is prefixed with a forward slash.
+ $route = '/' . ltrim($route, '/');
+
+ // Check for valid frontmatter
+ if (isset($data['frontmatter']) && !$this->checkValidFrontmatter($data['frontmatter'])) {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.INVALID_FRONTMATTER_COULD_NOT_SAVE'),
+ 'error');
+ return false;
+ }
+
+ // XSS Checks for page content
+ $xss_whitelist = $this->grav['config']->get('security.xss_whitelist', 'admin.super');
+ if (!$this->admin->authorize($xss_whitelist)) {
+ $check_what = ['header' => $data['header'] ?? '', 'frontmatter' => $data['frontmatter'] ?? '', 'content' => $data['content'] ?? ''];
+ $results = Security::detectXssFromArray($check_what);
+ if (!empty($results)) {
+ $this->admin->setMessage(' ' . $this->admin::translate('PLUGIN_ADMIN.XSS_ONSAVE_ISSUE'),
+ 'error');
+ return false;
+ }
+ }
+
+ $parent = $route && $route !== '/' && $route !== '.' && $route !== '/.' ? $pages->dispatch($route, true) : $pages->root();
+ $original_order = (int)trim($obj->order(), '.');
+
+ try {
+ // Change parent if needed and initialize move (might be needed also on ordering/folder change).
+ $obj = $obj->move($parent);
+ $this->preparePage($obj, false, $obj->language());
+ $obj->validate();
+
+ } catch (\Exception $e) {
+ $this->admin->setMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+ $obj->filter();
+
+ // rename folder based on visible
+ if ($original_order === 1000) {
+ // increment order to force reshuffle
+ $obj->order($original_order + 1);
+ }
+
+ if (isset($data['order']) && !empty($data['order'])) {
+ $reorder = explode(',', $data['order']);
+ }
+
+ // add or remove numeric prefix based on ordering value
+ if (isset($data['ordering'])) {
+ if ($data['ordering'] && !$obj->order()) {
+ $obj->order(static::getNextOrderInFolder($obj->parent()->path()));
+ $reorder = false;
+ } elseif (!$data['ordering'] && $obj->order()) {
+ $obj->folder($obj->slug());
+ }
+ }
+
+ $obj = $this->storeFiles($obj);
+
+ if ($obj) {
+ // Event to manipulate data before saving the object
+ $this->grav->fireEvent('onAdminSave', new Event(['object' => &$obj]));
+ $obj->save($reorder);
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_SAVED'), 'info');
+ $this->grav->fireEvent('onAdminAfterSave', new Event(['object' => $obj]));
+ }
+
+ if (method_exists($obj, 'unsetRouteSlug')) {
+ $obj->unsetRouteSlug();
+ }
+
+ $multilang = $this->isMultilang();
+
+ if ($multilang) {
+ if (!$obj->language()) {
+ $obj->language($this->grav['session']->admin_lang);
+ }
+ }
+ $admin_route = $this->admin->base;
+
+ $route = $obj->rawRoute();
+ $redirect_url = ($multilang ? '/' . $obj->language() : '') . $admin_route . '/' . $this->view . $route;
+ $this->setRedirect($redirect_url);
+
+ return true;
+ }
+
+ protected function taskSaveUser()
+ {
+ /** @var UserCollectionInterface $users */
+ $users = $this->grav['accounts'];
+
+ $user = $users->load($this->admin->route);
+
+ if (!$this->admin->authorize(['admin.super', 'admin.users'])) {
+ // no user file or not admin.super or admin.users
+ if ($user->username !== $this->grav['user']->username) {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' save.','error');
+
+ return false;
+ }
+ }
+
+ /** @var Data\Blueprint $blueprint */
+ $blueprint = $user->blueprints();
+ $data = $blueprint->processForm($this->admin->cleanUserPost((array)$this->data));
+ $data = new Data\Data($data, $blueprint);
+
+ try {
+ $data->validate();
+ $data->filter();
+ } catch (\Exception $e) {
+ $this->admin->setMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+
+ $user->update($data->toArray());
+
+ $user = $this->storeFiles($user);
+
+ if ($user) {
+ // Event to manipulate data before saving the object
+ $this->grav->fireEvent('onAdminSave', new Event(['object' => &$user]));
+ $user->save();
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_SAVED'), 'info');
+ $this->grav->fireEvent('onAdminAfterSave', new Event(['object' => $user]));
+ }
+
+ if ($user->username === $this->grav['user']->username) {
+ /** @var UserCollectionInterface $users */
+ $users = $this->grav['accounts'];
+
+ //Editing current user. Reload user object
+ $this->grav['user']->undef('avatar');
+ $this->grav['user']->merge($users->load($this->admin->route)->toArray());
+ }
+
+ return true;
+ }
+
+ protected function taskSaveDefault()
+ {
+ // Handle standard data types.
+ $obj = $this->prepareData((array)$this->data);
+
+ try {
+ $obj->validate();
+ } catch (\Exception $e) {
+ $this->admin->setMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+
+ $obj->filter();
+
+ $obj = $this->storeFiles($obj);
+
+ if ($obj) {
+ // Event to manipulate data before saving the object
+ $this->grav->fireEvent('onAdminSave', new Event(['object' => &$obj]));
+ $obj->save();
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_SAVED'), 'info');
+ $this->grav->fireEvent('onAdminAfterSave', new Event(['object' => $obj]));
+ }
+
+ // Force configuration reload.
+ /** @var Config $config */
+ $config = $this->grav['config'];
+ $config->reload();
+
+ return true;
+ }
+
+ /**
+ * @param string $frontmatter
+ *
+ * @return bool
+ */
+ public function checkValidFrontmatter($frontmatter)
+ {
+ try {
+ Yaml::parse($frontmatter);
+ } catch (\RuntimeException $e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Continue to the new page.
+ *
+ * @return bool True if the action was performed.
+ */
+ public function taskContinue()
+ {
+ $data = (array)$this->data;
+
+ if ($this->view === 'users') {
+ $username = strip_tags(strtolower($data['username']));
+ $this->setRedirect("{$this->view}/{$username}");
+
+ return true;
+ }
+
+ if ($this->view === 'groups') {
+ $this->setRedirect("{$this->view}/{$data['groupname']}");
+
+ return true;
+ }
+
+ if ($this->view !== 'pages') {
+ return false;
+ }
+
+ $route = $data['route'] !== '/' ? $data['route'] : '';
+ $folder = $data['folder'];
+ // Handle @slugify-{field} value, automatically slugifies the specified field
+ if (0 === strpos($folder, '@slugify-')) {
+ $folder = \Grav\Plugin\Admin\Utils::slug($data[substr($folder, 9)]);
+ }
+ $folder = ltrim($folder, '_');
+ if ($folder === '' || mb_strpos($folder, '/') !== false) {
+ throw new \RuntimeException('Creating page failed: bad folder name', 400);
+ }
+
+ if (!empty($data['modular'])) {
+ $folder = '_' . $folder;
+ }
+ $path = $route . '/' . $folder;
+
+ $this->admin->session()->{$path} = $data;
+
+ // Store the name and route of a page, to be used pre-filled defaults of the form in the future
+ $this->admin->session()->lastPageName = $data['name'];
+ $this->admin->session()->lastPageRoute = $data['route'];
+
+ $this->setRedirect("{$this->view}/" . ltrim($path, '/'));
+
+ return true;
+ }
+
+ /**
+ * Toggle the gpm.releases setting
+ */
+ protected function taskGpmRelease()
+ {
+ if (!$this->authorizeTask('configuration', ['admin.configuration', 'admin.super'])) {
+ return false;
+ }
+
+ // Default release state
+ $release = 'stable';
+ $reload = false;
+
+ // Get the testing release value if set
+ if ($this->post['release'] === 'testing') {
+ $release = 'testing';
+ }
+
+ $config = $this->grav['config'];
+ $current_release = $config->get('system.gpm.releases');
+
+ // If the releases setting is different, save it in the system config
+ if ($current_release !== $release) {
+ $data = new Data\Data($config->get('system'));
+ $data->set('gpm.releases', $release);
+
+ // Get the file location
+ $file = CompiledYamlFile::instance($this->grav['locator']->findResource('config://system.yaml'));
+ $data->file($file);
+
+ // Save the configuration
+ $data->save();
+ $config->reload();
+ $reload = true;
+ }
+
+ $this->admin->json_response = ['status' => 'success', 'reload' => $reload];
+
+ return true;
+ }
+
+ /**
+ * Keep alive
+ */
+ protected function taskKeepAlive()
+ {
+ exit();
+ }
+
+ /**
+ * Get Notifications
+ *
+ */
+ protected function taskGetNotifications()
+ {
+ if (!$this->authorizeTask('dashboard', ['admin.login', 'admin.super'])) {
+ $this->sendJsonResponse(['status' => 'error', 'message' => 'unauthorized']);
+ }
+
+ // do we need to force a reload
+ $refresh = $this->data['refresh'] === 'true';
+ $filter = $this->data['filter'] ?? '';
+ $filter_types = !empty($filter) ? array_map('trim', explode(',', $filter)) : [];
+
+ try {
+ $notifications = $this->admin->getNotifications($refresh);
+ $notification_data = [];
+
+ foreach ($notifications as $type => $type_notifications) {
+ if ($filter_types && in_array($type, $filter_types, true)) {
+ $twig_template = 'partials/notification-' . $type . '-block.html.twig';
+ $notification_data[$type] = $this->grav['twig']->processTemplate($twig_template, ['notifications' => $type_notifications]);
+ }
+ }
+
+ $json_response = [
+ 'status' => 'success',
+ 'notifications' => $notification_data
+ ];
+ } catch (\Exception $e) {
+ $json_response = ['status' => 'error', 'message' => $e->getMessage()];
+ }
+
+ $this->sendJsonResponse($json_response);
+ }
+
+ /** Get Newsfeeds */
+ protected function taskGetNewsFeed()
+ {
+ if (!$this->authorizeTask('dashboard', ['admin.login', 'admin.super'])) {
+ $this->sendJsonResponse(['status' => 'error', 'message' => 'unauthorized']);
+ }
+
+ $refresh = $this->data['refresh'] === 'true' ? true : false;
+
+ try {
+ $feed = $this->admin->getFeed($refresh);
+ $feed_data = $this->grav['twig']->processTemplate('partials/feed-block.html.twig', ['feed' => $feed]);
+
+ $json_response = [
+ 'status' => 'success',
+ 'feed_data' => $feed_data
+ ];
+ } catch (MalformedXmlException $e) {
+ $json_response = ['status' => 'error', 'message' => $e->getMessage()];
+ }
+
+ $this->sendJsonResponse($json_response);
+ }
+
+ /**
+ * Get update status from GPM
+ */
+ protected function taskGetUpdates()
+ {
+ if (!$this->authorizeTask('dashboard', ['admin.login', 'admin.super'])) {
+ return false;
+ }
+
+ $data = $this->post;
+ $flush = !empty($data['flush']);
+
+ if (isset($this->grav['session'])) {
+ $this->grav['session']->close();
+ }
+
+ try {
+ $gpm = new GravGPM($flush);
+
+ $resources_updates = $gpm->getUpdatable();
+ foreach ($resources_updates as $key => $update) {
+ if (!is_iterable($update)) {
+ continue;
+ }
+
+ foreach ($update as $slug => $item) {
+ $resources_updates[$key][$slug] = $item;
+ }
+ }
+
+ if ($gpm->grav !== null) {
+ $grav_updates = [
+ 'isUpdatable' => $gpm->grav->isUpdatable(),
+ 'assets' => $gpm->grav->getAssets(),
+ 'version' => GRAV_VERSION,
+ 'available' => $gpm->grav->getVersion(),
+ 'date' => $gpm->grav->getDate(),
+ 'isSymlink' => $gpm->grav->isSymlink()
+ ];
+
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'payload' => [
+ 'resources' => $resources_updates,
+ 'grav' => $grav_updates,
+ 'installed' => $gpm->countInstalled(),
+ 'flushed' => $flush
+ ]
+ ];
+ } else {
+ $this->admin->json_response = ['status' => 'error', 'message' => 'Cannot connect to the GPM'];
+
+ return false;
+ }
+
+ } catch (\Exception $e) {
+ $this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Handle getting a new package dependencies needed to be installed
+ *
+ * @return bool
+ */
+ protected function taskGetPackagesDependencies()
+ {
+ $data = $this->post;
+ $packages = isset($data['packages']) ? explode(',', $data['packages']) : '';
+ $packages = (array)$packages;
+
+ try {
+ $this->admin->checkPackagesCanBeInstalled($packages);
+ $dependencies = $this->admin->getDependenciesNeededToInstall($packages);
+ } catch (\Exception $e) {
+ $this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
+
+ return false;
+ }
+
+ $this->admin->json_response = ['status' => 'success', 'dependencies' => $dependencies];
+
+ return true;
+ }
+
+ protected function taskInstallDependenciesOfPackages()
+ {
+ $data = $this->post;
+ $packages = isset($data['packages']) ? explode(',', $data['packages']) : '';
+ $packages = (array)$packages;
+
+ $type = $data['type'] ?? '';
+
+ if (!$this->authorizeTask('install ' . $type, ['admin.' . $type, 'admin.super'])) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK')
+ ];
+
+ return false;
+ }
+
+ try {
+ $dependencies = $this->admin->getDependenciesNeededToInstall($packages);
+ } catch (\Exception $e) {
+ $this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
+
+ return false;
+ }
+
+ $result = Gpm::install(array_keys($dependencies), ['theme' => $type === 'theme']);
+
+ if ($result) {
+ $this->admin->json_response = ['status' => 'success', 'message' => 'Dependencies installed successfully'];
+ } else {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.INSTALLATION_FAILED')
+ ];
+ }
+
+ return true;
+ }
+
+ protected function taskInstallPackage($reinstall = false)
+ {
+ $data = $this->post;
+ $package = $data['package'] ?? '';
+ $type = $data['type'] ?? '';
+
+ if (!$this->authorizeTask('install ' . $type, ['admin.' . $type, 'admin.super'])) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK')
+ ];
+
+ return false;
+ }
+
+ try {
+ $result = Gpm::install($package, ['theme' => $type === 'theme']);
+ } catch (\Exception $e) {
+ $msg = $e->getMessage();
+ $msg = Utils::contains($msg, '401 Unauthorized') ? "ERROR: Authorization failed for this resource" : $msg;
+ $msg = Utils::contains($msg, '404 Not Found') ? "ERROR: Resource not found" : $msg;
+
+ $this->admin->json_response = ['status' => 'error', 'message' => $msg];
+
+ return false;
+ }
+
+ if ($result) {
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'message' => $this->admin::translate(is_string($result) ? $result : sprintf($this->admin::translate($reinstall ?: 'PLUGIN_ADMIN.PACKAGE_X_REINSTALLED_SUCCESSFULLY',
+ null), $package))
+ ];
+ } else {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate($reinstall ?: 'PLUGIN_ADMIN.INSTALLATION_FAILED')
+ ];
+ }
+
+ return true;
+ }
+
+ /**
+ * Handle removing a package
+ *
+ * @return bool
+ */
+ protected function taskRemovePackage()
+ {
+ $data = $this->post;
+ $package = $data['package'] ?? '';
+ $type = $data['type'] ?? '';
+
+ if (!$this->authorizeTask('uninstall ' . $type, ['admin.' . $type, 'admin.super'])) {
+ $json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK')
+ ];
+
+ return $this->sendJsonResponse($json_response, 403);
+ }
+
+ //check if there are packages that have this as a dependency. Abort and show which ones
+ $dependent_packages = $this->admin->getPackagesThatDependOnPackage($package);
+ if (count($dependent_packages) > 0) {
+ if (count($dependent_packages) > 1) {
+ $message = 'The installed packages ' . implode(' , ',
+ $dependent_packages) . ' depends on this package. Please remove those first.';
+ } else {
+ $message = 'The installed package ' . implode(' , ',
+ $dependent_packages) . ' depends on this package. Please remove it first.';
+ }
+
+ $json_response = ['status' => 'error', 'message' => $message];
+
+ return $this->sendJsonResponse($json_response, 200);
+ }
+
+ try {
+ $dependencies = $this->admin->dependenciesThatCanBeRemovedWhenRemoving($package);
+ $result = Gpm::uninstall($package, []);
+ } catch (\Exception $e) {
+ $json_response = ['status' => 'error', 'message' => $e->getMessage()];
+
+ return $this->sendJsonResponse($json_response, 200);
+ }
+
+ if ($result) {
+ $json_response = [
+ 'status' => 'success',
+ 'dependencies' => $dependencies,
+ 'message' => $this->admin::translate(is_string($result) ? $result : 'PLUGIN_ADMIN.UNINSTALL_SUCCESSFUL')
+ ];
+
+ return $this->sendJsonResponse($json_response, 200);
+ }
+
+ $json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.UNINSTALL_FAILED')
+ ];
+
+ return $this->sendJsonResponse($json_response, 200);
+ }
+
+ /**
+ * Handle reinstalling a package
+ */
+ protected function taskReinstallPackage()
+ {
+ $data = $this->post;
+
+ $slug = $data['slug'] ?? '';
+ $type = $data['type'] ?? '';
+ $package_name = $data['package_name'] ?? '';
+ $current_version = $data['current_version'] ?? '';
+
+ if (!$this->authorizeTask('install ' . $type, ['admin.' . $type, 'admin.super'])) {
+ $json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK')
+ ];
+
+ $this->sendJsonResponse($json_response, 403);
+ }
+
+ $url = "https://getgrav.org/download/{$type}s/$slug/$current_version";
+
+ $result = Gpm::directInstall($url);
+
+ if ($result === true) {
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'message' => $this->admin::translate(sprintf($this->admin::translate('PLUGIN_ADMIN.PACKAGE_X_REINSTALLED_SUCCESSFULLY',
+ null), $package_name))
+ ];
+ } else {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.REINSTALLATION_FAILED')
+ ];
+ }
+ }
+
+ /**
+ * Clear the cache.
+ *
+ * @return bool True if the action was performed.
+ */
+ protected function taskClearCache()
+ {
+ if (!$this->authorizeTask('clear cache', ['admin.cache', 'admin.super', 'admin.maintenance'])) {
+ return false;
+ }
+
+ // get optional cleartype param
+ $clear_type = $this->grav['uri']->param('cleartype');
+
+ if ($clear_type) {
+ $clear = $clear_type;
+ } else {
+ $clear = 'standard';
+ }
+
+ if ($clear === 'purge') {
+ $msg = Cache::purgeJob();
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'message' => $msg,
+ ];
+ } else {
+ $results = Cache::clearCache($clear);
+ if (count($results) > 0) {
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.CACHE_CLEARED') . ' ' . $this->admin::translate('PLUGIN_ADMIN.METHOD') . ': ' . $clear . ''
+ ];
+ } else {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.ERROR_CLEARING_CACHE')
+ ];
+ }
+ }
+
+
+
+ return true;
+ }
+
+ /**
+ * Clear the cache.
+ *
+ * @return bool True if the action was performed.
+ */
+ protected function taskHideNotification()
+ {
+ if (!$this->authorizeTask('hide notification', ['admin.login'])) {
+ return false;
+ }
+
+ $notification_id = $this->grav['uri']->param('notification_id');
+
+ if (!$notification_id) {
+ $this->admin->json_response = [
+ 'status' => 'error'
+ ];
+
+ return false;
+ }
+
+ $filename = $this->grav['locator']->findResource('user://data/notifications/' . $this->grav['user']->username . YAML_EXT,
+ true, true);
+ $file = CompiledYamlFile::instance($filename);
+ $data = (array)$file->content();
+ $data[] = $notification_id;
+ $file->save($data);
+
+ $this->admin->json_response = [
+ 'status' => 'success'
+ ];
+
+ return true;
+ }
+
+ /**
+ * Handle the backup action
+ *
+ * @return bool True if the action was performed.
+ */
+ protected function taskBackup()
+ {
+ $param_sep = $this->grav['config']->get('system.param_sep', ':');
+ if (!$this->authorizeTask('backup', ['admin.maintenance', 'admin.super'])) {
+ return false;
+ }
+
+ $download = $this->grav['uri']->param('download');
+
+ try {
+ if ($download) {
+ $file = base64_decode(urldecode($download));
+ $backups_root_dir = $this->grav['locator']->findResource('backup://', true);
+
+ if (0 !== strpos($file, $backups_root_dir)) {
+ header('HTTP/1.1 401 Unauthorized');
+ exit();
+ }
+
+ Utils::download($file, true);
+ }
+
+ $id = $this->grav['uri']->param('id', 0);
+ $backup = Backups::backup($id);
+ } catch (\Exception $e) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.AN_ERROR_OCCURRED') . '. ' . $e->getMessage()
+ ];
+
+ return true;
+ }
+
+ $download = urlencode(base64_encode($backup));
+ $url = rtrim($this->grav['uri']->rootUrl(false), '/') . '/' . trim($this->admin->base,
+ '/') . '/task' . $param_sep . 'backup/download' . $param_sep . $download . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');
+
+
+
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.YOUR_BACKUP_IS_READY_FOR_DOWNLOAD') . '. ' . $this->admin::translate('PLUGIN_ADMIN.DOWNLOAD_BACKUP') . ' ',
+ 'toastr' => [
+ 'timeOut' => 0,
+ 'extendedTimeOut' => 0,
+ 'closeButton' => true
+ ]
+ ];
+
+ return true;
+ }
+
+ /**
+ * Handle delete backup action
+ *
+ * @return bool
+ */
+ protected function taskBackupDelete()
+ {
+ $param_sep = $this->grav['config']->get('system.param_sep', ':');
+ if (!$this->authorizeTask('backup', ['admin.maintenance', 'admin.super'])) {
+ return false;
+ }
+
+ $backup = $this->grav['uri']->param('backup', null);
+
+ if (null !== $backup) {
+ $file = base64_decode(urldecode($backup));
+ $backups_root_dir = $this->grav['locator']->findResource('backup://', true);
+
+ $backup_path = $backups_root_dir . '/' . $file;
+
+ if (file_exists($backup_path)) {
+ unlink($backup_path);
+
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.BACKUP_DELETED'),
+ 'toastr' => [
+ 'closeButton' => true
+ ]
+ ];
+ } else {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.BACKUP_NOT_FOUND'),
+ ];
+ }
+ }
+ return true;
+ }
+
+ protected function taskGetChildTypes()
+ {
+ if (!$this->authorizeTask('get childtypes', ['admin.pages', 'admin.super'])) {
+ return false;
+ }
+
+ $data = $this->post;
+
+ $rawroute = $data['rawroute'] ?? null;
+
+ if ($rawroute) {
+ /** @var PageInterface $page */
+ $page = $this->grav['pages']->dispatch($rawroute);
+
+ if ($page) {
+ $child_type = $page->childType();
+
+ if ($child_type !== '') {
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'child_type' => $child_type
+ ];
+ return true;
+ }
+ }
+ }
+
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'child_type' => '',
+// 'message' => $this->admin::translate('PLUGIN_ADMIN.NO_CHILD_TYPE')
+ ];
+
+ return true;
+ }
+
+ /**
+ * Handles filtering the page by modular/visible/routable in the pages list.
+ */
+ protected function taskFilterPages()
+ {
+ if (!$this->authorizeTask('filter pages', ['admin.pages', 'admin.super'])) {
+ return;
+ }
+
+ $data = $this->post;
+
+ $flags = !empty($data['flags']) ? array_map('strtolower', explode(',', $data['flags'])) : [];
+ $queries = !empty($data['query']) ? explode(',', $data['query']) : [];
+
+ /** @var Collection $collection */
+ $collection = $this->grav['pages']->all();
+
+ if (count($flags)) {
+ // Filter by state
+ $pageStates = [
+ 'modular',
+ 'nonmodular',
+ 'visible',
+ 'nonvisible',
+ 'routable',
+ 'nonroutable',
+ 'published',
+ 'nonpublished'
+ ];
+
+ if (count(array_intersect($pageStates, $flags)) > 0) {
+ if (in_array('modular', $flags, true)) {
+ $collection = $collection->modular();
+ }
+
+ if (in_array('nonmodular', $flags, true)) {
+ $collection = $collection->nonModular();
+ }
+
+ if (in_array('visible', $flags, true)) {
+ $collection = $collection->visible();
+ }
+
+ if (in_array('nonvisible', $flags, true)) {
+ $collection = $collection->nonVisible();
+ }
+
+ if (in_array('routable', $flags, true)) {
+ $collection = $collection->routable();
+ }
+
+ if (in_array('nonroutable', $flags, true)) {
+ $collection = $collection->nonRoutable();
+ }
+
+ if (in_array('published', $flags, true)) {
+ $collection = $collection->published();
+ }
+
+ if (in_array('nonpublished', $flags, true)) {
+ $collection = $collection->nonPublished();
+ }
+ }
+ foreach ($pageStates as $pageState) {
+ if (($pageState = array_search($pageState, $flags, true)) !== false) {
+ unset($flags[$pageState]);
+ }
+ }
+
+ // Filter by page type
+ if ($flags) {
+ $types = [];
+
+ $pageTypes = array_keys(Pages::pageTypes());
+ foreach ($pageTypes as $pageType) {
+ if (($pageKey = array_search($pageType, $flags, true)) !== false) {
+ $types[] = $pageType;
+ unset($flags[$pageKey]);
+ }
+ }
+
+ if (count($types)) {
+ $collection = $collection->ofOneOfTheseTypes($types);
+ }
+ }
+
+ // Filter by page type
+ if ($flags) {
+ $accessLevels = $flags;
+ $collection = $collection->ofOneOfTheseAccessLevels($accessLevels);
+ }
+ }
+
+ if (!empty($queries)) {
+ foreach ($collection as $page) {
+ foreach ($queries as $query) {
+ $query = trim($query);
+ if (stripos($page->getRawContent(), $query) === false
+ && stripos($page->title(), $query) === false
+ && stripos($page->folder(), $query) === false
+ && stripos($page->slug(), \Grav\Plugin\Admin\Utils::slug($query)) === false
+ ) {
+ $collection->remove($page);
+ }
+ }
+ }
+ }
+
+ $results = [];
+ foreach ($collection as $path => $page) {
+ $results[] = $page->route();
+ }
+
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.PAGES_FILTERED'),
+ 'results' => $results
+ ];
+ $this->admin->collection = $collection;
+ }
+
+ /**
+ * Determines the file types allowed to be uploaded
+ *
+ * @return bool True if the action was performed.
+ */
+ protected function taskListmedia()
+ {
+ if (!$this->authorizeTask('list media', ['admin.pages', 'admin.super'])) {
+ return false;
+ }
+
+ $media = $this->getMedia();
+ if (!$media) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.NO_PAGE_FOUND')
+ ];
+
+ return false;
+ }
+
+ $media_list = [];
+ /**
+ * @var string $name
+ * @var Medium|ImageMedium $medium
+ */
+ foreach ($media->all() as $name => $medium) {
+
+ $metadata = [];
+ $img_metadata = $medium->metadata();
+ if ($img_metadata) {
+ $metadata = $img_metadata;
+ }
+
+ // Get original name
+ /** @var ImageMedium $source */
+ $source = method_exists($medium, 'higherQualityAlternative') ? $medium->higherQualityAlternative() : null;
+
+ $media_list[$name] = [
+ 'url' => $medium->display($medium->get('extension') === 'svg' ? 'source' : 'thumbnail')->cropZoom(400, 300)->url(),
+ 'size' => $medium->get('size'),
+ 'metadata' => $metadata,
+ 'original' => $source ? $source->get('filename') : null
+ ];
+ }
+
+ $this->admin->json_response = ['status' => 'success', 'results' => $media_list];
+
+ return true;
+ }
+
+ /**
+ * @return Media
+ */
+ protected function getMedia()
+ {
+ $this->uri = $this->uri ?? $this->grav['uri'];
+ $uri = $this->uri->post('uri');
+ $order = $this->uri->post('order') ?: null;
+
+ if ($uri) {
+ /** @var UniformResourceLocator $locator */
+ $locator = $this->grav['locator'];
+
+ $media_path = $locator->isStream($uri) ? $uri : null;
+ } else {
+ $page = $this->admin->page(true);
+
+ $media_path = $page ? $page->path() : null;
+ }
+ if ($order) {
+ $order = array_map('trim', explode(',', $order));
+ }
+
+ return $media_path ? new Media($media_path, $order) : null;
+ }
+
+ /**
+ * Handles adding a media file to a page
+ *
+ * @return bool True if the action was performed.
+ */
+ protected function taskAddmedia()
+ {
+ if (!$this->authorizeTask('add media', ['admin.pages', 'admin.super'])) {
+ return false;
+ }
+
+ /** @var Config $config */
+ $config = $this->grav['config'];
+
+ if (empty($_FILES)) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.EXCEEDED_POSTMAX_LIMIT')
+ ];
+
+ return false;
+ }
+
+ if (!isset($_FILES['file']['error']) || is_array($_FILES['file']['error'])) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.INVALID_PARAMETERS')
+ ];
+
+ return false;
+ }
+
+ // Check $_FILES['file']['error'] value.
+ switch ($_FILES['file']['error']) {
+ case UPLOAD_ERR_OK:
+ break;
+ case UPLOAD_ERR_NO_FILE:
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.NO_FILES_SENT')
+ ];
+
+ return false;
+ case UPLOAD_ERR_INI_SIZE:
+ case UPLOAD_ERR_FORM_SIZE:
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.EXCEEDED_FILESIZE_LIMIT')
+ ];
+
+ return false;
+ case UPLOAD_ERR_NO_TMP_DIR:
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.UPLOAD_ERR_NO_TMP_DIR')
+ ];
+
+ return false;
+ default:
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.UNKNOWN_ERRORS')
+ ];
+
+ return false;
+ }
+
+ $filename = $_FILES['file']['name'];
+
+ // Handle bad filenames.
+ if (!Utils::checkFilename($filename)) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD'),
+ $filename, 'Bad filename')
+ ];
+
+ return false;
+ }
+
+ // You should also check filesize here.
+ $grav_limit = Utils::getUploadLimit();
+ if ($grav_limit > 0 && $_FILES['file']['size'] > $grav_limit) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT')
+ ];
+
+ return false;
+ }
+
+
+ // Check extension
+ $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
+
+ // If not a supported type, return
+ if (!$extension || !$config->get("media.types.{$extension}")) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.UNSUPPORTED_FILE_TYPE') . ': ' . $extension
+ ];
+
+ return false;
+ }
+
+
+ $media = $this->getMedia();
+ if (!$media) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.NO_PAGE_FOUND')
+ ];
+
+ return false;
+ }
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $this->grav['locator'];
+ $path = $media->getPath();
+ if ($locator->isStream($path)) {
+ $path = $locator->findResource($path, true, true);
+ }
+
+ // Upload it
+ if (!move_uploaded_file($_FILES['file']['tmp_name'], sprintf('%s/%s', $path, $filename))) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.FAILED_TO_MOVE_UPLOADED_FILE')
+ ];
+
+ return false;
+ }
+
+ // Add metadata if needed
+ $include_metadata = Grav::instance()['config']->get('system.media.auto_metadata_exif', false);
+ $basename = str_replace(['@3x', '@2x'], '', pathinfo($filename, PATHINFO_BASENAME));
+
+ $metadata = [];
+
+ if ($include_metadata && isset($media[$basename])) {
+ $img_metadata = $media[$basename]->metadata();
+ if ($img_metadata) {
+ $metadata = $img_metadata;
+ }
+ }
+
+ $page = $this->admin->page(true);
+ if ($page) {
+ $this->grav->fireEvent('onAdminAfterAddMedia', new Event(['page' => $page]));
+ }
+
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_UPLOADED_SUCCESSFULLY'),
+ 'metadata' => $metadata,
+ ];
+
+ return true;
+ }
+
+ /**
+ * Handles deleting a media file from a page
+ *
+ * @return bool True if the action was performed.
+ */
+ protected function taskDelmedia()
+ {
+ if (!$this->authorizeTask('delete media', ['admin.pages', 'admin.super'])) {
+ return false;
+ }
+
+ $media = $this->getMedia();
+ if (!$media) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.NO_PAGE_FOUND')
+ ];
+
+ return false;
+ }
+
+ $filename = !empty($this->post['filename']) ? $this->post['filename'] : null;
+
+ // Handle bad filenames.
+ if (!Utils::checkFilename($filename)) {
+ $filename = null;
+ }
+
+ if (!$filename) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.NO_FILE_FOUND')
+ ];
+
+ return false;
+ }
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $this->grav['locator'];
+
+ $targetPath = $media->getPath() . '/' . $filename;
+ if ($locator->isStream($targetPath)) {
+ $targetPath = $locator->findResource($targetPath, true, true);
+ }
+ $fileParts = pathinfo($filename);
+
+ $found = false;
+
+ if (file_exists($targetPath)) {
+ $found = true;
+ $result = unlink($targetPath);
+
+ if (!$result) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . $filename
+ ];
+
+ return false;
+ }
+ }
+
+ // Remove Extra Files
+ foreach (scandir($media->getPath(), SCANDIR_SORT_NONE) as $file) {
+ if (preg_match("/{$fileParts['filename']}@\d+x\.{$fileParts['extension']}(?:\.meta\.yaml)?$|{$filename}\.meta\.yaml$/", $file)) {
+
+ $targetPath = $media->getPath() . '/' . $file;
+ if ($locator->isStream($targetPath)) {
+ $targetPath = $locator->findResource($targetPath, true, true);
+ }
+
+ $result = unlink($targetPath);
+
+ if (!$result) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . $filename
+ ];
+
+ return false;
+ }
+
+ $found = true;
+ }
+ }
+
+ if (!$found) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_NOT_FOUND') . ': ' . $filename
+ ];
+
+ return false;
+ }
+
+ $page = $this->admin->page(true);
+ if ($page) {
+ $this->grav->fireEvent('onAdminAfterDelMedia', new Event(['page' => $page]));
+ }
+
+ $this->admin->json_response = [
+ 'status' => 'success',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_DELETED') . ': ' . $filename
+ ];
+
+ return true;
+ }
+
+ /**
+ * Process the page Markdown
+ *
+ * @return bool True if the action was performed.
+ */
+ protected function taskProcessMarkdown()
+ {
+ if (!$this->authorizeTask('process markdown', ['admin.pages', 'admin.super'])) {
+ return false;
+ }
+
+ try {
+ $page = $this->admin->page(true);
+
+ if (!$page) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.NO_PAGE_FOUND')
+ ];
+
+ return false;
+ }
+
+ $this->preparePage($page, true);
+ $page->header();
+ $page->templateFormat('html');
+
+ // Add theme template paths to Twig loader
+ $template_paths = $this->grav['locator']->findResources('theme://templates');
+ $this->grav['twig']->twig->getLoader()->addLoader(new \Twig_Loader_Filesystem($template_paths));
+
+ $html = $page->content();
+
+ $this->admin->json_response = ['status' => 'success', 'preview' => $html];
+ } catch (\Exception $e) {
+ $this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Prepare a page to be stored: update its folder, name, template, header and content
+ *
+ * @param PageInterface $page
+ * @param bool $clean_header
+ * @param string $language
+ */
+ protected function preparePage(PageInterface $page, $clean_header = false, $language = '')
+ {
+ $input = (array)$this->data;
+
+ if (isset($input['folder']) && $input['folder'] !== $page->value('folder')) {
+ $order = $page->value('order');
+ $ordering = $order ? sprintf('%02d.', $order) : '';
+ $page->folder($ordering . $input['folder']);
+ }
+
+ if (isset($input['name']) && !empty($input['name'])) {
+ $type = strtolower($input['name']);
+ $name = preg_replace('|.*/|', '', $type);
+ if ($language) {
+ $name .= '.' . $language;
+ } else {
+ $language = $this->grav['language'];
+ if ($language->enabled()) {
+ $name .= '.' . $language->getLanguage();
+ }
+ }
+ $name .= '.md';
+ $page->name($name);
+ $page->template($type);
+ }
+
+ // Special case for Expert mode: build the raw, unset content
+ if (isset($input['frontmatter'], $input['content'])) {
+ $page->raw("---\n" . (string)$input['frontmatter'] . "\n---\n" . (string)$input['content']);
+ unset($input['content']);
+ // Handle header normally
+ } elseif (isset($input['header'])) {
+ $header = $input['header'];
+
+ foreach ($header as $key => $value) {
+ if ($key === 'metadata' && is_array($header[$key])) {
+ foreach ($header['metadata'] as $key2 => $value2) {
+ if (isset($input['toggleable_header']['metadata'][$key2]) && !$input['toggleable_header']['metadata'][$key2]) {
+ $header['metadata'][$key2] = '';
+ }
+ }
+ } elseif ($key === 'taxonomy' && is_array($header[$key])) {
+ foreach ($header[$key] as $taxkey => $taxonomy) {
+ if (is_array($taxonomy) && \count($taxonomy) === 1 && trim($taxonomy[0]) === '') {
+ unset($header[$key][$taxkey]);
+ }
+ }
+ } else {
+ if (isset($input['toggleable_header'][$key]) && !$input['toggleable_header'][$key]) {
+ $header[$key] = null;
+ }
+ }
+ }
+ if ($clean_header) {
+ $header = Utils::arrayFilterRecursive($header, function ($k, $v) {
+ return !(null === $v || $v === '');
+ });
+ }
+ $page->header((object)$header);
+ $page->frontmatter(Yaml::dump((array)$page->header(), 20));
+ }
+ // Fill content last because it also renders the output.
+ if (isset($input['content'])) {
+ $page->rawMarkdown((string)$input['content']);
+ }
+ }
+
+ /**
+ * Save page as a new copy.
+ *
+ * @return bool True if the action was performed.
+ * @throws \RuntimeException
+ */
+ protected function taskCopy()
+ {
+ if (!$this->authorizeTask('copy page', ['admin.pages', 'admin.super'])) {
+ return false;
+ }
+
+ // Only applies to pages.
+ if ($this->view !== 'pages') {
+ return false;
+ }
+
+ try {
+ /** @var Pages $pages */
+ $pages = $this->grav['pages'];
+
+ // Get the current page.
+ $original_page = $this->admin->page(true);
+
+ // Find new parent page in order to build the path.
+ $parent = $original_page->parent() ?: $pages->root();
+ // Make a copy of the current page and fill the updated information into it.
+ $page = $original_page->copy($parent);
+
+ $order = 0;
+ if ($page->order()) {
+ $order = $this->getNextOrderInFolder($page->parent()->path());
+ }
+
+ // Make sure the header is loaded in case content was set through raw() (expert mode)
+ $page->header();
+
+ if ($page->order()) {
+ $page->order($order);
+ }
+
+ $folder = $this->findFirstAvailable('folder', $page);
+ $slug = $this->findFirstAvailable('slug', $page);
+
+ $page->path($page->parent()->path() . DS . $page->order() . $folder);
+ $page->route($page->parent()->route() . '/' . $slug);
+ $page->rawRoute($page->parent()->rawRoute() . '/' . $slug);
+
+ // Append progressive number to the copied page title
+ $match = preg_split('/(\d+)(?!.*\d)/', $original_page->title(), 2, PREG_SPLIT_DELIM_CAPTURE);
+ $header = $page->header();
+ if (!isset($match[1])) {
+ $header->title = $match[0] . ' 2';
+ } else {
+ $header->title = $match[0] . ((int)$match[1] + 1);
+ }
+
+ $page->header($header);
+ $page->save(false);
+
+ $redirect = $this->view . $page->rawRoute();
+ $header = $page->header();
+
+ if (isset($header->slug)) {
+ $match = preg_split('/-(\d+)$/', $header->slug, 2, PREG_SPLIT_DELIM_CAPTURE);
+ $header->slug = $match[0] . '-' . (isset($match[1]) ? (int)$match[1] + 1 : 2);
+ }
+
+ $page->header($header);
+
+ $page->save();
+
+ $this->grav->fireEvent('onAdminAfterSave', new Event(['page' => $page]));
+
+ // Enqueue message and redirect to new location.
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_COPIED'), 'info');
+ $this->setRedirect($redirect);
+
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Copying page failed on error: ' . $e->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Find the first available $item ('slug' | 'folder') for a page
+ * Used when copying a page, to determine the first available slot
+ *
+ * @param string $item
+ * @param PageInterface $page
+ *
+ * @return string The first available slot
+ */
+ protected function findFirstAvailable($item, PageInterface $page)
+ {
+ if (!$page->parent()->children()) {
+ return $page->{$item}();
+ }
+
+ $withoutPrefix = function ($string) {
+ $match = preg_split('/^[0-9]+\./u', $string, 2, PREG_SPLIT_DELIM_CAPTURE);
+
+ return $match[1] ?? $match[0];
+ };
+
+ $withoutPostfix = function ($string) {
+ $match = preg_split('/-(\d+)$/', $string, 2, PREG_SPLIT_DELIM_CAPTURE);
+
+ return $match[0];
+ };
+
+ /* $appendedNumber = function ($string) {
+ $match = preg_split('/-(\d+)$/', $string, 2, PREG_SPLIT_DELIM_CAPTURE);
+ $append = (isset($match[1]) ? (int)$match[1] + 1 : 2);
+
+ return $append;
+ };*/
+
+ $highest = 1;
+ $siblings = $page->parent()->children();
+ $findCorrectAppendedNumber = function ($item, $page_item, $highest) use (
+ $siblings,
+ &$findCorrectAppendedNumber,
+ &$withoutPrefix
+ ) {
+ foreach ($siblings as $sibling) {
+ if ($withoutPrefix($sibling->{$item}()) == ($highest === 1 ? $page_item : $page_item . '-' . $highest)) {
+ $highest = $findCorrectAppendedNumber($item, $page_item, $highest + 1);
+
+ return $highest;
+ }
+ }
+
+ return $highest;
+ };
+
+ $base = $withoutPrefix($withoutPostfix($page->$item()));
+
+ $return = $base;
+ $highest = $findCorrectAppendedNumber($item, $base, $highest);
+
+ if ($highest > 1) {
+ $return .= '-' . $highest;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Reorder pages.
+ *
+ * @return bool True if the action was performed.
+ */
+ protected function taskReorder()
+ {
+ if (!$this->authorizeTask('reorder pages', ['admin.pages', 'admin.super'])) {
+ return false;
+ }
+
+ // Only applies to pages.
+ if ($this->view !== 'pages') {
+ return false;
+ }
+
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.REORDERING_WAS_SUCCESSFUL'), 'info');
+
+ return true;
+ }
+
+ /**
+ * Delete page.
+ *
+ * @return bool True if the action was performed.
+ * @throws \RuntimeException
+ */
+ protected function taskDelete()
+ {
+ if (!$this->authorizeTask('delete page', ['admin.pages', 'admin.super'])) {
+ return false;
+ }
+
+ // Only applies to pages.
+ if ($this->view !== 'pages') {
+ return false;
+ }
+
+ try {
+ $page = $this->admin->page();
+
+ if (count($page->translatedLanguages()) > 1) {
+ $page->file()->delete();
+ } else {
+ Folder::delete($page->path());
+ }
+
+ $this->grav->fireEvent('onAdminAfterDelete', new Event(['page' => $page]));
+
+ Cache::clearCache('invalidate');
+
+ // Set redirect to pages list.
+ $redirect = 'pages';
+
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_DELETED'), 'info');
+ $this->setRedirect($redirect);
+
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Deleting page failed on error: ' . $e->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Switch the content language. Optionally redirect to a different page.
+ *
+ */
+ protected function taskSwitchlanguage()
+ {
+ if (!$this->authorizeTask('switch language', ['admin.pages', 'admin.super'])) {
+ return false;
+ }
+
+ $data = (array)$this->data;
+
+ if (isset($data['lang'])) {
+ $language = $data['lang'];
+ } else {
+ $language = $this->grav['uri']->param('lang');
+ }
+
+ if (isset($data['redirect'])) {
+ $redirect = 'pages/' . $data['redirect'];
+ } else {
+ $redirect = 'pages';
+ }
+
+
+ if ($language) {
+ $this->grav['session']->admin_lang = $language ?: 'en';
+ }
+
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_SWITCHED_LANGUAGE'), 'info');
+
+ $admin_route = $this->admin->base;
+ $this->setRedirect('/' . $language . $admin_route . '/' . $redirect);
+
+ return true;
+ }
+
+ /**
+ * Handle direct install.
+ */
+ protected function taskDirectInstall()
+ {
+ if (!$this->authorizeTask('install', ['admin.super'])) {
+ return false;
+ }
+
+ $file_path = $this->data['file_path'] ?? null;
+
+ if (isset($_FILES['uploaded_file'])) {
+
+ // Check $_FILES['file']['error'] value.
+ switch ($_FILES['uploaded_file']['error']) {
+ case UPLOAD_ERR_OK:
+ break;
+ case UPLOAD_ERR_NO_FILE:
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.NO_FILES_SENT'), 'error');
+ return false;
+ case UPLOAD_ERR_INI_SIZE:
+ case UPLOAD_ERR_FORM_SIZE:
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.EXCEEDED_FILESIZE_LIMIT'), 'error');
+ return false;
+ case UPLOAD_ERR_NO_TMP_DIR:
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.UPLOAD_ERR_NO_TMP_DIR'), 'error');
+ return false;
+ default:
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.UNKNOWN_ERRORS'), 'error');
+ return false;
+ }
+
+ $file_name = $_FILES['uploaded_file']['name'];
+ $file_path = $_FILES['uploaded_file']['tmp_name'];
+
+ // Handle bad filenames.
+ if (!Utils::checkFilename($file_name)) {
+ $this->admin->json_response = [
+ 'status' => 'error',
+ 'message' => $this->admin::translate('PLUGIN_ADMIN.UNKNOWN_ERRORS')
+ ];
+
+ return false;
+ }
+ }
+
+
+ $result = Gpm::directInstall($file_path);
+
+ if ($result === true) {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.INSTALLATION_SUCCESSFUL'), 'info');
+ } else {
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.INSTALLATION_FAILED') . ': ' . $result,
+ 'error');
+ }
+
+ $this->setRedirect('/tools');
+
+ return true;
+ }
+
+ /**
+ * Save the current page in a different language. Automatically switches to that language.
+ *
+ * @return bool True if the action was performed.
+ */
+ protected function taskSaveas()
+ {
+ if (!$this->authorizeTask('save', $this->dataPermissions())) {
+ return false;
+ }
+
+ $data = (array)$this->data;
+ $language = $data['lang'];
+
+ if ($language) {
+ $this->grav['session']->admin_lang = $language ?: 'en';
+ }
+
+ $uri = $this->grav['uri'];
+ $obj = $this->admin->page($uri->route());
+ $this->preparePage($obj, false, $language);
+
+ $file = $obj->file();
+ if ($file) {
+ $filename = $this->determineFilenameIncludingLanguage($obj->name(), $language);
+
+ $path = $obj->path() . DS . $filename;
+ $aFile = File::instance($path);
+ $aFile->save();
+
+ $aPage = new Page();
+ $aPage->init(new \SplFileInfo($path), $language . '.md');
+ $aPage->header($obj->header());
+ $aPage->rawMarkdown($obj->rawMarkdown());
+ $aPage->template($obj->template());
+ $aPage->validate();
+ $aPage->filter();
+
+ $this->grav->fireEvent('onAdminSave', new Event(['page' => &$aPage]));
+ $aPage->save();
+ $this->grav->fireEvent('onAdminAfterSave', new Event(['page' => $aPage]));
+ }
+
+ $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_SWITCHED_LANGUAGE'), 'info');
+ $this->setRedirect('/' . $language . $uri->route());
+
+ return true;
+ }
+
+ /**
+ * The what should be the new filename when saving as a new language
+ *
+ * @param string $current_filename the current file name, including .md. Example: default.en.md
+ * @param string $language The new language it will be saved as. Example: 'it' or 'en-GB'.
+ *
+ * @return string The new filename. Example: 'default.it'
+ */
+ public function determineFilenameIncludingLanguage($current_filename, $language)
+ {
+ $ext = '.md';
+ $filename = substr($current_filename, 0, -strlen($ext));
+ $languages_enabled = $this->grav['config']->get('system.languages.supported', []);
+
+ $parts = explode('.', trim($filename, '.'));
+ $lang = array_pop($parts);
+
+ if ($lang === $language) {
+ return $filename . $ext;
+ } elseif (in_array($lang, $languages_enabled)) {
+ $filename = implode('.', $parts);
+ }
+
+ return $filename . '.' . $language . $ext;
+ }
+
+ protected function taskGetLevelListing()
+ {
+ $data = $this->getPost($_POST);
+
+ if (!isset($data['field'])) {
+ $json = [
+ 'status' => 'error',
+ 'message' => 'Bad Request'
+ ];
+ return $this->sendJsonResponse($json, 400);
+ }
+
+ // Base64 decode the route
+ $data['route'] = isset($data['route']) ? base64_decode($data['route']) : null;
+
+ $initial = $data['initial'] ?? null;
+ if ($initial) {
+ $data['leaf_route'] = $data['route'];
+ $data['route'] = null;
+ $data['level'] = 1;
+ }
+
+ [$status, $message, $response,] = $this->getLevelListing($data);
+
+ $json = [
+ 'status' => $status,
+ 'message' => $this->admin::translate($message ?? 'PLUGIN_ADMIN.NO_ROUTE_PROVIDED'),
+ 'data' => array_values($response)
+ ];
+
+ return $this->sendJsonResponse($json, 200);
+ }
+
+ protected function getLevelListing($data)
+ {
+ // Valid types are dir|file|link
+ $default_filters = ['type'=> ['root', 'dir'], 'name' => null, 'extension' => null];
+
+ $pages = $this->grav['pages'];
+ $pages_instances = $pages->instances();
+
+ $is_page = $data['page'] ?? true;
+ $route = $data['route'] ?? null;
+ $leaf_route = $data['leaf_route'] ?? null;
+ $sortby = $data['sortby'] ?? 'filename';
+ $order = $data['order'] ?? SORT_ASC;
+ $initial = $data['initial'] ?? null;
+ $filters = isset($data['filters']) ? $default_filters + json_decode($data['filters']) : $default_filters;
+ $filter_type = (array) $filters['type'];
+
+ $status = 'error';
+ $msg = null;
+ $response = [];
+ $children = null;
+ $sub_route = null;
+ $extra = null;
+ $root = false;
+
+ // Handle leaf_route
+ if ($leaf_route && $route !== $leaf_route) {
+ $nodes = explode('/', $leaf_route);
+ $sub_route = '/' . implode('/', array_slice($nodes, 1, $data['level']++));
+ $data['route'] = $sub_route;
+
+ [$status, $msg, $children, $extra] = $this->getLevelListing($data);
+ }
+
+ // Handle no route, assume page tree root
+ if (!$route) {
+ $is_page = false;
+ $route = $this->grav['locator']->findResource('page://', true);
+ $root = true;
+ }
+
+ if ($is_page) {
+ // Try the path
+ /** @var PageInterface $page */
+ $page = $pages->get(GRAV_ROOT . $route);
+
+ // Try a real route (like homepage)
+ if (is_null($page)) {
+ $page = $pages->find($route);
+ }
+
+ $path = $page ? $page->path() : null;
+ } else {
+ // Try a physical path
+ if (!Utils::startsWith($route, GRAV_ROOT)) {
+ $try_path = GRAV_ROOT . $route;
+ } else {
+ $try_path = $route;
+ }
+
+ $path = file_exists($try_path) ? $try_path : null;
+ }
+
+ $blueprintsData = $this->admin->page(true);
+
+ if (null !== $blueprintsData) {
+ if (method_exists($blueprintsData, 'blueprints')) {
+ $settings = $blueprintsData->blueprints()->schema()->getProperty($data['field']);
+ } elseif (method_exists($blueprintsData, 'getBlueprint')) {
+ $settings = $blueprintsData->getBlueprint()->schema()->getProperty($data['field']);
+ }
+
+ $filters = array_merge([], $filters, ($settings['filters'] ?? []));
+ $filter_type = $filters['type'] ?? $filter_type;
+ }
+
+
+ if ($path) {
+ /** @var \SplFileInfo $fileInfo */
+ $status = 'success';
+ $msg = 'PLUGIN_ADMIN.PAGE_ROUTE_FOUND';
+ foreach (new \DirectoryIterator($path) as $fileInfo) {
+ $fileName = $fileInfo->getFilename();
+ $filePath = str_replace('\\', '/', $fileInfo->getPathname());
+
+ if (($fileInfo->isDot() && $fileName !== '.' && $initial) || (Utils::startsWith($fileName, '.') && strlen($fileName) > 1)) {
+ continue;
+ }
+
+ if ($fileInfo->isDot()) {
+ if ($root) {
+ $payload = [
+ 'name' => '',
+ 'value' => '',
+ 'item-key' => '',
+ 'filename' => '.',
+ 'extension' => '',
+ 'type' => 'root',
+ 'modified' => $fileInfo->getMTime(),
+ 'size' => 0,
+ 'has-children' => false
+ ];
+ } else {
+ continue;
+ }
+ } else {
+ $file_page = $pages_instances[$filePath] ?? null;
+ $file_path = Utils::replaceFirstOccurrence(GRAV_ROOT, '', $filePath);
+ $type = $fileInfo->getType();
+
+ $child_path = $file_page ? $file_page->path() : $filePath;
+ $count_children = Folder::countChildren($child_path);
+
+ $payload = [
+ 'name' => $file_page ? $file_page->title() : $fileName,
+ 'value' => $file_page ? $file_page->rawRoute() : $file_path,
+ 'item-key' => basename($file_page ? $file_page->route() : $file_path),
+ 'filename' => $fileName,
+ 'extension' => $type === 'dir' ? '' : $fileInfo->getExtension(),
+ 'type' => $type,
+ 'modified' => $fileInfo->getMTime(),
+ 'size' => $count_children,
+ 'symlink' => false,
+ 'has-children' => $count_children > 0
+ ];
+ }
+
+ // Fix for symlink
+ if ($payload['type'] === 'link') {
+ $payload['symlink'] = true;
+ $physical_path = $fileInfo->getRealPath();
+ $payload['type'] = is_dir($physical_path) ? 'dir' : 'file';
+ }
+
+ // filter types
+ if ($filters['type']) {
+ if (!in_array($payload['type'], $filter_type)) {
+ continue;
+ }
+ }
+
+ // Simple filter for name or extension
+ if (($filters['name'] && Utils::contains($payload['basename'], $filters['name'])) ||
+ ($filters['extension'] && Utils::contains($payload['extension'], $filters['extension']))) {
+ continue;
+ }
+
+ // Add children if any
+ if ($filePath === $extra && is_array($children)) {
+ $payload['children'] = array_values($children);
+ }
+
+ $response[] = $payload;
+ }
+ } else {
+ $msg = 'PLUGIN_ADMIN.PAGE_ROUTE_NOT_FOUND';
+ }
+
+ // Sorting
+ $response = Utils::sortArrayByKey($response, $sortby, $order);
+
+ $temp_array = [];
+ foreach ($response as $index => $item) {
+ $temp_array[$item['type']][$index] = $item;
+ }
+
+ $sorted = Utils::sortArrayByArray($temp_array, $filter_type);
+ $response = Utils::arrayFlatten($sorted);
+
+ return [$status, $this->admin::translate($msg ?? 'PLUGIN_ADMIN.NO_ROUTE_PROVIDED'), $response, $path];
+ }
+
+ protected function taskConvertUrls()
+ {
+ $data = $this->getPost($_POST);
+ $converted_links = [];
+ $converted_images = [];
+ $status = 'success';
+ $message = 'All links converted';
+
+ $data['route'] = isset($data['route']) ? base64_decode($data['route']) : null;
+ $data['data'] = json_decode($data['data'] ?? '{}', true);
+
+ // use the route if passed, else use current page in admin as reference
+ $page_route = $data['route'] ?? $this->admin->page(true);
+
+ /** @var PageInterface */
+ $page = $this->grav['pages']->find($page_route);
+
+ if (!$page) {
+ $json = [
+ 'status' => 'error',
+ 'message' => 'Page Not Found'
+ ];
+ return $this->sendJsonResponse($json, 404);
+ }
+
+ if (!isset($data['data'])) {
+ $json = [
+ 'status' => 'error',
+ 'message' => 'Bad Request'
+ ];
+ return $this->sendJsonResponse($json, 400);
+ }
+
+ foreach ($data['data']['a'] ?? [] as $link) {
+ $converted_links[$link] = Excerpts::processLinkHtml($link, $page);
+ }
+
+ foreach ($data['data']['img'] ?? [] as $image) {
+ $converted_images[$image] = Excerpts::processImageHtml($image, $page);
+ }
+
+ $json = [
+ 'status' => $status,
+ 'message' => $message,
+ 'data' => ['links' => $converted_links, 'images' => $converted_images]
+ ];
+
+ return $this->sendJsonResponse($json, 200);
+ }
+}
diff --git a/user/plugins/admin/classes/gpm.php b/user/plugins/admin/classes/gpm.php
new file mode 100644
index 0000000..3d6909c
--- /dev/null
+++ b/user/plugins/admin/classes/gpm.php
@@ -0,0 +1,429 @@
+ GRAV_ROOT,
+ 'overwrite' => true,
+ 'ignore_symlinks' => true,
+ 'skip_invalid' => true,
+ 'install_deps' => true,
+ 'theme' => false
+ ];
+
+ /**
+ * @param Package[]|string[]|string $packages
+ * @param array $options
+ *
+ * @return string|bool
+ */
+ public static function install($packages, array $options)
+ {
+ $options = array_merge(self::$options, $options);
+
+ if (!Installer::isGravInstance($options['destination']) || !Installer::isValidDestination($options['destination'],
+ [Installer::EXISTS, Installer::IS_LINK])
+ ) {
+ return false;
+ }
+
+ $packages = is_array($packages) ? $packages : [$packages];
+ $count = count($packages);
+
+ $packages = array_filter(array_map(function ($p) {
+ return !is_string($p) ? $p instanceof Package ? $p : false : self::GPM()->findPackage($p);
+ }, $packages));
+
+ if (!$options['skip_invalid'] && $count !== count($packages)) {
+ return false;
+ }
+
+ $messages = '';
+
+ foreach ($packages as $package) {
+ if (isset($package->dependencies) && $options['install_deps']) {
+ $result = static::install($package->dependencies, $options);
+
+ if (!$result) {
+ return false;
+ }
+ }
+
+ // Check destination
+ Installer::isValidDestination($options['destination'] . DS . $package->install_path);
+
+ if (!$options['overwrite'] && Installer::lastErrorCode() === Installer::EXISTS) {
+ return false;
+ }
+
+ if (!$options['ignore_symlinks'] && Installer::lastErrorCode() === Installer::IS_LINK) {
+ return false;
+ }
+
+ $license = Licenses::get($package->slug);
+ $local = static::download($package, $license);
+
+ Installer::install($local, $options['destination'],
+ ['install_path' => $package->install_path, 'theme' => $options['theme']]);
+ Folder::delete(dirname($local));
+
+ $errorCode = Installer::lastErrorCode();
+ if ($errorCode) {
+ $msg = Installer::lastErrorMsg();
+ throw new \RuntimeException($msg);
+ }
+
+ if (count($packages) === 1) {
+ $message = Installer::getMessage();
+ if ($message) {
+ return $message;
+ }
+
+ $messages .= $message;
+ }
+ }
+
+ return $messages ?: true;
+ }
+
+ /**
+ * @param Package[]|string[]|string $packages
+ * @param array $options
+ *
+ * @return string|bool
+ */
+ public static function update($packages, array $options)
+ {
+ $options['overwrite'] = true;
+
+ return static::install($packages, $options);
+ }
+
+ /**
+ * @param Package[]|string[]|string $packages
+ * @param array $options
+ *
+ * @return string|bool
+ */
+ public static function uninstall($packages, array $options)
+ {
+ $options = array_merge(self::$options, $options);
+
+ $packages = (array)$packages;
+ $count = count($packages);
+
+ $packages = array_filter(array_map(function ($p) {
+
+ if (is_string($p)) {
+ $p = strtolower($p);
+ $plugin = static::GPM()->getInstalledPlugin($p);
+ $p = $plugin ?: static::GPM()->getInstalledTheme($p);
+ }
+
+ return $p instanceof Package ? $p : false;
+
+ }, $packages));
+
+ if (!$options['skip_invalid'] && $count !== count($packages)) {
+ return false;
+ }
+
+ foreach ($packages as $package) {
+
+ $location = Grav::instance()['locator']->findResource($package->package_type . '://' . $package->slug);
+
+ // Check destination
+ Installer::isValidDestination($location);
+
+ if (!$options['ignore_symlinks'] && Installer::lastErrorCode() === Installer::IS_LINK) {
+ return false;
+ }
+
+ Installer::uninstall($location);
+
+ $errorCode = Installer::lastErrorCode();
+ if ($errorCode && $errorCode !== Installer::IS_LINK && $errorCode !== Installer::EXISTS) {
+ $msg = Installer::lastErrorMsg();
+ throw new \RuntimeException($msg);
+ }
+
+ if (count($packages) === 1) {
+ $message = Installer::getMessage();
+ if ($message) {
+ return $message;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Direct install a file
+ *
+ * @param string $package_file
+ *
+ * @return string|bool
+ */
+ public static function directInstall($package_file)
+ {
+ if (!$package_file) {
+ return Admin::translate('PLUGIN_ADMIN.NO_PACKAGE_NAME');
+ }
+
+ $tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
+ $tmp_zip = $tmp_dir . '/Grav-' . uniqid('', false);
+
+ if (Response::isRemote($package_file)) {
+ $zip = GravGPM::downloadPackage($package_file, $tmp_zip);
+ } else {
+ $zip = GravGPM::copyPackage($package_file, $tmp_zip);
+ }
+
+ if (file_exists($zip)) {
+ $tmp_source = $tmp_dir . '/Grav-' . uniqid('', false);
+ $extracted = Installer::unZip($zip, $tmp_source);
+
+ if (!$extracted) {
+ Folder::delete($tmp_source);
+ Folder::delete($tmp_zip);
+ return Admin::translate('PLUGIN_ADMIN.PACKAGE_EXTRACTION_FAILED');
+ }
+
+ $type = GravGPM::getPackageType($extracted);
+
+ if (!$type) {
+ Folder::delete($tmp_source);
+ Folder::delete($tmp_zip);
+ return Admin::translate('PLUGIN_ADMIN.NOT_VALID_GRAV_PACKAGE');
+ }
+
+ if ($type === 'grav') {
+ Installer::isValidDestination(GRAV_ROOT . '/system');
+ if (Installer::IS_LINK === Installer::lastErrorCode()) {
+ Folder::delete($tmp_source);
+ Folder::delete($tmp_zip);
+ return Admin::translate('PLUGIN_ADMIN.CANNOT_OVERWRITE_SYMLINKS');
+ }
+
+ static::upgradeGrav($zip, $extracted);
+ } else {
+ $name = GravGPM::getPackageName($extracted);
+
+ if (!$name) {
+ Folder::delete($tmp_source);
+ Folder::delete($tmp_zip);
+ return Admin::translate('PLUGIN_ADMIN.NAME_COULD_NOT_BE_DETERMINED');
+ }
+
+ $install_path = GravGPM::getInstallPath($type, $name);
+ $is_update = file_exists($install_path);
+
+ Installer::isValidDestination(GRAV_ROOT . DS . $install_path);
+ if (Installer::lastErrorCode() === Installer::IS_LINK) {
+ Folder::delete($tmp_source);
+ Folder::delete($tmp_zip);
+ return Admin::translate('PLUGIN_ADMIN.CANNOT_OVERWRITE_SYMLINKS');
+ }
+
+ Installer::install($zip, GRAV_ROOT,
+ ['install_path' => $install_path, 'theme' => $type === 'theme', 'is_update' => $is_update],
+ $extracted);
+ }
+
+ Folder::delete($tmp_source);
+
+ if (Installer::lastErrorCode()) {
+ return Installer::lastErrorMsg();
+ }
+
+ } else {
+ return Admin::translate('PLUGIN_ADMIN.ZIP_PACKAGE_NOT_FOUND');
+ }
+
+ Folder::delete($tmp_zip);
+
+ return true;
+ }
+
+ /**
+ * @param Package $package
+ *
+ * @return string
+ */
+ private static function download(Package $package, $license = null)
+ {
+ $query = '';
+
+ if ($package->premium) {
+ $query = \json_encode(array_merge($package->premium, [
+ 'slug' => $package->slug,
+ 'license_key' => $license
+ ]));
+
+ $query = '?d=' . base64_encode($query);
+ }
+
+ try {
+ $contents = Response::get($package->zipball_url . $query, []);
+ } catch (\Exception $e) {
+ throw new \RuntimeException($e->getMessage());
+ }
+
+ $tmp_dir = Admin::getTempDir() . '/Grav-' . uniqid('', false);
+ Folder::mkdir($tmp_dir);
+
+ $bad_chars = array_merge(array_map('chr', range(0, 31)), ['<', '>', ':', '"', '/', '\\', '|', '?', '*']);
+
+ $filename = $package->slug . str_replace($bad_chars, '', basename($package->zipball_url));
+ $filename = preg_replace('/[\\\\\/:"*?&<>|]+/m', '-', $filename);
+
+ file_put_contents($tmp_dir . DS . $filename . '.zip', $contents);
+
+ return $tmp_dir . DS . $filename . '.zip';
+ }
+
+ /**
+ * @param array $package
+ * @param string $tmp
+ *
+ * @return string
+ */
+ private static function _downloadSelfupgrade(array $package, $tmp)
+ {
+ $output = Response::get($package['download'], []);
+ Folder::mkdir($tmp);
+ file_put_contents($tmp . DS . $package['name'], $output);
+
+ return $tmp . DS . $package['name'];
+ }
+
+ /**
+ * @return bool
+ */
+ public static function selfupgrade()
+ {
+ $upgrader = new Upgrader();
+
+ if (!Installer::isGravInstance(GRAV_ROOT)) {
+ return false;
+ }
+
+ if (is_link(GRAV_ROOT . DS . 'index.php')) {
+ Installer::setError(Installer::IS_LINK);
+
+ return false;
+ }
+
+ if (method_exists($upgrader, 'meetsRequirements') &&
+ method_exists($upgrader, 'minPHPVersion') &&
+ !$upgrader->meetsRequirements()) {
+ $error = [];
+ $error[] = 'Grav has increased the minimum PHP requirement. ';
+ $error[] = 'You are currently running PHP ' . phpversion() . ' ';
+ $error[] = ', but PHP ' . $upgrader->minPHPVersion() . ' is required.
';
+ $error[] = 'Additional information
';
+
+ Installer::setError(implode("\n", $error));
+
+ return false;
+ }
+
+ $update = $upgrader->getAssets()['grav-update'];
+ $tmp = Admin::getTempDir() . '/Grav-' . uniqid('', false);
+ if ($tmp) {
+ $file = self::_downloadSelfupgrade($update, $tmp);
+ $folder = Installer::unZip($file, $tmp . '/zip');
+ $keepFolder = false;
+ } else {
+ // If you make $tmp empty, you can install your local copy of Grav (for testing purposes only).
+ $file = 'grav.zip';
+ $folder = '~/phpstorm/grav-clones/grav';
+ //$folder = '/home/matias/phpstorm/rockettheme/grav-devtools/grav-clones/grav';
+ $keepFolder = true;
+ }
+
+ static::upgradeGrav($file, $folder, $keepFolder);
+
+ $errorCode = Installer::lastErrorCode();
+
+ if ($tmp) {
+ Folder::delete($tmp);
+ }
+
+ return !(is_string($errorCode) || ($errorCode & (Installer::ZIP_OPEN_ERROR | Installer::ZIP_EXTRACT_ERROR)));
+ }
+
+ private static 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/user/plugins/admin/classes/popularity.php b/user/plugins/admin/classes/popularity.php
new file mode 100644
index 0000000..7eef0cb
--- /dev/null
+++ b/user/plugins/admin/classes/popularity.php
@@ -0,0 +1,303 @@
+config = Grav::instance()['config'];
+
+ $this->data_path = Grav::instance()['locator']->findResource('log://popularity', true, true);
+ $this->daily_file = $this->data_path . '/' . self::DAILY_FILE;
+ $this->monthly_file = $this->data_path . '/' . self::MONTHLY_FILE;
+ $this->totals_file = $this->data_path . '/' . self::TOTALS_FILE;
+ $this->visitors_file = $this->data_path . '/' . self::VISITORS_FILE;
+
+ }
+
+ public function trackHit()
+ {
+ // Don't track bot or crawler requests
+ if (!Grav::instance()['browser']->isHuman()) {
+ return;
+ }
+
+ // Respect visitors "do not track" setting
+ if (!Grav::instance()['browser']->isTrackable()) {
+ return;
+ }
+
+ /** @var PageInterface $page */
+ $page = Grav::instance()['page'];
+ $relative_url = str_replace(Grav::instance()['base_url_relative'], '', $page->url());
+
+ // Don't track error pages or pages that have no route
+ if ($page->template() === 'error' || !$page->route()) {
+ return;
+ }
+
+ // Make sure no 'widcard-style' ignore matches this url
+ foreach ((array)$this->config->get('plugins.admin.popularity.ignore') as $ignore) {
+ if (fnmatch($ignore, $relative_url)) {
+ return;
+ }
+ }
+
+ // initial creation if it doesn't exist
+ if (!file_exists($this->data_path)) {
+ mkdir($this->data_path);
+ $this->flushPopularity();
+ }
+
+ // Update the data we want to track
+ $this->updateDaily();
+ $this->updateMonthly();
+ $this->updateTotals($page->route());
+ $this->updateVisitors(Grav::instance()['uri']->ip());
+
+ }
+
+ protected function updateDaily()
+ {
+
+ if (!$this->daily_data) {
+ $this->daily_data = $this->getData($this->daily_file);
+ }
+
+ $day_month_year = date(self::DAILY_FORMAT);
+
+ // get the daily access count
+ if (array_key_exists($day_month_year, $this->daily_data)) {
+ $this->daily_data[$day_month_year] = (int)$this->daily_data[$day_month_year] + 1;
+ } else {
+ $this->daily_data[$day_month_year] = 1;
+ }
+
+ // keep correct number as set by history
+ $count = (int)$this->config->get('plugins.admin.popularity.history.daily', 30);
+ $total = count($this->daily_data);
+
+ if ($total > $count) {
+ $this->daily_data = array_slice($this->daily_data, -$count, $count, true);
+ }
+
+ file_put_contents($this->daily_file, json_encode($this->daily_data));
+ }
+
+ /**
+ * @return array
+ */
+ public function getDailyChartData()
+ {
+ if (!$this->daily_data) {
+ $this->daily_data = $this->getData($this->daily_file);
+ }
+
+ $limit = (int)$this->config->get('plugins.admin.popularity.dashboard.days_of_stats', 7);
+ $chart_data = array_slice($this->daily_data, -$limit, $limit);
+
+ $labels = [];
+ $data = [];
+
+ /** @var Admin $admin */
+ $admin = Grav::instance()['admin'];
+ foreach ($chart_data as $date => $count) {
+ $labels[] = $admin::translate([
+ 'PLUGIN_ADMIN.' . strtoupper(date('D', strtotime($date)))]) .
+ ' ' . date('M d', strtotime($date));
+ $data[] = $count;
+ }
+
+ return ['labels' => $labels, 'data' => $data];
+ }
+
+ /**
+ * @return int
+ */
+ public function getDailyTotal()
+ {
+ if (!$this->daily_data) {
+ $this->daily_data = $this->getData($this->daily_file);
+ }
+
+ if (isset($this->daily_data[date(self::DAILY_FORMAT)])) {
+ return $this->daily_data[date(self::DAILY_FORMAT)];
+ }
+
+ return 0;
+ }
+
+ /**
+ * @return int
+ */
+ public function getWeeklyTotal()
+ {
+ if (!$this->daily_data) {
+ $this->daily_data = $this->getData($this->daily_file);
+ }
+
+ $day = 0;
+ $total = 0;
+ foreach (array_reverse($this->daily_data) as $daily) {
+ $total += $daily;
+ $day++;
+ if ($day === 7) {
+ break;
+ }
+ }
+
+ return $total;
+ }
+
+ /**
+ * @return int
+ */
+ public function getMonthlyTotal()
+ {
+ if (!$this->monthly_data) {
+ $this->monthly_data = $this->getData($this->monthly_file);
+ }
+ if (isset($this->monthly_data[date(self::MONTHLY_FORMAT)])) {
+ return $this->monthly_data[date(self::MONTHLY_FORMAT)];
+ }
+
+ return 0;
+ }
+
+ protected function updateMonthly()
+ {
+
+ if (!$this->monthly_data) {
+ $this->monthly_data = $this->getData($this->monthly_file);
+ }
+
+ $month_year = date(self::MONTHLY_FORMAT);
+
+ // get the monthly access count
+ if (array_key_exists($month_year, $this->monthly_data)) {
+ $this->monthly_data[$month_year] = (int)$this->monthly_data[$month_year] + 1;
+ } else {
+ $this->monthly_data[$month_year] = 1;
+ }
+
+ // keep correct number as set by history
+ $count = (int)$this->config->get('plugins.admin.popularity.history.monthly', 12);
+ $total = count($this->monthly_data);
+ $this->monthly_data = array_slice($this->monthly_data, $total - $count, $count);
+
+
+ file_put_contents($this->monthly_file, json_encode($this->monthly_data));
+ }
+
+ /**
+ * @return array
+ */
+ protected function getMonthyChartData()
+ {
+ if (!$this->monthly_data) {
+ $this->monthly_data = $this->getData($this->monthly_file);
+ }
+
+ $labels = [];
+ $data = [];
+
+ foreach ($this->monthly_data as $date => $count) {
+ $labels[] = date('M', strtotime($date));
+ $data[] = $count;
+ }
+
+ return ['labels' => $labels, 'data' => $data];
+ }
+
+ /**
+ * @param string $url
+ */
+ protected function updateTotals($url)
+ {
+ if (!$this->totals_data) {
+ $this->totals_data = $this->getData($this->totals_file);
+ }
+
+ // get the totals for this url
+ if (array_key_exists($url, $this->totals_data)) {
+ $this->totals_data[$url] = (int)$this->totals_data[$url] + 1;
+ } else {
+ $this->totals_data[$url] = 1;
+ }
+
+ file_put_contents($this->totals_file, json_encode($this->totals_data));
+ }
+
+ /**
+ * @param string $ip
+ */
+ protected function updateVisitors($ip)
+ {
+ if (!$this->visitors_data) {
+ $this->visitors_data = $this->getData($this->visitors_file);
+ }
+
+ // update with current timestamp
+ $this->visitors_data[hash('sha1', $ip)] = time();
+ $visitors = $this->visitors_data;
+ arsort($visitors);
+
+ $count = (int)$this->config->get('plugins.admin.popularity.history.visitors', 20);
+ $this->visitors_data = array_slice($visitors, 0, $count, true);
+
+ file_put_contents($this->visitors_file, json_encode($this->visitors_data));
+ }
+
+ /**
+ * @param string $path
+ *
+ * @return array
+ */
+ protected function getData($path)
+ {
+ if (file_exists($path)) {
+ return (array)json_decode(file_get_contents($path), true);
+ }
+
+ return [];
+ }
+
+
+ public function flushPopularity()
+ {
+ file_put_contents($this->daily_file, []);
+ file_put_contents($this->monthly_file, []);
+ file_put_contents($this->totals_file, []);
+ file_put_contents($this->visitors_file, []);
+ }
+}
diff --git a/user/plugins/admin/classes/themes.php b/user/plugins/admin/classes/themes.php
new file mode 100644
index 0000000..43883bb
--- /dev/null
+++ b/user/plugins/admin/classes/themes.php
@@ -0,0 +1,22 @@
+grav['themes'];
+ $themes->configure();
+ $themes->initTheme();
+
+ $this->grav->fireEvent('onAdminThemeInitialized');
+ }
+}
diff --git a/user/plugins/admin/classes/utils.php b/user/plugins/admin/classes/utils.php
new file mode 100644
index 0000000..067764b
--- /dev/null
+++ b/user/plugins/admin/classes/utils.php
@@ -0,0 +1,54 @@
+find($email, ['email']);
+ }
+
+ /**
+ * Generates a slug of the given string
+ *
+ * @param string $str
+ * @return string
+ */
+ public static function slug(string $str)
+ {
+ if (function_exists('transliterator_transliterate')) {
+ $str = transliterator_transliterate('Any-Latin; NFD; [:Nonspacing Mark:] Remove; NFC; [:Punctuation:] Remove;', $str);
+ } else {
+ $str = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str);
+ }
+
+ $str = strtolower($str);
+ $str = preg_replace('/[-\s]+/', '-', $str);
+ $str = preg_replace('/[^a-z0-9-]/i', '', $str);
+ $str = trim($str, '-');
+
+ return $str;
+ }
+}
diff --git a/user/plugins/admin/codeception.yml b/user/plugins/admin/codeception.yml
new file mode 100644
index 0000000..7d317a5
--- /dev/null
+++ b/user/plugins/admin/codeception.yml
@@ -0,0 +1,18 @@
+actor: Tester
+paths:
+ tests: tests
+ log: tests/_output
+ data: tests/_data
+ support: tests/_support
+ envs: tests/_envs
+settings:
+ bootstrap: _bootstrap.php
+ colors: true
+ memory_limit: 1024M
+extensions:
+ enabled:
+ - Codeception\Extension\RunFailed
+ # - Codeception\Extension\Recorder
+
+modules:
+ config:
diff --git a/user/plugins/admin/composer.json b/user/plugins/admin/composer.json
new file mode 100644
index 0000000..86ebc21
--- /dev/null
+++ b/user/plugins/admin/composer.json
@@ -0,0 +1,51 @@
+{
+ "name": "getgrav/grav-plugin-admin",
+ "type": "grav-plugin",
+ "description": "Admin plugin for Grav CMS",
+ "keywords": ["admin", "plugin", "manager", "panel"],
+ "homepage": "https://github.com/getgrav/grav-plugin-admin",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Team Grav",
+ "email": "devs@getgrav.org",
+ "homepage": "https://getgrav.org",
+ "role": "Developer"
+ }
+ ],
+ "support": {
+ "issues": "https://github.com/getgrav/grav-plugin-admin/issues",
+ "irc": "https://chat.getgrav.org",
+ "forum": "https://discourse.getgrav.org",
+ "docs": "https://github.com/getgrav/grav-plugin-admin/blob/master/README.md"
+ },
+ "require": {
+ "php": ">=7.1.3",
+ "ext-json": "*",
+ "composer/semver": "^1.4",
+ "p3k/picofeed": "@stable"
+ },
+ "require-dev": {
+ "codeception/codeception": "^2.4",
+ "fzaninotto/faker": "^1.8",
+ "symfony/yaml": "~4.1",
+ "symfony/console": "~4.1",
+ "symfony/finder": "~4.1",
+ "symfony/event-dispatcher": "~4.1"
+ },
+ "autoload": {
+ "classmap": [
+ "classes/",
+ "admin.php"
+ ]
+ },
+ "config": {
+ "platform": {
+ "php": "7.1.3"
+ }
+ },
+ "scripts": {
+ "test": "vendor/bin/codecept run unit",
+ "test-windows": "vendor\\bin\\codecept run unit"
+ }
+}
diff --git a/user/plugins/admin/composer.lock b/user/plugins/admin/composer.lock
new file mode 100644
index 0000000..72bf42d
--- /dev/null
+++ b/user/plugins/admin/composer.lock
@@ -0,0 +1,3028 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "8b3c07286e8c9baf049b2da482136b3a",
+ "packages": [
+ {
+ "name": "composer/semver",
+ "version": "1.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/semver.git",
+ "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/semver/zipball/c6bea70230ef4dd483e6bbcab6005f682ed3a8de",
+ "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.2 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.5 || ^5.0.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\Semver\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nils Adermann",
+ "email": "naderman@naderman.de",
+ "homepage": "http://www.naderman.de"
+ },
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ },
+ {
+ "name": "Rob Bast",
+ "email": "rob.bast@gmail.com",
+ "homepage": "http://robbast.nl"
+ }
+ ],
+ "description": "Semver library that offers utilities, version constraint parsing and validation.",
+ "keywords": [
+ "semantic",
+ "semver",
+ "validation",
+ "versioning"
+ ],
+ "time": "2020-01-13T12:06:48+00:00"
+ },
+ {
+ "name": "p3k/picofeed",
+ "version": "v0.1.38",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/aaronpk/picoFeed.git",
+ "reference": "989c0bcf2eac016a4104abce1aadff791fc287ab"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/aaronpk/picoFeed/zipball/989c0bcf2eac016a4104abce1aadff791fc287ab",
+ "reference": "989c0bcf2eac016a4104abce1aadff791fc287ab",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-iconv": "*",
+ "ext-libxml": "*",
+ "ext-simplexml": "*",
+ "ext-xml": "*",
+ "php": ">=5.3.0",
+ "zendframework/zendxml": "^1.0"
+ },
+ "require-dev": {
+ "phpdocumentor/reflection-docblock": "2.0.4",
+ "phpunit/phpunit": "4.8.26",
+ "symfony/yaml": "2.8.7"
+ },
+ "suggest": {
+ "ext-curl": "PicoFeed will use cURL if present"
+ },
+ "bin": [
+ "picofeed"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "PicoFeed": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Frédéric Guillot"
+ }
+ ],
+ "description": "Modern library to handle RSS/Atom feeds",
+ "homepage": "https://github.com/miniflux/picoFeed",
+ "time": "2017-11-30T00:16:58+00:00"
+ },
+ {
+ "name": "zendframework/zendxml",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/zendframework/ZendXml.git",
+ "reference": "eceab37a591c9e140772a1470338258857339e00"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/zendframework/ZendXml/zipball/eceab37a591c9e140772a1470338258857339e00",
+ "reference": "eceab37a591c9e140772a1470338258857339e00",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4",
+ "zendframework/zend-coding-standard": "~1.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev",
+ "dev-develop": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "ZendXml\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "Utility library for XML usage, best practices, and security in PHP",
+ "keywords": [
+ "ZendFramework",
+ "security",
+ "xml",
+ "zf"
+ ],
+ "abandoned": "laminas/laminas-xml",
+ "time": "2019-01-22T19:42:14+00:00"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "behat/gherkin",
+ "version": "v4.6.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Behat/Gherkin.git",
+ "reference": "25bdcaf37898b4a939fa3031d5d753ced97e4759"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Behat/Gherkin/zipball/25bdcaf37898b4a939fa3031d5d753ced97e4759",
+ "reference": "25bdcaf37898b4a939fa3031d5d753ced97e4759",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.5|~5",
+ "symfony/phpunit-bridge": "~2.7|~3|~4",
+ "symfony/yaml": "~2.3|~3|~4"
+ },
+ "suggest": {
+ "symfony/yaml": "If you want to parse features, represented in YAML files"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Behat\\Gherkin": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ }
+ ],
+ "description": "Gherkin DSL parser for PHP 5.3",
+ "homepage": "http://behat.org/",
+ "keywords": [
+ "BDD",
+ "Behat",
+ "Cucumber",
+ "DSL",
+ "gherkin",
+ "parser"
+ ],
+ "time": "2020-02-27T11:29:57+00:00"
+ },
+ {
+ "name": "codeception/codeception",
+ "version": "2.5.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Codeception/Codeception.git",
+ "reference": "b83a9338296e706fab2ceb49de8a352fbca3dc98"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Codeception/Codeception/zipball/b83a9338296e706fab2ceb49de8a352fbca3dc98",
+ "reference": "b83a9338296e706fab2ceb49de8a352fbca3dc98",
+ "shasum": ""
+ },
+ "require": {
+ "behat/gherkin": "^4.4.0",
+ "codeception/phpunit-wrapper": "^6.0.9|^7.0.6",
+ "codeception/stub": "^2.0",
+ "ext-curl": "*",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "facebook/webdriver": ">=1.1.3 <2.0",
+ "guzzlehttp/guzzle": ">=4.1.4 <7.0",
+ "guzzlehttp/psr7": "~1.0",
+ "php": ">=5.6.0 <8.0",
+ "symfony/browser-kit": ">=2.7 <5.0",
+ "symfony/console": ">=2.7 <5.0",
+ "symfony/css-selector": ">=2.7 <5.0",
+ "symfony/dom-crawler": ">=2.7 <5.0",
+ "symfony/event-dispatcher": ">=2.7 <5.0",
+ "symfony/finder": ">=2.7 <5.0",
+ "symfony/yaml": ">=2.7 <5.0"
+ },
+ "require-dev": {
+ "codeception/specify": "~0.3",
+ "facebook/graph-sdk": "~5.3",
+ "flow/jsonpath": "~0.2",
+ "monolog/monolog": "~1.8",
+ "pda/pheanstalk": "~3.0",
+ "php-amqplib/php-amqplib": "~2.4",
+ "predis/predis": "^1.0",
+ "squizlabs/php_codesniffer": "~2.0",
+ "symfony/process": ">=2.7 <5.0",
+ "vlucas/phpdotenv": "^3.0"
+ },
+ "suggest": {
+ "aws/aws-sdk-php": "For using AWS Auth in REST module and Queue module",
+ "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests",
+ "codeception/specify": "BDD-style code blocks",
+ "codeception/verify": "BDD-style assertions",
+ "flow/jsonpath": "For using JSONPath in REST module",
+ "league/factory-muffin": "For DataFactory module",
+ "league/factory-muffin-faker": "For Faker support in DataFactory module",
+ "phpseclib/phpseclib": "for SFTP option in FTP Module",
+ "stecman/symfony-console-completion": "For BASH autocompletion",
+ "symfony/phpunit-bridge": "For phpunit-bridge support"
+ },
+ "bin": [
+ "codecept"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": []
+ },
+ "autoload": {
+ "psr-4": {
+ "Codeception\\": "src/Codeception",
+ "Codeception\\Extension\\": "ext"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Bodnarchuk",
+ "email": "davert@mail.ua",
+ "homepage": "http://codegyre.com"
+ }
+ ],
+ "description": "BDD-style testing framework",
+ "homepage": "http://codeception.com/",
+ "keywords": [
+ "BDD",
+ "TDD",
+ "acceptance testing",
+ "functional testing",
+ "unit testing"
+ ],
+ "time": "2019-04-24T11:28:19+00:00"
+ },
+ {
+ "name": "codeception/phpunit-wrapper",
+ "version": "7.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Codeception/phpunit-wrapper.git",
+ "reference": "bc847bd4f8f6d09012543e2a856f19fe4ecdcf3a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/bc847bd4f8f6d09012543e2a856f19fe4ecdcf3a",
+ "reference": "bc847bd4f8f6d09012543e2a856f19fe4ecdcf3a",
+ "shasum": ""
+ },
+ "require": {
+ "phpunit/php-code-coverage": "^6.0",
+ "phpunit/phpunit": "7.5.*",
+ "sebastian/comparator": "^3.0",
+ "sebastian/diff": "^3.0"
+ },
+ "require-dev": {
+ "codeception/specify": "*",
+ "vlucas/phpdotenv": "^3.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Codeception\\PHPUnit\\": "src\\"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Davert",
+ "email": "davert.php@resend.cc"
+ }
+ ],
+ "description": "PHPUnit classes used by Codeception",
+ "time": "2019-12-23T06:55:58+00:00"
+ },
+ {
+ "name": "codeception/stub",
+ "version": "2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Codeception/Stub.git",
+ "reference": "853657f988942f7afb69becf3fd0059f192c705a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Codeception/Stub/zipball/853657f988942f7afb69becf3fd0059f192c705a",
+ "reference": "853657f988942f7afb69becf3fd0059f192c705a",
+ "shasum": ""
+ },
+ "require": {
+ "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.0.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Codeception\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Flexible Stub wrapper for PHPUnit's Mock Builder",
+ "time": "2019-03-02T15:35:10+00:00"
+ },
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "ae466f726242e637cebdd526a7d991b9433bacf1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1",
+ "reference": "ae466f726242e637cebdd526a7d991b9433bacf1",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^6.0",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "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.2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "http://ocramius.github.com/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "time": "2019-10-21T16:45:58+00:00"
+ },
+ {
+ "name": "facebook/webdriver",
+ "version": "1.7.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-webdriver/php-webdriver-archive.git",
+ "reference": "e43de70f3c7166169d0f14a374505392734160e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-webdriver/php-webdriver-archive/zipball/e43de70f3c7166169d0f14a374505392734160e5",
+ "reference": "e43de70f3c7166169d0f14a374505392734160e5",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "ext-zip": "*",
+ "php": "^5.6 || ~7.0",
+ "symfony/process": "^2.8 || ^3.1 || ^4.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.0",
+ "jakub-onderka/php-parallel-lint": "^0.9.2",
+ "php-coveralls/php-coveralls": "^2.0",
+ "php-mock/php-mock-phpunit": "^1.1",
+ "phpunit/phpunit": "^5.7",
+ "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0",
+ "squizlabs/php_codesniffer": "^2.6",
+ "symfony/var-dumper": "^3.3 || ^4.0"
+ },
+ "suggest": {
+ "ext-SimpleXML": "For Firefox profile creation"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-community": "1.5-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Facebook\\WebDriver\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "description": "A PHP client for Selenium WebDriver",
+ "homepage": "https://github.com/facebook/php-webdriver",
+ "keywords": [
+ "facebook",
+ "php",
+ "selenium",
+ "webdriver"
+ ],
+ "abandoned": "php-webdriver/webdriver",
+ "time": "2019-06-13T08:02:18+00:00"
+ },
+ {
+ "name": "fzaninotto/faker",
+ "version": "v1.9.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/fzaninotto/Faker.git",
+ "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/fc10d778e4b84d5bd315dad194661e091d307c6f",
+ "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.3 || ^7.0"
+ },
+ "require-dev": {
+ "ext-intl": "*",
+ "phpunit/phpunit": "^4.8.35 || ^5.7",
+ "squizlabs/php_codesniffer": "^2.9.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Faker\\": "src/Faker/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "François Zaninotto"
+ }
+ ],
+ "description": "Faker is a PHP library that generates fake data for you.",
+ "keywords": [
+ "data",
+ "faker",
+ "fixtures"
+ ],
+ "time": "2019-12-12T13:22:17+00:00"
+ },
+ {
+ "name": "guzzlehttp/guzzle",
+ "version": "6.5.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle.git",
+ "reference": "43ece0e75098b7ecd8d13918293029e555a50f82"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82",
+ "reference": "43ece0e75098b7ecd8d13918293029e555a50f82",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/promises": "^1.0",
+ "guzzlehttp/psr7": "^1.6.1",
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "ext-curl": "*",
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
+ "psr/log": "^1.1"
+ },
+ "suggest": {
+ "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+ "psr/log": "Required for using the Log middleware"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.5-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle is a PHP HTTP client library",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "rest",
+ "web service"
+ ],
+ "time": "2019-12-23T11:57:10+00:00"
+ },
+ {
+ "name": "guzzlehttp/promises",
+ "version": "v1.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/promises.git",
+ "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+ "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle promises library",
+ "keywords": [
+ "promise"
+ ],
+ "time": "2016-12-20T10:07:11+00:00"
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "1.6.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "239400de7a173fe9901b9ac7c06497751f00727a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a",
+ "reference": "239400de7a173fe9901b9ac7c06497751f00727a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0",
+ "psr/http-message": "~1.0",
+ "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "ext-zlib": "*",
+ "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
+ },
+ "suggest": {
+ "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Schultze",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "psr-7",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ],
+ "time": "2019-07-01T23:21:34+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.9.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef",
+ "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef",
+ "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": "2020-01-17T21:11:47+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": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a",
+ "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~6"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "FQSEN",
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ],
+ "time": "2018-08-07T13:53:10+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "4.3.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c",
+ "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0",
+ "phpdocumentor/type-resolver": "~0.4 || ^1.0.0",
+ "webmozart/assert": "^1.0"
+ },
+ "require-dev": {
+ "doctrine/instantiator": "^1.0.5",
+ "mockery/mockery": "^1.0",
+ "phpdocumentor/type-resolver": "0.4.*",
+ "phpunit/phpunit": "^6.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+ "time": "2019-12-28T18:55:12+00:00"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9",
+ "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1",
+ "phpdocumentor/reflection-common": "^2.0"
+ },
+ "require-dev": {
+ "ext-tokenizer": "^7.1",
+ "mockery/mockery": "~1",
+ "phpunit/phpunit": "^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
+ "time": "2019-08-22T18:11:29+00:00"
+ },
+ {
+ "name": "phpspec/prophecy",
+ "version": "v1.10.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpspec/prophecy.git",
+ "reference": "451c3cd1418cf640de218914901e51b064abb093"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093",
+ "reference": "451c3cd1418cf640de218914901e51b064abb093",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.2",
+ "php": "^5.3|^7.0",
+ "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
+ "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0",
+ "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "^2.5 || ^3.2",
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.10.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Prophecy\\": "src/Prophecy"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ },
+ {
+ "name": "Marcello Duarte",
+ "email": "marcello.duarte@gmail.com"
+ }
+ ],
+ "description": "Highly opinionated mocking framework for PHP 5.3+",
+ "homepage": "https://github.com/phpspec/prophecy",
+ "keywords": [
+ "Double",
+ "Dummy",
+ "fake",
+ "mock",
+ "spy",
+ "stub"
+ ],
+ "time": "2020-03-05T15:02:03+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": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "time": "2018-10-31T16:06:48+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "050bedf145a257b1ff02746c31894800e5122946"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946",
+ "reference": "050bedf145a257b1ff02746c31894800e5122946",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "time": "2018-09-13T20:33:42+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "time": "2015-06-21T13:50:34+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "2.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "1038454804406b0b5f5f520358e78c1c2f71501e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e",
+ "reference": "1038454804406b0b5f5f520358e78c1c2f71501e",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "time": "2019-06-07T04:22:29+00:00"
+ },
+ {
+ "name": "phpunit/php-token-stream",
+ "version": "3.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+ "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff",
+ "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Wrapper around PHP's tokenizer extension.",
+ "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+ "keywords": [
+ "tokenizer"
+ ],
+ "time": "2019-09-17T06:23:10+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "7.5.20",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "9467db479d1b0487c99733bb1e7944d32deded2c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c",
+ "reference": "9467db479d1b0487c99733bb1e7944d32deded2c",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.1",
+ "ext-dom": "*",
+ "ext-json": "*",
+ "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": {
+ "ext-soap": "*",
+ "ext-xdebug": "*",
+ "phpunit/php-invoker": "^2.0"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "7.5-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "time": "2020-01-08T08:45:45+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+ "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "time": "2017-02-14T16:28:37+00:00"
+ },
+ {
+ "name": "psr/http-message",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.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 interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "time": "2016-08-06T14:39:51+00:00"
+ },
+ {
+ "name": "ralouphie/getallheaders",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ralouphie/getallheaders.git",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpunit": "^5 || ^6.5"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/getallheaders.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "description": "A polyfill for getallheaders.",
+ "time": "2019-03-08T08:55:37+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
+ "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.7 || ^6.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": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "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": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da",
+ "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1",
+ "sebastian/diff": "^3.0",
+ "sebastian/exporter": "^3.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "time": "2018-07-12T15:12:46+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29",
+ "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.5 || ^8.0",
+ "symfony/process": "^2 || ^3.3 || ^4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "time": "2019-02-04T06:01:07+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "4.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368",
+ "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.5"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "time": "2019-11-20T08:46:58+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "3.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e",
+ "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "sebastian/recursion-context": "^3.0"
+ },
+ "require-dev": {
+ "ext-mbstring": "*",
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "http://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "time": "2019-09-14T09:02:43+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
+ "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "suggest": {
+ "ext-uopz": "*"
+ },
+ "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": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "time": "2017-04-27T15:39:26+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5",
+ "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "sebastian/object-reflector": "^1.1.1",
+ "sebastian/recursion-context": "^3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "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": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+ "time": "2017-03-03T06:23:57+00:00"
+ },
+ {
+ "name": "sebastian/resource-operations",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/resource-operations.git",
+ "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9"
+ },
+ "dist": {
+ "type": "zip",
+ "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/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "time": "2016-10-03T07:35:21+00:00"
+ },
+ {
+ "name": "symfony/browser-kit",
+ "version": "v4.4.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/browser-kit.git",
+ "reference": "090ce406505149d6852a7c03b0346dec3b8cf612"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/browser-kit/zipball/090ce406505149d6852a7c03b0346dec3b8cf612",
+ "reference": "090ce406505149d6852a7c03b0346dec3b8cf612",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3",
+ "symfony/dom-crawler": "^3.4|^4.0|^5.0"
+ },
+ "require-dev": {
+ "symfony/css-selector": "^3.4|^4.0|^5.0",
+ "symfony/http-client": "^4.3|^5.0",
+ "symfony/mime": "^4.3|^5.0",
+ "symfony/process": "^3.4|^4.0|^5.0"
+ },
+ "suggest": {
+ "symfony/process": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\BrowserKit\\": ""
+ },
+ "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 BrowserKit Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-23T10:00:59+00:00"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v4.4.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "4fa15ae7be74e53f6ec8c83ed403b97e23b665e9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/4fa15ae7be74e53f6ec8c83ed403b97e23b665e9",
+ "reference": "4fa15ae7be74e53f6ec8c83ed403b97e23b665e9",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/polyfill-php73": "^1.8",
+ "symfony/service-contracts": "^1.1|^2"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<3.4",
+ "symfony/event-dispatcher": "<4.3|>=5",
+ "symfony/lock": "<4.4",
+ "symfony/process": "<3.3"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "^3.4|^4.0|^5.0",
+ "symfony/dependency-injection": "^3.4|^4.0|^5.0",
+ "symfony/event-dispatcher": "^4.3",
+ "symfony/lock": "^4.4|^5.0",
+ "symfony/process": "^3.4|^4.0|^5.0",
+ "symfony/var-dumper": "^4.3|^5.0"
+ },
+ "suggest": {
+ "psr/log": "For using the console logger",
+ "symfony/event-dispatcher": "",
+ "symfony/lock": "",
+ "symfony/process": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "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 Console Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-24T13:10:00+00:00"
+ },
+ {
+ "name": "symfony/css-selector",
+ "version": "v4.4.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/css-selector.git",
+ "reference": "d0a6dd288fa8848dcc3d1f58b94de6a7cc5d2d22"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/d0a6dd288fa8848dcc3d1f58b94de6a7cc5d2d22",
+ "reference": "d0a6dd288fa8848dcc3d1f58b94de6a7cc5d2d22",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\CssSelector\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Jean-François Simon",
+ "email": "jeanfrancois.simon@sensiolabs.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony CssSelector Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-04T09:01:01+00:00"
+ },
+ {
+ "name": "symfony/dom-crawler",
+ "version": "v4.4.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/dom-crawler.git",
+ "reference": "11dcf08f12f29981bf770f097a5d64d65bce5929"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/11dcf08f12f29981bf770f097a5d64d65bce5929",
+ "reference": "11dcf08f12f29981bf770f097a5d64d65bce5929",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "conflict": {
+ "masterminds/html5": "<2.6"
+ },
+ "require-dev": {
+ "masterminds/html5": "^2.6",
+ "symfony/css-selector": "^3.4|^4.0|^5.0"
+ },
+ "suggest": {
+ "symfony/css-selector": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\DomCrawler\\": ""
+ },
+ "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 DomCrawler Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-29T10:05:28+00:00"
+ },
+ {
+ "name": "symfony/event-dispatcher",
+ "version": "v4.4.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher.git",
+ "reference": "4ad8e149799d3128621a3a1f70e92b9897a8930d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4ad8e149799d3128621a3a1f70e92b9897a8930d",
+ "reference": "4ad8e149799d3128621a3a1f70e92b9897a8930d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3",
+ "symfony/event-dispatcher-contracts": "^1.1"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<3.4"
+ },
+ "provide": {
+ "psr/event-dispatcher-implementation": "1.0",
+ "symfony/event-dispatcher-implementation": "1.1"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "^3.4|^4.0|^5.0",
+ "symfony/dependency-injection": "^3.4|^4.0|^5.0",
+ "symfony/expression-language": "^3.4|^4.0|^5.0",
+ "symfony/http-foundation": "^3.4|^4.0|^5.0",
+ "symfony/service-contracts": "^1.1|^2",
+ "symfony/stopwatch": "^3.4|^4.0|^5.0"
+ },
+ "suggest": {
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\EventDispatcher\\": ""
+ },
+ "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 EventDispatcher Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-04T09:32:40+00:00"
+ },
+ {
+ "name": "symfony/event-dispatcher-contracts",
+ "version": "v1.1.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher-contracts.git",
+ "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c43ab685673fb6c8d84220c77897b1d6cdbe1d18",
+ "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3"
+ },
+ "suggest": {
+ "psr/event-dispatcher": "",
+ "symfony/event-dispatcher-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\EventDispatcher\\": ""
+ }
+ },
+ "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": "Generic abstractions related to dispatching event",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "time": "2019-09-17T09:54:03+00:00"
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v4.4.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "ea69c129aed9fdeca781d4b77eb20b62cf5d5357"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/ea69c129aed9fdeca781d4b77eb20b62cf5d5357",
+ "reference": "ea69c129aed9fdeca781d4b77eb20b62cf5d5357",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "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 Finder Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-14T07:42:58+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.14.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/fbdeaec0df06cf3d51c93de80c7eb76e271f5a38",
+ "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.14-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "time": "2020-01-13T11:15:53+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.14.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/34094cfa9abe1f0f14f48f490772db7a775559f2",
+ "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.14-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ },
+ "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 for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2020-01-13T11:15:53+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php73",
+ "version": "v1.14.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php73.git",
+ "reference": "5e66a0fa1070bf46bec4bea7962d285108edd675"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/5e66a0fa1070bf46bec4bea7962d285108edd675",
+ "reference": "5e66a0fa1070bf46bec4bea7962d285108edd675",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.14-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": "2020-01-13T11:15:53+00:00"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v4.4.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "bf9166bac906c9e69fb7a11d94875e7ced97bcd7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/bf9166bac906c9e69fb7a11d94875e7ced97bcd7",
+ "reference": "bf9166bac906c9e69fb7a11d94875e7ced97bcd7",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.4-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": "2020-02-07T20:06:44+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v1.1.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "ffc7f5692092df31515df2a5ecf3b7302b3ddacf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ffc7f5692092df31515df2a5ecf3b7302b3ddacf",
+ "reference": "ffc7f5692092df31515df2a5ecf3b7302b3ddacf",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3",
+ "psr/container": "^1.0"
+ },
+ "suggest": {
+ "symfony/service-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ }
+ },
+ "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": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "time": "2019-10-14T12:27:06+00:00"
+ },
+ {
+ "name": "symfony/yaml",
+ "version": "v4.4.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/yaml.git",
+ "reference": "94d005c176db2080e98825d98e01e8b311a97a88"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/94d005c176db2080e98825d98e01e8b311a97a88",
+ "reference": "94d005c176db2080e98825d98e01e8b311a97a88",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "conflict": {
+ "symfony/console": "<3.4"
+ },
+ "require-dev": {
+ "symfony/console": "^3.4|^4.0|^5.0"
+ },
+ "suggest": {
+ "symfony/console": "For validating YAML files using the lint command"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Yaml\\": ""
+ },
+ "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 Yaml Component",
+ "homepage": "https://symfony.com",
+ "time": "2020-02-03T10:46:43+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
+ "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^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"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "time": "2019-06-13T22:48:21+00:00"
+ },
+ {
+ "name": "webmozart/assert",
+ "version": "1.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozart/assert.git",
+ "reference": "aed98a490f9a8f78468232db345ab9cf606cf598"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozart/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598",
+ "reference": "aed98a490f9a8f78468232db345ab9cf606cf598",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.3 || ^7.0",
+ "symfony/polyfill-ctype": "^1.8"
+ },
+ "conflict": {
+ "vimeo/psalm": "<3.6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.36 || ^7.5.13"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Assert\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Assertions to validate method input/output with nice error messages.",
+ "keywords": [
+ "assert",
+ "check",
+ "validate"
+ ],
+ "time": "2020-02-14T12:15:55+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {
+ "p3k/picofeed": 0
+ },
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=7.1.3",
+ "ext-json": "*"
+ },
+ "platform-dev": [],
+ "platform-overrides": {
+ "php": "7.1.3"
+ }
+}
diff --git a/user/plugins/admin/languages/ar.yaml b/user/plugins/admin/languages/ar.yaml
new file mode 100644
index 0000000..cbb42ed
--- /dev/null
+++ b/user/plugins/admin/languages/ar.yaml
@@ -0,0 +1,308 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "هذا إصدار بيتا! استخدم هذا في الإنتاج على مسؤوليتك الخاصة..."
+ ADMIN_REPORT_ISSUE: "وجدت مشكلة؟ الرجاء الإبلاغ عن GitHub."
+ EMAIL_FOOTER: "Powered by Grav - The Modern Flat File CMS"
+ LOGIN_BTN: "تسجل الدخول"
+ LOGIN_BTN_FORGOT: "نسيت"
+ LOGIN_BTN_RESET: "إعادة تعيين كلمة المرور"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "إرسال إرشادات إعادة تعيين"
+ LOGIN_BTN_CLEAR: "مسح النموذج"
+ LOGIN_BTN_CREATE_USER: "أنشاء مستخدم جديد"
+ LOGIN_LOGGED_IN: "لقد تم تسجيل بنجاح"
+ LOGIN_FAILED: "فشل تسجيل الخول"
+ LOGGED_OUT: "لقد قمت بتسجيل الخروج"
+ RESET_NEW_PASSWORD: "إدخال كلمة سر جديدة رجاءً …"
+ RESET_LINK_EXPIRED: "انتهت مدة صلاحية إعادة الارتباط، الرجاء المحاولة مرة أخرى"
+ RESET_PASSWORD_RESET: "لقد تم إعادة تعيين كلمة المرور"
+ RESET_INVALID_LINK: "اللينك خاطئ ، الرجاء المحاولة مرة أخرى"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "تم إرسال إرشادات إعادة تعيين كلمة المرور الخاصة بك عبر البريد الإلكتروني إلى %s"
+ FORGOT_FAILED_TO_EMAIL: "فشل في تعليمات البريد الإلكتروني، الرجاء المحاولة مرة أخرى لاحقاً"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "لا يمكن إعادة تعيين كلمة المرور ل %s، لم يتم تعيين عنوان البريد الإلكتروني"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "لا يوجد المستخدم مع اسم المستخدم %s "
+ FORGOT_EMAIL_NOT_CONFIGURED: "لا يمكن إعادة تعيين كلمة المرور. لم يتم تكوين هذا الموقع لإرسال رسائل البريد الإلكتروني"
+ FORGOT_EMAIL_SUBJECT: "طلب إعادة تعيين كلمة المرور %s"
+ FORGOT_EMAIL_BODY: "\"إعادة تعيين كلمة المرور\" عزيزي %1$s،
طلبا قدم في %4$s لإعادة تعيين كلمة المرور الخاصة بك.
انقر فوق هذا الخيار لإعادة تعيين الخاص بك كلمة مرور
بدلاً من ذلك، نسخ عنوان URL التالي في شريط العناوين في المستعرض الخاص بك:
%2$s
أطيب التحيات، %3$s
"
+ MANAGE_PAGES: "إدارة الصفحات"
+ CONFIGURATION: "الإعدادات"
+ PAGES: "الصفحات"
+ PLUGINS: "الملحقات"
+ PLUGIN: "البرنامج الإضافي"
+ THEMES: "المواضيع"
+ LOGOUT: "تسجيل الخروج"
+ BACK: "الرجوع"
+ NEXT: "التالي"
+ PREVIOUS: "السابق"
+ ADD_PAGE: "إضافة صفحة"
+ ADD_MODULAR: "إضافة وحدة"
+ MOVE: "انقل"
+ DELETE: "حذف"
+ UNSET: "تراجع عن التعيين"
+ VIEW: "عرض"
+ SAVE: "حفظ"
+ NORMAL: "عادي"
+ EXPERT: "خبير"
+ EXPAND_ALL: "عرض الكل"
+ COLLAPSE_ALL: "طي الكل"
+ ERROR: "خطأ"
+ CLOSE: "أغلق"
+ CANCEL: "إلغاء"
+ CONTINUE: "المتابعة"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "التأكيد مطلوب"
+ MODAL_CHANGED_DETECTED_TITLE: "تم الكشف عن التغييرات"
+ MODAL_CHANGED_DETECTED_DESC: "وقد تغييرات غير محفوظة. هل أنت متأكد من أنك تريد ترك دون الحفظ؟"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "التأكيد مطلوب"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "هل أنت متأكد من حذف هذا الملف؟ لا يمكن التراجع عن هذا الإجراء."
+ ADD_FILTERS: "إضافة عامل تصفية"
+ SEARCH_PAGES: "صفحات البحث"
+ VERSION: "النسخة"
+ WAS_MADE_WITH: "تم عمله مع"
+ BY: "بواسطة"
+ UPDATE_THEME: "تحديث الموضوع"
+ UPDATE_PLUGIN: "تحديث البرنامج الإضافي"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "من هذه السمة الآن متوفرة"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "من هذه الإضافة متوفرة الآن"
+ AUTHOR: "المؤلّف"
+ HOMEPAGE: "الصفحة الرئيسية"
+ DEMO: "عرض تجريبي"
+ BUG_TRACKER: "متتبع الأخطاء"
+ KEYWORDS: "الكلمات الرئيسية"
+ LICENSE: "الرخصة"
+ DESCRIPTION: "الوصف"
+ README: "الملف التمهيدي"
+ REMOVE_THEME: "إزالة الموضوع"
+ INSTALL_THEME: "تثبيت الموضوع"
+ THEME: "الموضوع"
+ BACK_TO_THEMES: "العودة إلى المواضيع"
+ BACK_TO_PLUGINS: "العودة إلى البرامج الإضافية"
+ CHECK_FOR_UPDATES: "التحقق من وجود تحديثات"
+ ADD: "أَضِف"
+ CLEAR_CACHE: "مسح ذاكرة التخزين المؤقتة"
+ CLEAR_CACHE_ALL_CACHE: "كافة المحفوظات"
+ CLEAR_CACHE_ASSETS_ONLY: "الاصول فقط"
+ CLEAR_CACHE_IMAGES_ONLY: "الصور فقط"
+ CLEAR_CACHE_CACHE_ONLY: "المحفوظات فقط"
+ CLEAR_CACHE_TMP_ONLY: "المؤقتة فقط"
+ DASHBOARD: "لوحة المعلومات"
+ UPDATES_AVAILABLE: "تحديثات متوفّرة"
+ DAYS: "أيام"
+ UPDATE: "تحديث"
+ BACKUP: "نسخة احتياطية"
+ BACKUPS: "النسخ الاحتياطية"
+ BACKUP_NOW: "قم بحفض نسخة احتياطية"
+ BACKUPS_STATS: "قم بحفض نسخة احتياطية لاحصائيات"
+ BACKUPS_HISTORY: "قم بحفض نسخة احتياطية للسجل"
+ BACKUPS_PROFILES: "قم بحفض نسخة احتياطي للحسابات"
+ BACKUPS_COUNT: "عدد النسخ الاحتياطية"
+ BACKUPS_PROFILES_COUNT: "عدد الحسابات"
+ BACKUPS_TOTAL_SIZE: "المساحة المستعملة"
+ BACKUPS_NEWEST: "النسخ الاحتياطية الحديثة"
+ BACKUPS_OLDEST: "النسخ الاحتياطية القديمة"
+ BACKUPS_PURGE: "تطهير"
+ BACKUPS_NOT_GENERATED: "لم يتم إنشاء نسخ احتياطية بعد..."
+ BACKUPS_PURGE_NUMBER: "استخدام %s من %s من فتحات النسخ الاحتياطي"
+ BACKUPS_PURGE_TIME: "%s ايام من النسخ الاحتياطية بقية"
+ BACKUPS_PURGE_SPACE: "استعمال %s من %s"
+ BACKUP_DELETED: "تم حذف النسخة الاحتياطية بنجاح"
+ BACKUP_NOT_FOUND: "لم يتم العثور على نسخة احتياطية"
+ BACKUP_DATE: "تاريخ النسخ الاحتياطي"
+ STATISTICS: "إحصائيات"
+ TODAY: "اليوم"
+ WEEK: "اسبوع"
+ MONTH: "شهر"
+ LATEST_PAGE_UPDATES: "آخر التحديثات"
+ MAINTENANCE: "الصيانه"
+ UPDATED: "تم تحديثه"
+ MON: "الإثنين"
+ TUE: "الثلاثاء"
+ WED: "الإربعاء"
+ THU: "الخميس"
+ FRI: "الجمعة"
+ SAT: "السبت"
+ SUN: "الأحد"
+ COPY: "نسخ"
+ EDIT: "تحرير"
+ CREATE: "انشاء"
+ GRAV_ADMIN: "مدير جريف"
+ GRAV_OFFICIAL_PLUGIN: "اضاف رسمية ل جريف"
+ GRAV_OFFICIAL_THEME: "سمة جريف رسمية"
+ PLUGIN_SYMBOLICALLY_LINKED: "هذه الاضافه مرتبطة بمرجع. لن يام اكتشاف التحديثات."
+ THEME_SYMBOLICALLY_LINKED: "هذه السمة مرتبطة بمرجع. لم يتم اكتشاف التحديثات"
+ REMOVE_PLUGIN: "حذف الإضافة"
+ INSTALL_PLUGIN: "تثبيت الإضافة"
+ AVAILABLE: "متوفر"
+ INSTALLED: "مثبت"
+ INSTALL: "تثبيت"
+ ACTIVE_THEME: "سمة نشطة"
+ SWITCHING_TO: "التبديل إلى"
+ SWITCHING_TO_DESCRIPTION: "بالتبديل إلى سمة مختلفة, لايوجد أي ضمانات بأن صفحات الواجهة مدعومة, من المحتمل أن تحدث أخطاء عند محاولة تحميل الصفحات المذكورة."
+ SWITCHING_TO_CONFIRMATION: "هل تريد التبديل الى السمة والمتابعة"
+ CREATE_NEW_USER: "إنشاء مستخدم جديد"
+ REMOVE_USER: "حذف المستخدم"
+ ACCESS_DENIED: "الدخول ممنوع"
+ ACCOUNT_NOT_ADMIN: "لا يملك حسابك أي صلاحيات للإدارة"
+ PHP_INFO: "معلومات بي إش بي"
+ INSTALLER: "المثبت"
+ AVAILABLE_THEMES: "سمات متوفرة"
+ AVAILABLE_PLUGINS: "إضافات متوفرة"
+ INSTALLED_THEMES: "السمات المثبتة"
+ INSTALLED_PLUGINS: "الإضافات المثبتة"
+ BROWSE_ERROR_LOGS: "سجل أخطاء التصفح"
+ SITE: "موقع"
+ INFO: "معلومات"
+ SYSTEM: "النظام"
+ USER: "المستخدم"
+ ADD_ACCOUNT: "إضافة حساب"
+ SWITCH_LANGUAGE: "تبديل اللغة"
+ SUCCESSFULLY_ENABLED_PLUGIN: "تم تفعيل الإضافة بنجاح"
+ SUCCESSFULLY_DISABLED_PLUGIN: "تم إيقاف الإضافة بنجاح"
+ SUCCESSFULLY_CHANGED_THEME: "تم تبديل السمة الإفتراضية بنجاح"
+ INSTALLATION_FAILED: "فشل التثبيت"
+ INSTALLATION_SUCCESSFUL: "تم التثبيت بنجاح"
+ UNINSTALL_FAILED: "فشل الغاء التثبيت"
+ UNINSTALL_SUCCESSFUL: "تم إلغاء التثبيت بنجاح"
+ SUCCESSFULLY_SAVED: "حفظ بنجاح"
+ SUCCESSFULLY_COPIED: "نسخ بنجاح"
+ REORDERING_WAS_SUCCESSFUL: "إعادة ترتيب تم بنجاح"
+ SUCCESSFULLY_DELETED: "تم الحذف بنجاح"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "تم تغيير اللغة بنجاح"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "لديك أذونات غير كافية للقيام بالمهمة"
+ CACHE_CLEARED: "تم مسح الذاكرة المؤقتة"
+ METHOD: "الأسلوب"
+ ERROR_CLEARING_CACHE: "خطأ مسح ذاكرة التخزين المؤقت"
+ AN_ERROR_OCCURRED: "حدث خطأ ما"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "النسخة الاحتياطية جاهزة للتحميل"
+ DOWNLOAD_BACKUP: "تنزيل النسخة الاحتياطية"
+ PAGES_FILTERED: "الصفحات التي تمت تصفيتها"
+ NO_PAGE_FOUND: "لم يتم العثور على أي صفحة"
+ INVALID_PARAMETERS: "متغيرات غير صالحة"
+ NO_FILES_SENT: "لا توجد ملفات أرسلت حتى الآن"
+ EXCEEDED_FILESIZE_LIMIT: "تجاوز الحد الأقصى لحجم ملف التكوين بي إتش بي"
+ UNKNOWN_ERRORS: "خطأ غير معروف"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "تجاوز الحد الأقصى لحجم ملف التكوين بي إتش بي"
+ UNSUPPORTED_FILE_TYPE: "نوع ملف غير معتمد"
+ FAILED_TO_MOVE_UPLOADED_FILE: "فشل في تحميل الملف"
+ FILE_UPLOADED_SUCCESSFULLY: "تم رفع الملف بنجاح"
+ FILE_DELETED: "تم حذف الملف"
+ FILE_COULD_NOT_BE_DELETED: "لا يمكن حذف الملف"
+ FILE_NOT_FOUND: "لم يتم العثور على الملف"
+ NO_FILE_FOUND: "لا يوجد ملف"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "تم تحديث النظام بنجاح الى"
+ GRAV_UPDATE_FAILED: "فشل تحديث نظام جراف"
+ EVERYTHING_UPDATED: "تم تحديث كل شيء"
+ UPDATES_FAILED: "فشل التحديث"
+ AVATAR_BY: "الصورة الرمزية التي"
+ AVATAR_UPLOAD_OWN: "أو قم بتحميل الخاصة بك..."
+ LAST_BACKUP: "آخر نسخ احتياطي"
+ FULL_NAME: "الإسم الكامل"
+ USERNAME: "إسم المستخدم"
+ EMAIL: "البريد الإلكتروني"
+ USERNAME_EMAIL: "إسم المستخدم أو البريد الإلكتروني"
+ PASSWORD: "كلمة السر"
+ PASSWORD_CONFIRM: "تأكيد كلمة السر"
+ TITLE: "العنوان"
+ LANGUAGE: "اللّغة"
+ ACCOUNT: "الحساب"
+ EMAIL_VALIDATION_MESSAGE: "يجب أن يكون البريد الإلكتروني صحيحاً"
+ PASSWORD_VALIDATION_MESSAGE: "يجب أن تحتوي كلمة المرور على الأقل على رقم وعلى حرف كبير وعلى حرف صغير، و أن تكون مكونة على الأقل من 8 أحرف أو أكثر"
+ LANGUAGE_HELP: "تعيين اللغة المفضلة"
+ MEDIA: "وسائط"
+ DEFAULTS: "الإعدادات الافتراضية"
+ SITE_TITLE: "عنوان الموقع"
+ SITE_TITLE_PLACEHOLDER: "عنوان الموقع العريض"
+ SITE_TITLE_HELP: "العنوان الإفتراضي لموقعك, غالبا يستخدم في السمات"
+ SITE_DEFAULT_LANG: "اللغة الإفتراضية"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "اللغة الإفتراضية المستخدمة في السمات وسم "
+ SITE_DEFAULT_LANG_HELP: "اللغة الإفتراضية المستخدمة في السمات وسم "
+ DEFAULT_AUTHOR: "المؤلف الافتراضي"
+ DEFAULT_AUTHOR_HELP: "إسم المؤلف الإفتراضي, يستخدم عادة في السمات او محتوى الصفحة"
+ DEFAULT_EMAIL: "البريد الإلكتروني الإفتراضي"
+ DEFAULT_EMAIL_HELP: "البريد الإلكتروني للإشارة في المواضيع أو صفحات"
+ TAXONOMY_TYPES: "أنواع التصنيف"
+ TAXONOMY_TYPES_HELP: "يجب أن يتم تعريف أنواع التصنيف هنا إذا كنت ترغب في استخدامها في صفحات"
+ PAGE_SUMMARY: "ملخص الصفحة"
+ ENABLED: "فعال"
+ ENABLED_HELP: "تمكين صفحة الموجز (الملخص ترجع نفس محتوى الصفحة)"
+ 'YES': "نعم"
+ 'NO': "لا"
+ SUMMARY_SIZE: "حجم الملخص"
+ SUMMARY_SIZE_HELP: "مقدار أحرف صفحة لاستخدامها كمحتوى موجز"
+ FORMAT: "الشكل"
+ FORMAT_HELP: "اختصار = الاستخدام الأولى لوقوع محدد أو الحجم؛ = فترة طويلة سيتم تجاهل موجز محدد"
+ SHORT: "قصير"
+ LONG: "طويل"
+ DELIMITER: "الفاصل"
+ DELIMITER_HELP: "موجز محدد (الافتراضي '= = =')"
+ METADATA: "البيانات الوصفية"
+ METADATA_HELP: "قيم بيانات التعريف الافتراضية التي سيتم عرضها في كل صفحة ما لم يتم تجاوز الصفحة"
+ NAME: "الإسم"
+ CONTENT: "المحتوى"
+ SIZE: "حجم"
+ ACTION: "اجراء"
+ REDIRECTS_AND_ROUTES: "اعادة التوجيه و المسارات"
+ CUSTOM_REDIRECTS: "تخصيص اعادة التوجيه"
+ CUSTOM_REDIRECTS_HELP: "مسارات لإعادة التوجيه إلى صفحات أخرى. الاستبدال الاساسي ل Regex صحيح"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/ الخاص / الاسم المستعار"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/ الخاص /إعادة التوجيه"
+ CUSTOM_ROUTES: "تخصيص المسارات"
+ CUSTOM_ROUTES_HELP: "مسارات لاسماء المستعارة إلى صفحات أخرى. الاستبدال الاساسي ل Regex صحيح"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/ الخاص / الاسم المستعار"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/ الخاص / المسار"
+ FILE_STREAMS: "تيارات الملف"
+ DEFAULT: "الإعدادات العامة"
+ PAGE_MEDIA: "صور و فيديوهات الصفحة"
+ OPTIONS: "الخيارات"
+ PUBLISHED: "نُشِرَ"
+ PUBLISHED_HELP: "بشكل افتراضي، يتم نشر صفحة إلا إذا قمت بتعيين المنشور: false أو عن طريق publish_date يجري في المستقبل، أو unpublish_date في الماضي"
+ DATE: "التاريخ"
+ ROBOTS: "الروبوتات"
+ ADVANCED: "خيارات متقدمة"
+ SETTINGS: "الإعدادات"
+ FOLDER_NAME: "إسم المجلد"
+ MENU: "القائمة"
+ USE_GLOBAL: "الاستخدام العام"
+ ROUTABLE: "قابل للتوجيه"
+ ROUTABLE_HELP: "إذا أمكن ولوج هذه الصفحة عبر عنوان URL"
+ VISIBLE: "مرئي"
+ ASCENDING: "تصاعدي"
+ DESCENDING: "تنازلي"
+ PAGE_TITLE: "موضوع عنوان الصفحة"
+ PAGE_TITLE_HELP: "عنوان الصفحة"
+ PAGE: "صفحة"
+ MODULAR_TEMPLATE: "قالب متعدد الفقرات"
+ FILENAME: "اسم الملف"
+ PARENT_PAGE: "الصفحة الأصل"
+ HOME_PAGE: "الصفحة الرئيسية"
+ TIMEZONE: "المنطقة الزمنية"
+ LANGUAGES: "اللغات"
+ EXPIRES: "انتهاء الصلاحية"
+ LAST_MODIFIED: "آخر تعديل"
+ SESSION: "الجلسة"
+ CURRENT: "الحالي"
+ SAVE_AS: "حفظ كـ"
+ AND: "و"
+ FULLY_UPDATED: "تم تحديث النظام بالكامل"
+ GROUPS: "الفِرَق"
+ SESSION_SECURE: "آمن"
+ ADD_FOLDER: "إضافة مجلد"
+ ADD_ITEM: "إضافة عنصر"
+ LOADING: "جار التحميل …"
+ PACKAGES_SUCCESSFULLY_UPDATED: "تم تحديث حزمة أو حزمات بنجاح."
+ INSERT: "إدراج"
+ UNDO: "تراجع"
+ REDO: "إعادة"
+ HEADERS: "العناوين الرأسية"
+ ITALIC: "مائل"
+ LINK: "رابط"
+ IMAGE: "صورة"
+ PREVIEW: "معاينة"
+ PUBLISHING: "النشر"
+ IMAGE_OPTIONS: "خيارات الصورة"
+ ALL: "الكل"
+ FROM: "من"
+ TO: "إلى"
+ RELEASE_DATE: "تاريخ الإصدار"
+ DROPZONE_REMOVE_FILE: "إزالة الملف"
+ TOOLS: "الأدوات"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_REGENERATE: "إعادة التوليد"
diff --git a/user/plugins/admin/languages/bg.yaml b/user/plugins/admin/languages/bg.yaml
new file mode 100644
index 0000000..35da781
--- /dev/null
+++ b/user/plugins/admin/languages/bg.yaml
@@ -0,0 +1,361 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Това е Бета версия! Използвате на ваша отговорност..."
+ ADMIN_REPORT_ISSUE: "Открили сте проблем? Моля, съобщете за него в GitHub."
+ EMAIL_FOOTER: "Задвижван от Grav - Модерният Флат Файл CMS"
+ LOGIN_BTN: "Вход"
+ LOGIN_BTN_FORGOT: "Забравена парола"
+ LOGIN_BTN_RESET: "Промяна на паролата"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Изпращане на инструкциите за възстановяването"
+ LOGIN_BTN_CLEAR: "Изтриване на формуляра"
+ LOGIN_BTN_CREATE_USER: "Създаване на потребител"
+ LOGIN_LOGGED_IN: "Влязохте успешно"
+ LOGIN_FAILED: "Влизането не е успешно"
+ LOGGED_OUT: "Излязохте от системата"
+ RESET_NEW_PASSWORD: "Въведете нова парола …"
+ RESET_LINK_EXPIRED: "Връзката за нулиране е изтекла, опитайте отново"
+ RESET_PASSWORD_RESET: "Паролата е променена"
+ RESET_INVALID_LINK: "Използвана невалидна връзка за нулиране, моля опитайте отново"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "На Вашия имейл, бяха изпратени инструкции за възстановяване на паролата"
+ FORGOT_FAILED_TO_EMAIL: "Неуспешно изпращане на имейл с инструкции, опитайте по-късно"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Паролата на %s не може да бъде обновена, няма въведен имейл адрес"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Потребител с име %s не съществува"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Не може да обнови паролата. Този сайт не е конфигуриран да изпраща имейли"
+ FORGOT_EMAIL_SUBJECT: "%s - искане за смяна на парола"
+ FORGOT_EMAIL_BODY: "Смяна на парола Уважаеми %1$s
На %4$s бе направено искане за смяна на парола.
Натиснете тук за да обновите паролата си
Като алтернатива, можете да копирате линка в адресната лента на браузъра си:
%2$s
С уважение, %3$s
"
+ MANAGE_PAGES: "Управление на страниците"
+ CONFIGURATION: "Настройки"
+ PAGES: "Страници"
+ PLUGINS: "Разширения"
+ PLUGIN: "Разширение"
+ THEMES: "Теми"
+ LOGOUT: "Изход"
+ BACK: "Назад"
+ NEXT: "Напред"
+ PREVIOUS: "Назад"
+ ADD_PAGE: "Добавяне на страница"
+ ADD_MODULAR: "Добавяне на модулна страница"
+ MOVE: "Преместване"
+ DELETE: "Изтриване"
+ UNSET: "Незададен"
+ VIEW: "Виж"
+ SAVE: "Запазване"
+ NORMAL: "Обикновен"
+ EXPERT: "Експертен"
+ EXPAND_ALL: "Разгъване на всички"
+ COLLAPSE_ALL: "Свиване на всички"
+ ERROR: "Грешка"
+ CLOSE: "Затваряне"
+ CANCEL: "Отказ"
+ CONTINUE: "Продължаване"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Изисква се потвърждение"
+ MODAL_CHANGED_DETECTED_TITLE: "Засечени са промени"
+ MODAL_CHANGED_DETECTED_DESC: "Имате незапазени промени. Наистина ли искате да излезете без да сте ги запазили?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Изисква се потвърждение"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Наистина ли искате да изтриете този файл? Това действие не може да бъде отменено."
+ ADD_FILTERS: "Добавяне на филтри"
+ SEARCH_PAGES: "Търсене"
+ VERSION: "Версия"
+ WAS_MADE_WITH: "е създаден с"
+ BY: "от"
+ UPDATE_THEME: "Актуализация на тема"
+ UPDATE_PLUGIN: "Актуализация на разширение"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "на тази тема е наличен"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "на този плъгин е вече наличен"
+ AUTHOR: "Автор"
+ HOMEPAGE: "Страница"
+ DEMO: "Демо"
+ BUG_TRACKER: "Докладване за грешки"
+ KEYWORDS: "Ключови думи"
+ LICENSE: "Лиценз"
+ DESCRIPTION: "Описание"
+ README: "Документация"
+ REMOVE_THEME: "Премахване на тема"
+ INSTALL_THEME: "Инсталиране на тема"
+ THEME: "Тема"
+ BACK_TO_THEMES: "Обратно към темите"
+ BACK_TO_PLUGINS: "Обратно към разширенията"
+ CHECK_FOR_UPDATES: "Проверка за актуализации"
+ ADD: "Добавяне"
+ CLEAR_CACHE: "Изтриване на временните файлове"
+ CLEAR_CACHE_ALL_CACHE: "Всички"
+ CLEAR_CACHE_ASSETS_ONLY: "Само Assets"
+ CLEAR_CACHE_IMAGES_ONLY: "Само изображенията"
+ CLEAR_CACHE_CACHE_ONLY: "Само временните файлове"
+ CLEAR_CACHE_TMP_ONLY: "само временен"
+ DASHBOARD: "Контролен панел"
+ UPDATES_AVAILABLE: "Налични актуализации"
+ DAYS: "Дни"
+ UPDATE: "Актуализация"
+ BACKUP: "Резервно копие"
+ BACKUPS: "Резервно копие"
+ BACKUP_NOW: "Създай резервно копие"
+ BACKUPS_STATS: "Статистика резервни копия"
+ BACKUPS_HISTORY: "История резервни копия"
+ BACKUPS_PURGE_CONFIG: "Настройка изтриване на резервно копие"
+ BACKUPS_PROFILES: "Профили на резервни копия"
+ BACKUPS_COUNT: "Брой резервни копия"
+ BACKUPS_PROFILES_COUNT: "Брой профили"
+ BACKUPS_TOTAL_SIZE: "Използвано пространство"
+ BACKUPS_NEWEST: "Най-ново резервно копие"
+ BACKUPS_OLDEST: "Най-старо резервно копие"
+ BACKUPS_PURGE: "Изтриване"
+ BACKUPS_NOT_GENERATED: "Все още не са генериране резервни копия..."
+ BACKUPS_PURGE_NUMBER: "Използва %s от %s слота за резервни копия"
+ BACKUPS_PURGE_TIME: "Остават %s дни за резервно копие"
+ BACKUPS_PURGE_SPACE: "Използва %s от %s"
+ BACKUP_DELETED: "Успешно изтрито резервно копие"
+ BACKUP_NOT_FOUND: "Не е намерено резервно копие"
+ BACKUP_DATE: "Данни за резервно копие"
+ STATISTICS: "Статистика"
+ TODAY: "Днес"
+ WEEK: "Седмица"
+ MONTH: "Месец"
+ LATEST_PAGE_UPDATES: "Скоро актуализирани страници"
+ MAINTENANCE: "Техническа поддръжка"
+ UPDATED: "Актуализиран"
+ MON: "пон"
+ TUE: "вт"
+ WED: "ср"
+ THU: "чт"
+ FRI: "пт"
+ SAT: "сб"
+ SUN: "нд"
+ COPY: "Копиране"
+ EDIT: "Редактиране"
+ CREATE: "Създаване"
+ GRAV_ADMIN: "Grav Admin"
+ GRAV_OFFICIAL_PLUGIN: "Официално разширение на Grav"
+ GRAV_OFFICIAL_THEME: "Официална тема на Grav"
+ PLUGIN_SYMBOLICALLY_LINKED: "Този плъгин е символично свързан. Актуализации няма да бъдат отразени."
+ THEME_SYMBOLICALLY_LINKED: "Тази тема е символично свързана. Актуализации няма да бъдат отразени."
+ REMOVE_PLUGIN: "Премахване на разширение"
+ INSTALL_PLUGIN: "Инсталиране на разширение"
+ AVAILABLE: "Налични"
+ INSTALLED: "Инсталирани"
+ INSTALL: "Инсталиране"
+ ACTIVE_THEME: "Активна тема"
+ SWITCHING_TO: "Превключване към"
+ SWITCHING_TO_DESCRIPTION: "При превключването към различна тема няма гаранция, че всички страници са поддържани, което може да доведе до потенциални грешки при опит за зареждане на тези страници."
+ SWITCHING_TO_CONFIRMATION: "Искате ли да продължите и да превключите към темата"
+ CREATE_NEW_USER: "Създаване на нов потребител"
+ REMOVE_USER: "Премахване на потребител"
+ ACCESS_DENIED: "Нямате достъп"
+ ACCOUNT_NOT_ADMIN: "вашият профил няма администраторски права"
+ PHP_INFO: "Информация за PHP"
+ INSTALLER: "Инсталатор"
+ AVAILABLE_THEMES: "Налични теми"
+ AVAILABLE_PLUGINS: "Налични разширения"
+ INSTALLED_THEMES: "Инсталирани теми"
+ INSTALLED_PLUGINS: "Инсталирани разширения"
+ BROWSE_ERROR_LOGS: "Преглед на дневниците за грешки"
+ SITE: "Сайт"
+ INFO: "Информация"
+ SYSTEM: "Система"
+ USER: "Потребител"
+ ADD_ACCOUNT: "Добавяне на профил"
+ SWITCH_LANGUAGE: "Превключване на езика"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Разширението е активирано успешно"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Разширението е спряно"
+ SUCCESSFULLY_CHANGED_THEME: "Промяната на подразбиращата се тема е успешно"
+ INSTALLATION_FAILED: "Неуспешна инсталация"
+ INSTALLATION_SUCCESSFUL: "Инсталацията е успешна"
+ UNINSTALL_FAILED: "Неуспешно деинсталиране"
+ UNINSTALL_SUCCESSFUL: "Деинсталирането е успешно"
+ SUCCESSFULLY_SAVED: "Успешно запазено"
+ SUCCESSFULLY_COPIED: "Успешно копирано"
+ REORDERING_WAS_SUCCESSFUL: "Записът бе успешен"
+ SUCCESSFULLY_DELETED: "Успешно изтрити"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Езикът е променен успешно"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Нямате достатъчно права за тази задача"
+ CACHE_CLEARED: "Временните файлове са изчистени"
+ METHOD: "Метод"
+ ERROR_CLEARING_CACHE: "Грешка при изтриването на временните файлове"
+ AN_ERROR_OCCURRED: "Възникна грешка"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Резервното копие е готово за изтегляне"
+ DOWNLOAD_BACKUP: "Изтегляне на резервното копие"
+ PAGES_FILTERED: "Филтрирани страници"
+ NO_PAGE_FOUND: "Няма намерени страници"
+ INVALID_PARAMETERS: "Невалидни параметри"
+ NO_FILES_SENT: "Няма изпратени файлове"
+ EXCEEDED_FILESIZE_LIMIT: "Надхвърлен лимит за размер на PHP конфигурационен файл"
+ UNKNOWN_ERRORS: "Неизвестни грешки"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Превишен лимит за размера на конфигурационен GRAV файл"
+ UNSUPPORTED_FILE_TYPE: "Този файлов формат не се поддържа"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Преместването на качения файл не е успешно."
+ FILE_UPLOADED_SUCCESSFULLY: "Файлът е качен успешно"
+ FILE_DELETED: "Файлът е изтрит"
+ FILE_COULD_NOT_BE_DELETED: "Файлът не може да бъде изтрит"
+ FILE_NOT_FOUND: "Файлът не е намерен"
+ NO_FILE_FOUND: "Няма намерени файлове"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav беше успешно актуализиран до"
+ GRAV_UPDATE_FAILED: "Актуализацията на Grav е неуспешна"
+ EVERYTHING_UPDATED: "Всичко е актуализирано"
+ UPDATES_FAILED: "Актуализациите не бяха успшено"
+ AVATAR_BY: "Аватар от"
+ AVATAR_UPLOAD_OWN: "Или качи собствени..."
+ LAST_BACKUP: "Последно резервно копие"
+ FULL_NAME: "Пълно име"
+ USERNAME: "Потребителско име"
+ EMAIL: "Ел. поща"
+ USERNAME_EMAIL: "Потребителско име или инейл"
+ PASSWORD: "Парола"
+ PASSWORD_CONFIRM: "Потвърждение на паролата"
+ TITLE: "Титла"
+ LANGUAGE: "Език"
+ ACCOUNT: "Профил"
+ EMAIL_VALIDATION_MESSAGE: "Ел. поща трябва да бъде валидна"
+ PASSWORD_VALIDATION_MESSAGE: "Паролата трябва да съдържа поне един номер, една главна буква, една малка буква и да съдържа поне 8 или повече знака"
+ LANGUAGE_HELP: "Задаване на любим език"
+ MEDIA: "Медиа"
+ DEFAULTS: "По подразбиране"
+ SITE_TITLE: "Заглавие на сайта"
+ SITE_TITLE_PLACEHOLDER: "Заглавие за всички страници"
+ SITE_TITLE_HELP: "Подразбиращо се заглавие за вашият сайт, често се използва от темите"
+ SITE_DEFAULT_LANG: "Език по подразбиране"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Език по подразбиране, използван от тага на темите"
+ SITE_DEFAULT_LANG_HELP: "Език по подразбиране, използван от тага на темите"
+ DEFAULT_AUTHOR: "Подразбиращ се автор"
+ DEFAULT_AUTHOR_HELP: "Име на автор по подразбиране, често използвано в теми или страници"
+ DEFAULT_EMAIL: "Имейл по подразбиране"
+ DEFAULT_EMAIL_HELP: "Имейл по подразбиране, използван в теми или страници"
+ TAXONOMY_TYPES: "Видове таксономии"
+ TAXONOMY_TYPES_HELP: "Типовете таксономия трябва да бъдат дефинирани тук, ако искате да ги използвате в страници"
+ PAGE_SUMMARY: "Резюме на страницата"
+ ENABLED: "Включен"
+ ENABLED_HELP: "Разреши извлечение (извлечението връща същото съдържание, като в страницата)"
+ 'YES': "Да"
+ 'NO': "Не"
+ SUMMARY_SIZE: "Размер на резюмето"
+ SUMMARY_SIZE_HELP: "Брой знаци, които да бъдат използвани при създаването на резюме за страницата"
+ FORMAT: "Формат"
+ SHORT: "Къс"
+ LONG: "Дълъг"
+ DELIMITER: "Делител"
+ DELIMITER_HELP: "Разделител на извлечението (по подразбиране '===')"
+ METADATA: "Мета-данни"
+ METADATA_HELP: "Ще бъдат показани метадата стойностите по подразбиране на всяка страница, освен ако не са отхвърлени от страницата"
+ NAME: "Име"
+ CONTENT: "Съдържание"
+ SIZE: "Размер"
+ ACTION: "Действие"
+ REDIRECTS_AND_ROUTES: "Пренасочвания и пътища"
+ CUSTOM_REDIRECTS: "Потребителски пренасочвания"
+ CUSTOM_REDIRECTS_HELP: "маршрути за пренасочване към сдруга страница. Подмяна със стандартен Regex е валидна"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/your/redirect"
+ CUSTOM_ROUTES: "Потребителски пренасочвания"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/your/route"
+ FILE_STREAMS: "Потоци файлове"
+ DEFAULT: "По подразбиране"
+ PAGE_MEDIA: "Страница с медия"
+ OPTIONS: "Опции"
+ PUBLISHED: "Публикувано"
+ PUBLISHED_HELP: "По подразбиране страницата се публикува, освен ако не е зададео published: false или publish_date е в бъдеще или unpublish_date е в миналото"
+ DATE: "Дата"
+ DATE_HELP: "Променливата за дата позволява да се зададе дата асоциирана със страницата."
+ PUBLISHED_DATE: "Дата на публикуване"
+ PUBLISHED_DATE_HELP: "Дата, на която автоматично ще се публикува."
+ UNPUBLISHED_DATE: "Дата на непубликуване"
+ UNPUBLISHED_DATE_HELP: "Може да зададе дата за автоматично непубликуване."
+ ROBOTS: "Роботи"
+ TAXONOMIES: "Таксономии"
+ TAXONOMY: "Таксономия"
+ ADVANCED: "Разширено"
+ SETTINGS: "Настройки"
+ FOLDER_NUMERIC_PREFIX: "Цифров префикс на папка"
+ FOLDER_NUMERIC_PREFIX_HELP: "Цифров префикс осигуряващ ръчно подреждане и по-добра видимост"
+ FOLDER_NAME: "Име на папка"
+ FOLDER_NAME_HELP: "Името на папката, която ще се съхрани във файлова система за тази страница"
+ PARENT: "Родител"
+ DEFAULT_OPTION_ROOT: "- Коренова папка -"
+ DEFAULT_OPTION_SELECT: "- Избор -"
+ DISPLAY_TEMPLATE: "Показване на шаблон"
+ BODY_CLASSES: "Класове на тялото"
+ ORDERING: "Подреждане"
+ PAGE_ORDER: "Подредба на страниците"
+ OVERRIDES: "Замени"
+ MENU: "Меню"
+ MENU_HELP: "Стрингът, който ще се използва в меню. Ако не се зададе, ще се използва Title."
+ SLUG: "Слъг"
+ SLUG_HELP: "Слъг променливата Ви позволява да зададете конкретна порция от URL на страницата"
+ SLUG_VALIDATE_MESSAGE: "Слъговете могат да съдържат само малки букви, цифри и тирета"
+ PROCESS: "Обработване"
+ PROCESS_HELP: "Контролирайте обработката на страниците. Може да бъде за отделна страница или глобално"
+ DEFAULT_CHILD_TYPE: "Тип по подразбиране"
+ USE_GLOBAL: "Използвай глобални"
+ ROUTABLE: "Маршрутизируем"
+ ROUTABLE_HELP: "Ако страницата е достъпна през URL"
+ CACHING: "Създаване на временни файлове"
+ VISIBLE: "Видим"
+ VISIBLE_HELP: "Определя дали една страница е видима в навигацията."
+ DISABLED: "Изключено"
+ ITEMS: "Елементи"
+ ORDER_BY: "Подреждане по"
+ ORDER: "Подреждане"
+ FOLDER: "Папка"
+ ASCENDING: "Възходящо"
+ DESCENDING: "Низходящо"
+ ADD_MODULAR_CONTENT: "Добави модулно съдържание"
+ PAGE_TITLE: "Заглавие на страницата"
+ PAGE_TITLE_HELP: "Заглавието на страницата"
+ PAGE: "Страница"
+ MODULAR_TEMPLATE: "Модулен шаблон"
+ FRONTMATTER: "Встъпление"
+ FILENAME: "Име на файла"
+ PARENT_PAGE: "Родителска страница"
+ HOME_PAGE: "Начална страница"
+ HOME_PAGE_HELP: "Страницата, която Grav ще използва по подразбиране за начална страница"
+ DEFAULT_THEME: "Тема по подразбиране"
+ DEFAULT_THEME_HELP: "Задаване на темата по подразбиране, която Grav ще използва (по подразбиране това е Antimatter)"
+ TIMEZONE: "Часова зона"
+ TIMEZONE_HELP: "Презаписване на времевата зона на сървъра"
+ SHORT_DATE_FORMAT: "Кратък формат дата"
+ SHORT_DATE_FORMAT_HELP: "Задай кратък формат дата, който може да се използва от темите"
+ LONG_DATE_FORMAT: "Пълен формат дата"
+ LONG_DATE_FORMAT_HELP: "Задай пълен формат дата, който може да се използва от темите"
+ DEFAULT_ORDERING: "По подразбиране"
+ DEFAULT_ORDERING_DEFAULT: "По подразбиране - според име на папка"
+ DEFAULT_ORDERING_FOLDER: "Папка - според името на папката без префикс"
+ DEFAULT_ORDERING_TITLE: "Заглавие - според заглавно поле в главата"
+ DEFAULT_ORDERING_DATE: "Дата - според поле за дата в главата"
+ DEFAULT_ORDER_DIRECTION: "Подреждане по подразбиране"
+ DEFAULT_ORDER_DIRECTION_HELP: "Посоката на страниците в списък"
+ DEFAULT_PAGE_COUNT: "Брой страници по подразбиране"
+ DEFAULT_PAGE_COUNT_HELP: "Максимален брой страници в списък по подразбиране"
+ DATE_BASED_PUBLISHING: "Публикуване според датата"
+ DATE_BASED_PUBLISHING_HELP: "Автоматично (не)публикувай постове според датата"
+ EVENTS: "Събития"
+ EVENTS_HELP: "Пускане или спиране на специфични събития. Спирането на някои събития може да счупи определени приставки"
+ REDIRECT_DEFAULT_ROUTE: "Пренасочване на пътя по подразбиране"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Автоматично пренасочване към пътя по подразбиране на страницата"
+ LANGUAGES: "Езици"
+ SUPPORTED: "Поддържани"
+ SUPPORTED_HELP: "Списък от двубуквени езикови кодове, отделени със запетая (пример 'bg,en,de')"
+ TRANSLATIONS_ENABLED: "Преводите са включен"
+ HTTP_HEADERS: "HTTP заглавки"
+ EXPIRES: "Изтича на"
+ CACHE_CONTROL: "HTTP кеш-контрол"
+ LAST_MODIFIED: "Последна промяна"
+ CACHE_CHECK_METHOD: "Мотод проверка на кеша"
+ CACHE_DRIVER: "Кеш драйвър"
+ CACHE_PREFIX: "Кеш представка"
+ CACHE_PURGE: "Изтриване на стр кеш"
+ LIFETIME: "Продължителност на живот"
+ GZIP_COMPRESSION: "Gzip компресия"
+ GZIP_COMPRESSION_HELP: "Разреши GZip компресия на Grav страницата, за оптимизация."
+ CSS_PIPELINE: "CSS pipeline"
+ JAVASCRIPT_PIPELINE: "JavaScript pipeline"
+ LOG_HANDLER: "Обработка на лога"
+ DEBUGGER: "Дибъгър"
+ SESSION: "Сесия"
+ TIMEOUT: "Таймаут"
+ CURRENT: "Текущ"
+ SAVE_AS: "Запази като"
+ AND: "и"
+ UPDATE_AVAILABLE: "Налична актуализация"
+ FULLY_UPDATED: "Напълно обновен"
+ SAVE_LOCATION: "Местоположение за запис"
+ IGNORE_HIDDEN_HELP: "Игнорирай всички файлове и папки, които започват с точка"
+ WRAPPED_SITE: "Опаковани сайт"
diff --git a/user/plugins/admin/languages/br.yaml b/user/plugins/admin/languages/br.yaml
new file mode 100644
index 0000000..cdd75df
--- /dev/null
+++ b/user/plugins/admin/languages/br.yaml
@@ -0,0 +1,600 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Un ermaeziadenn beta an hini eo! Arverit en endro produadur gant evezh..."
+ ADMIN_REPORT_ISSUE: "Kavet hoc'h eus ur gudenn? Danevellit anezhi war Github."
+ EMAIL_FOOTER: "Lusket gant Grav - Ar CMS Restr plad modern"
+ LOGIN_BTN: "Anv arveriad"
+ LOGIN_BTN_FORGOT: "Ankouaet"
+ LOGIN_BTN_RESET: "Adderaouekaat ar ger-tremen"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Kas an ditouroù adderaouekaat"
+ LOGIN_BTN_CLEAR: "Skarzhañ ar furmskrid"
+ LOGIN_BTN_CREATE_USER: "Krouiñ an arveriad"
+ LOGIN_LOGGED_IN: "Kennasket oc'h gant berzh"
+ LOGIN_FAILED: "C'hwitadenn war ar c'hennask"
+ LOGGED_OUT: "Digennasket oc'h"
+ RESET_NEW_PASSWORD: "Enankit ur ger-tremen nevez …"
+ RESET_LINK_EXPIRED: "Diamzeret eo an ere adderaouekaat, klaskit en-dro"
+ RESET_PASSWORD_RESET: "Adderaouekaet eo bet ar ger-tremen"
+ RESET_INVALID_LINK: "Ere adderaouekaat didalvoudek, klaskit en-dro"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Kaset eo bet an ditouroù da adderaouekaat ho ker-tremen d'ho chmolec'h postel"
+ FORGOT_FAILED_TO_EMAIL: "C'hwitadenn en ur gas an ditouroù, klaskit en-dro diwezhatoc'h"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "N'haller ket adderaouekaat ar ger-tremen evit %s, chomlec'h postel ebet arventennet"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "N'eus ket eus an arveriad gant an anv %s "
+ FORGOT_EMAIL_NOT_CONFIGURED: "N'haller ket adderaouekaat ar ger-tremen. N'eo ket kefluniet al lec'hienn evit kas posteloù"
+ FORGOT_EMAIL_SUBJECT: "%s Goulenn adderaouekaat ar ger-tremen"
+ FORGOT_EMAIL_BODY: "Adderaouekaat ar ger-tremen/h1> %1$s,
Graet eo bet un azgoulenn war %4$s evit adderaouekaat ho ker-tremen.
Klikit amañ da adderaouekaat ho ker-tremen
Gallout a rit ivez eilañ an URL da heul er varrenn chomlec'h en ho merdeer:
%2$s
A galon, %3$s
"
+ MANAGE_PAGES: "Ardeiñ ar pajennoù"
+ CONFIGURATION: "Kefluniadur"
+ PAGES: "Pajennoù"
+ PLUGINS: "Enlugelladoù"
+ PLUGIN: "Enlugellad"
+ THEMES: "Neuzioù"
+ LOGOUT: "Digennaskañ"
+ BACK: "Distreiñ"
+ NEXT: "War-lerc'h"
+ PREVIOUS: "Diaraog"
+ ADD_PAGE: "Ouzhpennañ ur bajenn"
+ ADD_MODULAR: "Ouzhpennañ ur mollad"
+ MOVE: "Dilec'hiañ"
+ DELETE: "Dilemel"
+ VIEW: "Gwel"
+ SAVE: "Enrollañ"
+ NORMAL: "Reoliek"
+ EXPERT: "Kemplezhoc'h"
+ EXPAND_ALL: "Astenn an holl"
+ COLLAPSE_ALL: "Bihanaat an holl"
+ ERROR: "Fazi"
+ CLOSE: "Serriñ"
+ CANCEL: "Nullañ"
+ CONTINUE: "Kenderc'hel"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Kadarnadur azgoulennet"
+ MODAL_CHANGED_DETECTED_TITLE: "Kemmoù dinoet"
+ MODAL_CHANGED_DETECTED_DESC: "Kemmoù dienrollet a zo. Sur oc'h e fell deoc'h kuitaat hep enrollañ?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Kadarnadur azgoulennet"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Sur oc'h e fell deoc'h dilemel ar restr-mañ? N'haller ket dizober ar gwered-mañ."
+ ADD_FILTERS: "Ouzhpennañ siloù"
+ SEARCH_PAGES: "Klask pajennoù"
+ VERSION: "Handelv"
+ WAS_MADE_WITH: "Savet eo bet gant"
+ BY: "Gant"
+ UPDATE_THEME: "Hizivaat an neuz"
+ UPDATE_PLUGIN: "Hizivaat an enlugellad"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "an neuz-mañ a zo hegerz"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "an enlugellad-mañ a zo hegerz"
+ AUTHOR: "Aozer"
+ HOMEPAGE: "Pennbajenn"
+ DEMO: "Tañva"
+ BUG_TRACKER: "Heulier beugoù"
+ KEYWORDS: "Gerioù-alc'hwez"
+ LICENSE: "Lañvaz"
+ DESCRIPTION: "Deskrivadur"
+ README: "Skoazell"
+ REMOVE_THEME: "Dilemel an neuz"
+ INSTALL_THEME: "Staliañ an neuz"
+ THEME: "Neuz"
+ BACK_TO_THEMES: "Distreiñ d'an neuzioù"
+ BACK_TO_PLUGINS: "Distreiñ d'an enlugelladoù"
+ CHECK_FOR_UPDATES: "Klask hizivadennoù"
+ ADD: "Ouzhpenañ"
+ CLEAR_CACHE: "Skarzhañ ar c'hrubuilh"
+ CLEAR_CACHE_ALL_CACHE: "Ar c'hrubuilh a-bezh"
+ CLEAR_CACHE_ASSETS_ONLY: "Loazioù nemetken"
+ CLEAR_CACHE_IMAGES_ONLY: "Skeudennoù nemetken"
+ CLEAR_CACHE_CACHE_ONLY: "Krubuilh nemetken"
+ CLEAR_CACHE_TMP_ONLY: "Padennek hepken"
+ DASHBOARD: "Taolenn labour"
+ UPDATES_AVAILABLE: "Hizivadennoù hegerz"
+ DAYS: "Devezhioù"
+ UPDATE: "Hizivadenn"
+ BACKUP: "Gwared"
+ STATISTICS: "Stadegoù"
+ TODAY: "Hiziv"
+ WEEK: "Sizhun"
+ MONTH: "Miz"
+ LATEST_PAGE_UPDATES: "Hizivadennoù diwezhañ ar bajenn"
+ MAINTENANCE: "Trezalc'h"
+ UPDATED: "Hizivaet"
+ MON: "Lun"
+ TUE: "Meu"
+ WED: "Mer"
+ THU: "Yao"
+ FRI: "Gwe"
+ SAT: "Sad"
+ SUN: "Sul"
+ COPY: "Eilañ"
+ EDIT: "Kemmañ"
+ CREATE: "Krouiñ"
+ GRAV_ADMIN: "Merour Grav"
+ GRAV_OFFICIAL_PLUGIN: "Enlugellad Kefridiel Grav"
+ GRAV_OFFICIAL_THEME: "Neuz Kefridiel Grav"
+ PLUGIN_SYMBOLICALLY_LINKED: "Gant un ere arouezus eo lakaet an enlugellad. Ne vo ket dinoet an hizivadennoù."
+ THEME_SYMBOLICALLY_LINKED: "Gant un ere arouezus eo lakaet an neuz. Ne vo ket dinoet an hizivadennoù"
+ REMOVE_PLUGIN: "Dilemel an enlugellad"
+ INSTALL_PLUGIN: "Staliañ an enlugellad"
+ AVAILABLE: "Hegerz"
+ INSTALLED: "Staliet"
+ INSTALL: "Staliañ"
+ ACTIVE_THEME: "Neuz oberiant"
+ SWITCHING_TO: "Kemmañ da"
+ SWITCHING_TO_DESCRIPTION: "En ur gemmañ d'un neuz disheñvel n'eus gwarant ebet e vo skoret an holl frammoù pajenn, ar pezh a zegasfe fazioù en ur gargañ ar pajennoù-mañ."
+ SWITCHING_TO_CONFIRMATION: "Fellout a ra deoc'h kenderc'hel ha kemmañ an neuz"
+ CREATE_NEW_USER: "Krouiñ un arveriad nevez"
+ REMOVE_USER: "Dilemel an arveriad"
+ ACCESS_DENIED: "Haeziñ nac'het"
+ ACCOUNT_NOT_ADMIN: "n'hoc'h eus ket an aotreoù a-zere"
+ PHP_INFO: "Titouroù PHP"
+ INSTALLER: "Stalier"
+ AVAILABLE_THEMES: "Neuzioù hegerz"
+ AVAILABLE_PLUGINS: "Enlugelladoù hegerz"
+ INSTALLED_THEMES: "Neuzioù staliet"
+ INSTALLED_PLUGINS: "Enlugelladoù staliet"
+ BROWSE_ERROR_LOGS: "Furchal er c'herzhlevr fazioù"
+ SITE: "Lec'hienn"
+ INFO: "Titouroù"
+ SYSTEM: "Reizhiad"
+ USER: "Arveriad"
+ ADD_ACCOUNT: "Ouzhpennañ ur gont"
+ SWITCH_LANGUAGE: "Kemmañ ar yezh"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Gweredekaet an enlugellad gant berzh"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Diweredekaet an enlugellad gant berzh"
+ SUCCESSFULLY_CHANGED_THEME: "Kemmet an neuz dre ziouer gant berzh"
+ INSTALLATION_FAILED: "C'hwitadenn war ar staliadur"
+ INSTALLATION_SUCCESSFUL: "Berzh war ar staliadur"
+ UNINSTALL_FAILED: "C'hwitadenn war an distaliadur"
+ UNINSTALL_SUCCESSFUL: "Berzh war an distaliadur"
+ SUCCESSFULLY_SAVED: "Enrollet gant berzh"
+ SUCCESSFULLY_COPIED: "Eilet gant berzh"
+ REORDERING_WAS_SUCCESSFUL: "Adurzhiet gant berzh"
+ SUCCESSFULLY_DELETED: "Dilamet gant berzh"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Kemmet ar yezh gant berzh"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "N'ho peus ket trawalc'h a aotreoù evit ar gwered"
+ CACHE_CLEARED: "Skarzhet ar c'hrubuilh"
+ METHOD: "Hentenn"
+ ERROR_CLEARING_CACHE: "Fazi en ur skarzhañ ar c'hrubuilh"
+ AN_ERROR_OCCURRED: "Degouezhet ez eus bet ur fazi"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Prest eo ho kwared da vezañ pellgarget"
+ DOWNLOAD_BACKUP: "Pellgargañ ar gwared"
+ PAGES_FILTERED: "Pajennoù silet"
+ NO_PAGE_FOUND: "Pajenn ebet kavet"
+ INVALID_PARAMETERS: "Arventennoù didalvoudek"
+ NO_FILES_SENT: "Restr ebet kaset"
+ UNKNOWN_ERRORS: "Fazioù dianav"
+ UNSUPPORTED_FILE_TYPE: "Doare restr amskor"
+ FAILED_TO_MOVE_UPLOADED_FILE: "C'hwitadenn en ur zilec'hiañ ar restr pellgaset."
+ FILE_UPLOADED_SUCCESSFULLY: "Restr pellgaset gant berzh"
+ FILE_DELETED: "Restr dilamet"
+ FILE_COULD_NOT_BE_DELETED: "N'haller ket dilemel ar restr"
+ FILE_NOT_FOUND: "N'eus ket bet kavet ar restr"
+ NO_FILE_FOUND: "Restr ebet kavet"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Hizivaet eo bet Grav da"
+ GRAV_UPDATE_FAILED: "C'hwitadenn war hizivadenn Grav"
+ EVERYTHING_UPDATED: "Hizivaet pep tra"
+ UPDATES_FAILED: "C'hwitadenn war a hizivadennoù"
+ AVATAR_BY: "Avatar gant"
+ LAST_BACKUP: "Gwared diwezhañ"
+ FULL_NAME: "Anv klok"
+ USERNAME: "Anv arveriad"
+ EMAIL: "Chomlec'h postel"
+ PASSWORD: "Ger-tremen"
+ PASSWORD_CONFIRM: "Kadarnat ar ger-tremen"
+ TITLE: "Titl"
+ LANGUAGE: "Yezh"
+ ACCOUNT: "Kont"
+ EMAIL_VALIDATION_MESSAGE: "Ret eo reiñ ur chomlec'h postel talvoudek"
+ PASSWORD_VALIDATION_MESSAGE: "Ret eo d'ar ger-tremen enderc'hel ur niverenn, ul lizherenn vras hag ul lizherenn vihan hag 8 arouezenn d'an nebeutañ"
+ LANGUAGE_HELP: "Dibabit ar yezh"
+ MEDIA: "Media"
+ DEFAULTS: "Dre ziouer"
+ SITE_TITLE: "Titl al lec'hienn"
+ SITE_TITLE_PLACEHOLDER: "Titl ledan al lec'hienn"
+ SITE_TITLE_HELP: "Titl dre ziouer ho lec'hienn, arveret en neuzioù"
+ SITE_DEFAULT_LANG: "Yezh defot"
+ DEFAULT_AUTHOR: "Aozer dre ziouer"
+ DEFAULT_AUTHOR_HELP: "Un anv aozer dre ziouer, arveret en neuzioù pe er pajennoù"
+ DEFAULT_EMAIL: "Chomlec'h postel dre ziouer"
+ DEFAULT_EMAIL_HELP: "Ur chomlec'h postel dre ziouer, arveret en neuze pe er pajennoù"
+ TAXONOMY_TYPES: "Doareoù rummadoù"
+ TAXONOMY_TYPES_HELP: "An doareoù rummadoù a rank bezañ erspizet amañ ma fell deoc'h arverañ anezho er pajennoù"
+ PAGE_SUMMARY: "Berradenn ar bajenn"
+ ENABLED: "Gweredekaet"
+ ENABLED_HELP: "Gweredekaat berradenn ar bajenn (ar verradenn a zistro an hevelep tra hag endalc'had ar bajenn)"
+ 'YES': "Ya"
+ 'NO': "Ket"
+ SUMMARY_SIZE: "Ment ar verradenn"
+ SUMMARY_SIZE_HELP: "An niverenn a arouezenn da arverañ evel berradenn ur bajenn"
+ FORMAT: "Mentrezh"
+ FORMAT_HELP: "berr = arverañ degouezh kentañ an disranner pe ment; hir = laosket e vo an disranner berradenn a-gostez"
+ SHORT: "Berr"
+ LONG: "Hir"
+ DELIMITER: "Disranner"
+ DELIMITER_HELP: "Disranner ar verradenn (diouer '===')"
+ METADATA: "Metaroadennoù"
+ METADATA_HELP: "Skrammet e vo ar gwerzhioù metaroadennoù dre ziouer war an holl bajennoù war-bouez m'eo flastret gant ar bajenn"
+ NAME: "Anv"
+ CONTENT: "Endalc'had"
+ REDIRECTS_AND_ROUTES: "Adheñchañ ha treugoù"
+ CUSTOM_REDIRECTS: "Adheñchañ personelaet"
+ CUSTOM_REDIRECTS_HELP: "treugoù da adheñchañ davet pajennoù all. Talvoudek eo an amsaviñ regex"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/un/anv"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/un/adeñchañ"
+ CUSTOM_ROUTES: "Treugoù personelaet"
+ CUSTOM_ROUTES_HELP: "treugoù da adheñchañ davet pajennoù all. Talvoudek eo an amsaviñ Regex"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/ho/anv"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/ho/treug"
+ FILE_STREAMS: "Lanvioù restroù"
+ DEFAULT: "Dre ziouer"
+ PAGE_MEDIA: "Media ar bajenn"
+ OPTIONS: "Dibarzhioù"
+ PUBLISHED: "Embannet"
+ PUBLISHED_HELP: "Dre ziouer eo embannet ur bajenn war-bouez m'eo lakaet da \"Embannet: ket\" pe dre un deiziad embann en dazont, pe un deiziad diembannañ tremenet"
+ DATE: "Deiziad"
+ DATE_HELP: "Ar vaezienn deiziad a laosk ac'hanoc'h da arventennañ un deiziad liammet gant ar bajenn."
+ PUBLISHED_DATE: "Deiziad embann"
+ PUBLISHED_DATE_HELP: "Gallout a rit reiñ un deiziad da embann ent emgefreek."
+ UNPUBLISHED_DATE: "Deiziad diembannañ"
+ UNPUBLISHED_DATE_HELP: "Gallout a rit reiñ un deiziad evit diembannañ ent emgefreek."
+ ROBOTS: "Robotoù"
+ TAXONOMIES: "Rummadoù"
+ TAXONOMY: "Rummad"
+ ADVANCED: "Kempleshoc'h"
+ SETTINGS: "Arventennoù"
+ FOLDER_NUMERIC_PREFIX: "Rakger niverel an teuliad"
+ FOLDER_NUMERIC_PREFIX_HELP: "Rakgerioù niverel evit urzhiañ gant an dorn ha emplegañ ar gwelusted"
+ FOLDER_NAME: "Anv an teuliad"
+ FOLDER_NAME_HELP: "Anv an teuliad a vo kadavet er reizhiad restroù evit ar bajenn"
+ PARENT: "Kar"
+ DEFAULT_OPTION_ROOT: "- Gwrizienn -"
+ DEFAULT_OPTION_SELECT: "- Diuzañ -"
+ DISPLAY_TEMPLATE: "Skrammañ ar patrom"
+ DISPLAY_TEMPLATE_HELP: "An doare pajenn a ziviz peseurt patrom twig a zeznaouo ar bajenn"
+ BODY_CLASSES: "Klasoù korf"
+ ORDERING: "Urzh"
+ PAGE_ORDER: "Urzh ar pajennoù"
+ OVERRIDES: "Flastrañ"
+ MENU: "Lañser"
+ MENU_HELP: "Ar chadennoù da arverañ el lañser. Ma n'eo ket arventennet, Titl a vo arveret."
+ SLUG: "Slug"
+ SLUG_HELP: "An argemenn slug a aotren ac'hanoc'h da arventennañ URL lodenn ar bajenn"
+ SLUG_VALIDATE_MESSAGE: "Lizherennoù bihan, sifroù ha tiredoù a c'hall bezañ er slug hepken"
+ PROCESS: "Keweriañ"
+ PROCESS_HELP: "Reoliañ penaos eo keweriet ar pajennoù. Gallout a ra bezañ lakaet dre bajenn kentoc'h eget en un doare hollek"
+ DEFAULT_CHILD_TYPE: "Doare bugel dre ziouer"
+ USE_GLOBAL: "Arverañ Hollek"
+ ROUTABLE: "Treugus"
+ ROUTABLE_HELP: "M'eo haezadus ar bajenn dre un URL"
+ CACHING: "Krubuilhiñ"
+ VISIBLE: "Gwelus"
+ VISIBLE_HELP: "Despizañ a ra gwelusted ur bajenn er merdeiñ."
+ DISABLED: "Diweredekaet"
+ ITEMS: "Ergorennoù"
+ ORDER_BY: "Urzhiañ dre"
+ ORDER: "Urzh"
+ FOLDER: "Teuliad"
+ ASCENDING: "War-gresk"
+ DESCENDING: "War-zigresk"
+ ADD_MODULAR_CONTENT: "Ouzhpennañ un endalc'had molladel"
+ PAGE_TITLE: "Titl ar bajenn"
+ PAGE_TITLE_HELP: "Titl ar bajenn"
+ PAGE: "Pajenn"
+ MODULAR_TEMPLATE: "Patrom molladel"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Anv ar restr"
+ PARENT_PAGE: "Pajenn gar"
+ HOME_PAGE: "Pennbajenn"
+ HOME_PAGE_HELP: "Pajenn arveret gant Grav evel pajenn degemer dre ziouer"
+ DEFAULT_THEME: "Neuz dre ziouer"
+ DEFAULT_THEME_HELP: "Arventennañ an neuz arveret gant Grav dre ziouer (Antimatter dre ziouer)"
+ TIMEZONE: "Gwerzhid-eur"
+ TIMEZONE_HELP: "Flastrañ gwerzhid-eur dre ziouer an dafariad"
+ SHORT_DATE_FORMAT: "Mentrezh skrammañ an deiziad berr"
+ SHORT_DATE_FORMAT_HELP: "Arventennan ar mentrezh deiziad berr da arverañ gant an neuzioù"
+ LONG_DATE_FORMAT: "Mentrezh deiziad hir"
+ LONG_DATE_FORMAT_HELP: "Arventennañ ar mentrezh deiziad hir a vo arveret en neuzioù"
+ DEFAULT_ORDERING: "Urzh dre ziouer"
+ DEFAULT_ORDERING_HELP: "Pajennoù er roll a vo skrammet en urzh-mañ war-bouez m'eo flastret"
+ DEFAULT_ORDERING_DEFAULT: "Dre ziouer - diazezet war anv an teuliad"
+ DEFAULT_ORDERING_FOLDER: "Teuliad - diazezet war anv an teuliad hep rakger"
+ DEFAULT_ORDERING_TITLE: "Titl - diazezet war vaezienn ditl an talbenn"
+ DEFAULT_ORDERING_DATE: "Deiziad - diazezet war vaezienn deiziad an talbenn"
+ DEFAULT_ORDER_DIRECTION: "Tu an urzh dre ziouer"
+ DEFAULT_ORDER_DIRECTION_HELP: "Tu ar pajennoù er roll"
+ DEFAULT_PAGE_COUNT: "Niver a bajennoù dre ziouer"
+ DEFAULT_PAGE_COUNT_HELP: "Niver a bajennoù en ur roll d'ar muiañ"
+ DATE_BASED_PUBLISHING: "Embannadenn diazezet war un deiziad"
+ DATE_BASED_PUBLISHING_HELP: "(Di)embann pennadoù ent emgefreek hervez o deiziad"
+ EVENTS: "Darvoudoù"
+ EVENTS_HELP: "(Di)weredekaat darvoudoù resis. Diweredekaat anezho a c'hall terriñ enlugelladoù"
+ REDIRECT_DEFAULT_ROUTE: "Adheñchañ an treug dre ziouer"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Adheñchañ ent emgefreek d'un treug pajenn dre ziouer"
+ LANGUAGES: "Yezhoù"
+ SUPPORTED: "Skoret"
+ SUPPORTED_HELP: "Roll bonegoù yezh 2 lizherenn ennañ disrannet gant skejoù (skouer: 'br, cy, en')"
+ TRANSLATIONS_ENABLED: "Troidigezhioù gweredekaet"
+ TRANSLATIONS_ENABLED_HELP: "Skor an troidigezhioù e Grav, an enlugelladoù hag an askouezhioù"
+ TRANSLATIONS_FALLBACK: "Troidigezh dre ziouer"
+ TRANSLATIONS_FALLBACK_HELP: "Arverañ un droidigezh all ma n'eus ket eus ar tezh oberiant"
+ ACTIVE_LANGUAGE_IN_SESSION: "Yezhoù oberiant en estez"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Kadaviñ ar yezh oberiant en estez"
+ HTTP_HEADERS: "Talbennoù HTTP"
+ EXPIRES: "Diamzer"
+ EXPIRES_HELP: "Arventennañ an talbenn diamzeriñ e eilennoù."
+ LAST_MODIFIED: "Kemmet da ziwezhañ"
+ LAST_MODIFIED_HELP: "Arventennañ an talbenn kemmet da ziwezhañ a c'hall skoazell da wellaat ar proksi ha krubuilh ar merdeer"
+ ETAG: "ETag"
+ ETAG_HELP: "Arventennañ an talbenn etag evit skoazell da c'houzout peur eo bet kemmet ur bajenn"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Arventennañ a ra an talbenn `Vary: Accept Encoding` evit skoazell gant ar proksi hag ar c'hrubuilh CDN"
+ MARKDOWN_EXTRA_HELP: "Gweredekaat ar skor dre ziouer evit Markdown Ectra - https://michelf.ca/projects/php-markdown/extra/"
+ AUTO_LINE_BREAKS: "Tremen d'al linenn ent emgefreek"
+ AUTO_LINE_BREAKS_HELP: "Gweredekaat skor tremen al linenn ent emgefreek e Markdown"
+ AUTO_URL_LINKS: "Ereoù URL emgefreek"
+ AUTO_URL_LINKS_HELP: "Gweredekaat amdroadur emgefreek an URLoù da ereoù HTML"
+ ESCAPE_MARKUP: "Gwareziñ an HTML"
+ ESCAPE_MARKUP_HELP: "Gwareziñ ar c'hlavioù e elfennoù HTML"
+ CACHING_HELP: "Trec'haoler hollek evit (di)weredekaat krubuilh Grav"
+ CACHE_CHECK_METHOD: "Hentenn gwiriekaat ar c'hrubuilh"
+ CACHE_CHECK_METHOD_HELP: "Dibab an hentenn arveret gant Grav evit gwiriekaat m'eo bet kemmer ar restoù pajenn."
+ CACHE_DRIVER: "Sturier Krubuilh"
+ CACHE_DRIVER_HELP: "Dibab pe sturier krubuilh a zo arveret Grav. 'Dinoiñ emgefreek' a glask kavout pe zoare a zo an hini gwellañ"
+ CACHE_PREFIX: "Rakger ar c'hrubuilh"
+ CACHE_PREFIX_HELP: "Lodenn naoudi an alc'hwez Grav. Na gemmit anezhi ma n'ouzit ket petra rit."
+ CACHE_PREFIX_PLACEHOLDER: "Deveret eus an URL diazez (flastret en un enkañ ur chadenn dargouezhek)"
+ LIFETIME: "Padelezh buhez"
+ LIFETIME_HELP: "Arventennañ padelezh ar c'hrubuilh e eilennoù. 0 = anvevenn"
+ GZIP_COMPRESSION: "Koazhadur Gzip"
+ GZIP_COMPRESSION_HELP: "Gweredekaat koazhadur Gzip ar bajenn Grav evit kreskiñ an digonusted."
+ TWIG_TEMPLATING: "Patromiñ Twig"
+ TWIG_CACHING: "Krubuilh Twig"
+ TWIG_CACHING_HELP: "Reoliañ wikefre krubuilh Twig. Laoskit gweredekaet evit an digonusted gwellañ."
+ TWIG_DEBUG: "Diveugañ Twig"
+ TWIG_DEBUG_HELP: "Aotren an dibarzh evit chom hep kargañ an askouezh diveugañ Twig"
+ DETECT_CHANGES: "Dinoiñ ar c'hemmoù"
+ DETECT_CHANGES_HELP: "Adkempunet e vo krubuilh Twig ent emgefreek ma vez dinoet kemmoù er patromoù Twig"
+ AUTOESCAPE_VARIABLES: "Gwareziñ an argemennoù ent emgefreek"
+ AUTOESCAPE_VARIABLES_HELP: "Gwareziñ an holl argemennoù ent emgefreek. Moarvat e torro ho lec'hienn"
+ ASSETS: "Madoù"
+ CSS_PIPELINE: "Arrevellañ CSS"
+ CSS_PIPELINE_HELP: "Arrevellañ ar CSS a zo unvanadur meur a loaz CSS en ur restr hepken"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Ebarzhiñ restroù estren en arrevellañ CSS"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "URLoù diavaez a zo gant daveoù restroù daveel a-wechoù ha ne rankont ket bezañ arrevellet"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "Deoueziñ an arrevellañ CSS da gentañ"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Deoueziñ an arrevellañ CSS a-raok kement dave CSS all ha n'int ket enkorfet"
+ CSS_MINIFY: "Bihanadur CSS"
+ CSS_MINIFY_HELP: "Bihanaat ar CSS e-pad an arrevellañ"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "Amsaviñ bihanadur ar CSS Windows"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Amsaviñ ar bihanadur evit savennoù Windows. Faos dre ziouer abalamour da ThreadStackSize"
+ CSS_REWRITE: "Adskrivañ CSS"
+ CSS_REWRITE_HELP: "Adskrivañ kement URL daveel CSS e-pad an arrevellañ"
+ JAVASCRIPT_PIPELINE: "Arrevellañ Javascript"
+ JAVASCRIPT_PIPELINE_HELP: "An arrevellañ JS a zo unvanadur meur a restr JS en ur restr hepken"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Enkorfañ ar JS diavaez evit an arrevellañ"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Urloù diavaez o deus daveoù restroù daveel a-wechoù ha ne rankont ket bezañ arrevellet"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Arrevellañ JS da gentañ"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Deoueziñ an arrevellañ JS a-raok kement dave JS all ha n'int ket enkorfet"
+ JAVASCRIPT_MINIFY: "Bihanat ar javascript"
+ JAVASCRIPT_MINIFY_HELP: "Bihanaat ar JS e-pad an arrevellañ"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Gweredekaat ar boneg-amzer war al loazioù"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Gweredekaat bonegoù-amzer al loazioù"
+ COLLECTIONS: "Dastumadegoù"
+ ERROR_HANDLER: "Dornataour fazioù"
+ DISPLAY_ERRORS: "Skrammañ ar fazioù"
+ DISPLAY_ERRORS_HELP: "Skrammañ ur bajenn fazi gant munudoù"
+ LOG_ERRORS: "Kerzhlevr ar fazioù"
+ LOG_ERRORS_HELP: "Lakaat kerzhlevr ar fazioù en teuliad /logs"
+ DEBUGGER: "Diveuger"
+ DEBUGGER_HELP: "Gweredekaat diveuger Grav hag an arventennoù da heul"
+ DEBUG_TWIG: "Diveugañ Twig"
+ DEBUG_TWIG_HELP: "Gweredekaat diveugañ ar patromoù Twig"
+ SHUTDOWN_CLOSE_CONNECTION: "Shutdown a serr ar c'hennask"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Serriñ ar c'hennask a-raok gervel onShutdown(). 'false' evit diveugañ"
+ DEFAULT_IMAGE_QUALITY: "Perzhded skeudenn dre ziouer"
+ DEFAULT_IMAGE_QUALITY_HELP: "Perzhded skeudenn dre ziouer da arverañ e-pad adstandilhonañ ar skeudennoù (85%)"
+ CACHE_ALL: "Lakaat an holl skeudennoù er c'hrubuilh"
+ CACHE_ALL_HELP: "Lakaat an holl skeudennoù da dremen dre reizhiad krubuilh Grav zoken ma n'o deus dornatadur media ebet"
+ IMAGES_DEBUG: "Rouedigell diveugañ ar skeudenn"
+ IMAGES_DEBUG_HELP: "Diskouez un diflugell a-us d'ar skeudennoù a ziskouez an donder piksel pa labourer war Retina da skouer"
+ UPLOAD_LIMIT: "Bevenn ment ar restroù da bellgas"
+ UPLOAD_LIMIT_HELP: "Lakaat ar ment restroù uhelañ e eizhbitoù (0 a zo anvevenn)"
+ ENABLE_MEDIA_TIMESTAMP: "Gweredekaat ar boneg-amzer war ar media"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Ouzhpennañ ur boneg-amzer diazezet war an deiziad kemmadur evit pep elfenn media"
+ SESSION: "Estez"
+ SESSION_ENABLED_HELP: "Gweredekaat skor an estez evit Grav"
+ TIMEOUT: "Diamzeriñ"
+ TIMEOUT_HELP: "Lakaat an amzer diamzeriñ e eilennoù"
+ SESSION_NAME_HELP: "Un naoudi arveret da stummañ anv toupin an estez"
+ ABSOLUTE_URLS: "URL dizave"
+ ABSOLUTE_URLS_HELP: "URLoù dizave pe daveel evit 'base_url'"
+ PARAMETER_SEPARATOR: "Disranner arventenn"
+ PARAMETER_SEPARATOR_HELP: "An disranner evit an arventennoù tremenet a c'hall bezañ kemmet evit Apache war Windows"
+ TASK_COMPLETED: "Trevell echuet"
+ EVERYTHING_UP_TO_DATE: "Pep tra a zo hizivaet"
+ UPDATES_ARE_AVAILABLE: "hizivadennoù hegerz"
+ IS_AVAILABLE_FOR_UPDATE: "a zo gant un hizivadenn hegerz"
+ IS_NOW_AVAILABLE: "a zo hegerz"
+ CURRENT: "Bremanel"
+ UPDATE_GRAV_NOW: "Hizivaat Grav bremañ"
+ GRAV_SYMBOLICALLY_LINKED: "Gant un ere arouezel eo staliet Grav. Dihegerz eo an hizivadenn"
+ UPDATING_PLEASE_WAIT: "Oc'h hizivaat... gortozit, emañ o pellgargañ"
+ OF_THIS: "eus an"
+ OF_YOUR: "eus ho"
+ HAVE_AN_UPDATE_AVAILABLE: "en deus un hizivadenn hegerz"
+ SAVE_AS: "Enrollañ evel"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Sur oc'h e fell deoc'h dilemel ar bajenn-mañ hag holl he bugale? M'eo troet ar bajenn en ur yezh all e vo miret an troidigezhioù a rankout a reot o dilemel en un doare distag. E mod all e vo dilamet teuliad ar bajenn gant an is-pajennoù. N'haller ket dizober ar gwered-mañ."
+ AND: "ha"
+ UPDATE_AVAILABLE: "Hizivadenn hegerz"
+ METADATA_KEY: "Alc'hwez (sk. 'Gerioù-alc'hwez')"
+ METADATA_VALUE: "Gwerzh (sk. 'Blog, Grav')"
+ USERNAME_HELP: "Etre 3 ha 16 arouezenn e rank an anv arveriad bezañ o kontañ al lizherennoù bihan, an niverennoù, an islinennoù hag ar barrennigoù. N'eo ket aotreet al lizherennoù bras, an esaouennoù hag an arouezennoù arbennik"
+ FULLY_UPDATED: "Hizivaet"
+ SAVE_LOCATION: "Lec'hiadur enrollañ"
+ PAGE_FILE: "Patrom pajenn"
+ PAGE_FILE_HELP: "Anv restr patrom ar bajenn, ha patrom skrammañ ar bajenn dre ziouer"
+ NO_USER_ACCOUNTS: "Kont arveriad ebet kavet, krouit unan da gentañ..."
+ REDIRECT_TRAILING_SLASH: "Adheñchañ ar veskell dibenn"
+ REDIRECT_TRAILING_SLASH_HELP: "Ober un adheñchañ 301 e-lerc'h merañ an beskell dibenn an URI en un doare treuzwelus."
+ DEFAULT_DATE_FORMAT: "Mentrezh deiziad ar bajenn"
+ DEFAULT_DATE_FORMAT_HELP: "Mentrezh deiziad ar bajenn arveret gant Grav. Dre ziouer, Grav a glask divinout mentrezh an deiziad met gallout a rit erspizañ unan gant kevreadur deiziad PHP (sk.: Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Divinout en emgefreek"
+ IGNORE_FILES: "Leuskel restroù a-gostez"
+ IGNORE_FILES_HELP: "Restroù da leuskel a-gostez e-pad keweriañ ar pajennoù"
+ IGNORE_FOLDERS: "Leuskel teuliadoù a-gostez"
+ IGNORE_FOLDERS_HELP: "Teuliadoù resis da leuskel a-gostez e-pad keweriañ ar pajennoù"
+ HTTP_ACCEPT_LANGUAGE: "Lakaat yezh ar merdeer"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Gallout a rit klask arventennañ ar yezh gant hini ar talbenn `http_accept_language` ar merdeer"
+ OVERRIDE_LOCALE: "Flastrañ ar yezh"
+ OVERRIDE_LOCALE_HELP: "Flastrañ arventenn yezh PHP diazezet war ar yezh vremanel"
+ REDIRECT: "Adheñchañ ar bajenn"
+ REDIRECT_HELP: "Enankit hent ur bajenn pe un URL diavaez da adheñchañ ar bajenn. Sk. '/un/hent' pe 'http://ulload.bzh'"
+ PLUGIN_STATUS: "Stad an elugellad"
+ INCLUDE_DEFAULT_LANG: "Enkorfañ ar yezh dre ziouer"
+ INCLUDE_DEFAULT_LANG_HELP: "Ouzhpennañ a raio ar yezh dre ziouer en holl URLoù er yezh dre ziouer. Sk. '/br/blog/post'"
+ ALLOW_URL_TAXONOMY_FILTERS: "URL siloù rummad"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Dastumadegoù pajennoù a aotren ac'hanoc'h da silañ dre '/rummad:gwerzh'."
+ REDIRECT_DEFAULT_CODE: "Boneg adheñchan dre ziouer"
+ REDIRECT_DEFAULT_CODE_HELP: "Boneg stad HTTP da arverañ evit adheñchañ"
+ IGNORE_HIDDEN: "Leuskel ar re kuzhet a-gostez"
+ IGNORE_HIDDEN_HELP: "Leuskel an holl restroù ha teuliadoù a grog gant ur POENT"
+ WRAPPED_SITE: "Lec'hienn enkorfet"
+ WRAPPED_SITE_HELP: "Evit ma ouife an neuzioù/enlugelladoù m'eo enkorfet Grav en ur savenn all"
+ FALLBACK_TYPES: "Aotren doareoù fallback"
+ FALLBACK_TYPES_HELP: "Doareoù restr aotreet a c'hall bezañ kavet m'int haezet dre hent ar bajenn. An holl zoareoù media skoret dre ziouer."
+ INLINE_TYPES: "Doareoù fallback enkorfet"
+ INLINE_TYPES_HELP: "Ur roll doareoù restroù a rank bezañ skrammet en un doare enkorfet kentoc'h eget pellgarget"
+ APPEND_URL_EXT: "Ouzhpennañ an astenn d'an URL"
+ APPEND_URL_EXT_HELP: "Ouzhpennañ a raio un astenn personelaet da URL ar bajenn. Talvezout a ra e glasko Grav ur patrom anvet `..twig`"
+ PAGE_MODES: "Modoù pajenn"
+ PAGE_TYPES: "Doareoù pajenn"
+ ACCESS_LEVELS: "Liveoù haeziñ"
+ GROUPS: "Strolladoù"
+ GROUPS_HELP: "Roll ar strolladoù gant an arveriad enno"
+ ADMIN_ACCESS: "Haeziñ ardoer"
+ SITE_ACCESS: "Haeziñ d'al lec'hienn"
+ INVALID_SECURITY_TOKEN: "Reveziadenn diogelroez didalvoudek"
+ ACTIVATE: "Gweredekaat"
+ TWIG_UMASK_FIX: "Ratreadur Umask"
+ TWIG_UMASK_FIX_HELP: "Twig a grou ar restroù krubuilh gant 0755 dre ziouer, ar ratreañ a lak anezho da 0755"
+ CACHE_PERMS: "Aotreoù ar c'hrubuilh"
+ CACHE_PERMS_HELP: "Aotreoù dre ziouer teuliad ar c'hrubuilh. 0755 pe 0775 peurvuiañ, hervez ar c'hefluniadur"
+ REMOVE_SUCCESSFUL: "Dilamet gant berzh"
+ REMOVE_FAILED: "C'hwitadenn war an dilemel"
+ HIDE_HOME_IN_URLS: "Kuzhat hent ar pennbajenn en URL"
+ HIDE_HOME_IN_URLS_HELP: "Gwiriekaat a raio n'eo ket daveet hent skoueriek an degemer gant hentoù dre ziouer ar pajennoù dindan an degemer"
+ TWIG_FIRST: "Keweriañ an Twig da gentañ"
+ TWIG_FIRST_HELP: "M'ho peus gweredekaat keweriañ ar bajenn Twig e c'hallit kefluniañ Twig evit e geweriañ a-raok pe goude ar Markdown"
+ SESSION_SECURE: "Diogel"
+ SESSION_SECURE_HELP: "M'eo gwir, diskouez a ra eo ret d'ar c'hehentiñ evit an toupin-mañ bezañ graet war un treuzkas diogel. DIWALLIT: Gweredekait an dra-se war lec'hiennoù e HTTPS nemetken"
+ SESSION_HTTPONLY: "HTTP nemetken"
+ SESSION_HTTPONLY_HELP: "M'eo gwir, diskouez a ra eo ret d'ar c'hehentiñ evit an toupin-mañ bezañ graet war un treuzkas HTTP ha n'eo ket aotreet kemmañ ar Javascript"
+ REVERSE_PROXY: "Proksi en tu-gin"
+ REVERSE_PROXY_HELP: "Gweredekait an dra-se m'hoc'h a-dreñv ur proksi en tu-gin hag ho peus diaesterioù gant an URLoù oc'h enderc'hel ur porzh didalvoudek"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Frontmatter didalvoudek, n'haller ket enrollan"
+ ADD_FOLDER: "Ouzhpennañ un teuliad"
+ PROXY_URL: "URL ar proksi"
+ PROXY_URL_HELP: "Enankit HERBERC'HIER pe IP ar proksi hag ar PORZH"
+ NOTHING_TO_SAVE: "Netra da enrollañ"
+ FILE_ERROR_ADD: "Degouezhet ez eus bet ur fazi en ur glask enrollañ ar restr"
+ FILE_ERROR_UPLOAD: "Degouezhet ez eus bet ur fazi en ur glask pellgas ar restr"
+ FILE_UNSUPPORTED: "Doare restr anskor"
+ ADD_ITEM: "Ouzhpennañ un elfenn"
+ FILE_TOO_LARGE: "Re leden eo ar restr evit bezañ pellgaset. %s eo an uhelañ aotreet hervez hoc'h arventennoù PHP. Kreskit an arventenn PHP`post_max_size`"
+ INSTALLING: "O staliañ"
+ LOADING: "O kargañ.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "Ret eo deoc'h staliañ an amzalc'hoù da-heul a-raok:"
+ ERROR_INSTALLING_PACKAGES: "Fazi en ur staliañ ar pakad(où)"
+ INSTALLING_DEPENDENCIES: "O staliañ an amzalc'hoù..."
+ INSTALLING_PACKAGES: "O staliañ ar pakad(où).."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Pakad(où) staliet gant berzh."
+ READY_TO_INSTALL_PACKAGES: "Prest da staliañ ar pakad(où)"
+ PACKAGES_NOT_INSTALLED: "N'eo ket stalied ar pakadoù"
+ PACKAGES_NEED_UPDATE: "Staliet eo ar pakadoù endeo, met re gozh eo"
+ PACKAGES_SUGGESTED_UPDATE: "Staliet eo ar pakadoù endeo, dereat eo an handelv, met hizivaet e vint evit ma vefec'h en handelv diwezhañ"
+ REMOVE_THE: "Dilemel an %s"
+ CONFIRM_REMOVAL: "Sur oc'h e fell deoc'h dilemel %s?"
+ REMOVED_SUCCESSFULLY: "%s dilamet gant berzh"
+ ERROR_REMOVING_THE: "Fazi en ur zilemel %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "An amzalc'hoù da heul a zo azgoulennet gant %s, met n'eo ket azgoulennet gant ur pakad all. Ma ne arverit ket anezho e c'hallit o dilemel adalek amañ."
+ READY_TO_UPDATE_PACKAGES: "Prest da hizivaat ar pakad(où)"
+ ERROR_UPDATING_PACKAGES: "Fazi en ur hizivaat ar pakad(où)"
+ UPDATING_PACKAGES: "Oc'h hizivaat ar pakad(où).."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Pakad(où) hizivaet gant berzh."
+ UPDATING: "Hizivaet"
+ GPM_RELEASES: "Ermaeziadennoù GPM"
+ GPM_RELEASES_HELP: "Dibabit 'Amprouiñ' evit staliañ an handelv beta pe amprouiñ"
+ AUTO: "Oto"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stabil"
+ TESTING: "Amprouiñ"
+ FRONTMATTER_PROCESS_TWIG: "Keweriañ frontmatter Twig"
+ FRONTMATTER_PROCESS_TWIG_HELP: "P'eo oberiant e c'hallit arverañ argemennoù kefluniañ Twig e frontmatter ar bajenn"
+ FRONTMATTER_IGNORE_FIELDS: "Leuskel maeziennoù Frontmatter a-gostez"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Maeziennoù Frontmatter a c'hall enderc'hel Twig met ne rankont ket bezañ keweriet, evel 'forms'"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Pakad %s staliet gant berzh"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Urzh ar c'har, diweredekaet eo an urzhiañ"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Diwelus eo ar bajenn, diweredekaet eo an urzhiañ"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "N'eo ket skoret an urzhiañ dre an ardeiñ dre ma zo ouzhpenn 200 c'hoar"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "EVEZHIADENN: n'hallit ket ouzhpennañ restroù media evit enrollañ ar bajenn. Klikit war 'Enrollañ' a-us"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "EVEZHIADENN: ret eo enrollañ ar bajenn a-raok pellgas restroù dezhi."
+ DROP_FILES_HERE_TO_UPLOAD: "Lakait ho restroù amañ pe klikit amañ "
+ INSERT: "Enlakaat"
+ UNDO: "Dizober"
+ REDO: "Adober"
+ HEADERS: "Talbennoù"
+ BOLD: "Tev"
+ ITALIC: "Stouet"
+ STRIKETHROUGH: "Barrennet"
+ SUMMARY_DELIMITER: "Bonner diverrad"
+ LINK: "Liamm"
+ IMAGE: "Skeudenn"
+ BLOCKQUOTE: "Meneg"
+ UNORDERED_LIST: "Roll dizurzh"
+ ORDERED_LIST: "Roll urzhiet"
+ EDITOR: "Embanner"
+ PREVIEW: "Alberz"
+ FULLSCREEN: "Skramm a-bezh"
+ MODULAR: "Modulel"
+ NON_ROUTABLE: "Dihentus"
+ NON_MODULAR: "Divodulel"
+ NON_VISIBLE: "Diwelus"
+ NON_PUBLISHED: "Diembannet"
+ CHARACTERS: "arouezioù"
+ PUBLISHING: "Embann"
+ NOTIFICATIONS: "Notifiadenn"
+ MEDIA_TYPES: "Doareoù media"
+ IMAGE_OPTIONS: "Opsionoù skeudenn"
+ MIME_TYPE: "Doare Mime"
+ THUMB: "Miniaturenn"
+ TYPE: "Doare"
+ FILE_EXTENSION: "Astenn ar fichenn"
+ LEGEND: "Alc'hwez ar bajenn"
+ MEMCACHE_SERVER: "Servijer Memcached"
+ MEMCACHE_SERVER_HELP: "Chomlec'h ar servijer Memcached"
+ MEMCACHE_PORT: "Porzh Memcached"
+ MEMCACHE_PORT_HELP: "Porzh ar servijer Memcached"
+ MEMCACHED_SERVER: "Servijer Memcached"
+ MEMCACHED_SERVER_HELP: "Chomlec'h ar servijer Memcached"
+ MEMCACHED_PORT: "Porzh Memcached"
+ MEMCACHED_PORT_HELP: "Porzh ar servijer Memcached"
+ REDIS_SERVER: "Servijer Redis"
+ REDIS_SERVER_HELP: "Chomlec'h ar servijer Redis"
+ REDIS_PORT: "Porzh Redis"
+ REDIS_PORT_HELP: "Porzh ar servijer Redis"
+ ALL: "Tout"
+ FROM: "eus"
+ TO: "da"
+ RESOURCE_FILTER: "Sil..."
+ FORCE_SSL: "Forsañ SSL"
+ DROPZONE_CANCEL_UPLOAD: 'Nullañ ar gargamant'
+ DROPZONE_REMOVE_FILE: "Dilemel ar fichenn"
+ PREMIUM_PRODUCT: "Premium"
+ ERROR_SIMPLE: "Fazi simpl"
+ ERROR_SYSTEM: "Fazi Sistem"
+ NOT_SET: "Pas termenet"
+ PERMISSIONS: "Permisionoù"
+ REINSTALL_PLUGIN: "Adstaliañ ar Plugin"
+ REINSTALL_THEME: "Adstaliañ an Tem"
+ REINSTALL_THE: "Adstaliañ ar %s"
+ REINSTALLATION_FAILED: "Adstaliañ c'hwitet"
+ TOOLS: "Ostilhoù"
+ DIRECT_INSTALL: "Staliañ war-eeun"
+ 2FA_CODE_INPUT: "000000"
diff --git a/user/plugins/admin/languages/ca.yaml b/user/plugins/admin/languages/ca.yaml
new file mode 100644
index 0000000..8a848cd
--- /dev/null
+++ b/user/plugins/admin/languages/ca.yaml
@@ -0,0 +1,641 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Aquesta és una versió beta! Utilitza-la en producció sota el teu propi risc..."
+ ADMIN_REPORT_ISSUE: "Has trobat algun problema? Sisplau, reporta'l a GitHub."
+ EMAIL_FOOTER: "Funcionant amb Grav - El CMS de fitxers plans modern"
+ LOGIN_BTN: "Inicia sessió"
+ LOGIN_BTN_FORGOT: "Ho he oblidat"
+ LOGIN_BTN_RESET: "Restablir contrasenya"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Envia instruccions pel reset"
+ LOGIN_BTN_CLEAR: "Netejar formulari"
+ LOGIN_BTN_CREATE_USER: "Crear usuari"
+ LOGIN_LOGGED_IN: "S'ha iniciat sessió correctament"
+ LOGIN_FAILED: "No s'ha pogut iniciar sessió"
+ LOGGED_OUT: "S'ha tancat la sessió"
+ RESET_NEW_PASSWORD: "Sisplau introdueix una nova contasenya …"
+ RESET_LINK_EXPIRED: "L'enllaç per a restablir contrasenya ha expirat, torna a provar"
+ RESET_PASSWORD_RESET: "S'ha restablert la contrasenya"
+ RESET_INVALID_LINK: "L'enllaç per a restablir contrasenya invàl·lid, torna a provar"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Les instruccions per a restablir la contrasenya s'han enviat per correu electrònic a %s"
+ FORGOT_FAILED_TO_EMAIL: "S'ha fallat al enviar les instruccions, sisplau torna-ho a provar"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "No es pot restablir la contrasenya per a %s, no té cap email assignat"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "L'usuari %s no existeix"
+ FORGOT_EMAIL_NOT_CONFIGURED: "No es pot restablir la contrasenya. Aquest lloc no està configurat per enviar missatges de correu electrònic"
+ FORGOT_EMAIL_SUBJECT: "%s Petició de restabliment de contrasenya"
+ FORGOT_EMAIL_BODY: "Restabliment de contrasenya Benvolgut/da %1$s,
S'ha fet una petició a %4$s per a restablir la contrasenya.
Fes clic aquí per a restablir la contrasenya
Altrament, copia el següent URL al teu navegador:
%2$s
Atentament, %3$s
"
+ MANAGE_PAGES: "Gestiona pàgines"
+ CONFIGURATION: "Configuració"
+ PAGES: "Pàgines"
+ PLUGINS: "Plugins"
+ PLUGIN: "Plugin"
+ THEMES: "Temes"
+ LOGOUT: "Tanca sessió"
+ BACK: "Enrere"
+ ADD_PAGE: "Afegeix pàgina"
+ ADD_MODULAR: "Afegeix modular"
+ MOVE: "Mou"
+ DELETE: "Esborra"
+ SAVE: "Desa"
+ NORMAL: "Normal"
+ EXPERT: "Expert"
+ EXPAND_ALL: "Expandeix tot"
+ COLLAPSE_ALL: "Col·lapsa tot"
+ ERROR: "Error"
+ CLOSE: "Tanca"
+ CANCEL: "Cancel·la"
+ CONTINUE: "Continua"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Confirmació requerida"
+ MODAL_CHANGED_DETECTED_TITLE: "Canvis detectats"
+ MODAL_CHANGED_DETECTED_DESC: "Tens canvis no desats. Estàs segur que vols sortir sense desar?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Confirmació requerida"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Estàs segur que vols eliminar aquest fitxer? Aquesta acció no es pot desfer."
+ ADD_FILTERS: "Afegeix filtres"
+ SEARCH_PAGES: "Cerca pàgines"
+ VERSION: "Versió"
+ WAS_MADE_WITH: "Es va fer amb"
+ BY: "Per"
+ UPDATE_THEME: "Actualitza tema"
+ UPDATE_PLUGIN: "Actualitza plugin"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "d'aquest tema està disponible"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "d'aquest plugin està disponible"
+ AUTHOR: "Autor/a"
+ HOMEPAGE: "Pàgina d'inici"
+ DEMO: "Demo"
+ BUG_TRACKER: "Rastrejador d'errors"
+ KEYWORDS: "Paraules clau"
+ LICENSE: "Llicència"
+ DESCRIPTION: "Descripció"
+ README: "Llegiu-me"
+ REMOVE_THEME: "Elimina tema"
+ INSTALL_THEME: "Instal·la tema"
+ THEME: "Tema"
+ BACK_TO_THEMES: "Torna a Temes"
+ BACK_TO_PLUGINS: "Torna a Plugins"
+ CHECK_FOR_UPDATES: "Cerca actualitzacions"
+ ADD: "Afegeix"
+ CLEAR_CACHE: "Neteja la memòria cache"
+ CLEAR_CACHE_ALL_CACHE: "Tota la cache"
+ CLEAR_CACHE_ASSETS_ONLY: "Només assets"
+ CLEAR_CACHE_IMAGES_ONLY: "Només imatges"
+ CLEAR_CACHE_CACHE_ONLY: "Només cache"
+ CLEAR_CACHE_TMP_ONLY: "Només tmp"
+ DASHBOARD: "Panell de control"
+ UPDATES_AVAILABLE: "Hi ha actualizacions disponibles"
+ DAYS: "Dies"
+ UPDATE: "Actualitza"
+ BACKUP: "Còpia de seguretat"
+ STATISTICS: "Estadístiques"
+ TODAY: "Avui"
+ WEEK: "Setmana"
+ MONTH: "Mes"
+ LATEST_PAGE_UPDATES: "Últimes pàgines actualitzades"
+ MAINTENANCE: "Manteniment"
+ UPDATED: "Actualitzat"
+ MON: "Dl."
+ TUE: "Dt."
+ WED: "Dc."
+ THU: "Dj."
+ FRI: "Dv."
+ SAT: "Ds."
+ SUN: "Dg."
+ COPY: "Copia"
+ EDIT: "Edita"
+ CREATE: "Crea"
+ GRAV_ADMIN: "Administració Grav"
+ GRAV_OFFICIAL_PLUGIN: "Plugin oficial de Grav"
+ GRAV_OFFICIAL_THEME: "Tema oficial de Grav"
+ PLUGIN_SYMBOLICALLY_LINKED: "Aquest plugin està lligat simbòlicament. Les actualitzacions no seran detectades."
+ THEME_SYMBOLICALLY_LINKED: "Aquest tema està lligat simbòlicament. Les actualitzacions no seran detectades"
+ REMOVE_PLUGIN: "Elimina plugin"
+ INSTALL_PLUGIN: "Instal·la plugin"
+ AVAILABLE: "Disponible"
+ INSTALLED: "Instal·lat"
+ INSTALL: "Instal·la"
+ ACTIVE_THEME: "Tema actiu"
+ SWITCHING_TO: "Canviant a"
+ SWITCHING_TO_DESCRIPTION: "Al canviar a un tema diferent, no es garanteix que tots els estils de pàgina siguin compatibles, potencialment, pot haver-hi errors al intentar carregar aquestes pàgines."
+ SWITCHING_TO_CONFIRMATION: "Vols continuar i canviar de tema"
+ CREATE_NEW_USER: "Crea nou usuari"
+ REMOVE_USER: "Elimina usuari"
+ ACCESS_DENIED: "Accés denegat"
+ ACCOUNT_NOT_ADMIN: "el teu compte no té permisos d'administrador"
+ PHP_INFO: "Informació PHP"
+ INSTALLER: "Instal·lador"
+ AVAILABLE_THEMES: "Temes disponibles"
+ AVAILABLE_PLUGINS: "Plugins disponibles"
+ INSTALLED_THEMES: "Temes instal·lats"
+ INSTALLED_PLUGINS: "Plugins instal·lats"
+ BROWSE_ERROR_LOGS: "Examina registres d'errors"
+ SITE: "Lloc web"
+ INFO: "Info"
+ SYSTEM: "Sistema"
+ USER: "Usuari"
+ ADD_ACCOUNT: "Afegeix compte"
+ SWITCH_LANGUAGE: "Canvia l'idioma"
+ SUCCESSFULLY_ENABLED_PLUGIN: "El plugin s'ha activat correctament"
+ SUCCESSFULLY_DISABLED_PLUGIN: "El plugin s'ha desactivat correctament"
+ SUCCESSFULLY_CHANGED_THEME: "S'ha canviat el tema per defecte correctament"
+ INSTALLATION_FAILED: "La instal·lació ha fallat"
+ INSTALLATION_SUCCESSFUL: "Instal·lació satisfactòria"
+ UNINSTALL_FAILED: "Desinstal·lació fallida"
+ UNINSTALL_SUCCESSFUL: "Desinstal·lació satisfactòria"
+ SUCCESSFULLY_SAVED: "Desat satisfactòriament"
+ SUCCESSFULLY_COPIED: "Copiat satisfactòriament"
+ REORDERING_WAS_SUCCESSFUL: "Reordenació satisfactòria"
+ SUCCESSFULLY_DELETED: "Eliminat satisfactòriament"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Idioma canviat satisfactòriament"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "No tens permisos suficients per a la tasca"
+ CACHE_CLEARED: "Memòria cache esborrada"
+ METHOD: "Mètode"
+ ERROR_CLEARING_CACHE: "Error al esborrar cache"
+ AN_ERROR_OCCURRED: "S'ha produït un error"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "La teva còpia de seguretat està llesta per a descarregar"
+ DOWNLOAD_BACKUP: "Descarrega còpia de seguretat"
+ PAGES_FILTERED: "Pàgines filtrades"
+ NO_PAGE_FOUND: "No s'han trobat pàgines"
+ INVALID_PARAMETERS: "Els paràmetres són invàlids"
+ NO_FILES_SENT: "No s'han enviat fitxers"
+ EXCEEDED_FILESIZE_LIMIT: "S'ha excedit el límit de tamany de fitxer de la configuració de PHP"
+ UNKNOWN_ERRORS: "Hi ha hagut errors desconeguts"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "S'ha excedit el límit de tamany de fitxer de la configuració de Grav"
+ UNSUPPORTED_FILE_TYPE: "Tipus de fitxer no suportat"
+ FAILED_TO_MOVE_UPLOADED_FILE: "S'ha fallat al moure el fitxer carregat."
+ FILE_UPLOADED_SUCCESSFULLY: "S'ha carregat el fitxer amb èxit"
+ FILE_DELETED: "S'ha esborrat el fitxer"
+ FILE_COULD_NOT_BE_DELETED: "No s'ha pogut esborrar el fitxer"
+ FILE_NOT_FOUND: "No s'ha trobat el fitxer"
+ NO_FILE_FOUND: "No s'han trobat fitxers"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav ha estat actualitzat amb èxit a"
+ GRAV_UPDATE_FAILED: "Ha fallat l'actualització de Grav"
+ EVERYTHING_UPDATED: "Tot està actualitzat"
+ UPDATES_FAILED: "Han fallat les actualitzacions"
+ AVATAR_BY: "Avatar per"
+ LAST_BACKUP: "Última còpia de seguretat"
+ FULL_NAME: "Nom complet"
+ USERNAME: "Nom d'usuari"
+ EMAIL: "Email"
+ USERNAME_EMAIL: "Nom d'usuari o correu electrònic"
+ PASSWORD: "Contrasenya"
+ PASSWORD_CONFIRM: "Confirma contrasenya"
+ TITLE: "Títol"
+ LANGUAGE: "Llengua"
+ ACCOUNT: "Compte d'usuari"
+ EMAIL_VALIDATION_MESSAGE: "Ha de ser una adreça de correu electrònic vàl·lida"
+ PASSWORD_VALIDATION_MESSAGE: "La contrasenya ha de contenir almenys un número i una lletra majúscula i minúscula, i almenys 8 o més caràcters"
+ LANGUAGE_HELP: "Estableix la llengua preferida"
+ MEDIA: "Mèdia"
+ DEFAULTS: "Per defecte"
+ SITE_TITLE: "Títol del lloc"
+ SITE_TITLE_PLACEHOLDER: "Títol a tot el lloc"
+ SITE_TITLE_HELP: "Títol per defecte pel teu lloc, sovint utilitzat en els temes"
+ SITE_DEFAULT_LANG: "Llenguatge per defecte"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Llenguatge per defecte per a ser utilitzat per l'etiqueta del tema "
+ SITE_DEFAULT_LANG_HELP: "Llenguatge per defecte per a ser utilitzat per l'etiqueta del tema "
+ DEFAULT_AUTHOR: "Autor/a per defecte"
+ DEFAULT_AUTHOR_HELP: "Un nom d'autor/a per defecte, algunes vegades utilitzat en temes o contingut de les pàgines"
+ DEFAULT_EMAIL: "Email per defecte"
+ DEFAULT_EMAIL_HELP: "Un correu electrònic per referenciar en temes o pàgines"
+ TAXONOMY_TYPES: "Tipus de taxonomia"
+ TAXONOMY_TYPES_HELP: "Els tipus de taxonomia han de definir-se aquí si es desitja utilitzar-les en pàgines"
+ PAGE_SUMMARY: "Resum de pàgina"
+ ENABLED: "Habilitat"
+ ENABLED_HELP: "Habilita resum de pàgina (el resum retorna el mateix que el contingut de la pàgina)"
+ 'YES': "Sí"
+ 'NO': "No"
+ SUMMARY_SIZE: "Mida del resum"
+ SUMMARY_SIZE_HELP: "La quantitat de caràcters d'una pàgina a utilitzar com a resum del contingut"
+ FORMAT: "Format"
+ FORMAT_HELP: "curt = utilitza la primera ocurrència del delimitador o mida; llarg = s'ignorarà el delimitador del resum"
+ SHORT: "Curt"
+ LONG: "Llarg"
+ DELIMITER: "Delimitador"
+ DELIMITER_HELP: "El delimitador de resum (per defecte '===')"
+ METADATA: "Metadades"
+ METADATA_HELP: "Valors de metadades per defecte que es mostraran a cada pàgina excepte si es sobreescriuen a la pàgina"
+ NAME: "Nom"
+ CONTENT: "Contingut"
+ REDIRECTS_AND_ROUTES: "Redireccions i rutes"
+ CUSTOM_REDIRECTS: "Redireccions personalitzades"
+ CUSTOM_REDIRECTS_HELP: "rutes per redirigir a altres pàgines. Substitució de Regex estàndard és vàl·lida"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/el/teu/àlies"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/la/teva/redirecció"
+ CUSTOM_ROUTES: "Rutes personalitzades"
+ CUSTOM_ROUTES_HELP: "rutes a àlies a altres pàgines. Substitució de Regex estàndard és vàl·lida"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/el/teu/àlies"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/la/teva/ruta"
+ FILE_STREAMS: "Streams de fitxers"
+ DEFAULT: "Per defecte"
+ PAGE_MEDIA: "Contingut multimèdia de la pàgina"
+ OPTIONS: "Opcions"
+ PUBLISHED: "Publicat"
+ PUBLISHED_HELP: "Per defect, una pàgina es publica excepte que estableixis explícitament 'published': false o posant una publish_date al futur o una unpublish_date al passat"
+ DATE: "Data"
+ DATE_HELP: "La variable data permet definir específicament una data associada a aquesta pàgina."
+ PUBLISHED_DATE: "Data de publicació"
+ PUBLISHED_DATE_HELP: "Pots proporcionar una data per provocar automàticament la publicació."
+ UNPUBLISHED_DATE: "Data de despublicació"
+ UNPUBLISHED_DATE_HELP: "Pots proporcionar una data per provocar automàticament la despublicació."
+ ROBOTS: "Robots"
+ TAXONOMIES: "Taxonomies"
+ TAXONOMY: "Taxonomia"
+ ADVANCED: "Avançat"
+ SETTINGS: "Configuració"
+ FOLDER_NUMERIC_PREFIX: "Prefix numèric carpeta"
+ FOLDER_NUMERIC_PREFIX_HELP: "Prefix numèric que proporciona ordenació manual i implica la visibilitat"
+ FOLDER_NAME: "Nom de la carpeta"
+ FOLDER_NAME_HELP: "El nom de carpeta que s'emmagatzemarà al sistema de fitxers per a aquesta pàgina"
+ PARENT: "Pare"
+ DEFAULT_OPTION_ROOT: "-Root-"
+ DEFAULT_OPTION_SELECT: "- selecciona -"
+ DISPLAY_TEMPLATE: "Plantilla a mostrar"
+ DISPLAY_TEMPLATE_HELP: "El tipus de pàgina que es tradueix a la plantilla de Twig en què es renderitza la pàgina"
+ BODY_CLASSES: "Classes del Body"
+ ORDERING: "Ordenació"
+ PAGE_ORDER: "Ordre de pàgines"
+ OVERRIDES: "Sobreescriu"
+ MENU: "Menú"
+ MENU_HELP: "El text a utilitzar en un menú. Si no s'estableix, s'utilitzarà el Títol."
+ SLUG: "Slug"
+ SLUG_HELP: "La variable slug permet definir específicament la part de l'URL que correspon a la pàgina"
+ SLUG_VALIDATE_MESSAGE: "L'slug ha de contenir només caràcters alfanumèrics en minúscules i guions"
+ PROCESS: "Processa"
+ PROCESS_HELP: "Control de com es processen les pàgines. Configurable per a pàgina enlloc de globalment"
+ DEFAULT_CHILD_TYPE: "Tipus de fill per defecte"
+ USE_GLOBAL: "Ús global"
+ ROUTABLE: "Accessible"
+ ROUTABLE_HELP: "Si aquesta pàgina és accessible des d'una URL"
+ CACHING: "Caching"
+ VISIBLE: "Visible"
+ VISIBLE_HELP: "Determina si una pàgina és visible en la navegació."
+ DISABLED: "Deshabilitat"
+ ITEMS: "Ítems"
+ ORDER_BY: "Ordena per"
+ ORDER: "Ordena"
+ FOLDER: "Carpeta"
+ ASCENDING: "Ascendent"
+ DESCENDING: "Descendent"
+ ADD_MODULAR_CONTENT: "Afegeix contingut modular"
+ PAGE_TITLE: "Títol de pàgina"
+ PAGE_TITLE_HELP: "El títol de la pàgina"
+ PAGE: "Pàgina"
+ MODULAR_TEMPLATE: "Plantilla modular"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Nom de fitxer"
+ PARENT_PAGE: "Pàgina pare"
+ HOME_PAGE: "Pàgina d'inici"
+ HOME_PAGE_HELP: "La pàgina que utilitzarà Grav com a destinació per defecte"
+ DEFAULT_THEME: "Tema per defecte"
+ DEFAULT_THEME_HELP: "Estableix el tema per defecte que utilitzarà Grav (per defecte és Antimatter)"
+ TIMEZONE: "Zona horària"
+ TIMEZONE_HELP: "Sobreescriu la zona horària del servidor"
+ SHORT_DATE_FORMAT: "Format de data curt"
+ SHORT_DATE_FORMAT_HELP: "Estableix el format de data curt que poden utilitzar els temes"
+ LONG_DATE_FORMAT: "Format de data llarg"
+ LONG_DATE_FORMAT_HELP: "Estableix el format de data llarg que poden utilitzar els temes"
+ DEFAULT_ORDERING: "Ordre per defecte"
+ DEFAULT_ORDERING_HELP: "Les pàgines de la llista seran generades utilitzant aquest ordre excepte que se sobreescrigui"
+ DEFAULT_ORDERING_DEFAULT: "Per defecte - basat en el nom de la carpeta"
+ DEFAULT_ORDERING_FOLDER: "Carpeta - basat en el nom de carpeta sense prefix"
+ DEFAULT_ORDERING_TITLE: "Títol - basat en el camp de títol de la capçalera"
+ DEFAULT_ORDERING_DATE: "Data - basat en el camp data de la capçalera"
+ DEFAULT_ORDER_DIRECTION: "Direcció d'ordre per defecte"
+ DEFAULT_ORDER_DIRECTION_HELP: "La direcció de les pàgines en una llista"
+ DEFAULT_PAGE_COUNT: "Compte de pàgina per defecte"
+ DEFAULT_PAGE_COUNT_HELP: "Nombre màxim de compte de pàgines en una llista"
+ DATE_BASED_PUBLISHING: "Publicació basada en la data"
+ DATE_BASED_PUBLISHING_HELP: "(Des)publicar automàticament posts basant-se en la seva data"
+ EVENTS: "Esdeveniments"
+ EVENTS_HELP: "Habilita o inhabilita esdeveniments concrets. Deshabilitant-los pot trencar plugins"
+ REDIRECT_DEFAULT_ROUTE: "Ruta de redirecció per defecte"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Redirigir automàticament a la ruta per defecte d'una pàgina"
+ LANGUAGES: "Idiomes"
+ SUPPORTED: "Suportat"
+ SUPPORTED_HELP: "Llista separada per comes de codis d'idioma de 2 lletres (per exemple 'en,fr,de')"
+ TRANSLATIONS_ENABLED: "Traduccions habilitades"
+ TRANSLATIONS_ENABLED_HELP: "Suport a traduccions a Grav, plugins i extensions"
+ TRANSLATIONS_FALLBACK: "Fallback de traduccions"
+ TRANSLATIONS_FALLBACK_HELP: "Fallback en traduccions suportades si l'idioma actiu no existeix"
+ ACTIVE_LANGUAGE_IN_SESSION: "Idioma actiu a la sessió"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Emmagatzema l'idioma actiu a la sessió"
+ HTTP_HEADERS: "Capçaleres HTTP"
+ EXPIRES: "Caduca"
+ EXPIRES_HELP: "Estableix la capçalera d'expiració. El valor és en segons."
+ LAST_MODIFIED: "Darrera modificació"
+ LAST_MODIFIED_HELP: "Estableix la capçalera de darrera modificació que pot optimitzar el proxy i la cache del navegador"
+ ETAG: "ETag"
+ ETAG_HELP: "Estableix la capçalera d'etag per ajudar a identificar quan una pàgina ha estat modificada"
+ VARY_ACCEPT_ENCODING: "Variar accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Estableix la capçalera 'Vary: Accept Encoding' per ajudar amb el proxy i la cache CDN"
+ MARKDOWN_EXTRA_HELP: "Habilita suport per defecte per a Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ AUTO_LINE_BREAKS: "Salts de línia automàtics"
+ AUTO_LINE_BREAKS_HELP: "Habilita el suport per a salts de línia automàtics a Markdown"
+ AUTO_URL_LINKS: "Enllaços URL automàtics"
+ AUTO_URL_LINKS_HELP: "Habilita l'autoconversió d'URLs a hyperlinks HTML"
+ ESCAPE_MARKUP: "Escape markup"
+ ESCAPE_MARKUP_HELP: "Escape markup tags en entitats HTML"
+ CACHING_HELP: "Interruptor ON/OFF global per habilitar/deshabilitar cache de Grav"
+ CACHE_CHECK_METHOD: "Mètode de verificació de cache"
+ CACHE_CHECK_METHOD_HELP: "Seleccionar el mètode que utilitza Grav per comprovar si s'han modificat arxius de pàgina."
+ CACHE_DRIVER: "Contolador de cache"
+ CACHE_DRIVER_HELP: "Selecciona quin controlador de cache de Grav cal usar. 'Auto Detect' intenta trobar el millor per a tu"
+ CACHE_PREFIX: "Prefix de cache"
+ CACHE_PREFIX_HELP: "Un identificador per part de la clau de Grav. No la canviïs excepte que sàpigues el que estàs fent."
+ CACHE_PREFIX_PLACEHOLDER: "Derivat de l'URL base (sobreescriu introduint strings aleatòris)"
+ LIFETIME: "Cicle de vida"
+ LIFETIME_HELP: "Defineix el cicle de vida de la cache en segons. 0 = infinit"
+ GZIP_COMPRESSION: "Compressió gzip"
+ GZIP_COMPRESSION_HELP: "Habilita la compressió GZip de la pàgina de Grav per augmentar el rendiment."
+ TWIG_TEMPLATING: "Plantilles de Twig"
+ TWIG_CACHING: "Cache de Twig"
+ TWIG_CACHING_HELP: "Controla el mecanisme de cache de Twig. Deixa'l habilitat per a millor rendiment."
+ TWIG_DEBUG: "Depuració de Twig"
+ TWIG_DEBUG_HELP: "Permet l'opció de no carregar l'extensió de depuració de Twig"
+ DETECT_CHANGES: "Detectar canvis"
+ DETECT_CHANGES_HELP: "Twig recompilarà automàticament la cache de Twig si detecta canvis a les plantilles de Twig"
+ AUTOESCAPE_VARIABLES: "Variables d'autoescape"
+ AUTOESCAPE_VARIABLES_HELP: "Autoescapa totes les variables. Això probablement trencarà el teu lloc"
+ ASSETS: "Assets"
+ CSS_PIPELINE: "CSS pipeline"
+ CSS_PIPELINE_HELP: "El CSS pipeline és l'unificació de diversos recursos CSS en un sol fitxer"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Inclou fitxers externs en el CSS pipeline"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "A vegades, algunes URLs externes tenen referències relatives de fitxer i no s'hi hauria de fer pipelining"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "Processar pimer el CSS pipeline"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Intrepreta el CSS pipeline abans de qualsevol altra referència CSS que no estigui inclosa"
+ CSS_MINIFY: "Minifica CSS"
+ CSS_MINIFY_HELP: "Minifica el CSS durant el pipelining"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "Sobreesciu la minificació de CSS a Windows"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Sobreescriu la minificació en plataformes Windows. Fals per defecte degut a ThreadStackSize"
+ CSS_REWRITE: "Reescriptura CSS"
+ CSS_REWRITE_HELP: "Reescriu qualsevol URL relativa de CSS durant el pipelining"
+ JAVASCRIPT_PIPELINE: "JavaScript pipeline"
+ JAVASCRIPT_PIPELINE_HELP: "El JS pipeline és l'unificació de diversos recursos JS en un sol fitxer"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Inclou fitxers externs en el JS pipeline"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "A vegades, les URLs externes tenen referències d'arxiu i no s'hi hauria de fer pipelining"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Interpreta primer el JS pipeline"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Interpreta el JS pipeline abans de qualsevol altra referència JS que no estigui inclosa"
+ JAVASCRIPT_MINIFY: "Minificació JavaScript"
+ JAVASCRIPT_MINIFY_HELP: "Minifica el JS durant el pipelining"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Habilita les marques de temps als assets"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Habilita les marques de temps a assets"
+ COLLECTIONS: "Col·leccions"
+ ERROR_HANDLER: "Controlador d'errors"
+ DISPLAY_ERRORS: "Mostra errors"
+ DISPLAY_ERRORS_HELP: "Mostra pàgina d'error full backstrace-style"
+ LOG_ERRORS: "Registre d'errors"
+ LOG_ERRORS_HELP: "Registre d'errors a la carpeta /logs"
+ DEBUGGER: "Depurador"
+ DEBUGGER_HELP: "Habilita depurador de Grav i les configuracions següents"
+ DEBUG_TWIG: "Depuració de Twig"
+ DEBUG_TWIG_HELP: "Habilita la depuració de plantilles de Twig"
+ SHUTDOWN_CLOSE_CONNECTION: "Al apagar tanca la connexió"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Tanca la connexió abans de cridar onShutdown(). False per a la depuració"
+ DEFAULT_IMAGE_QUALITY: "Qualitat d'imatge per defecte"
+ DEFAULT_IMAGE_QUALITY_HELP: "Qualitat d'imatge per defecte per a ser utilitzada quan es remostri o es guardi a cache les imatges (85%)"
+ CACHE_ALL: "Guardar totes les imatges a cache"
+ CACHE_ALL_HELP: "Guarda totes les imatges al sistema cache de Grav fins i tot si no tenen cap manipulació de mèdia"
+ IMAGES_DEBUG: "Marca d'aigua de depuració"
+ IMAGES_DEBUG_HELP: "Mostra un overlay sobre les imatges indicant la profunditat de píxels quan es treballa amb retina, per exemple"
+ UPLOAD_LIMIT: "Límit de tamany de fitxer"
+ UPLOAD_LIMIT_HELP: "Defineix el tamany màxim de càrrega en bytes (0 = il·limitat)"
+ ENABLE_MEDIA_TIMESTAMP: "Permet timestamps en fitxers multimèdia"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Afegeix un timestamp basat en la data d'última modificació a cada element multimèdia"
+ SESSION: "Sessió"
+ SESSION_ENABLED_HELP: "Habilita suport de sessions a Grav"
+ TIMEOUT: "Temps d'espera"
+ TIMEOUT_HELP: "Estableix el temps d'espera de la sessió en segons"
+ SESSION_NAME_HELP: "Un identificador usat per formar el nom de la galeta de sessió"
+ ABSOLUTE_URLS: "URLs absolutes"
+ ABSOLUTE_URLS_HELP: "URLs absolutes o relatives per a 'base_url'"
+ PARAMETER_SEPARATOR: "Separador de paràmetres"
+ PARAMETER_SEPARATOR_HELP: "Separador per a paràmetres passats que es poden canviar d'Apache a Windows"
+ TASK_COMPLETED: "Tasca completada"
+ EVERYTHING_UP_TO_DATE: "Tot està actualitzat"
+ UPDATES_ARE_AVAILABLE: "hi ha actualitzacions disponibles"
+ IS_AVAILABLE_FOR_UPDATE: "està disponible per a l'actualització"
+ IS_NOW_AVAILABLE: "ja està disponible"
+ CURRENT: "Actual"
+ UPDATE_GRAV_NOW: "Actualitza Grav ara"
+ GRAV_SYMBOLICALLY_LINKED: "Grav està lligat simbòlicament. Les actualitzacions no estaran disponibles"
+ UPDATING_PLEASE_WAIT: "Actualitzant... descarregant, espera sisplau"
+ OF_THIS: "d'aquest/a"
+ OF_YOUR: "del teu"
+ HAVE_AN_UPDATE_AVAILABLE: "té disponible una actualització"
+ SAVE_AS: "Desa com a"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Esteu segur que voleu suprimir aquesta pàgina i tots els seus fills? Si la pàgina es tradueix en altres llengües, les traduccions es mantindran i han de ser esborrades per separat. En cas contrari la carpeta de la pàgina serà eliminada juntament amb les seves subpàgines. Aquesta acció no es pot desfer."
+ AND: "i"
+ UPDATE_AVAILABLE: "Actualització disponible"
+ METADATA_KEY: "Clau (p. ex. 'paraules clau')"
+ METADATA_VALUE: "Valor (per exemple, \"Blog, Grav\")"
+ USERNAME_HELP: "El nom d'usuari ha de tenir entre 3 i 16 caràcters, incloent minúscules, números, guions i guions baixos. Lletres majúscules, espais i caràcters especials no estan permesos"
+ FULLY_UPDATED: "Completament actualitzat"
+ SAVE_LOCATION: "Desa a la ubicació"
+ PAGE_FILE: "Plantilla de pàgina"
+ PAGE_FILE_HELP: "Nom d'arxiu de la plantilla de pàgina i plantilla de visualització per defecte"
+ NO_USER_ACCOUNTS: "No s'han trobat comptes d'usuari, sisplau crea'n una..."
+ REDIRECT_TRAILING_SLASH: "Redirigeix barra final"
+ REDIRECT_TRAILING_SLASH_HELP: "Realitza una redirecció 301 en lloc de manteniment transparent de barra final."
+ DEFAULT_DATE_FORMAT: "Format de data de pàgina"
+ DEFAULT_DATE_FORMAT_HELP: "Format de data de pàgina utilitzat per Grav. Per defecte, Grav intenta endivinar el format de data, tot i això pots especificar un format utilitzant la sintaxi de data de PHP (p.e., Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Endevina automàticament"
+ IGNORE_FILES: "Ignora fitxers"
+ IGNORE_FILES_HELP: "Arxius específics a ignorar al processar pàgines"
+ IGNORE_FOLDERS: "Ignora carpetes"
+ IGNORE_FOLDERS_HELP: "Carpetes específiques a ignorar al processar pàgines"
+ HTTP_ACCEPT_LANGUAGE: "Estableix la llengua a partir del navegador"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Pots optar per intentar establir la llengua basant-se en l'etiqueta de la capçalera 'http_accept_language' en el navegador"
+ OVERRIDE_LOCALE: "Sobreescriu la configuració local"
+ OVERRIDE_LOCALE_HELP: "Sobreescriu la configuració local en PHP basant-se en la llengua actual"
+ REDIRECT: "Redirecció de pàgina"
+ REDIRECT_HELP: "Escriu una ruta de pàgina o URL externa per aqueta pàgina a redirigir a per exemple, '/alguna/ruta' o 'https://algunlloc.com'"
+ PLUGIN_STATUS: "Estat del plugin"
+ INCLUDE_DEFAULT_LANG: "Inclou llengua predeterminada"
+ INCLUDE_DEFAULT_LANG_HELP: "Això sobreposarà totes les URLs amb la llengua per defecte. Per exemple, 'ca/blog/el-meu-post'"
+ ALLOW_URL_TAXONOMY_FILTERS: "Filtres de taxonomia d'URL"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Col·lecions basades en pàgines que et permeten filtrar per '/taxonomia:valor'."
+ REDIRECT_DEFAULT_CODE: "Codi de redirecció per defecte"
+ REDIRECT_DEFAULT_CODE_HELP: "El codi d'estat HTTP per utilitzar en redireccions"
+ IGNORE_HIDDEN: "Ignora ocults"
+ IGNORE_HIDDEN_HELP: "Ignora tots els fitxers i carpetes que comencin amb un punt"
+ WRAPPED_SITE: "Lloc encapsulat"
+ WRAPPED_SITE_HELP: "Per a que els temes/plugins sàpiguen si Grav està encapsulat en una altra plataforma"
+ FALLBACK_TYPES: "Tipus de fallback permesos"
+ FALLBACK_TYPES_HELP: "Tipus de fitxers permesos que es poden trobar si s'accedeix per la ruta de la pàgina. Per defecte, qualsevol tipus de mèdia compatible."
+ INLINE_TYPES: "Tipus de fallback en línia"
+ INLINE_TYPES_HELP: "Una llista de tipus d'arxius que han de ser mostrats en línia enlloc de descarregats"
+ APPEND_URL_EXT: "Afegeix extensió de direcció URL"
+ APPEND_URL_EXT_HELP: "Afegirà una extensió personalitzarà a l'URL de la pàgina. Tingues en compte que això farà que Grav busqui la plantilla ''..twig'"
+ PAGE_MODES: "Modes de pàgina"
+ PAGE_TYPES: "Tipus de pàgina"
+ ACCESS_LEVELS: "Nivells d'accés"
+ GROUPS: "Grups"
+ GROUPS_HELP: "Llista de grups dels quals l'usuari en forma part"
+ ADMIN_ACCESS: "Accés d'administrador"
+ SITE_ACCESS: "Accés al lloc"
+ INVALID_SECURITY_TOKEN: "Clau de seguretat invàl·lida"
+ ACTIVATE: "Activa"
+ TWIG_UMASK_FIX: "Corregeix Umask"
+ TWIG_UMASK_FIX_HELP: "Per defecte Twig crea arxius cache com a 0755, la correcció ho canvia a 0775"
+ CACHE_PERMS: "Permisos de cache"
+ CACHE_PERMS_HELP: "Permisos de carpeta de cache per defecte. Normalment 0755 o 0775 segons la configuració"
+ REMOVE_SUCCESSFUL: "Eliminació amb èxit"
+ REMOVE_FAILED: "No s'ha pogut eliminar"
+ HIDE_HOME_IN_URLS: "Amaga ruta de la pàgina inicial en les URLs"
+ HIDE_HOME_IN_URLS_HELP: "S'assegurarà que les rutes per defecte de qualsevol pàgina sota la pàgina inicial no facin referència a la ruta regular de la pàgina inicial"
+ TWIG_FIRST: "Processa Twig primer"
+ TWIG_FIRST_HELP: "Si el processament de pàgina de Twig està habilitat, es pot configurar que Twig faci el processament després de markdown"
+ SESSION_SECURE: "Segur"
+ SESSION_SECURE_HELP: "Si és cert, indica que la comunicació per aquesta cookie ha de ser sobre una connexió encriptada. ALERTA: Habilita aquesta opicó en llocs que funcionen exclusivament en HTTPS"
+ SESSION_HTTPONLY: "Només HTTP"
+ SESSION_HTTPONLY_HELP: "Si és cert, indica que les cookies hauran de ser utilitzades només sobre HTTP, i les modificacions de JavaScript no estan permeses"
+ REVERSE_PROXY: "Proxy invers"
+ REVERSE_PROXY_HELP: "Habilita-ho si estàs darrere d'un proxy invers i tens problemes amb URLs que continguin ports incorrectes"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Frontmatter invàlid, no s'ha pogut desar"
+ ADD_FOLDER: "Afegeix carpeta"
+ PROXY_URL: "URL del proxy"
+ PROXY_URL_HELP: "Escriu el HOST proxy o IP i PORt"
+ NOTHING_TO_SAVE: "Res a desar"
+ FILE_ERROR_ADD: "S'ha produït un error mentre s'intentava afegir el fitxer"
+ FILE_ERROR_UPLOAD: "S'ha produït un error mentre s'intentava carregar el fitxer"
+ FILE_UNSUPPORTED: "Tipus de fitxer no suportat"
+ ADD_ITEM: "Afegeix ítem"
+ FILE_TOO_LARGE: "El fitxers és massa gran per carregar-se, el màxim permès és %s segons la teva configuració PHP. Incrementa la opció PHP 'post_max_size'"
+ INSTALLING: "Instal·lant"
+ LOADING: "Carregant..."
+ DEPENDENCIES_NOT_MET_MESSAGE: "Les següents dependències han de ser cobertes primer:"
+ ERROR_INSTALLING_PACKAGES: "S'ha produït un error mentre s'instal·lava el(s) paquet(s)"
+ INSTALLING_DEPENDENCIES: "Instal·lant dependències..."
+ INSTALLING_PACKAGES: "Instal·lant paquet(s)..."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Paquet(s) instal·lat(s) correctament."
+ READY_TO_INSTALL_PACKAGES: "Preparat per instal·lar el(s) paquet(s)"
+ PACKAGES_NOT_INSTALLED: "No s'han instal·lat els paquets"
+ PACKAGES_NEED_UPDATE: "Els paquets ja estaven instal·lats, però eren massa antics"
+ PACKAGES_SUGGESTED_UPDATE: "Els paquets ja estaven instal·lats, i la versió està bé, però s'actualitzaran per tal d'estar al dia"
+ REMOVE_THE: "Elimina el %s"
+ CONFIRM_REMOVAL: "Estàs segur que vols eliminar aquest %s?"
+ REMOVED_SUCCESSFULLY: "S'ha eliminat %s correctament"
+ ERROR_REMOVING_THE: "S'ha produït un error eliminant el %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "El %s requeria les següents dependències, les quals no són requerides per cap altre paquet instal·lat. Si no les estàs utilitzant, les pots eliminar directament des d'aquí."
+ READY_TO_UPDATE_PACKAGES: "Preparat per actualizar el(s) paquet(s)"
+ ERROR_UPDATING_PACKAGES: "S'ha produït un error al actualitzar el(s) paquet(s)"
+ UPDATING_PACKAGES: "Actualitzant paquet(s)..."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Paquet(s) actualitzat(s) correctament."
+ UPDATING: "Actualitzant"
+ GPM_RELEASES: "Versions GPM"
+ GPM_RELEASES_HELP: "Escull 'Provant' per instal·lar versions beta o en proves"
+ GPM_METHOD: "Mètode de recuperació remota"
+ GPM_METHOD_HELP: "Quan es posa a Automàtic, Grav determinarà si fopen està disponible i l'utilitzarà, d'altra forma s'utilitzarà cURL. Per a forçar l'ús d'un o altre, canvia la configuració."
+ GPM_VERIFY_PEER: "Verificació a distància de peer (SSL)"
+ GPM_VERIFY_PEER_HELP: "Alguns proveïdors semblen fallar en la verificació del certificat SSL de getgrav.org, el qual causa que GPM no funcioni. Si aquest és el teu cas, desactivar aquesta configuració pot ajudar"
+ AUTO: "Automàtic"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Estable"
+ TESTING: "Provant"
+ FRONTMATTER_PROCESS_TWIG: "Processa frontmatter de Twig"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Quan està habilitat, pots utilitzar les variables de configuració de Twig a la frontmatter de pàgina"
+ FRONTMATTER_IGNORE_FIELDS: "Ignora camps frontmatter"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Certs camps de frontmatter poden contenir Twig però no han de ser processats, com ara els formularis"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Paquet %s instal·lat correctament"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "L'ordre està definit pel pare, l'ordenament està deshabilitat"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "La pàgina no és visible, l'ordenament està deshabilitat"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "L'ordre a través de l'administrador no està suportat degut a que hi ha més de 200 elements"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "NOTA: No es poden afegir fitxers multimèdia fins que no s'hagi desat la pàgina. Fes clic a 'Desa' a la part superior"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "NOTA: Cal desar la pàgina haver de pujar-hi fitxers."
+ DROP_FILES_HERE_TO_UPLOAD: "Deixa anar els teus arxius aquí o fes clic en aquesta àrea "
+ INSERT: "Inserta"
+ UNDO: "Desfés"
+ REDO: "Refés"
+ HEADERS: "Capçaleres"
+ BOLD: "Negreta"
+ ITALIC: "Cursiva"
+ STRIKETHROUGH: "Tatxat"
+ SUMMARY_DELIMITER: "Delimitador del resum"
+ LINK: "Enllaç"
+ IMAGE: "Imatge"
+ BLOCKQUOTE: "Citació"
+ UNORDERED_LIST: "Llista sense ordre"
+ ORDERED_LIST: "Llista ordenada"
+ EDITOR: "Editor"
+ PREVIEW: "Vista prèvia"
+ FULLSCREEN: "Pantalla completa"
+ MODULAR: "Modular"
+ NON_ROUTABLE: "No accessible"
+ NON_MODULAR: "No modular"
+ NON_VISIBLE: "No visible"
+ NON_PUBLISHED: "No publicat"
+ CHARACTERS: "caràcters"
+ PUBLISHING: "Publicació"
+ NOTIFICATIONS: "Notificacions"
+ MEDIA_TYPES: "Tipus de multimèdia"
+ IMAGE_OPTIONS: "Opcions d'imatge"
+ MIME_TYPE: "Tipus Mime"
+ THUMB: "Miniatura"
+ TYPE: "Tipus"
+ FILE_EXTENSION: "Extensió de fitxer"
+ LEGEND: "Llegenda de pàgina"
+ MEMCACHE_SERVER: "Servidor Memcache"
+ MEMCACHE_SERVER_HELP: "L'adreça del servidor Memcache"
+ MEMCACHE_PORT: "Port Memcache"
+ MEMCACHE_PORT_HELP: "El port del servidor Memcache"
+ MEMCACHED_SERVER: "Servidor Memcached"
+ MEMCACHED_SERVER_HELP: "L'adreça del servidor Memcached"
+ MEMCACHED_PORT: "Port Memcached"
+ MEMCACHED_PORT_HELP: "El port del servidor Memcached"
+ REDIS_SERVER: "Servidor de Redis"
+ REDIS_SERVER_HELP: "L'adreça del servidor de Redis"
+ REDIS_PORT: "Port Redis"
+ REDIS_PORT_HELP: "El port del servidor Redis"
+ ALL: "Tot"
+ FROM: "de"
+ TO: "a"
+ RELEASE_DATE: "Data de llançament"
+ SORT_BY: "Ordena per"
+ RESOURCE_FILTER: "Filtrar..."
+ FORCE_SSL: "Força SSL"
+ FORCE_SSL_HELP: "Força SSL globalment, si està habilitat, quan el lloc web és accedir per HTTP, Grav envia un redireccionament a la pàgina HTTPS"
+ NEWS_FEED: "Canal de notícies"
+ EXTERNAL_URL: "URL externa"
+ SESSION_PATH: "El camí de la sessió"
+ SESSION_PATH_HELP: "Utilitza només si has escollit una URL base personalitzada (has reescrit el domini del lloc web/subcarpeta)"
+ CUSTOM_BASE_URL: "URL base personalitzada"
+ CUSTOM_BASE_URL_HELP: "Utilitza si vols reescriure el domini del lloc web o utilitzar una subcarpeta diferent que la que utilitza Grav. Per exemple: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'No es pot utilitzar "%s" fora de les pàgines.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'No es pot pujar el fitxer %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'No es pot moure el fitxer %s to "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'Cancel·la càrrega'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Estàs segur que vols cancel·lar aquesta càrrega?'
+ DROPZONE_DEFAULT_MESSAGE: 'Deixa anar els teus fitxers aquí o fes clic en aquesta àrea '
+ DROPZONE_FALLBACK_MESSAGE: 'El teu navegador no suporta arrosegar i deixar anar per a carregar fitxers.'
+ DROPZONE_FALLBACK_TEXT: 'Si us plau, utilitza el formulari clàssic inferior per carregar els teus fitxers com es feia abans.'
+ DROPZONE_FILE_TOO_BIG: 'El fitxer és molt gran ({{filesize}}MiB). Mida màxima de fitxer: {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "No pots carregar fitxers d'aquest tipus."
+ DROPZONE_MAX_FILES_EXCEEDED: "No pots carregar més fitxers."
+ DROPZONE_REMOVE_FILE: "Esborra fitxer"
+ DROPZONE_RESPONSE_ERROR: "El servidor ha respost amb el codi {{statusCode}}."
+ PREMIUM_PRODUCT: "Premium"
+ ADMIN_CACHING: "Habilita el cache de l'administració"
+ ADMIN_CACHING_HELP: "El cache a l'administració pot ser controlat independentment del de la pàgina front-end"
+ DESTINATION_NOT_SPECIFIED: "Destinació no especificat"
+ UPLOAD_ERR_NO_TMP_DIR: "No es troba una carpeta temporal"
+ SESSION_SPLIT: "Separació de sessió"
+ SESSION_SPLIT_HELP: "Separació de sessions independent entre el lloc web i altres plugins (com ara l'administració)"
+ ERROR_FULL_BACKTRACE: "Error Complert Backtrace"
+ ERROR_SIMPLE: "Error simple"
+ ERROR_SYSTEM: "Error del sistema"
+ IMAGES_AUTO_FIX_ORIENTATION: "Fixar l'orientació automàtica"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Corregir automàticament l'orientació de la imatge basada en les dades Exif"
+ REDIS_SOCKET: "Socket Redis"
+ REDIS_SOCKET_HELP: "El Socket Redis"
+ NOT_SET: "No establert"
+ PERMISSIONS: "Permisos"
+ ALLOW_WEBSERVER_GZIP: "Permetre WebServer Gzip"
+ OFFLINE_WARNING: "La connexió a la GPM no es pot establir"
+ CONFIRM_REINSTALL: "Segur que vols re-instal·lar %s?"
+ REINSTALLED_SUCCESSFULLY: "%s re-instal·lat correctament"
+ TOOLS: "Eines"
+ GPM_OFFICIAL_ONLY: "Només GPM oficial"
+ SORTABLE_PAGES: "Pàgines ordenables:"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Ordre de visualització de descendents"
+ PWD_PLACEHOLDER: "cadena complexa de al menys 8 caràcters"
+ PWD_REGEX: "Regex clau"
+ USERNAME_REGEX: "RegEx nom d'usuari"
diff --git a/user/plugins/admin/languages/cs.yaml b/user/plugins/admin/languages/cs.yaml
new file mode 100644
index 0000000..69922fb
--- /dev/null
+++ b/user/plugins/admin/languages/cs.yaml
@@ -0,0 +1,819 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Jedná se o beta verzi! V ostrém provozu používejte pouze na vlastní nebezpečí..."
+ ADMIN_REPORT_ISSUE: "Objevili jste problém? Nahlaste ho, prosím, na GitHub."
+ EMAIL_FOOTER: "Beží na Grav CMS - Moderní správce obsahu pomocí souborů prostých textů"
+ LOGIN_BTN: "Přihlásit"
+ LOGIN_BTN_FORGOT: "Obnovit heslo"
+ LOGIN_BTN_RESET: "Obnovit heslo"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Odeslány pokyny pro obnovu hesla"
+ LOGIN_BTN_CLEAR: "Vymazat formulář"
+ LOGIN_BTN_CREATE_USER: "Vytvořit uživatele"
+ LOGIN_LOGGED_IN: "Byli jste úspěšně přihlášeni"
+ LOGIN_FAILED: "Přihlášení se nezdařilo"
+ LOGGED_OUT: "Byli jste odhlášeni"
+ RESET_NEW_PASSWORD: "Prosím, zadejte nové heslo …"
+ RESET_LINK_EXPIRED: "Odkaz pro obnovení hesla vypršel, zkuste to, prosím, znovu"
+ RESET_PASSWORD_RESET: "Heslo bylo změněno"
+ RESET_INVALID_LINK: "Použit neplatný odkaz pro obnovu hesla, zkuste to, prosím, znovu"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Pokyny pro obnovení hesla byly odeslány na vaši e-mailovou adresu"
+ FORGOT_FAILED_TO_EMAIL: "Nepodařilo se odeslat instrukce pro obnovu hesla, zkuste to, prosím, později"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Heslo pro %s nelze obnovit, není zadána e-mailová adresa"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Uživatel %s neexistuje"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Heslo nelze obnovit. Tento web nemá nastaveno odesílání e-mailů"
+ FORGOT_EMAIL_SUBJECT: "%s Požadavek na obnovení hesla"
+ FORGOT_EMAIL_BODY: "Obnovení hesla Vážený %1$s,
požadavek na obnovu hesla byl zadán na %4$s .
Klikněte zde pro nastavení nového hesla
Případně, zkopírujte následující adresu URL do adresního řádku vašeho prohlížeče:
%2$s
Děkujeme, %3$s
"
+ MANAGE_PAGES: "Spravovat stránky"
+ CONFIGURATION: "Nastavení"
+ PAGES: "Stránky"
+ PLUGINS: "Doplňky"
+ PLUGIN: "Doplněk"
+ THEMES: "Šablony"
+ LOGOUT: "Odhlásit"
+ BACK: "Zpět"
+ NEXT: "Následující"
+ PREVIOUS: "Předchozí"
+ ADD_PAGE: "Přidat stránku"
+ ADD_MODULAR: "Přidat modulární stránku"
+ MOVE: "Přesunout"
+ DELETE: "Smazat"
+ UNSET: "Zrušit nastavení"
+ VIEW: "Zobrazit"
+ SAVE: "Uložit"
+ NORMAL: "Normální"
+ EXPERT: "Expertní"
+ EXPAND_ALL: "Rozbalit vše"
+ COLLAPSE_ALL: "Sbalit vše"
+ ERROR: "Chyba"
+ CLOSE: "Zavřít"
+ CANCEL: "Zrušit"
+ CONTINUE: "Pokračovat"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Vyžadováno potvrzení"
+ MODAL_CHANGED_DETECTED_TITLE: "Zjištěny změny"
+ MODAL_CHANGED_DETECTED_DESC: "Máte neuložené změny. Jste si jisti, že chcete odejít bez uložení?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Vyžadováno potvrzení"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Opravdu chcete tento soubor smazat? Tato akce je nevratná."
+ ADD_FILTERS: "Přidat filtry"
+ SEARCH_PAGES: "Hledat stránky"
+ VERSION: "Verze"
+ WAS_MADE_WITH: "Byl vytvořen s"
+ BY: "Od"
+ UPDATE_THEME: "Aktualizovat šablonu"
+ UPDATE_PLUGIN: "Aktualizovat doplněk"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "této šablony je nyní dostupná"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "tohoto doplňku je nyní dostupná"
+ AUTHOR: "Autor"
+ HOMEPAGE: "Domovská stránka"
+ DEMO: "Demo"
+ BUG_TRACKER: "Správa chyb"
+ KEYWORDS: "Klíčová slova"
+ LICENSE: "Licence"
+ DESCRIPTION: "Popis"
+ README: "Soubor README"
+ REMOVE_THEME: "Odstranit šablonu"
+ INSTALL_THEME: "Nainstalovat šablonu"
+ THEME: "Šablona"
+ BACK_TO_THEMES: "Zpět na šablony"
+ BACK_TO_PLUGINS: "Zpět na doplňky"
+ CHECK_FOR_UPDATES: "Zkontrolovat aktualizace"
+ ADD: "Přidat"
+ CLEAR_CACHE: "Vyprázdnit mezipaměť"
+ CLEAR_CACHE_ALL_CACHE: "Všechny mezipaměti"
+ CLEAR_CACHE_ASSETS_ONLY: "Pouze zdroje"
+ CLEAR_CACHE_IMAGES_ONLY: "Pouze obrázky"
+ CLEAR_CACHE_CACHE_ONLY: "Pouze mezipaměť"
+ CLEAR_CACHE_TMP_ONLY: "Pouze dočasné soubory"
+ DASHBOARD: "Přehled"
+ UPDATES_AVAILABLE: "Dostupné aktualizace"
+ DAYS: "Dny"
+ UPDATE: "Aktualizovat"
+ BACKUP: "Zálohování"
+ BACKUPS: "Zálohování"
+ BACKUP_NOW: "Zálohovat nyní"
+ BACKUPS_STATS: "Statistiky zálohování"
+ BACKUPS_HISTORY: "Historie zálohování"
+ BACKUPS_PURGE_CONFIG: "Konfigurace vyčištění záloh"
+ BACKUPS_PROFILES: "Profily zálohování"
+ BACKUPS_COUNT: "Počet záloh"
+ BACKUPS_PROFILES_COUNT: "Počet profilů"
+ BACKUPS_TOTAL_SIZE: "Využité místo"
+ BACKUPS_NEWEST: "Nejnovější záloha"
+ BACKUPS_OLDEST: "Nejstarší záloha"
+ BACKUPS_PURGE: "Vyčistit"
+ BACKUPS_NOT_GENERATED: "Zatím nebyly vytvořeny žádné zálohy..."
+ BACKUPS_PURGE_NUMBER: "Použito %s z %s záložního místa"
+ BACKUPS_PURGE_TIME: "%s dnů záloh zbývá"
+ BACKUPS_PURGE_SPACE: "Použito %s z %s"
+ BACKUP_DELETED: "Záloha úspěšně smazána"
+ BACKUP_NOT_FOUND: "Záloha nalezena"
+ BACKUP_DATE: "Datum zálohy"
+ STATISTICS: "Statistiky"
+ TODAY: "Dnes"
+ WEEK: "Týden"
+ MONTH: "Měsíc"
+ LATEST_PAGE_UPDATES: "Poslední aktualizace stránky"
+ MAINTENANCE: "Údržba"
+ UPDATED: "Aktualizováno"
+ MON: "Po"
+ TUE: "Út"
+ WED: "St"
+ THU: "Čt"
+ FRI: "Pá"
+ SAT: "So"
+ SUN: "Ne"
+ COPY: "Kopírovat"
+ EDIT: "Upravit"
+ CREATE: "Vytvořit"
+ GRAV_ADMIN: "Správa Grav"
+ GRAV_OFFICIAL_PLUGIN: "Oficiální doplněk Gravu"
+ GRAV_OFFICIAL_THEME: "Oficiální šablona Grav"
+ PLUGIN_SYMBOLICALLY_LINKED: "Tento zásuvný modul je nainstalován pomocí symbolického linku. Změny v něm nebudou zjištěny."
+ THEME_SYMBOLICALLY_LINKED: "Tato šablona je nainstalována pomocí symbolického linku. Změny v ní nebudou zjištěny"
+ REMOVE_PLUGIN: "Odstranit doplněk"
+ INSTALL_PLUGIN: "Nainstalovat doplněk"
+ AVAILABLE: "K dispozici"
+ INSTALLED: "Instalováno"
+ INSTALL: "Nainstalovat"
+ ACTIVE_THEME: "Aktivní šablona"
+ SWITCHING_TO: "Přepnout na"
+ SWITCHING_TO_DESCRIPTION: "Přepnutím na jinou šablonu nelze garantovat, že všechny vzhledy stránek budou podporované, což může způsobit jejich nedostupnost."
+ SWITCHING_TO_CONFIRMATION: "Chcete pokračovat a přepnout na jinou šablonu"
+ CREATE_NEW_USER: "Vytvořit nového uživatele"
+ REMOVE_USER: "Odstranit uživatele"
+ ACCESS_DENIED: "Přístup byl odepřen"
+ ACCOUNT_NOT_ADMIN: "váš účet nemá administrátorská oprávnění"
+ PHP_INFO: "PHP Info"
+ INSTALLER: "Instalátor"
+ AVAILABLE_THEMES: "Dostupné šablony"
+ AVAILABLE_PLUGINS: "Dostupné doplňky"
+ INSTALLED_THEMES: "Nainstalované šablony"
+ INSTALLED_PLUGINS: "Nainstalované doplňky"
+ BROWSE_ERROR_LOGS: "Prohlédnout systémové záznamy"
+ SITE: "Web"
+ INFO: "Info"
+ SYSTEM: "Systém"
+ USER: "Uživatel"
+ ADD_ACCOUNT: "Přidat účet"
+ SWITCH_LANGUAGE: "Přepnout jazyk"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Zásuvný modul byl úspěšně povolen"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Zásuvný modul byl úspěšně zakázán"
+ SUCCESSFULLY_CHANGED_THEME: "Úspěšně změněna výchozí šablona"
+ INSTALLATION_FAILED: "Instalace se nezdařila"
+ INSTALLATION_SUCCESSFUL: "Instalace byla úspěšná"
+ UNINSTALL_FAILED: "Odinstalace se nezdařila"
+ UNINSTALL_SUCCESSFUL: "Odinstalace byla úspěšná"
+ SUCCESSFULLY_SAVED: "Úspěšně uloženo"
+ SUCCESSFULLY_COPIED: "Úspěšně zkopírováno"
+ REORDERING_WAS_SUCCESSFUL: "Změna pořadí byla úspěšná"
+ SUCCESSFULLY_DELETED: "Úspěšně smazáno"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Jazyk úspěšně změněn"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Nemáte dostatečná oprávnění pro úlohu"
+ CACHE_CLEARED: "Mezipaměť smazána"
+ METHOD: "Metoda"
+ ERROR_CLEARING_CACHE: "Nepodařilo se smazat mezipaměť"
+ AN_ERROR_OCCURRED: "Došlo k chybě"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Vaše záloha je připravena ke stažení"
+ DOWNLOAD_BACKUP: "Stáhnout zálohu"
+ PAGES_FILTERED: "Stránky vyfiltrovány"
+ NO_PAGE_FOUND: "Stránka nenalezena"
+ INVALID_PARAMETERS: "Neplatné parametry"
+ NO_FILES_SENT: "Žádné soubory nebyly odeslány"
+ EXCEEDED_FILESIZE_LIMIT: "Překročen limit velikosti souboru nastaveného konfiguračním souborem PHP"
+ EXCEEDED_POSTMAX_LIMIT: "Byl překročen post_max_size konfigurace PHP"
+ UNKNOWN_ERRORS: "Neznámé chyby"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Překročen limit velikosti souboru nastaveného konfiguračním souborem Grav"
+ UNSUPPORTED_FILE_TYPE: "Nepodporovaný typ souboru"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Přesunutí nahraného souboru se nezdařilo."
+ FILE_UPLOADED_SUCCESSFULLY: "Soubor úspěšně nahrán"
+ FILE_DELETED: "Soubor smazán"
+ FILE_COULD_NOT_BE_DELETED: "Soubor nebylo možné odstranit"
+ FILE_NOT_FOUND: "Soubor nenalezen"
+ NO_FILE_FOUND: "Nebyl nalezen žádný soubor"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav byl úspěšně aktualizován na verzi"
+ GRAV_UPDATE_FAILED: "Aktualizace Grav se nezdařila"
+ EVERYTHING_UPDATED: "Vše bylo aktulizováno"
+ UPDATES_FAILED: "Aktualizace se nezdařily"
+ AVATAR_BY: "Ikona uživatele od"
+ AVATAR_UPLOAD_OWN: "Nebo si nahrajte vlastní..."
+ LAST_BACKUP: "Poslední záloha"
+ FULL_NAME: "Jméno a příjmení"
+ USERNAME: "Uživatelské jméno"
+ EMAIL: "E-mailová adresa"
+ USERNAME_EMAIL: "Uživatelské jméno nebo e-mailová adresa"
+ PASSWORD: "Heslo"
+ PASSWORD_CONFIRM: "Potvrzení hesla"
+ TITLE: "Název"
+ LANGUAGE: "Jazyk"
+ ACCOUNT: "Účet"
+ EMAIL_VALIDATION_MESSAGE: "Musíte zadat platnou e-mailovou adresu"
+ PASSWORD_VALIDATION_MESSAGE: "Heslo musí obsahovat alespoň jedno číslo, jedno velké a malé písmeno a musí být alespoň 8 znaků dlouhé"
+ LANGUAGE_HELP: "Nastavte Váš preferovaný jazyk"
+ MEDIA: "Média"
+ DEFAULTS: "Výchozí hodnoty"
+ SITE_TITLE: "Název webu"
+ SITE_TITLE_PLACEHOLDER: "Titulek napříč celým webem"
+ SITE_TITLE_HELP: "Výchozí titulek webu, většinou použit v šablonách"
+ SITE_DEFAULT_LANG: "Výchozí jazyk"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Výchozí jazyk použitý ve značce šablony"
+ SITE_DEFAULT_LANG_HELP: "Výchozí jazyk použitý ve značce šablony"
+ DEFAULT_AUTHOR: "Výchozí autor"
+ DEFAULT_AUTHOR_HELP: "Výchozí jméno autora, často použito v šablonách či obsazích stránek"
+ DEFAULT_EMAIL: "Výchozí e-mailová adresa"
+ DEFAULT_EMAIL_HELP: "Výchozí e-mailová adresa, na kterou je odkazováno v šablonách či stránkách"
+ TAXONOMY_TYPES: "Typy taxonomií"
+ TAXONOMY_TYPES_HELP: "Typy taxonomií musí být vytvořeny zde, aby bylo možné je použít v nastavení stránek"
+ PAGE_SUMMARY: "Souhrn stránky"
+ ENABLED: "Zapnuto"
+ ENABLED_HELP: "Zapnout souhrn stránky (souhrn je stejný jako obsah stránky)"
+ 'YES': "Ano"
+ 'NO': "Ne"
+ SUMMARY_SIZE: "Souhrnná velikost"
+ SUMMARY_SIZE_HELP: "Počet znaků stránky, který je použit jako souhrn"
+ FORMAT: "Formát"
+ FORMAT_HELP: "krátký = až po první výskyt oddělovače nebo do délky souhrnu; dlouhý = ignorovat oddělovač"
+ SHORT: "Krátký"
+ LONG: "Dlouhý"
+ DELIMITER: "Oddělovač"
+ DELIMITER_HELP: "Oddělovač souhrnu (výchozí '===')"
+ METADATA: "Metadata"
+ METADATA_HELP: "Výchozí metadata, která budou zobrazena na každé stránce pokud nejsou přepsána v nastavení stránky"
+ NAME: "Název"
+ CONTENT: "Obsah"
+ SIZE: "Velikost"
+ ACTION: "Akce"
+ REDIRECTS_AND_ROUTES: "Přesměrování a cesty"
+ CUSTOM_REDIRECTS: "Vlastní přesměrování"
+ CUSTOM_REDIRECTS_HELP: "Cesty jsou přesměrovány na jiné stránky. Použití regulárních výrazů je možné"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/váš/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/vaše/přesměrování"
+ CUSTOM_ROUTES: "Vlastní cesty"
+ CUSTOM_ROUTES_HELP: "Cesty jsou aliasy pro jiné stránky. Použití regulárních výrazů je možné"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/váš/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/vaše/cesta"
+ FILE_STREAMS: "Soubory"
+ DEFAULT: "Výchozí"
+ PAGE_MEDIA: "Média stránky"
+ OPTIONS: "Možnosti"
+ PUBLISHED: "Zveřejněná"
+ PUBLISHED_HELP: "Ve výchozím nastavení je stránka publikovaná, pokud nenastavíte 'published: false', 'publish_date' do budoucnosti nebo 'unpublish_date' do minulosti"
+ DATE: "Datum"
+ DATE_HELP: "Můžete nastavit konkrétní datum, které bude přiřazené k této stránce."
+ PUBLISHED_DATE: "Datum zveřejnění"
+ PUBLISHED_DATE_HELP: "Můžete nastavit datum, kdy bude článek automaticky publikován"
+ UNPUBLISHED_DATE: "Datum skrytí"
+ UNPUBLISHED_DATE_HELP: "Můžete nastavit datum, kdy bude článek automaticky skryt."
+ ROBOTS: "Roboti"
+ TAXONOMIES: "Taxonomie"
+ TAXONOMY: "Taxonomie"
+ ADVANCED: "Pokročilé"
+ SETTINGS: "Nastavení"
+ FOLDER_NUMERIC_PREFIX: "Číselná předpona složky"
+ FOLDER_NUMERIC_PREFIX_HELP: "Číselná předpona, která umožňuje manuální řazení a zajišťuje viditelnost"
+ FOLDER_NAME: "Název složky"
+ FOLDER_NAME_HELP: "Název složky, která bude vytvořena v souborovém systému pro tuto stránku"
+ PARENT: "Nadřazená stránka"
+ DEFAULT_OPTION_ROOT: "- Root -"
+ DEFAULT_OPTION_SELECT: "- Vyberte -"
+ DISPLAY_TEMPLATE: "Šablona pro zobrazení"
+ DISPLAY_TEMPLATE_HELP: "Název šablony, která bude použita pro zobrazení této stránky"
+ BODY_CLASSES: "Třídy pro element"
+ ORDERING: "Řazení"
+ PAGE_ORDER: "Pořadí stránek"
+ OVERRIDES: "Přepsat výchozí nastavení"
+ MENU: "Menu"
+ MENU_HELP: "Řetězec, který má být zobrazený v menu. Pokud není nastavený, použije se titulek."
+ SLUG: "Slug"
+ SLUG_HELP: "Slug umožněje nastavit část URL pro tuto konkrétní stránku"
+ SLUG_VALIDATE_MESSAGE: "Slug může obsahovat pouze znaky malé abecedy, čísla a pomlčky."
+ PROCESS: "Zpracování stránky"
+ PROCESS_HELP: "Nastavte jaký renderovací engine se má použít pro zpracování stránky"
+ DEFAULT_CHILD_TYPE: "Výchozí typ nové stránky"
+ USE_GLOBAL: "Použít globálně"
+ ROUTABLE: "Přístupná"
+ ROUTABLE_HELP: "Pokud je tato stránka dosažitelná prostřednictvím adresy URL"
+ CACHING: "Použít cache"
+ VISIBLE: "Viditelná"
+ VISIBLE_HELP: "Určuje, zda je stránka viditelná v navigaci."
+ DISABLED: "Vypnuto"
+ ITEMS: "Položky"
+ ORDER_BY: "Řadit podle"
+ ORDER: "Pořadí"
+ FOLDER: "Složka"
+ ASCENDING: "Vzestupně"
+ DESCENDING: "Sestupně"
+ ADD_MODULAR_CONTENT: "Přidat modulární obsah"
+ PAGE_TITLE: "Název stránky"
+ PAGE_TITLE_HELP: "Titulek stránky"
+ PAGE: "Stránka"
+ MODULAR_TEMPLATE: "Modulární šablona"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Název souboru"
+ PARENT_PAGE: "Nadřazená stránka"
+ HOME_PAGE: "Domovská stránka"
+ HOME_PAGE_HELP: "Stránka, kterou Grav použije jako výchozí při příchodu na web"
+ DEFAULT_THEME: "Výchozí šablona"
+ DEFAULT_THEME_HELP: "Nastaví výchozí šablonu kterou bude Grav používat (výchozí je Antimatter)"
+ TIMEZONE: "Časové pásmo"
+ TIMEZONE_HELP: "Přepíše výchozí časovou zónu serveru"
+ SHORT_DATE_FORMAT: "Krátký formát datumů"
+ SHORT_DATE_FORMAT_HELP: "Nastaví krátký formát data, který bude použit v šablonách"
+ LONG_DATE_FORMAT: "Dlouhý formát datumů"
+ LONG_DATE_FORMAT_HELP: "Nastaví dlouhý formát data, který bude použit v šablonách"
+ DEFAULT_ORDERING: "Výchozí řazení"
+ DEFAULT_ORDERING_HELP: "Stránky budou v seznamu zobrazeny v tomto pořadí, pokud nemá konkrétní stránka jiné nastavení"
+ DEFAULT_ORDERING_DEFAULT: "Výchozí - podle celého názvu adresáře"
+ DEFAULT_ORDERING_FOLDER: "Adresáře - podle názvu adresáře bez prefixů"
+ DEFAULT_ORDERING_TITLE: "Titulku - podle titulku nastaveného v hlavičce stránky"
+ DEFAULT_ORDERING_DATE: "Datumu - podle datumu nastaveného v hlavičce stránky"
+ DEFAULT_ORDER_DIRECTION: "Výchozí směr řazení"
+ DEFAULT_ORDER_DIRECTION_HELP: "Směr řazení ve výpisu stránek"
+ DEFAULT_PAGE_COUNT: "Výchozí počet stránek"
+ DEFAULT_PAGE_COUNT_HELP: "Výchozí počet zobrazených stránek v seznamu"
+ DATE_BASED_PUBLISHING: "Publikace podle datumu"
+ DATE_BASED_PUBLISHING_HELP: "Automaticky publikovat/skrýt stránky podle jejich datumu"
+ EVENTS: "Události"
+ EVENTS_HELP: "Povolit nebo zakázat konkrétní události. Zakázání některých událostí může mít za následek nefunkčnost některých doplňků"
+ REDIRECT_DEFAULT_ROUTE: "Přesměrovat výchozí cestu"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Automatické přesměrování na výchozí cestu stránky"
+ LANGUAGES: "Jazyky"
+ SUPPORTED: "Podporováno"
+ SUPPORTED_HELP: "Dvouznakové jazykové kódy oddělené čárkou (například 'en,fr,de')"
+ TRANSLATIONS_ENABLED: "Překlady povoleny"
+ TRANSLATIONS_ENABLED_HELP: "Podpora překladů v Gravu, doplňcích a rozšířeních"
+ TRANSLATIONS_FALLBACK: "Hledat i v ostatních jazycích"
+ TRANSLATIONS_FALLBACK_HELP: "Pokud překlad neexistuje v aktivním jazyce, prohledat i ostatní jazyky"
+ ACTIVE_LANGUAGE_IN_SESSION: "Aktivní jazyk v session"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Uložit aktivní jazyk do session"
+ HTTP_HEADERS: "HTTP Hlavičky"
+ EXPIRES: "Expires"
+ EXPIRES_HELP: "Nastaví 'expires' záznam v hlavičce. Hodnota je v sekundách."
+ CACHE_CONTROL: "HTTP kontrola mezipaměti"
+ CACHE_CONTROL_HELP: "Nastavte platnou hodnotu kontroly mezipaměti, jako např. `no-cache, no-store, must-revalidate`"
+ LAST_MODIFIED: "Naposledy změněno"
+ LAST_MODIFIED_HELP: "Nastaví datum 'Last Modified' v HTTP hlavičce, které může pomoci při optimalizaci cachování na straně proxy serveru a prohlížeče"
+ ETAG: "ETag"
+ ETAG_HELP: "Nastaví 'ETag' záznam v HTTP hlavičce, který pomáhá rozeznat kdy byla stránka naposledy upravena"
+ VARY_ACCEPT_ENCODING: "Vary: Accept Encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Nastaví 'Vary: Accept Encoding' záznam v HTTP hlavičce, který pomáhá s cachováním na straně proxy a CDN"
+ MARKDOWN: "Markdown"
+ MARKDOWN_EXTRA: "Markdown extra"
+ MARKDOWN_EXTRA_HELP: "Povolit ve výchozím nastavení syntaxi Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ MARKDOWN_EXTRA_ESCAPE_FENCES: "Escapovat prvky HTML v markdown zvláštním značením"
+ MARKDOWN_EXTRA_ESCAPE_FENCES_HELP: "Escapování prvků HTML v markdown zvláštním značením"
+ AUTO_LINE_BREAKS: "Automatické zalamování řádků"
+ AUTO_LINE_BREAKS_HELP: "Povolit ve výchozím nastavení automatické zalamování řádků v Markdownu"
+ AUTO_URL_LINKS: "Převádět URL na odkazy"
+ AUTO_URL_LINKS_HELP: "Povolit automatické převádění URL na odkazy pomocí elementu"
+ ESCAPE_MARKUP: "Escapovat HTML specifické znaky"
+ ESCAPE_MARKUP_HELP: "Automaticky escapovat HTML specifické znaky na jejich bezpečné ekvivalenty"
+ CACHING_HELP: "Globální vypnutí/zapnutí cachování v Gravu"
+ CACHE_CHECK_METHOD: "Metoda kontroly cache stránek"
+ CACHE_CHECK_METHOD_HELP: "Vyberte metodu, pomocí které Grav může zkontrolovat jestli byly soubory stránky modifikovány"
+ CACHE_DRIVER: "Cache"
+ CACHE_DRIVER_HELP: "Vyberte jaký způsob cachování ma Grav používat. 'Auto Detect' se pokusí najít ten nejlepší pro Vaši konfiguraci."
+ CACHE_PREFIX: "Prefix cache"
+ CACHE_PREFIX_HELP: "Identifikátor pro klíče použité Gravem. Neupravujte pokud si nejste jistí co děláte."
+ CACHE_PREFIX_PLACEHOLDER: "Odvozený od URL webu (přepište náhodným řetězcem)"
+ CACHE_PURGE_JOB: "Spustit plánovanou čistící úlohu"
+ CACHE_PURGE_JOB_HELP: "Pomocí plánovače můžete pravidelně vymazat starou mezipaměť prostřednictvím této úlohy"
+ CACHE_CLEAR_JOB: "Spustit plánovanou čistící úlohu"
+ CACHE_CLEAR_JOB_HELP: "Prostřednictvím služby Plánovač můžete pravidelně mazat mezipaměť Gravu"
+ CACHE_JOB_TYPE: "Typ úlohy mezipaměti"
+ CACHE_JOB_TYPE_HELP: "Buď vymazat \"standardní\" složky mezipaměti, nebo \"všechny\" složky"
+ CACHE_PURGE: "Vyčistit starou mezipaměť"
+ LIFETIME: "Platnost"
+ LIFETIME_HELP: "Délka platnosti záznamů v cachi. 0 = nekonečně."
+ GZIP_COMPRESSION: "Komprese Gzip"
+ GZIP_COMPRESSION_HELP: "Zapnout GZip kompresi stránek pro rychlejší načítání."
+ TWIG_TEMPLATING: "Twig šablony"
+ TWIG_CACHING: "Cachovat Twig šablony"
+ TWIG_CACHING_HELP: "Nastavuje zda se využite cachování Twig šablon. Nechejte zapnuté pro nějlepší výkon."
+ TWIG_DEBUG: "Twig debug mód"
+ TWIG_DEBUG_HELP: "Umožňuje vypnout načítání rozšíření Twig Debugger"
+ DETECT_CHANGES: "Detekovat změny"
+ DETECT_CHANGES_HELP: "Twig bude automaticky aktualizovat cache pokud detekuje změnu v Twig šabloně"
+ AUTOESCAPE_VARIABLES: "Automaticky escapovat proměnné"
+ AUTOESCAPE_VARIABLES_HELP: "Escapování všech proměnných bude mít pravděpodobně za následek nefunkčnost webu"
+ ASSETS: "Assety"
+ CSS_PIPELINE: "Sloučit CSS soubory"
+ CSS_PIPELINE_HELP: "Spojí všechny CSS soubory do jednoho"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Zahrnout externí CSS do sloučeného CSS"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Externí URL adresy někdy obsahují relativní odkazy na soubory a tudíž by neměli být sloučeny"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "Jako první vykreslit sloučené CSS"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Vykreslit sloučené CSS před jakékoliv jiné nezahrnuté CSS reference"
+ CSS_MINIFY: "Minifikovat CSS soubory"
+ CSS_MINIFY_HELP: "Minifikuje všechny CSS soubory"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "Minifikace CSS na Windows"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Přepsat nastavení minifikace CSS pro Windows. Ve výchozím nastavení je vypnuto kvůli ThreadStackSize"
+ CSS_REWRITE: "Přepsat URL uvnitř CSS"
+ CSS_REWRITE_HELP: "Přepíše relativní URL uvnitř CSS souborů"
+ JAVASCRIPT_PIPELINE: "Spojit soubory s JavaScripty"
+ JAVASCRIPT_PIPELINE_HELP: "Spojí všechny JavaScriptové soubory do jednoho"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Zahrnout externí JS v sloučeném JS"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Externí URL adresy někdy obsahují relativní odkazy na soubory a tudíž by neměli být sloučeny"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Jako první vykreslit sloučené JS"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Vykreslit sloučené JS před jakékoliv jiné nezahrnuté JS reference"
+ JAVASCRIPT_MINIFY: "Minifikovat JavaScripty"
+ JAVASCRIPT_MINIFY_HELP: "Minifikuje všechny JavaScriptové soubory"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Povolit časové značky na assetech"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Všechny assetu budou mít v URL i čas vytvoření"
+ COLLECTIONS: "Kolekce"
+ ERROR_HANDLER: "Hlášení chyb"
+ DISPLAY_ERRORS: "Zobrazit chyby"
+ DISPLAY_ERRORS_HELP: "Zobrazit celou stránku s výpisem z backtrace"
+ LOG_ERRORS: "Logovat chyby"
+ LOG_ERRORS_HELP: "Logovat chyby do /logs adresáře"
+ LOG_HANDLER: "Správce logů"
+ LOG_HANDLER_HELP: "Kam ukládat výstup z logu"
+ SYSLOG_FACILITY: "Syslog"
+ SYSLOG_FACILITY_HELP: "Výstup ze Syslog"
+ DEBUGGER: "Debugger"
+ DEBUGGER_HELP: "Povolit Grav debugger a následující nastavení"
+ DEBUG_TWIG: "Debugovat Twig"
+ DEBUG_TWIG_HELP: "Povolit debugování Twig šablon"
+ SHUTDOWN_CLOSE_CONNECTION: "Uzavírat spojení"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Uzavřít spojení před zavoláním 'onShutdown()' události. Vypnout při povoleném debuggování"
+ DEFAULT_IMAGE_QUALITY: "Výchozí kvalita obrázků"
+ DEFAULT_IMAGE_QUALITY_HELP: "Výchozí kvalita obrázků při úpravách nebo cachování (85%)"
+ CACHE_ALL: "Cachovat všechny obrázky"
+ CACHE_ALL_HELP: "Zpracovat všechny obrázky pomocí cachovacího systému Gravu ikdyž nevyžadují žádné úpravy"
+ IMAGES_DEBUG: "Vodoznak pro lepší ladění"
+ IMAGES_DEBUG_HELP: "Zobrazit vrstvu přes obrázek znázorňující hustotu pixelů v obrázku například pro retina displaye"
+ IMAGES_SEOFRIENDLY: "SEO-přívětivý název obrázku"
+ IMAGES_SEOFRIENDLY_HELP: "Je-li tato volba zapnuta, zobrazí se nejprve název obrázku a potom menší hodnota hash, která odráží zpracované operace"
+ UPLOAD_LIMIT: "Limit pro uploadované soubory"
+ UPLOAD_LIMIT_HELP: "Nastaví maximální povolenou velikost pro uploadované soubory v bytech (0 = neomezeně)"
+ ENABLE_MEDIA_TIMESTAMP: "Zapnout časové značky na médiích"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Přidá časovou značku podle poslední modifikace do každé URL odkazující na média"
+ SESSION: "Relace"
+ SESSION_ENABLED_HELP: "Povolit podporu relací v rámci Grav"
+ TIMEOUT: "Časový limit"
+ TIMEOUT_HELP: "Nastaví platnost session v sekundách"
+ SESSION_NAME_HELP: "Identifikátor pro vytvoření názvu pro session cookie"
+ SESSION_UNIQUENESS: "Jedinečný řetězec"
+ SESSION_UNIQUENESS_HELP: "MD5 hash kořenové cesty Gravu, např. `GRAV_ROOT` (výchozí) nebo založený na náhodném `osoleném (security.salt)` řetězci."
+ ABSOLUTE_URLS: "Absolutní URL"
+ ABSOLUTE_URLS_HELP: "Absolutní nebo relativní URL v proměnné 'base_url'"
+ PARAMETER_SEPARATOR: "Oddělovač parametrů"
+ PARAMETER_SEPARATOR_HELP: "Oddělovač parametrů, který lze změnit pro Apache na Windows"
+ TASK_COMPLETED: "Úkol dokončen"
+ EVERYTHING_UP_TO_DATE: "Vše je aktualizováno"
+ UPDATES_ARE_AVAILABLE: "jsou dostupné nové aktualizace"
+ IS_AVAILABLE_FOR_UPDATE: "je možné aktualizovat"
+ IS_NOW_AVAILABLE: "je nyní k dispozici"
+ CURRENT: "Aktuální"
+ UPDATE_GRAV_NOW: "Aktualizovat nyní Grav"
+ GRAV_SYMBOLICALLY_LINKED: "Grav je symbolicky propojen, aktualizace nebude k dispozici"
+ UPDATING_PLEASE_WAIT: "Probíhá aktualizace... prosím čekejte na stáhnutí"
+ OF_THIS: "této"
+ OF_YOUR: "Vaší"
+ HAVE_AN_UPDATE_AVAILABLE: "má k dispozici novou verzi"
+ SAVE_AS: "Uložit jako"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Opravdu chcete smazat tuto stránku a všechny její podstránky? Překlady této stránky nebudou smazány společně s touto stránkou a musí být odstraněny samostatně. Adresář se stránkou bude smazán společně s jejími podstránkami. Tato akce je nevratná."
+ AND: "a"
+ UPDATE_AVAILABLE: "Dostupná aktualizace"
+ METADATA_KEY: "Klíč (např. 'Keywords')"
+ METADATA_VALUE: "Hodnota (např. 'Blog, Grav')"
+ USERNAME_HELP: "Uživatelské jméno by mělo mít 3 až 16 znaků včetně malých písmen, čísel, podtržítka a pomlčky. Velká písmena, mezery a speciální znaky nejsou povoleny"
+ FULLY_UPDATED: "Úplně aktualizované"
+ SAVE_LOCATION: "Uložit umístění"
+ PAGE_FILE: "Šablona stránky"
+ PAGE_FILE_HELP: "Název souboru se stránkou. Ve výchozím nastavení je to také název šablony použitý pro tuto stránku."
+ NO_USER_ACCOUNTS: "Nebyly nalezen žádný uživatelský účet, prosím, nejdříve si jeden vytvořte"
+ NO_USER_EXISTS: "Pro tento účet neexistuje žádný místní uživatel, nelze uložit..."
+ REDIRECT_TRAILING_SLASH: "Přesměrovat URL končící na '/'"
+ REDIRECT_TRAILING_SLASH_HELP: "Provést přesměrování pomocí kódu 301 namísto ignorování pokud URL končí lomítkem."
+ DEFAULT_DATE_FORMAT: "Formát data stránky"
+ DEFAULT_DATE_FORMAT_HELP: "Formát datumu použitý Gravem. Ve výchozím nastavení se Grav pokusí uhodnout Váš formát datumů. Můžete jej ale nastavit i sami pomocí stejné syntaxe jako se využívá v PHP (např.: Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Automaticky odhadnout"
+ IGNORE_FILES: "Ignorovat soubory"
+ IGNORE_FILES_HELP: "Ignorovat tyto soubory při zpracování stránky"
+ IGNORE_FOLDERS: "Ignorovat složky"
+ IGNORE_FOLDERS_HELP: "Ignorovat tyto adresáře při zpracování stránky"
+ HIDE_EMPTY_FOLDERS: "Skrýt prázdné složky"
+ HIDE_EMPTY_FOLDERS_HELP: "Pokud složka neobsahuje žádný soubor .md, měla by být skrytá v navigaci, stejně jako nepřístupná"
+ HTTP_ACCEPT_LANGUAGE: "Nastavit jazyk dle prohlížeče"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Zkusí nastavit jazyk podle `http_accept_language` hlavičky v prohlížeči"
+ OVERRIDE_LOCALE: "Přepsat lokalitu"
+ OVERRIDE_LOCALE_HELP: "Přepíše lokalitu nastavenou v PHP podle aktuálního jazyku"
+ REDIRECT: "Přesměrování stránky"
+ REDIRECT_HELP: "Zadejte cestu stránky nebo externí URL, kam bude tato stránka přesměrována. Např. `/nějaká/cesta` nebo `http://nějakýweb.com`"
+ PLUGIN_STATUS: "Stav doplňku"
+ INCLUDE_DEFAULT_LANG: "Zahrnout výchozí jazyk"
+ INCLUDE_DEFAULT_LANG_HELP: "Předřadí všem adresám URL ve výchozím jazyce výchozí jazyk. Např.: `/en/blog/my-post`"
+ PAGES_FALLBACK_ONLY: "Stránky jsou pouze záložní"
+ PAGES_FALLBACK_ONLY_HELP: "Pouze \"záložní\" pro vyhledání obsahu stránky pomocí podporovaných jazyků, výchozím chováním je zobrazení jakéhokoli nalezeného jazyka, pokud chybí aktivní jazyk"
+ ALLOW_URL_TAXONOMY_FILTERS: "Filtry taxonomie v URL"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Umožňuje filtrovat stránky pomocí taxonomií jako `/taxonomie:hodnota`."
+ REDIRECT_DEFAULT_CODE: "Výchozí kód přesměrování"
+ REDIRECT_DEFAULT_CODE_HELP: "Nastaví HTTP status kód použitý pro všechna přesměrování"
+ IGNORE_HIDDEN: "Ignorovat skryté"
+ IGNORE_HIDDEN_HELP: "Ignorovat všechny soubory, které začínají tečkou"
+ WRAPPED_SITE: "Součást jiného webu"
+ WRAPPED_SITE_HELP: "Pro šablony/doplňky, aby věděly, zda je Grav zapouzdřen další platformou"
+ FALLBACK_TYPES: "Povolené typy souborů"
+ FALLBACK_TYPES_HELP: "Povolené typy souborů, které jsou přístupné prostřednictvím cesty stránky. Ve výchozím nastavení jsou podporovány všechny typy médií."
+ INLINE_TYPES: "Typy souborů vložené přímo do stránky"
+ INLINE_TYPES_HELP: "Typy souborů, které mají být zobrazeny přímo ve stránce namísto stahování"
+ APPEND_URL_EXT: "Přidat za URL příponu"
+ APPEND_URL_EXT_HELP: "Přidá vlastní připonu do URL stránky. To znamená, že Grav bude hledat šablonu s názvem `..twig`"
+ PAGE_MODES: "Mód stránky"
+ PAGE_TYPES: "Typ stránky"
+ PAGE_TYPES_HELP: "Určuje typy stránek, které Grav podporuje, a pořadí určuje, na který typ se vrátí, pokud je požadavek nejednoznačný"
+ ACCESS_LEVELS: "Úrovně přístupu"
+ GROUPS: "Skupiny"
+ GROUPS_HELP: "Seznam skupin uživatele"
+ ADMIN_ACCESS: "Přístup správce"
+ SITE_ACCESS: "Přístup na web"
+ INVALID_SECURITY_TOKEN: "Neplatný bezpečnostní token"
+ ACTIVATE: "Povolit"
+ TWIG_UMASK_FIX: "Umask Fix"
+ TWIG_UMASK_FIX_HELP: "Ve výchozím nastavení Twig vytváří cachovací soubory s oprávněním 0755, po zapnutí bude nastavovat 0775"
+ CACHE_PERMS: "Oprávnění cache"
+ CACHE_PERMS_HELP: "Výchozí nastavení oprávnění adresářů. Většinou 0755 nebo 0775 podle konfigurace serveru"
+ REMOVE_SUCCESSFUL: "Úspěšně odstraněno"
+ REMOVE_FAILED: "Odstranění se nezdařilo"
+ HIDE_HOME_IN_URLS: "Skrýt cestu domovské stránky v URL"
+ HIDE_HOME_IN_URLS_HELP: "Zajistí, že výchozí cesty pro všechny stránky umístěné pod domovskou stránkou neobsahují cestu domovské stránky"
+ TWIG_FIRST: "Zpracovat nejdříve Twigem"
+ TWIG_FIRST_HELP: "Pokud povolíte zpracovávání stránek Twigem, můžete upravit stránku ještě před zpracování markdownem"
+ SESSION_SECURE: "Bezpečnost"
+ SESSION_SECURE_HELP: "Pokud zapnete, musí být veškerá komunikace identifikovaná touto cookie prováděna přes HTTPS. POZOR: Zapněte pouze pro weby, které běží pouze na HTTPS"
+ SESSION_HTTPONLY: "Pouze HTTP"
+ SESSION_HTTPONLY_HELP: "Pokud zapnete, cookie mohou být použity pouze přes HTTP a nemohou být upraveny pomocí JavaScriptu"
+ REVERSE_PROXY: "Reverzní proxy"
+ REVERSE_PROXY_HELP: "Zapněte, pokud jste za reverzní proxy and máte problémy s URL obsahující nesprávné porty"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Nesprávný formát frontmatter, nelze uložit"
+ ADD_FOLDER: "Přidat složku"
+ PROXY_URL: "Adresa proxy serveru"
+ PROXY_URL_HELP: "Zadejte server proxy hostitele nebo adresu IP a PORT"
+ NOTHING_TO_SAVE: "Nic k uložení"
+ FILE_ERROR_ADD: "Došlo k chybě při pokusu o přidání souboru"
+ FILE_ERROR_UPLOAD: "Nastala chyba při nahrávání souborů"
+ FILE_UNSUPPORTED: "Nepodporovaný typ souboru"
+ ADD_ITEM: "Přidat položku"
+ FILE_TOO_LARGE: "Soubor je příliš velký. Maximální povolená velikost je %s. Zvětšete prosím `post_max_size` v nastavení PHP"
+ INSTALLING: "Probíhá instalace"
+ LOADING: "Probíhá načítání.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "Nejprve je třeba nainstalovat následující komponenty:"
+ ERROR_INSTALLING_PACKAGES: "Nastala chyba při instalování bálíčků"
+ INSTALLING_DEPENDENCIES: "Instalování komponentů..."
+ INSTALLING_PACKAGES: "Probíhá instalace balíčků.."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Balíčky byly úspěšně nainstalovány."
+ READY_TO_INSTALL_PACKAGES: "Vše připraveno na instalaci balíčků"
+ PACKAGES_NOT_INSTALLED: "Balíčky nenainstalovány"
+ PACKAGES_NEED_UPDATE: "Již nainstalované balíčky, které jsou příliš staré"
+ PACKAGES_SUGGESTED_UPDATE: "Balíčky nainstalované ve verzi, která je v pořádku, budou aktualizovány na nejnovější verzi"
+ REMOVE_THE: "Odstraňte %s"
+ CONFIRM_REMOVAL: "Opravdu chcete smazat %s?"
+ REMOVED_SUCCESSFULLY: "%s úspěšně odstraněn"
+ ERROR_REMOVING_THE: "Nastala chyba při odstraňování %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "%s vyžadoval následující komponenty, které nejsou vyžadovány ostatními nainstalovanými balíčky. Pokud je nepoužíváte, můžete je přímo zde hned odstranit."
+ READY_TO_UPDATE_PACKAGES: "Vše připraveno na aktualizaci balíčků"
+ ERROR_UPDATING_PACKAGES: "Nastala chyba při aktualizaci bálíčků"
+ UPDATING_PACKAGES: "Probíhá aktualizování balíčků.."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Balíčky byly úspěšně aktualizovány."
+ UPDATING: "Probíhá aktualizace"
+ GPM_RELEASES: "Verze GPM"
+ GPM_RELEASES_HELP: "Zvolte 'Testovací' pro instalaci beta nebo testovací verze"
+ GPM_METHOD: "Metoda Remote Fetch"
+ GPM_METHOD_HELP: "Pokud nastaveno na Auto, Grav zjistí, jestli je fopen povoleno a použije jej, jinak se vrátí zpět k používání cURL. K vynucení použití jednoho či druhého přepněte nastavení."
+ GPM_VERIFY_PEER: "Vzdálené ověření Peer (SSL)"
+ GPM_VERIFY_PEER_HELP: "Někteří poskytovatelé nepřijímají getgrav.org SSL certifikát, což způsobuje nefunkčnost GPM. Pokud je toto váš případ, zkuste vypnout toto nastavení"
+ AUTO: "Auto"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stabilní"
+ TESTING: "Testovací"
+ FRONTMATTER_PROCESS_TWIG: "Zpracovat Twig ve frontmatteru"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Pokud je aktivní, můžete použít nastavovací proměnné Twigu ve frontmatteru stránky"
+ FRONTMATTER_IGNORE_FIELDS: "Ignorovat pole ve frontmatteru"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Určitá pole ve frontmatteru můžou obsahovat Twig, ale neměla by být zpracována. Například formuláře"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Balíček %s byl úspěšně nainstalován"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Pořadí nadřazeného nastavení, řazení zakázáno"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Stránka není viditelná, řazení zakázáno"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "Řazení přes admin není podporováno, protože existuje více než 200 sourozenců"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Řazení stránek je zakázáno pro tuto stánku, protože Číselná předpona složky není povolena"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "Poznámka: Nelze přidat mediální soubory, dokud neuložíte na stránku. Stačí kliknout na 'Uložit' na horní části stránky"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "POZNÁMKA: Musíte stránku uložit před nahráním souborů."
+ DROP_FILES_HERE_TO_UPLOAD: "Přetáhněte sem své soubory nebo klikněte na tuto plochu "
+ INSERT: "Vložit"
+ UNDO: "Odvolat"
+ REDO: "Opakovat"
+ HEADERS: "Nadpisy"
+ BOLD: "Tučně"
+ ITALIC: "Kurzíva"
+ STRIKETHROUGH: "Přeškrtnout"
+ SUMMARY_DELIMITER: "Oddělovač shnutí"
+ LINK: "Odkaz"
+ IMAGE: "Obrázek"
+ BLOCKQUOTE: "Citace"
+ UNORDERED_LIST: "Nečíslovaný seznam"
+ ORDERED_LIST: "Číslovaný seznam"
+ EDITOR: "Editor"
+ PREVIEW: "Náhled"
+ FULLSCREEN: "Na celou obrazovku"
+ MODULAR: "Modulární"
+ MODULAR_SETUP: "Modulární nastavení"
+ NON_ROUTABLE: "Nepřístupná"
+ NON_MODULAR: "Nemodulární"
+ NON_VISIBLE: "Neviditelná"
+ NON_PUBLISHED: "Nezveřejněná"
+ CHARACTERS: "znaků"
+ PUBLISHING: "Zveřejnění"
+ NOTIFICATIONS: "Notifikace"
+ MEDIA_TYPES: "Typy médií"
+ IMAGE_OPTIONS: "Možnosti obrázku"
+ MIME_TYPE: "MIME typ"
+ THUMB: "Náhled"
+ TYPE: "Typ"
+ FILE_EXTENSION: "Přípona souboru"
+ LEGEND: "Legenda stránky"
+ MEMCACHE_SERVER: "Server memcache"
+ MEMCACHE_SERVER_HELP: "Adresa Memcache serveru"
+ MEMCACHE_PORT: "Memcached port"
+ MEMCACHE_PORT_HELP: "Port serveru Memcached"
+ MEMCACHED_SERVER: "Memcached server"
+ MEMCACHED_SERVER_HELP: "Adresu serveru Memcached"
+ MEMCACHED_PORT: "Memcached port"
+ MEMCACHED_PORT_HELP: "Port serveru Memcached"
+ REDIS_SERVER: "Redis server"
+ REDIS_SERVER_HELP: "Adresa Redis serveru"
+ REDIS_PORT: "Redis port"
+ REDIS_PORT_HELP: "Port Redis serveru"
+ REDIS_PASSWORD: "Heslo a tajný kód pro Redis"
+ ALL: "Vše"
+ FROM: "od"
+ TO: "do"
+ RELEASE_DATE: "Datum vydání"
+ SORT_BY: "Seřadit podle"
+ RESOURCE_FILTER: "Filtr..."
+ FORCE_SSL: "Vynutit SSL"
+ FORCE_SSL_HELP: "Globálně vynutit SSL, je-li povoleno, web dosažený prostřednictvím protokolu HTTP, Grav přesměruje na HTTPS stránku"
+ NEWS_FEED: "Novinky"
+ EXTERNAL_URL: "Externí adresa URL"
+ SESSION_PATH: "Adresa k session"
+ SESSION_PATH_HELP: "Užívejte, jen když zvolíte vlastní základní adresu URL (přepíšete doménu stránky / podsložky)"
+ CUSTOM_BASE_URL: "Vlastní základní adresa URL"
+ CUSTOM_BASE_URL_HELP: "Užívejte, chcete-li přepsat doménu stránky nebo užít jinou podsložku, než která je v Grav užívaná. Například http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'Nelze použít "%s" mimo stránky.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Nepodařilo se nahrát soubor %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'Nepodařilo přesunout soubor %s do "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'Zrušit nahrávání'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Opravdu si přejete toto nahrávání zrušit?'
+ DROPZONE_DEFAULT_MESSAGE: 'Přetáhněte sem své soubory nebo klikněte na tuto plochu '
+ DROPZONE_FALLBACK_MESSAGE: 'Váš prohlížeč nepodporuje "drag & drop" nahrávání souborů.'
+ DROPZONE_FALLBACK_TEXT: 'Použijte, prosím, záložní formulář níže pro nahrávání souborů.'
+ DROPZONE_FILE_TOO_BIG: 'Soubor je příliš velký ({{filesize}}MiB). Maximální velikost souboru: {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "Nelze nahrát soubory tohoto typu."
+ DROPZONE_MAX_FILES_EXCEEDED: "Nelze nahrát žádné další soubory."
+ DROPZONE_REMOVE_FILE: "Odstranit soubor"
+ DROPZONE_RESPONSE_ERROR: "Server odpověděl kódem {{statusCode}}."
+ PREMIUM_PRODUCT: "Prémium"
+ ADMIN_CACHING: "Povolit Admin Caching"
+ ADMIN_CACHING_HELP: "Ukládání do mezipaměti v administraci lze ovládat nezávisle z front-end webu"
+ DESTINATION_NOT_SPECIFIED: "Cíl není zadán"
+ UPLOAD_ERR_NO_TMP_DIR: "Chybí dočasná složka"
+ SESSION_SPLIT: "Rozdělit relaci"
+ SESSION_SPLIT_HELP: "Nezávisle rozdělené relace mezi webem a dalšími doplňky (jako admin)"
+ ERROR_FULL_BACKTRACE: "Úplná chybová hlášení"
+ ERROR_SIMPLE: "Jednoduchá chyba"
+ ERROR_SYSTEM: "Systémová chyba"
+ IMAGES_AUTO_FIX_ORIENTATION: "Opravit orientaci automaticky"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Automaticky opravovat natočení obrázku podle EXIF data souboru"
+ REDIS_SOCKET: "Redis socket"
+ REDIS_SOCKET_HELP: "Redis socket"
+ NOT_SET: "Nenastaveno"
+ PERMISSIONS: "Přístupová práva"
+ NEVER_CACHE_TWIG: "Nikdy nacachovat Twig"
+ NEVER_CACHE_TWIG_HELP: "Do mezipaměti ukládat jen obsah a Zpracovat Twig při každém načtení. Bude ignorovat nastavení twig_first."
+ ALLOW_WEBSERVER_GZIP: "Povolit WebServer Gzip"
+ ALLOW_WEBSERVER_GZIP_HELP: "Ve výchozím nastavení vypnuto. Je-li povolena, komprese Gzip/Deflate bude fungovat, ale připojení http nebudou uzavřeny před událostí onShutDown(), a tím způsobuje pomalejší načítání stránek"
+ OFFLINE_WARNING: "Nelze navázat spojení s GPM"
+ CLEAR_IMAGES_BY_DEFAULT: "Vyčistit mezipaměť obrázků jako výchozí"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "Ve výchozím nastavení jsou obrázky z mezipaměti odstraněny při jakémkoli její čištění. To může být vypnuto"
+ CLI_COMPATIBILITY: "CLI kompatibilita"
+ CLI_COMPATIBILITY_HELP: "Zajišťuje, že budou použity jen stabilní mezipamětní ovladače (soubor, redis, memcache, atd.)"
+ REINSTALL_PLUGIN: "Přeinstalovat doplněk"
+ REINSTALL_THEME: "Přeinstalovat šablonu"
+ REINSTALL_THE: "Reinstalovat %s"
+ CONFIRM_REINSTALL: "Jste si jistí, že chcete %s přeinstalovat?"
+ REINSTALLED_SUCCESSFULLY: "%s byl úspěšně přeinstalován"
+ ERROR_REINSTALLING_THE: "Chyba při reinstalaci %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Balíček %s byl úspěšně přeinstalován"
+ REINSTALLATION_FAILED: "Chyba při přeinstalaci"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "Není nainstalovaná nejnovější verze. Klepnutím na Pokračovat odeberete současnou verzi a nainstalujete nejnověšjí dostupnou verzi"
+ TOOLS: "Nástroje"
+ DIRECT_INSTALL: "Přímá instalace"
+ NO_PACKAGE_NAME: "Nespecifikovaný název balíčku"
+ PACKAGE_EXTRACTION_FAILED: "Extrakce baličku se nezdařila"
+ NOT_VALID_GRAV_PACKAGE: "Není platným balíčkem Grav"
+ NAME_COULD_NOT_BE_DETERMINED: "Jméno nemohlo být určeno"
+ CANNOT_OVERWRITE_SYMLINKS: "Nelze přepsat symbolické odkazy"
+ ZIP_PACKAGE_NOT_FOUND: "ZIP soubor nebyl nalezen"
+ GPM_OFFICIAL_ONLY: "Pouze oficiální GPM"
+ GPM_OFFICIAL_ONLY_HELP: "Povolit pouze přímé instalace z oficiálního repozitáře GPM."
+ NO_CHILD_TYPE: "Žádný odvozený typ pro zdrojové směrování"
+ SORTABLE_PAGES: "Tříditelné stánky:"
+ UNSORTABLE_PAGES: "Netříditelné stránky"
+ ADMIN_SPECIFIC_OVERRIDES: "Specifická přepsání admina"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Pořadí zobrazení dětí"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "Pořadí v jakém bude odvozená stránka zobrazena v pohledu 'Strany' správcovského zásuvného modulu"
+ PWD_PLACEHOLDER: "komplexní řetězec nejméně 8 znaků dlouhý"
+ PWD_REGEX: "Regex hesla"
+ PWD_REGEX_HELP: "Výchozí: Hesla musí obsahovat minimálně jedno číslo, jedno velké a malé písmeno a jejich délka musí být nejméně 8 znaků"
+ USERNAME_PLACEHOLDER: "jen malá písmena, např. 'admin'"
+ USERNAME_REGEX: "Regex uživatelského jména"
+ USERNAME_REGEX_HELP: "Výchozí: jen malá písmena, znaky, číslice, pomlčky a podtržítka. 3 - 16 znaků"
+ CONTENT_PADDING: "Odsazení obsahu"
+ CONTENT_PADDING_HELP: "Povolit/zakázat obsah odsazení kolem oblasti obsahu pro poskytnutí více místa"
+ ENABLE_AUTO_METADATA: "Automatická metadata z Exif"
+ ENABLE_AUTO_METADATA_HELP: "Automaticky generovat soubory matadat pro obrázky s Exif informacemi"
+ 2FA_TITLE: "Dvoufaktorové ověření"
+ 2FA_INSTRUCTIONS: "##### Dvoufaktorové ověřování\nMáte na svém účtu aktivováno **dvoufaktorové ověřování**. Pro dokončení přihlášení použijte svou aplikaci pro **dvoufaktorové ověřování ** a zadejte **šestimístný kód**."
+ 2FA_REGEN_HINT: "Obnovení tajného kódu bude vyžadovat aktualizaci vaší autentifikační aplikace"
+ 2FA_LABEL: "Přístup správce"
+ 2FA_FAILED: "Špatně zadaný kód dvoufaktorového ověření, zkuste to znovu."
+ 2FA_ENABLED: "Dvoufaktorové ověření povoleno"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "Tajný kód dvoufaktorového ověření"
+ 2FA_SECRET_HELP: "Naskenujte tento QR kód do vaší [Autentifikační aplikace](https://learn.getgrav.org/admin-panel/2fa#apps). Důležité je také uložit tajný kód na bezpečném místě, pokud bude zapotřebí aplikaci přeinstalovat. Pro více informací se podívejte do [dokumentace Grav](https://learn.getgrav.org/admin-panel/2fa)"
+ 2FA_REGENERATE: "Přegenerovat"
+ FORCE_LOWERCASE_URLS: "Vynutit malá písmena v URL"
+ FORCE_LOWERCASE_URLS_HELP: "Ve výchozím nastavení Grav nastaví všechny slugy a cesty na malá písmena. Změnou nastavení na hodnotu false, mohou slugy a cesty obsahovat Velká Písmena"
+ INTL_ENABLED: "Integrace modulu Intl"
+ INTL_ENABLED_HELP: "Použijte modul Intl PHP a porovnávejte třídění s kolekcemi založenými na UTF8"
+ VIEW_SITE_TIP: "Zobrazit web"
+ TOOLS_DIRECT_INSTALL_TITLE: "Přímá instalace balíčků Grav"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "Instalovat balíček prostřednictvím staženého ZIP souboru"
+ TOOLS_DIRECT_INSTALL_UPLOAD_DESC: "Můžete snadno nainstalovat platnou Grav šablonu , platný doplněk nebo dokonce Grav aktualizační balíček Zip prostřednictvím této metody. Tento balíček nemusí být registrován prostřednictvím GPM a umožňuje vám snadno vrátit se k předchozí verzi nebo vytvořit instalaci pro testování."
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "Instalovat balíček prostřednictvím odkazu vzdálené adresy URL"
+ TOOLS_DIRECT_INSTALL_URL_DESC: "Případně můžete také odkázat na úplnou adresu URL k balíčku ZIP souboru a nainstalovat jej prostřednictvím této vzdálené adresy URL."
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Nahrát a instalovat"
+ ROUTE_OVERRIDES: "Přepsání cesty"
+ ROUTE_DEFAULT: "Výchozí cesta"
+ ROUTE_CANONICAL: "Kanonická cesta"
+ ROUTE_ALIASES: "Aliasy cesty"
+ OPEN_NEW_TAB: "Otevřít v nové záložce"
+ PAGEMEDIA_RESIZER: "> Následující nastavení platí pro obrázky nahrané prostřednictvím stránky, sekce Média stránky. Velikost šířky / výšky obrázku se automaticky proporcionálně zmenší, pokud překročí limity nastavené na vstupu. Hodnoty min a max určují rozsahy velikosti nahraných obrázků. Nastavte pole na hodnotu 0, aby nedošlo k žádné manipulaci."
+ SESSION_INITIALIZE: "Inicializovat relaci"
+ SESSION_INITIALIZE_HELP: "Přinutí Grav k nastartování sezení. Tato funkce je zapotřebí pro jakoukoliv interakci s uživatelem jako je např. přihlášení, formuláře apod. Admin rozšíření není tímto nastavením ovlivněno."
+ STRICT_YAML_COMPAT: "YAML kompatibilita"
+ STRICT_YAML_COMPAT_HELP: "Přejít zpět na Symfony 2.4 YAML parser, pokud nativní nebo 3.4 parser selžou"
+ STRICT_TWIG_COMPAT: "Twig kompatibilita"
+ STRICT_TWIG_COMPAT_HELP: "Povolí zastaralé nastavení Twig autoescape. Pokud je zakázáno, filter |raw je požadován pro HTML výstup, protože Twig bude výstup automaticky ukončovat"
+ SCHEDULER: "Plánovač"
+ SCHEDULER_INSTALL_INSTRUCTIONS: "Návod k instalaci"
+ SCHEDULER_INSTALLED_READY: "Nainstalováno a připraveno"
+ SCHEDULER_CRON_NA: "Cron není k dispozici"
+ SCHEDULER_NOT_ENABLED: "Není povoleno"
+ SCHEDULER_SETUP: "Nastavení plánovače"
+ SCHEDULER_INSTRUCTIONS: "Grav plánovač umožňuje vytvořit a naplánovat vlastní úlohy. Poskytuje také metodu pro Grav doplňky k programové integraci a dynamickému přidávaní úloh, které mají být spouštěny v pravidelných intervalech."
+ SCHEDULER_POST_INSTRUCTIONS: "Chcete-li povolit funkci plánovače, je nutné přidat Grav plánovač do systémového souboru crontab. Spusťte příkaz uvedený výše z terminálu, čímž dojde k automatickému přidání. Po uložení obnovte tuto stránku pro zobrazení stavu \"připraven\"."
+ SCHEDULER_JOBS: "Vlastní úlohy plánování"
+ SCHEDULER_STATUS: "Stav plánovače"
+ SCHEDULER_RUNAT: "Spustit v"
+ SCHEDULER_RUNAT_HELP: "Formát syntaxe \"v\" Cronu"
+ SCHEDULER_OUTPUT: "Výstupní soubor"
+ SCHEDULER_OUTPUT_HELP: "Cesta/název výstupního souboru (z kořenové složky instalace Gravu)"
+ SCHEDULER_OUTPUT_TYPE: "Typ výstupu"
+ SCHEDULER_OUTPUT_TYPE_HELP: "Buď připojit ke stejnému souboru každé spuštění, nebo přepsat soubor s každým spuštěním"
+ SCHEDULER_EMAIL: "E-mail"
+ SCHEDULER_EMAIL_HELP: "E-mail pro odeslání výstupu. Poznámka: vyžaduje nastavit výstupní soubor"
+ SECURITY: "Zabezpečení"
+ XSS_SECURITY: "XSS zabezpečení obsahu"
+ XSS_WHITELIST_PERMISSIONS: "Povolená oprávnění"
+ XSS_WHITELIST_PERMISSIONS_HELP: "Uživatelé s těmito oprávněními přeskočí XSS pravidla při ukládání obsahu"
+ XSS_ON_EVENTS: "Filtr událostí"
+ XSS_INVALID_PROTOCOLS: "Filtr neplatných protokolů"
+ XSS_INVALID_PROTOCOLS_LIST: "Seznam neplatných protokolů"
+ XSS_MOZ_BINDINGS: "Filtr Moz vazeb"
+ XSS_HTML_INLINE_STYLES: "Filtr HTML inline stylů"
+ XSS_DANGEROUS_TAGS: "Filtr nebezpečných značek HTML"
+ XSS_DANGEROUS_TAGS_LIST: "Seznam nebezpečných značek HTML"
+ XSS_ONSAVE_ISSUE: "Ukládání selhalo: zjištěn XSS problém..."
+ XSS_ISSUE: "UPOZORNĚNÍ: Grav nalezl potenciální XSS problémy v %s "
+ UPLOADS_SECURITY: "Bezpečnost nahrávání"
+ UPLOADS_DANGEROUS_EXTENSIONS: "Nebezpečná rozšíření"
+ UPLOADS_DANGEROUS_EXTENSIONS_HELP: "Blokovat tato rozšíření při nahrávání navzdory povoleným MIME typům"
+ REPORTS: "Zprávy"
+ LOGS: "Protokoly"
+ LOG_VIEWER_FILES: "Prohlížeč souborů protokolu"
+ LOG_VIEWER_FILES_HELP: "Soubory v /logs/, které budou k dispozici pro zobrazení v nabídce Nástroje - Protokoly. Například \"grav\" = /logs/grav.log"
+ BACKUPS_STORAGE_PURGE_TRIGGER: "Spouštěč vymazání úložiště záloh"
+ BACKUPS_MAX_COUNT: "Maximální počet záloh"
+ BACKUPS_MAX_COUNT_HELP: "0 je neomezené"
+ BACKUPS_MAX_SPACE: "Maximální prostor pro zálohy"
+ BACKUPS_MAX_RETENTION_TIME: "Maximální doba uchování"
+ BACKUPS_MAX_RETENTION_TIME_APPEND: "ve dnech"
+ BACKUPS_PROFILE_NAME: "Název zálohy"
+ BACKUPS_PROFILE_ROOT_FOLDER: "Kořenová složka"
+ BACKUPS_PROFILE_ROOT_FOLDER_HELP: "Může být absolutní cesta nebo proud"
+ BACKUPS_PROFILE_EXCLUDE_PATHS: "Vyloučit cesty"
+ BACKUPS_PROFILE_EXCLUDE_PATHS_HELP: "Absolutní cesty k vyloučení, jedna na řádek"
+ BACKUPS_PROFILE_EXCLUDE_FILES: "Vyloučit soubory"
+ BACKUPS_PROFILE_EXCLUDE_FILES_HELP: "Konkrétní soubory nebo složky k vyloučení, jeden/jedna na řádek"
+ BACKUPS_PROFILE_SCHEDULE: "Povolit naplánovanou úlohu"
+ BACKUPS_PROFILE_SCHEDULE_AT: "Spustit naplánovanou úlohu"
+ COMMAND: "Příkaz"
+ EXTRA_ARGUMENTS: "Další argumenty"
diff --git a/user/plugins/admin/languages/cy.yaml b/user/plugins/admin/languages/cy.yaml
new file mode 100644
index 0000000..9d5d965
--- /dev/null
+++ b/user/plugins/admin/languages/cy.yaml
@@ -0,0 +1,90 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Mae hwn yn fersiwn beta! Defnyddio hwn yn cynhyrchu ar risg eich hun..."
+ ADMIN_REPORT_ISSUE: "Canfod problem? Rhowch wybod ar GitHub."
+ EMAIL_FOOTER: " wedi'u pweru gan Grav -ffeil fflat Modern CMS"
+ LOGIN_BTN: "Mewngofnodi"
+ LOGIN_BTN_FORGOT: "Anghofio"
+ LOGIN_BTN_RESET: "Ailosod cyfrinair"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Anfon cyfarwyddiadau ailosod"
+ LOGIN_BTN_CLEAR: "Ffurflen clir"
+ LOGIN_BTN_CREATE_USER: "Creu defnyddiwr"
+ LOGIN_LOGGED_IN: "Wedi eich mewngofnodi llwyddiannus"
+ LOGIN_FAILED: "Wedi methu mewngofnodi"
+ LOGGED_OUT: "Chi allgofnodi"
+ RESET_LINK_EXPIRED: "Ailosod cysylltiad wedi dod i ben, rhowch gynnig arall arni"
+ RESET_PASSWORD_RESET: "Mae wedi'i ailosod cyfrinair"
+ RESET_INVALID_LINK: "Mae annilys yn ailosod cyswllt a ddefnyddir, rhowch gynnig arni eto"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Mae cyfarwyddiadau i ailosod eich cyfrinair wedi'u hanfon drwy e-bost at %s"
+ FORGOT_FAILED_TO_EMAIL: "Wedi methu anfon e-bost cyfarwyddiadau, rhowch gynnig arall arni rywbryd eto"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Does dim modd ailosod cyfrinair %s, pennir nad oes cyfeiriad e-bost"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Nid yw'r defnyddiwr gyda'r enw defnyddiwr %s yn bodoli"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Does dim modd ailosod cyfrinair. Nid yw'r safle hwn wedi'i ffurfweddu i anfon negeseuon e-bost"
+ FORGOT_EMAIL_SUBJECT: "Cais ailosod cyfrinair %s"
+ FORGOT_EMAIL_BODY: "Ailosod cyfrinair %1$s Annwyl,
Gwnaed cais ar %4$s i ailosod eich cyfrinair.
< br / > hyn i ailosod eich cyfrinair cliciwch < br / > < br / >
Fel arall, gopïo URL canlynol i'r bar cyfeiriad eich porwr:
%2$s
< br / > Cofion, < br / > < br / >%3$s
"
+ MANAGE_PAGES: "Rheoli tudalennau"
+ CONFIGURATION: "Ffurfweddiad"
+ PAGES: "Tudalennau"
+ PLUGINS: "Ategion"
+ PLUGIN: "Ategyn"
+ THEMES: "Themâu"
+ LOGOUT: "Allgofnodi"
+ BACK: "Yn ôl"
+ ADD_PAGE: "Ychwanegu Tudalen"
+ ADD_MODULAR: "Ychwanegu modiwlaidd"
+ MOVE: "Symud"
+ DELETE: "Dileu"
+ SAVE: "Cadw"
+ NORMAL: "Arferol"
+ EXPERT: "Arbenigol"
+ EXPAND_ALL: "Ehangu'r cyfan"
+ COLLAPSE_ALL: "Crebachu'r cyfan"
+ ERROR: "Gwall"
+ CLOSE: "Cau"
+ CANCEL: "Canslo"
+ CONTINUE: "Yn parhau"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Cadarnhad sydd yn ofynnol"
+ MODAL_CHANGED_DETECTED_TITLE: "Ganfod newidiadau"
+ MODAL_CHANGED_DETECTED_DESC: "Wedi ichi golli'r newidiadau. Ydych chi'n siŵr eich bod am adael heb arbed?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Cadarnhad sydd yn ofynnol"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Ydych chi'n siŵr eich bod am ddileu'r ffeil hon? Ni ellir dad-wneud y cam gweithredu hwn."
+ ADD_FILTERS: "Ychwanegu hidlydd"
+ SEARCH_PAGES: "Tudalennau chwilio"
+ VERSION: "Fersiwn"
+ WAS_MADE_WITH: "Wedi greu hefo"
+ BY: "Gan"
+ AUTHOR: "Awdur"
+ HOMEPAGE: "Hafan"
+ KEYWORDS: "Allweddeiriau"
+ LICENSE: "Trwydded"
+ DESCRIPTION: "Disgrifiad"
+ THEME: "Thema"
+ BACK_TO_THEMES: "Ôl i themâu"
+ BACK_TO_PLUGINS: "Ôl i ategion"
+ CHECK_FOR_UPDATES: "Chwilio am ddiweddariadau"
+ ADD: "Ychwanegu"
+ CLEAR_CACHE: "Clirio storfa"
+ CLEAR_CACHE_ALL_CACHE: "Holl storfa"
+ CLEAR_CACHE_ASSETS_ONLY: "Asedau yn unig"
+ CLEAR_CACHE_IMAGES_ONLY: "Llyniau yn unig"
+ CLEAR_CACHE_CACHE_ONLY: "Storfa yn unig"
+ DASHBOARD: "Dangosfwrdd"
+ UPDATES_AVAILABLE: "Diweddariadau ar gael"
+ DAYS: "Diwrnod"
+ UPDATE: "Diweddaru"
+ STATISTICS: "Ystadegau"
+ TODAY: "Heddiw"
+ WEEK: "Wythnos"
+ MONTH: "Mis"
+ UPDATED: "Wedi'w ddiweddaru"
+ MON: "Llu"
+ TUE: "Maw"
+ WED: "Mer"
+ THU: "Iau"
+ FRI: "Gwe"
+ SAT: "Sad"
+ SUN: "Sul"
+ COPY: "Copi"
+ EDIT: "Golygu"
+ CREATE: "Creu"
+ AVAILABLE: "Ar gael"
diff --git a/user/plugins/admin/languages/da.yaml b/user/plugins/admin/languages/da.yaml
new file mode 100644
index 0000000..572aeb8
--- /dev/null
+++ b/user/plugins/admin/languages/da.yaml
@@ -0,0 +1,737 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Dette er en beta-udgivelse! Brug i produktionsmiljøer er på egen risiko..."
+ ADMIN_REPORT_ISSUE: "Har du fundet et problem? Så bedes du rapportere det på GitHub."
+ EMAIL_FOOTER: "Drevet af Grav - det moderne fladfil-CMS"
+ LOGIN_BTN: "Login"
+ LOGIN_BTN_FORGOT: "Glemt"
+ LOGIN_BTN_RESET: "Nulstil adgangskode"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Send nulstillingsinstruktioner"
+ LOGIN_BTN_CLEAR: "Ryd formular"
+ LOGIN_BTN_CREATE_USER: "Opret bruger"
+ LOGIN_LOGGED_IN: "Du er nu logget ind"
+ LOGIN_FAILED: "Login fejlede"
+ LOGGED_OUT: "Du er nu logget ud"
+ RESET_NEW_PASSWORD: "Skriv venligst en ny adgangskode"
+ RESET_LINK_EXPIRED: "Nulstillingslinket er udløbet; prøv venligst igen"
+ RESET_PASSWORD_RESET: "Adgangskoden er blevet nulstillet"
+ RESET_INVALID_LINK: "Du har benyttet et ugyldigt nulstillingslink; prøv venligst igen"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Vejledning i at nulstille din adgangskode er sendt via e-mail til %s"
+ FORGOT_FAILED_TO_EMAIL: "Mislykkedes at maile vejledninger, forsøg venligst igen senere"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Kan ikke nulstille adgangskode til %s; der er ikke angivet en e-mail-adresse"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Der findes ikke en bruger med brugernavnet %s "
+ FORGOT_EMAIL_NOT_CONFIGURED: "Kan ikke nulstille adgangskoden. Dette site er ikke konfigureret til at sende e-mails"
+ FORGOT_EMAIL_SUBJECT: "%s Anmodning om nulstilling af adgangskode"
+ FORGOT_EMAIL_BODY: "Adgangskodenulstilling Hej %1$s
Der blev den%4$s anmodet om at nulstille din adgangskode.
Klik her for at gennemføre adgangskodenulstillingen
Alternativt kan du indsætte flg. URL i din browsers adressebjælke:
%2$s
Venlig hilsen, %3$s
"
+ MANAGE_PAGES: "Administrer sider"
+ CONFIGURATION: "Opsætning"
+ PAGES: "Sider"
+ PLUGINS: "Plugins"
+ PLUGIN: "Plugin"
+ THEMES: "Temaer"
+ LOGOUT: "Logud"
+ BACK: "Tilbage"
+ NEXT: "Næste"
+ PREVIOUS: "Foregående"
+ ADD_PAGE: "Tilføj side"
+ ADD_MODULAR: "Tilføj modulær"
+ MOVE: "Flyt"
+ DELETE: "Slet"
+ UNSET: "Ikke-indstillet"
+ VIEW: "Se"
+ SAVE: "Gem"
+ NORMAL: "Normal"
+ EXPERT: "Ekspert"
+ EXPAND_ALL: "Udfold alle"
+ COLLAPSE_ALL: "Sammenfold alle"
+ ERROR: "Fejl"
+ CLOSE: "Luk"
+ CANCEL: "Annullér"
+ CONTINUE: "Fortsæt"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Bekræftelse kræves"
+ MODAL_CHANGED_DETECTED_TITLE: "Ændringer registreret"
+ MODAL_CHANGED_DETECTED_DESC: "Ændringer er ikke gemt. Sikker på, at du vil fortsætte uden at gemme?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Bekræftelse kræves"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Sikker på, at du vil slette denne fil? Handlingen kan ikke fortrydes."
+ ADD_FILTERS: "Tilføj filtre"
+ SEARCH_PAGES: "Søg efter sider"
+ VERSION: "Version"
+ WAS_MADE_WITH: "Blev lavet med"
+ BY: "Af"
+ UPDATE_THEME: "Opdatere tema"
+ UPDATE_PLUGIN: "Opdater Plugin"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "af dette tema er nu tilgængelig"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "af dette plugin er nu tilgængelig"
+ AUTHOR: "Forfatter"
+ HOMEPAGE: "Websted"
+ DEMO: "Demo"
+ BUG_TRACKER: "Sporing af fejl"
+ KEYWORDS: "Nøgleord"
+ LICENSE: "Licens"
+ DESCRIPTION: "Beskrivelse"
+ README: "Læs-mig"
+ REMOVE_THEME: "Fjern Tema"
+ INSTALL_THEME: "Installer Tema"
+ THEME: "Tema"
+ BACK_TO_THEMES: "Tilbage til Temaer"
+ BACK_TO_PLUGINS: "Tilbage til Plugins"
+ CHECK_FOR_UPDATES: "Søg efter Opdateringer"
+ ADD: "Tilføj"
+ CLEAR_CACHE: "Ryd Cache"
+ CLEAR_CACHE_ALL_CACHE: "Alle Caches"
+ CLEAR_CACHE_ASSETS_ONLY: "Kun Aktiver"
+ CLEAR_CACHE_IMAGES_ONLY: "Kun Billeder"
+ CLEAR_CACHE_CACHE_ONLY: "Kun Cache"
+ CLEAR_CACHE_TMP_ONLY: "Kun tmp"
+ DASHBOARD: "Kontrolpanel"
+ UPDATES_AVAILABLE: "Opdateringer tilgængelige"
+ DAYS: "Dage"
+ UPDATE: "Opdatér"
+ BACKUP: "Backup"
+ BACKUPS: "Sikkerhedskopier"
+ BACKUP_NOW: "Sikkerhedskopiér nu"
+ BACKUPS_STATS: "Sikkerhedskopieringsstatistik"
+ BACKUPS_HISTORY: "Sikkerhedskopieringshistorik"
+ BACKUPS_PURGE_CONFIG: "Opsætning, Sikkerhedskopieringsrensning"
+ BACKUPS_PROFILES: "Sikkerhedskopieringsprofiler"
+ BACKUPS_COUNT: "Antal Sikkerhedskopier"
+ BACKUPS_PROFILES_COUNT: "Antal Profiler"
+ BACKUPS_TOTAL_SIZE: "Forbrugt lagerplads"
+ BACKUPS_NEWEST: "Seneste sikkerhedskopi"
+ BACKUPS_OLDEST: "Ældste sikkerhedskopi"
+ BACKUPS_PURGE: "Rense"
+ BACKUPS_NOT_GENERATED: "Ingen sikkerhedskopier oprettet endnu..."
+ BACKUPS_PURGE_NUMBER: "Benytter %s af %s sikkerhedskopipladser"
+ BACKUPS_PURGE_TIME: "%s dages sikkerhedskopiering resterer"
+ BACKUPS_PURGE_SPACE: "Benytter %s af %s"
+ BACKUP_DELETED: "Sikkerhedskopi blev slettet"
+ BACKUP_NOT_FOUND: "Sikkerhedskopi ikke fundet"
+ BACKUP_DATE: "Sikkerhedskopieringsdato"
+ STATISTICS: "Statistikker"
+ TODAY: "I dag"
+ WEEK: "Uge"
+ MONTH: "Måned"
+ LATEST_PAGE_UPDATES: "Seneste Sideopdateringer"
+ MAINTENANCE: "Vedligeholdelse"
+ UPDATED: "Opdateret"
+ MON: "Man"
+ TUE: "Tirs"
+ WED: "Ons"
+ THU: "Tor"
+ FRI: "Fre"
+ SAT: "Lør"
+ SUN: "Søn"
+ COPY: "Kopiér"
+ EDIT: "Redigér"
+ CREATE: "Opret"
+ GRAV_ADMIN: "Grav Admin"
+ GRAV_OFFICIAL_PLUGIN: "Grav Officielt Plugin"
+ GRAV_OFFICIAL_THEME: "Grav Officielt Tema"
+ PLUGIN_SYMBOLICALLY_LINKED: "Dette plugin er symbolsk linket. Ændringer opdages ikke."
+ THEME_SYMBOLICALLY_LINKED: "Dette tema er symbolsk linket. Ændringer opdaget ikke"
+ REMOVE_PLUGIN: "Fjern Plugin"
+ INSTALL_PLUGIN: "Installér Plugin"
+ AVAILABLE: "Tilgængelig"
+ INSTALLED: "Installeret"
+ INSTALL: "Installér"
+ ACTIVE_THEME: "Aktivt tema"
+ SWITCHING_TO: "Skifte til"
+ SWITCHING_TO_DESCRIPTION: "Der er ingen garanti for, at alle layoutsider understøttes ved skift til andet tema, hvilket potentielt kan forårsage fejl under indlæsning af nævnte sider."
+ SWITCHING_TO_CONFIRMATION: "Vil du forsætte og skifte til temaet"
+ CREATE_NEW_USER: "Opret ny bruger"
+ REMOVE_USER: "Fjern bruger"
+ ACCESS_DENIED: "Adgang nægtet"
+ ACCOUNT_NOT_ADMIN: "din konto har ikke administratorrettigheder"
+ PHP_INFO: "PHP-info"
+ INSTALLER: "Installation"
+ AVAILABLE_THEMES: "Tilgængelige temaer"
+ AVAILABLE_PLUGINS: "Tilgængelige plugins"
+ INSTALLED_THEMES: "Installerede temaer"
+ INSTALLED_PLUGINS: "Installerede plugins"
+ BROWSE_ERROR_LOGS: "Gennemse Fejl-logs"
+ SITE: "Site"
+ INFO: "Info"
+ SYSTEM: "System"
+ USER: "Bruger"
+ ADD_ACCOUNT: "Tilføj konto"
+ SWITCH_LANGUAGE: "Skift Sprog"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Plugin aktiveret"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Plugin deaktiveret"
+ SUCCESSFULLY_CHANGED_THEME: "Standardtema ændret"
+ INSTALLATION_FAILED: "Installation mislykkedes"
+ INSTALLATION_SUCCESSFUL: "Installation fuldført"
+ UNINSTALL_FAILED: "Afinstallation mislykkedes"
+ UNINSTALL_SUCCESSFUL: "Afinstallation fuldført"
+ SUCCESSFULLY_SAVED: "Gemt"
+ SUCCESSFULLY_COPIED: "Kopieret"
+ REORDERING_WAS_SUCCESSFUL: "Rækkefølge ændret"
+ SUCCESSFULLY_DELETED: "Slettet"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Sprog skiftet"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Du har ikke tilstrækkelige tilladelser til opgaven"
+ CACHE_CLEARED: "Cache ryddet"
+ METHOD: "Metode"
+ ERROR_CLEARING_CACHE: "Fejl under sletning af cache"
+ AN_ERROR_OCCURRED: "Der opstod en fejl"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Din backup er klar til download"
+ DOWNLOAD_BACKUP: "Download backup"
+ PAGES_FILTERED: "Filtrerede sider"
+ NO_PAGE_FOUND: "Ingen side fundet"
+ INVALID_PARAMETERS: "Ugyldige parametre"
+ NO_FILES_SENT: "Ingen filer sendt"
+ EXCEEDED_FILESIZE_LIMIT: "Størrelsesbegrænsning for PHP-configurationsfil overskredet"
+ UNKNOWN_ERRORS: "Ukendt fejl"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Størrelsesbegrænsning for Grav-configurationsfil overskredet"
+ UNSUPPORTED_FILE_TYPE: "Ikke-understøttet filtype"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Kunne ikke flytte den uploadede fil."
+ FILE_UPLOADED_SUCCESSFULLY: "Fil-upload lykkedes"
+ FILE_DELETED: "Filen blev slettet"
+ FILE_COULD_NOT_BE_DELETED: "Filen kunne ikke slettes"
+ FILE_NOT_FOUND: "Filen blev ikke fundet"
+ NO_FILE_FOUND: "Ingen fil blev fundet"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav blev opdateret til"
+ GRAV_UPDATE_FAILED: "Opdatering af Grav mislykkedes"
+ EVERYTHING_UPDATED: "Alt opdateret"
+ UPDATES_FAILED: "Opdateringer mislykkedes"
+ AVATAR_BY: "Avatar af"
+ AVATAR_UPLOAD_OWN: "Eller upload din egen..."
+ LAST_BACKUP: "Seneste backup"
+ FULL_NAME: "Fulde navn"
+ USERNAME: "Brugernavn"
+ EMAIL: "E-mail"
+ USERNAME_EMAIL: "Brugernavn eller e-mail"
+ PASSWORD: "Adgangskode"
+ PASSWORD_CONFIRM: "Bekræft adgangskode"
+ TITLE: "Titel"
+ LANGUAGE: "Sprog"
+ ACCOUNT: "Konto"
+ EMAIL_VALIDATION_MESSAGE: "Skal være en gyldig e-mail"
+ PASSWORD_VALIDATION_MESSAGE: "Adgangskode skal indeholde minimum ét ciffer, én versal, én minuskel samt udgøres af minimum otte tegn"
+ LANGUAGE_HELP: "Opsæt foretrukne sprog"
+ MEDIA: "Medier"
+ DEFAULTS: "Standarder"
+ SITE_TITLE: "Sitets Titel"
+ SITE_TITLE_PLACEHOLDER: "Overordnet webstedstitel"
+ SITE_TITLE_HELP: "Sitets standard-titel. Bruges ofte i temaer"
+ SITE_DEFAULT_LANG: "Standardsprog"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Standardsprog benyttet af temaets -etiket"
+ SITE_DEFAULT_LANG_HELP: "Standardsprog benyttet af temaets -etiket"
+ DEFAULT_AUTHOR: "Standard-forfatter"
+ DEFAULT_AUTHOR_HELP: "Standard-forfatternavn. Bruges ofte i temaer og sideindhold"
+ DEFAULT_EMAIL: "Standard-e-mail"
+ DEFAULT_EMAIL_HELP: "En standard-e-mail, som der kan henvises til i temaer og på sider"
+ TAXONOMY_TYPES: "Taksonomi-typer"
+ TAXONOMY_TYPES_HELP: "Taksonomi-typer skal defineres her hvis du vil bruge dem på sider"
+ PAGE_SUMMARY: "Side-resume"
+ ENABLED: "Aktiveret"
+ ENABLED_HELP: "Aktiver side-resume (resumeet returnerer det samme som sideindholdet)"
+ 'YES': "Ja"
+ 'NO': "Nej"
+ SUMMARY_SIZE: "Resume-længde"
+ SUMMARY_SIZE_HELP: "Det antal af en sides karakter, som skal bruges som resume"
+ FORMAT: "Format"
+ FORMAT_HELP: "kort = brug første forekomst af afgrænser eller størrelse; lang = afgrænser ignoreres"
+ SHORT: "Kort"
+ LONG: "Lang"
+ DELIMITER: "Afgrænser"
+ DELIMITER_HELP: "Resume-afgrænseren (standard: '===')"
+ METADATA: "Metadata"
+ METADATA_HELP: "Standard-metadataværdier, som vises på alle sider, medmindre de tilsidesættes af siden"
+ NAME: "Navn"
+ CONTENT: "Indhold"
+ SIZE: "Størrelse"
+ ACTION: "Handling"
+ REDIRECTS_AND_ROUTES: "Omdirigeringer og ruter"
+ CUSTOM_REDIRECTS: "Brugerdefinerede omdirigeringer"
+ CUSTOM_REDIRECTS_HELP: "ruter til omdirigering til andre sider. Gængse regex-erstatninger kan benyttes"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/dit/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/din/omdirigering"
+ CUSTOM_ROUTES: "Tilpassede ruter"
+ CUSTOM_ROUTES_HELP: "ruter til alias til andre sider. Gængs regex-erstatning er gyldig"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/dit/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/din/rute"
+ FILE_STREAMS: "Filstrømme"
+ DEFAULT: "Standard"
+ PAGE_MEDIA: "Sidemedier"
+ OPTIONS: "Indstillinger"
+ PUBLISHED: "Udgivet"
+ PUBLISHED_HELP: "Som standard offentliggøres en side medmindre du udtrykkeligt sætter published: false eller via en fremtidig publish_date eller fortidig unpublish_date"
+ DATE: "Dato"
+ DATE_HELP: "Dato-variablen tillader dig at sætte en specifik dato associeret med denne side."
+ PUBLISHED_DATE: "Udgivelsesdato"
+ PUBLISHED_DATE_HELP: "Kan give en dato for automatisk at udløse udgivelse."
+ UNPUBLISHED_DATE: "Afpubliceringsdato"
+ UNPUBLISHED_DATE_HELP: "Opsætning en dato for automatisk at afpublisere."
+ ROBOTS: "Robotter"
+ TAXONOMIES: "Taksonomier"
+ TAXONOMY: "Taksonomi"
+ ADVANCED: "Avanceret"
+ SETTINGS: "Indstillinger"
+ FOLDER_NUMERIC_PREFIX: "Numerisk Mappepræfiks"
+ FOLDER_NUMERIC_PREFIX_HELP: "Numerisk præfiks, der muliggør manuel sortering og indebærer synlighed"
+ FOLDER_NAME: "Mappenavn"
+ FOLDER_NAME_HELP: "Mappenavnet, som skal gemmes i filsystemet, for denne side"
+ PARENT: "Overordnet"
+ DEFAULT_OPTION_ROOT: "- Rod -"
+ DEFAULT_OPTION_SELECT: "- Vælg -"
+ DISPLAY_TEMPLATE: "Visningsskabelon"
+ DISPLAY_TEMPLATE_HELP: "Sidetypen, som oversættes til den twig-template, som viser siden"
+ BODY_CLASSES: "Body-klasser"
+ ORDERING: "Soreting"
+ PAGE_ORDER: "Side-sortering"
+ OVERRIDES: "Tilsidesættelser"
+ MENU: "Menu"
+ MENU_HELP: "Den streng, som skal anvendes i en menu. Hvis ingen angives, bruges Title."
+ SLUG: "Slug"
+ SLUG_HELP: "Slug-variablen bruges til specifikt at angive sidens andel af URL'en"
+ SLUG_VALIDATE_MESSAGE: "En slug må kun indeholde små alfanumeriske karakterer og bindestreger"
+ PROCESS: "Processer"
+ PROCESS_HELP: "Kontroller hvordan sider processeres. Kan angives per side i stedet for globalt"
+ DEFAULT_CHILD_TYPE: "Standardtitel for underordnede"
+ USE_GLOBAL: "Anvend Global"
+ ROUTABLE: "Kan routes"
+ ROUTABLE_HELP: "Om denne side kan tilgås via en URL"
+ CACHING: "Caching"
+ VISIBLE: "Synlig"
+ VISIBLE_HELP: "Afgør om en side er synlig i navigationen."
+ DISABLED: "Deaktiveret"
+ ITEMS: "Elementer"
+ ORDER_BY: "Sorter Efter"
+ ORDER: "Sortering"
+ FOLDER: "Mappe"
+ ASCENDING: "Stigende"
+ DESCENDING: "Faldende"
+ ADD_MODULAR_CONTENT: "Tilføj Modulært Indhold"
+ PAGE_TITLE: "Sidetitel"
+ PAGE_TITLE_HELP: "Sidens titel"
+ PAGE: "Side"
+ MODULAR_TEMPLATE: "Modulær Skabelon"
+ FRONTMATTER: "Forskræp"
+ FILENAME: "Filnavn"
+ PARENT_PAGE: "Overordnet Side"
+ HOME_PAGE: "Hjem-side"
+ HOME_PAGE_HELP: "Siden som Grav bruger som standard-destinationsside"
+ DEFAULT_THEME: "Standardtema"
+ DEFAULT_THEME_HELP: "Gravs standardtema (som udgangspunkt Antimatter)"
+ TIMEZONE: "Tidszone"
+ TIMEZONE_HELP: "Tilsidesæt serverens standardtidszone"
+ SHORT_DATE_FORMAT: "Kort datoformat-visning"
+ SHORT_DATE_FORMAT_HELP: "Angiv det korte datoformat, der kan bruges af temaer"
+ LONG_DATE_FORMAT: "Lang datoformat-visning"
+ LONG_DATE_FORMAT_HELP: "Angiv det lange datoformat, der kan bruges af temaer"
+ DEFAULT_ORDERING: "Standardsortering"
+ DEFAULT_ORDERING_HELP: "Sider i en liste bliver vist i denne rækkefølge, medmindre den tilsidesættes"
+ DEFAULT_ORDERING_DEFAULT: "Standard - baseret på mappenavn"
+ DEFAULT_ORDERING_FOLDER: "Mappe - baseret på mappenavn uden præfiks"
+ DEFAULT_ORDERING_TITLE: "Titel - baseret på titel-felt i headeren"
+ DEFAULT_ORDERING_DATE: "Dato - baseret på dato-felt i headeren"
+ DEFAULT_ORDER_DIRECTION: "Standard-sorteringsretning"
+ DEFAULT_ORDER_DIRECTION_HELP: "Den retning, som siderne i en liste skal sorteres i"
+ DEFAULT_PAGE_COUNT: "Standard-sideantal"
+ DEFAULT_PAGE_COUNT_HELP: "Standard for maksimalt antal sider i en liste"
+ DATE_BASED_PUBLISHING: "Datobaseret publicering"
+ DATE_BASED_PUBLISHING_HELP: "(Af)publicer automatisk indhold baseret på dets dato"
+ EVENTS: "Begivenheder"
+ EVENTS_HELP: "Aktiver og deaktiver specifikke begivenheder. Deaktivering af disse kan ødelægge plugins"
+ REDIRECT_DEFAULT_ROUTE: "Standardrute for omdirigeringer"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Omdiriger automatisk til en sides standardrute"
+ LANGUAGES: "Sprog"
+ SUPPORTED: "Understøttet"
+ SUPPORTED_HELP: "Kommasepareret liste af sprogkoder på hver to bogstaver (f.eks. 'en,da,fr')"
+ TRANSLATIONS_ENABLED: "Oversættelser aktiveret"
+ TRANSLATIONS_ENABLED_HELP: "Understøt oversættelser i Grav, plugins og udvidelser"
+ TRANSLATIONS_FALLBACK: "Fallback til oversættelser"
+ TRANSLATIONS_FALLBACK_HELP: "Fallback gennem understøttede oversættelser hvis det aktive sprog ikke findes"
+ ACTIVE_LANGUAGE_IN_SESSION: "Aktivt sprog i session"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Gem det aktive sprog i sessionen"
+ HTTP_HEADERS: "HTTP-headere"
+ EXPIRES: "Udløber"
+ EXPIRES_HELP: "Indstiller expires-headeren. Værdien angives i sekunder."
+ CACHE_CONTROL: "HTTP Cache-kontrol"
+ CACHE_CONTROL_HELP: "Angive en gyldig cache-kontrolværdi såsom 'no-cache, no-butik, must-revalidate'"
+ LAST_MODIFIED: "Senest ændret"
+ LAST_MODIFIED_HELP: "Angiver \"last modified\"-headeren, der kan hjælpe med at optimere proxy og browsercachelagring"
+ ETAG: "ETag"
+ ETAG_HELP: "Indstiller etag-header for at hjælpe med at identificere, hvornår en side er blevet ændret"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Angiver \"Vary: Accept Encoding\"-headeren, der hjælper med proxy og CDN-cachelagring"
+ MARKDOWN: "Markdown"
+ MARKDOWN_EXTRA: "Markdown Extra"
+ MARKDOWN_EXTRA_HELP: "Aktiver standardunderstøttelse for Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ MARKDOWN_EXTRA_ESCAPE_FENCES: "Escape HTML-elementer i Markdown Extra-låger"
+ MARKDOWN_EXTRA_ESCAPE_FENCES_HELP: "Escaper HTML-elementer i Markdown Extra-låger"
+ AUTO_LINE_BREAKS: "Automatiske linjeskift"
+ AUTO_LINE_BREAKS_HELP: "Aktiver understøttelse af automatiske linjeskift i markdown"
+ AUTO_URL_LINKS: "Automatiske URL-links"
+ AUTO_URL_LINKS_HELP: "Aktiver automatisk konvertering af URL'er til HTML-hyperlinks"
+ ESCAPE_MARKUP: "Escape markup"
+ ESCAPE_MARKUP_HELP: "Escape markup-tags til HMTL-entiteter"
+ CACHING_HELP: "Global TÆND/SLUK-kontakt til aktivering/deaktivering af Grav-caching"
+ CACHE_CHECK_METHOD: "Metode til cache-tjek"
+ CACHE_CHECK_METHOD_HELP: "Angiv den metode, som Grav skal bruge til at tjekke om en side er blevet ændret."
+ CACHE_DRIVER: "Cache-driver"
+ CACHE_DRIVER_HELP: "Angiv hvilken cache-driver, som Grav skal bruge. 'Auto-detekter' forsøger at finde den bedste for dig"
+ CACHE_PREFIX: "Cache-præfiks"
+ CACHE_PREFIX_HELP: "Identifikator for en del af Grav-nøglen. Ændr kun hvis du ved, hvad du laver."
+ CACHE_PREFIX_PLACEHOLDER: "Afledt af base-URL'en (tilsidesæt ved at indtaste en arbitrær streng)"
+ CACHE_PURGE_JOB: "Kør planlagt rensningsjob"
+ CACHE_PURGE_JOB_HELP: "Du kan med planlæggeren periodisk rydde ud gamle Doctrine-filcachemapper med dette job"
+ CACHE_CLEAR_JOB: "Kør planlagt rydningsjob"
+ CACHE_CLEAR_JOB_HELP: "Du kan med planlæggeren periodisk rydde Grav-cachen"
+ CACHE_JOB_TYPE: "Cachejobtype"
+ CACHE_JOB_TYPE_HELP: "Ryd cachen enten for 'standard' eller 'alle' mapper"
+ CACHE_PURGE: "Rens gammel cache"
+ LIFETIME: "Livstid"
+ LIFETIME_HELP: "Angiver cache-livstiden i sekunder. 0 = uendelig"
+ GZIP_COMPRESSION: "Gzip-kompression"
+ GZIP_COMPRESSION_HELP: "Aktiver GZip-kompression af Grav-siden for øget ydeevne."
+ TWIG_TEMPLATING: "Twig-skabeloner"
+ TWIG_CACHING: "Twig-cache"
+ TWIG_CACHING_HELP: "Indstil Twigs cache-mekanisme. Lad feltet være aktiveret for at opnå den bedste ydeevne."
+ TWIG_DEBUG: "Twig-debug"
+ TWIG_DEBUG_HELP: "Giver mulighed for ikke at indlæse Twigs debugger-udvidelse"
+ DETECT_CHANGES: "Registrer ændringer"
+ DETECT_CHANGES_HELP: "Twig genkompilerer automatisk sin cache hvis den registrerer ændringer i Twig-skabeloner"
+ AUTOESCAPE_VARIABLES: "Escape automatisk variabler"
+ AUTOESCAPE_VARIABLES_HELP: "Escaper automatisk alle variabler. Dette vil højst sandsynligt ødelægge dit site"
+ ASSETS: "Aktiver"
+ CSS_PIPELINE: "CSS-pipeline"
+ CSS_PIPELINE_HELP: "CSS-pipelinen forener flere CSS-ressourcer i én fil"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Inkluder eksterne enheder i CSS pipelinen"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Eksterne URL'er har, til tider, relative fil referencer, og bør ikke blive pipelinet"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "Pre-rendering af CSS pipelinen"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Render CSS pipelinen før alle andre CSS referencer, der ikke er inkluderet"
+ CSS_MINIFY: "CSS-minificering"
+ CSS_MINIFY_HELP: "Minificer CSS ved brug af pipeline"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "Tilsidesæt Windows' CSS-minificering"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Tilsidesættelse af minificering på Windows-platforme. Standard = Fra pga. ThreadStackSize"
+ CSS_REWRITE: "CSS-omskrivning"
+ CSS_REWRITE_HELP: "Omskriv relative CSS-URL'er under pipelining"
+ JAVASCRIPT_PIPELINE: "Javascript-pipeline"
+ JAVASCRIPT_PIPELINE_HELP: "JS-pipelinen samler flere JS-ressourcer i én fil"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Inkluder eksterne enheder i JS pipelinen"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Eksterne URL'er har, til tider, relative fil referencer, og bør ikke blive pipelinet"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Pre-rendering af JS pipelinen"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Render JS pipelinen før alle andre JS referencer, der ikke er inkluderet"
+ JAVASCRIPT_MINIFY: "Javascript-minificering"
+ JAVASCRIPT_MINIFY_HELP: "Minificer JS ved brug af pipeline"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Aktiver tidsstempler på aktiver"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Aktiver tidsstempler på aktiver"
+ COLLECTIONS: "Kollektioner"
+ ERROR_HANDLER: "Fejlbehandler"
+ DISPLAY_ERRORS: "Visningsfejl"
+ DISPLAY_ERRORS_HELP: "Vis fuld backtrace på fejlside"
+ LOG_ERRORS: "Log fejl"
+ LOG_ERRORS_HELP: "Log fejl i mappen /logs"
+ LOG_HANDLER: "Loghåndtering"
+ LOG_HANDLER_HELP: "Hvor loggerne skal placeres"
+ SYSLOG_FACILITY: "Syslog-facilitet"
+ SYSLOG_FACILITY_HELP: "Syslog-facilitet til output"
+ DEBUGGER: "Fejlfinder"
+ DEBUGGER_HELP: "Aktivér Grav-fejlfinder og flg. indstillinger"
+ DEBUG_TWIG: "Fejlfind i Twig"
+ DEBUG_TWIG_HELP: "Aktivér fejlfinding i Twig-skabeloner"
+ SHUTDOWN_CLOSE_CONNECTION: "Luk forbindelse ved nedlukning"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Luk forbindelsen inden onShutdown()-kald. Slået fra ved debugging"
+ DEFAULT_IMAGE_QUALITY: "Standard-billedkvalitet"
+ DEFAULT_IMAGE_QUALITY_HELP: "Standardbilledkvalitet til brug ved cachelagring af billeder (85%)"
+ CACHE_ALL: "Cache alle billeder"
+ CACHE_ALL_HELP: "Kør alle billeder - også ikke-manipulerede - gennem Gravs cache-system"
+ IMAGES_DEBUG: "Vandmærke ved debugging af billeder"
+ IMAGES_DEBUG_HELP: "Vis overlejring på billeder. Overlejringen indikerer billedets pixel-dybde til brug ved f.eks. visning på retina-skærme"
+ IMAGES_SEOFRIENDLY: "SEO-venlige billednavne"
+ IMAGES_SEOFRIENDLY_HELP: "Når aktiveret, vises billednavnet først, dernæst en mindre hash, der afspejler udførte operationer"
+ UPLOAD_LIMIT: "Fil-upload grænse"
+ UPLOAD_LIMIT_HELP: "Angiv maks. filstørrelse i bytes (0 = ubegrænset)"
+ ENABLE_MEDIA_TIMESTAMP: "Aktivér tidsstempler på medier"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Føjer et tidsstempel baseret på seneste ændringsdato til hvert medieemne"
+ SESSION: "Session"
+ SESSION_ENABLED_HELP: "Aktivér sessionsunderstøttelse i Grav"
+ TIMEOUT: "Timeout"
+ TIMEOUT_HELP: "Angiver sessionstimeout i sekunder"
+ SESSION_NAME_HELP: "En identifikator, der benytts til at danne navnet på sessionens-cookie'en"
+ SESSION_UNIQUENESS: "Unik streng"
+ SESSION_UNIQUENESS_HELP: "MD5-hash af Gravs rodsti, dvs. `GRAV_ROOT` (standard) eller én baseret på den tilfældige` security.salt`-streng."
+ ABSOLUTE_URLS: "Absolutte URL'er"
+ ABSOLUTE_URLS_HELP: "Absolutte eller relative URL'er til 'base_url'"
+ PARAMETER_SEPARATOR: "Parameterseparator"
+ PARAMETER_SEPARATOR_HELP: "Separator til godkendte parametre, som kan ændres til Apache på Windows"
+ TASK_COMPLETED: "Opgave fuldført"
+ EVERYTHING_UP_TO_DATE: "Alt er opdateret"
+ UPDATES_ARE_AVAILABLE: "opdatering(-er) er tilgængelige"
+ IS_AVAILABLE_FOR_UPDATE: "er tilgængelig for opdatering"
+ IS_NOW_AVAILABLE: "er nu tilgængelig"
+ CURRENT: "Aktuel"
+ UPDATE_GRAV_NOW: "Opdater Grav nu"
+ GRAV_SYMBOLICALLY_LINKED: "Grav er forbundet med symbolsk link. Opgraderingen vil ikke være tilgængelig"
+ UPDATING_PLEASE_WAIT: "Opdatering... Vent venligst; downloader"
+ OF_THIS: "af denne"
+ OF_YOUR: "af dit"
+ HAVE_AN_UPDATE_AVAILABLE: "har en tilgængelig opdatering"
+ SAVE_AS: "Gem som"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Sikker på, at du vil slette denne side og alle dens undersider? Er siden oversat til andre sprog, vil disse oversættelser blive bevaret og skal slettes separat. I modsat fald bliver sidemappen og dens undersider slettet. Denne handling er irreversibel."
+ AND: "og"
+ UPDATE_AVAILABLE: "Opdatering tilgængelig"
+ METADATA_KEY: "Nøgle (f.eks. 'Nøgleord')"
+ METADATA_VALUE: "Værdi (f.eks. 'Blog, Grav')"
+ USERNAME_HELP: "Brugernavnet skal være mellem 3 og 16 karakterer og kan indeholde små bogstaver, tal, understreger og bindestreger. Store bogstaver, mellemrum og specielle karakter er ikke tilladt"
+ FULLY_UPDATED: "Fuldt opdateret"
+ SAVE_LOCATION: "Gemmeplacering"
+ PAGE_FILE: "Sideskabelon"
+ PAGE_FILE_HELP: "Sideskabelon filnavn, og standard visnings-skabelonen for denne side"
+ NO_USER_ACCOUNTS: "Der blev ikke fundet nogen brugerkonto; opret venligst en først..."
+ NO_USER_EXISTS: "Ingen lokalbruger til denne konto, kan ikke gemme..."
+ REDIRECT_TRAILING_SLASH: "Omdiriger efterstillet skråstreg"
+ REDIRECT_TRAILING_SLASH_HELP: "Udfør en 301-omdirigering i stedet for gennemsigtigt at håndtere URI'er med efterstillet skråstreg."
+ DEFAULT_DATE_FORMAT: "Sidedatoformat"
+ DEFAULT_DATE_FORMAT_HELP: "Det side-datoformat, som Grav skal bruge. Som standard forsøger Grav at gætte dit datoformat, men du kan specificeret et format med PHP's dato-syntaks (f.eks. Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Gæt automatisk"
+ IGNORE_FILES: "Ignorer filer"
+ IGNORE_FILES_HELP: "Specifikke filer, som skal ignoreres ved behandlingen af sider"
+ IGNORE_FOLDERS: "Ignorer mapper"
+ IGNORE_FOLDERS_HELP: "Specifikke mapper, som skal ignoreres ved behandlingen af sider"
+ HIDE_EMPTY_FOLDERS: "Skjul tomme mapper"
+ HIDE_EMPTY_FOLDERS_HELP: "Har mappen ikke en .md-fil, skal den være skjult i navigationen og være ikke-rootbar"
+ HTTP_ACCEPT_LANGUAGE: "Indstil sprog fra browser"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Du kan forsøge at indstille sproget på basis af browserens \"http_accept_language\"-headertag"
+ OVERRIDE_LOCALE: "Tilsidesæt lokation"
+ OVERRIDE_LOCALE_HELP: "Tilsidesæt PHP's \"locale\"-indstilling baseret på det nuværende sprog"
+ REDIRECT: "Side-omdirigering"
+ REDIRECT_HELP: "Angiv en side-rute eller ekstern URL, som denne side skal omdirigere til, f.eks. \"/en/rute\" eller \"http://etsite.com\""
+ PLUGIN_STATUS: "Plugin-status"
+ INCLUDE_DEFAULT_LANG: "Inkluder standardsproget"
+ INCLUDE_DEFAULT_LANG_HELP: "Dette foranstiller standardsproget i alle standardsprogets URL'er, f.eks. \"/da/blog/mit-indlæg\""
+ PAGES_FALLBACK_ONLY: "Kun tilbagefaldsside"
+ PAGES_FALLBACK_ONLY_HELP: "Kun 'fallback' for at finde sideindhold via understøttede sprog. Standardadfærd er at vise et hvilket som helst tilgængeligt sprog, såfremt det aktivt sprog mangler"
+ ALLOW_URL_TAXONOMY_FILTERS: "URL-taksonomifiltre"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Sidebaserede samlingergør det muligt at filtrere via \"/taxonomy:værdi\"."
+ REDIRECT_DEFAULT_CODE: "Standardomdirigeringskode"
+ REDIRECT_DEFAULT_CODE_HELP: "HTTP-statuskode til brug ved omdirigeringer"
+ IGNORE_HIDDEN: "Ignorer skjulte"
+ IGNORE_HIDDEN_HELP: "Ignorer alle filer og mapper, der begynder med punktum"
+ WRAPPED_SITE: "Indpakket site"
+ WRAPPED_SITE_HELP: "Bruges af temaer/plugins til at bestemme, om Grav er pakket ind i en anden platform"
+ FALLBACK_TYPES: "Tilladte fallback-typer"
+ FALLBACK_TYPES_HELP: "Tilladte filtyper, der kan tilgås med en side-rute. Som standard sat til alle understøttede medietyper."
+ INLINE_TYPES: "Indlejrede fallback-typer"
+ INLINE_TYPES_HELP: "En liste over filtyper, der skal vises indlejret i stedet for hentes"
+ APPEND_URL_EXT: "Tilføj URL-efternavn"
+ APPEND_URL_EXT_HELP: "Tilføjer et brugerdefineret efternavn til sidens webadresse. Bemærk, at dette vil få Grav til at kigge efter \". . twig\"-skabeloner"
+ PAGE_MODES: "Side-tilstande"
+ PAGE_TYPES: "Sidetyper"
+ PAGE_TYPES_HELP: "Bestemmer de sidetyper, som Grav understøtter, og rækkefølgen bestemmer fallback-typen, der skal benyttes, såfremt anmodningen er tvetydig"
+ ACCESS_LEVELS: "Adgangsniveauer"
+ GROUPS: "Grupper"
+ GROUPS_HELP: "Liste af grupper, som brugeren er medlem af"
+ ADMIN_ACCESS: "Admin-adgang"
+ SITE_ACCESS: "Webstedsadgang"
+ INVALID_SECURITY_TOKEN: "Ugyldigt sikkerhedstoken"
+ ACTIVATE: "Aktivér"
+ TWIG_UMASK_FIX: "Afmaskeringsfiks"
+ TWIG_UMASK_FIX_HELP: "Som standard opretter Twig cachede filer som 0755. Dette fiks skifter dette til 0775"
+ CACHE_PERMS: "Cachetilladelser"
+ CACHE_PERMS_HELP: "Standard cachemappetilladelser. Som regel 0755 eller 0775, afhængig af opsætningen"
+ REMOVE_SUCCESSFUL: "Fjernet"
+ REMOVE_FAILED: "Fjernelse mislykkedes"
+ HIDE_HOME_IN_URLS: "Skjul hjem-route i URL'er"
+ HIDE_HOME_IN_URLS_HELP: "Sikrer at standard-routes for enhver side under hjem ikke henviser til hjemsidens normale route"
+ TWIG_FIRST: "Behandl Twig først"
+ TWIG_FIRST_HELP: "Har du aktiveret Twig-sidebehandling, kan du opsætte Twig til at gøre dette før eller efter markdown"
+ SESSION_SECURE: "Sikker"
+ SESSION_SECURE_HELP: "Angiver, om kommunikation med denne cookie skal ske krypteret. ADVARSEL: Brug kun dette på sites, der kører udelukkende på HTTPS"
+ SESSION_HTTPONLY: "Kun HTTP"
+ SESSION_HTTPONLY_HELP: "Hvis denne indstilling er aktiveret bruges cookes kun over HTTP og Javascript-ændringer tillades ikke"
+ REVERSE_PROXY: "Omvendt proxy"
+ REVERSE_PROXY_HELP: "Aktiver denne hvis du er bag en omvendt proxy og har problemer med URL'er, der indeholder ukorrekte porte"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Ugyldigt forskræp; kunne ikke gemme"
+ ADD_FOLDER: "Tilføj mappe"
+ PROXY_URL: "Proxy-URL"
+ PROXY_URL_HELP: "Indtast proxyens host eller IP og port"
+ NOTHING_TO_SAVE: "Intet at gemme"
+ FILE_ERROR_ADD: "Der opstod en fejl under forsøg på at tilføje filen"
+ FILE_ERROR_UPLOAD: "Der opstod en fejl under forsøget på at uploade filer"
+ FILE_UNSUPPORTED: "Ikke-understøttet filtype"
+ ADD_ITEM: "Tilføj element"
+ FILE_TOO_LARGE: "Filen er for stor til at blive uploadet, maksimal tilladt er %s ifølge dine PHP-indstillinger. Øge din 'post_max_size' PHP indstilling"
+ INSTALLING: "Installerer"
+ LOADING: "Indlæser.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "De følgende afhængigheder skal være opfyldt først:"
+ ERROR_INSTALLING_PACKAGES: "Fejl under installering af af pakke(r)"
+ INSTALLING_DEPENDENCIES: "Installerer afhængigheder..."
+ INSTALLING_PACKAGES: "Installerer pakke(r).."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Pakke(r) installeret korrekt."
+ READY_TO_INSTALL_PACKAGES: "Klar til at installere pakke(r)"
+ PACKAGES_NOT_INSTALLED: "Pakker ikke installeret"
+ PACKAGES_NEED_UPDATE: "Pakker allerede installeret, men forældede"
+ PACKAGES_SUGGESTED_UPDATE: "Pakker allerede installeret, og versionen er i orden, men vil blive opdateret for at holde dig up to date"
+ REMOVE_THE: "Fjern %s"
+ CONFIRM_REMOVAL: "Er du sikker på, at du ønsker at slette %s?"
+ REMOVED_SUCCESSFULLY: "%s fjernet korrekt"
+ ERROR_REMOVING_THE: "Fejl i forsøget på at fjerne %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "%s krævede følgende afhængigheder, som ikke er påkrævet af andre pakker. Hvis du ikke bruger dem, kan du fjerne dem her."
+ READY_TO_UPDATE_PACKAGES: "Klar til at opdatere pakke(r)"
+ ERROR_UPDATING_PACKAGES: "Fejl under opdatering af pakke(r)"
+ UPDATING_PACKAGES: "Opdaterer pakke(r).."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Pakke(r) opdateret korrekt."
+ UPDATING: "Updaterer"
+ GPM_RELEASES: "GPM udgivelser"
+ GPM_RELEASES_HELP: "Vælg 'Test' for at installere beta eller testversioner"
+ GPM_METHOD: "Fjernhentningsmetode"
+ GPM_METHOD_HELP: "Når indstillet til Auto afgør Grav, om fopen er tilgængelig og benytter den, ellers benyttes cURL. For at gennemtvinge brugen af den ene eller den, skift indstillingen."
+ GPM_VERIFY_PEER: "Fjerngodkend Peer (SSL)"
+ GPM_VERIFY_PEER_HELP: "Visse udbydere synes at svigte med at verificere getgrav.org SSL-certifikat, hvilket betyder, at GPM ikke vil fungere. Er dette tilfældet for dig, vil deaktivering af denne indstilling måske hjælpe"
+ AUTO: "Auto"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stabil"
+ TESTING: "Tester"
+ FRONTMATTER_PROCESS_TWIG: "Processer frontmatter Twig"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Når aktiveret kan du bruge Twig's konfigurationsvariabler i sidens frontmatter"
+ FRONTMATTER_IGNORE_FIELDS: "Ignorer frontmatter felter"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Visse frontmatter felter kan indeholde Twig, men skal ikke behandles. Så som \"formularer\""
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Pakke %s installeret"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Overordnet rækkefølge-indstilling, orden de-aktiveret"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Siden er ikke synlig, orden de-aktiveret"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "Sortering af rækkefølge via administrationen er ikke tilgængelig, fordi der er mere end 200 under-elementer"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Sidebestilling er deaktiveret for denne side, da Numerisk Mappenpræfiks ikke er aktiveret"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "OBS: Du kan ikke tilføje medie-filer før du har gemt siden. Klik på \"Gem\" øverst"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "OBS: Siden skal gemmes før du kan tilføje filer til den."
+ DROP_FILES_HERE_TO_UPLOAD: "Smid dine filer her eller, Klik i dette område "
+ INSERT: "Indsæt"
+ UNDO: "Fortryd"
+ REDO: "Gendan"
+ HEADERS: "Overskrifter"
+ BOLD: "Fed"
+ ITALIC: "Kursiv"
+ STRIKETHROUGH: "Gennemstreget"
+ SUMMARY_DELIMITER: "Sammendragsafgrænser"
+ LINK: "Link"
+ IMAGE: "Billede"
+ BLOCKQUOTE: "Citatblok"
+ UNORDERED_LIST: "Usorteret liste"
+ ORDERED_LIST: "Sorteret liste"
+ EDITOR: "Editor"
+ PREVIEW: "Eksempelvisning"
+ FULLSCREEN: "Fuld skærm"
+ MODULAR: "Modulær"
+ MODULAR_SETUP: "Modulopsætning"
+ NON_ROUTABLE: "Ikke tilgængelig"
+ NON_MODULAR: "Ikke modulær"
+ NON_VISIBLE: "Ikke-synlig"
+ NON_PUBLISHED: "Ikke udgivet"
+ CHARACTERS: "tegn"
+ PUBLISHING: "Udgivelse"
+ NOTIFICATIONS: "Notifikationer"
+ MEDIA_TYPES: "Medietyper"
+ IMAGE_OPTIONS: "Billedmuligheder"
+ MIME_TYPE: "MIME-type"
+ THUMB: "Miniature"
+ TYPE: "Type"
+ FILE_EXTENSION: "Filendelse"
+ LEGEND: "Sideoversigt"
+ MEMCACHE_SERVER: "Memcache-server"
+ MEMCACHE_SERVER_HELP: "Memcache-serveradressen"
+ MEMCACHE_PORT: "Memcache-port"
+ MEMCACHE_PORT_HELP: "Memcache-serverporten"
+ MEMCACHED_SERVER: "Memcachet-server"
+ MEMCACHED_SERVER_HELP: "Memcachet-serveradresse"
+ MEMCACHED_PORT: "Memcache-port"
+ MEMCACHED_PORT_HELP: "Memcachet-serverport"
+ REDIS_SERVER: "Redis-server"
+ REDIS_SERVER_HELP: "Redis-serveradressen"
+ REDIS_PORT: "Redis-port"
+ REDIS_PORT_HELP: "Redis-serverport"
+ REDIS_PASSWORD: "Redis-adgangskode/hemmelighed"
+ ALL: "Alle"
+ FROM: "fra"
+ TO: "til"
+ RELEASE_DATE: "Udgivelsesdato"
+ SORT_BY: "Sortér efter"
+ RESOURCE_FILTER: "Filtrér..."
+ FORCE_SSL: "Gennemtving SSL"
+ FORCE_SSL_HELP: "Gennemtving SSL globalt, hvis aktiveret, når webstedet nås via HTTP, Grav sender en omdirigering til HTTPS-siden"
+ NEWS_FEED: "Nyhedsfeed"
+ EXTERNAL_URL: "Ekstern URL"
+ SESSION_PATH: "Sessionssti"
+ SESSION_PATH_HELP: "Benyt kun, hvis du vælger en tilpasset basis-URL (du omskrive webstedsdomæne/undermappe)"
+ CUSTOM_BASE_URL: "Tilpasset basis-URL"
+ CUSTOM_BASE_URL_HELP: "Benyt, hvis du ønsker at omskrive webstedsdomæne eller benytte en anden undermappe end den, Grav benytter. Eksempel: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'Kan ikke benytte "%s" uden for sider.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Kan ikke uploade fil %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'Kan ikke flytte fil %s til "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'Annullér upload'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Sikker på, at du vil annullere denne upload?'
+ DROPZONE_DEFAULT_MESSAGE: 'Slip dine filer hér eller, klik i dette område '
+ DROPZONE_FALLBACK_MESSAGE: 'Din browser understøtter ikke træk-og-slip fil-uploads.'
+ DROPZONE_FALLBACK_TEXT: 'Benyt venligst formularen nedenfor for alternativt at uploade filer ''som i gamle dage''.'
+ DROPZONE_FILE_TOO_BIG: 'Filen er for stor ({{filesize}}MiB). Maks. filstørrelse: {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "Filer af denne type kan ikke uploades."
+ DROPZONE_MAX_FILES_EXCEEDED: "Der kan ikke uploades flere filer."
+ DROPZONE_REMOVE_FILE: "Fjern fil"
+ DROPZONE_RESPONSE_ERROR: "Serveren svarede med koden {{statusCode}}."
+ PREMIUM_PRODUCT: "Premium"
+ ADMIN_CACHING: "Aktivere Admin-cachelagring"
+ ADMIN_CACHING_HELP: "Cachelagring i admin kan styres uafhængigt af frontend-webstedet"
+ DESTINATION_NOT_SPECIFIED: "Destination er ikke angivet"
+ UPLOAD_ERR_NO_TMP_DIR: "Mangler en midlertidige mappe"
+ SESSION_SPLIT: "Sessionsopdeling"
+ SESSION_SPLIT_HELP: "Opdel sessioner uafhængigt mellem websted og andre plugins (f.eks. Admin)"
+ ERROR_FULL_BACKTRACE: "Vis detaljeret Backtrace fejlinfo"
+ ERROR_SIMPLE: "Vis simpel fejlinfo"
+ ERROR_SYSTEM: "Systemfejl"
+ IMAGES_AUTO_FIX_ORIENTATION: "Korrigér orientering automatisk"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Korrigér automatisk billedretningen baseret på Exif-dataene"
+ REDIS_SOCKET: "Redis socket"
+ REDIS_SOCKET_HELP: "Redis socket"
+ NOT_SET: "Ikke indstillet"
+ PERMISSIONS: "Rettigheder"
+ NEVER_CACHE_TWIG: "Mellemlagre aldrig Twig"
+ NEVER_CACHE_TWIG_HELP: "Cache kun indhold og behandle Twig for hver side. Ignorerer twig_first-indstilling."
+ ALLOW_WEBSERVER_GZIP: "Tillad WebServer Gzip"
+ ALLOW_WEBSERVER_GZIP_HELP: "Deaktiveret som standard. Når aktiveret, vil WebServer-konfigureret Gzip-/Deflate-kompression fungere, men HTTP-forbindelsen vil ikke blive lukket før onShutDown() begivenheden forårsager langsommere sideindlæsning"
+ OFFLINE_WARNING: "Kan ikke etablere forbindelse til GPM"
+ CLEAR_IMAGES_BY_DEFAULT: "Ryd som standard billede-cachen"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "Som standard ryddes behandlede billeder for alle caches. Dette kan deaktiveres"
+ CLI_COMPATIBILITY: "CLI-kompatibilitet"
+ CLI_COMPATIBILITY_HELP: "Sikrer, at kun ikke-flygtig Cache-drivere benyttes (fil, redis, memcache etc.)"
+ REINSTALL_PLUGIN: "Geninstallere plugin"
+ REINSTALL_THEME: "Geninstallere tema"
+ REINSTALL_THE: "Geninstallér %s"
+ CONFIRM_REINSTALL: "Sikker på, at du vil geninstallere dette %s?"
+ REINSTALLED_SUCCESSFULLY: "%s geninstalleret"
+ ERROR_REINSTALLING_THE: "Fejl under geninstallering af %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Pakke %s geninstalleret"
+ REINSTALLATION_FAILED: "Geninstallation mislykkedes"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "Den installerede version er ikke den seneste udgivelse. Ved at klikke på Fortsæt fjerner du fjerne den aktuelle version og installerer den senest tilgængelige version"
+ TOOLS: "Værktøjer"
+ NO_PACKAGE_NAME: "Pakkenavn ikke angivet"
+ PACKAGE_EXTRACTION_FAILED: "Pakkeudpakning mislykkedes"
+ NOT_VALID_GRAV_PACKAGE: "Ikke en gyldig Grav-pakke"
+ NAME_COULD_NOT_BE_DETERMINED: "Navn kunne ikke fastslås"
+ CANNOT_OVERWRITE_SYMLINKS: "Kan ikke overskrive symlinks"
+ ZIP_PACKAGE_NOT_FOUND: "ZIP-pakke blev ikke fundet"
+ GPM_OFFICIAL_ONLY: "Kun officiel GPM"
+ GPM_OFFICIAL_ONLY_HELP: "Tillade kun direkte installationer fra det officielle GPM-repositorie."
+ NO_CHILD_TYPE: "Ingen underordnet type for denne rawroute"
+ SORTABLE_PAGES: "Sorterbare sider:"
+ ADMIN_SPECIFIC_OVERRIDES: "Admin-specifikke tilsidesætter"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Underordnedes visningsrækkefølge"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "Rækkefølgen, hvori undersider til denne side skal vises i visningen 'Sider' i Admin-plugin"
+ PWD_PLACEHOLDER: "kompleks streng minimum 8 tegn lang"
+ PWD_REGEX: "Adgangskode Regex"
+ PWD_REGEX_HELP: "Adgangskode skal som standard indeholde mindst ét ciffer, én versal og én minuskel samt udgøres af minimum otte tegn"
+ USERNAME_PLACEHOLDER: "kun minuskler, f.eks. 'admin'"
+ USERNAME_REGEX: "Brugernavn Regex"
+ USERNAME_REGEX_HELP: "Som standard kun minuskler, cifre, bindestreger og understregninger på 3-16 tegn"
+ CONTENT_PADDING: "Indholdsrammer"
+ CONTENT_PADDING_HELP: "Aktivér/Deaktivér indholdsrammer omkring indholdsområde for at give mere plads"
+ ENABLE_AUTO_METADATA: "Auto-metadata fra Exif"
+ ENABLE_AUTO_METADATA_HELP: "Automatisk generere metadatafiler til billeder med exif-oplysninger"
+ 2FA_TITLE: "Totrinsautentificering"
+ 2FA_INSTRUCTIONS: "### 2-faktor Autentificering \nDu har **2FA** aktiveret for denne konto. Benyt venligst din **2FA**-app til at angive den aktuelle **6-cifrede kode** for at fuldføre loginprocessen."
+ 2FA_LABEL: "Admin-adgang"
+ 2FA_FAILED: "Ugyldig 2-faktor autentificeringskode, forsøg venligst igen..."
+ 2FA_ENABLED: "2FA aktiveret"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "2FA hemmelighed"
+ 2FA_REGENERATE: "Regenerér"
+ FORCE_LOWERCASE_URLS: "Brug små bogstaver i URL'er"
diff --git a/user/plugins/admin/languages/de.yaml b/user/plugins/admin/languages/de.yaml
new file mode 100644
index 0000000..c19ab6a
--- /dev/null
+++ b/user/plugins/admin/languages/de.yaml
@@ -0,0 +1,769 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Dies ist eine Beta-Version! Benutzung auf eigene Gefahr..."
+ ADMIN_REPORT_ISSUE: "Fehler gefunden? Bitte melden Sie ihn auf GitHub."
+ EMAIL_FOOTER: " Umgesetzt mit Grav - dem modernen Flat-File CMS"
+ LOGIN_BTN: "Anmelden"
+ LOGIN_BTN_FORGOT: "Passwort vergessen"
+ LOGIN_BTN_RESET: "Passwort zurücksetzen"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Neues Passwort anfordern"
+ LOGIN_BTN_CLEAR: "Formular leeren"
+ LOGIN_BTN_CREATE_USER: "Benutzer erstellen"
+ LOGIN_LOGGED_IN: "Anmeldung erfolgreich"
+ LOGIN_FAILED: "Anmeldung fehlgeschlagen"
+ LOGGED_OUT: "Sie wurden abgemeldet"
+ RESET_NEW_PASSWORD: "Bitte geben Sie ein neues Passwort ein …"
+ RESET_LINK_EXPIRED: "Der Link zum Zurücksetzen Ihres Passwortes ist abgelaufen, bitte probieren Sie es erneut"
+ RESET_PASSWORD_RESET: "Das Passwort wurde zurückgesetzt"
+ RESET_INVALID_LINK: "Der Link zum Zurücksetzen Ihres Passwortes ist ungültig, bitte probieren Sie es erneut"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Anweisungen zum Zurücksetzen des Passwortes wurden an %s gesendet"
+ FORGOT_FAILED_TO_EMAIL: "Anweisungen zum Zurücksetzen des Passwortes konnten nicht versendet werden, bitte probieren Sie es später erneut"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Das Passwort für %s kann nicht geändert werden, da keine E-Mail-Adresse hinterlegt ist"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Es existiert kein Benutzer mit dem Namen %s "
+ FORGOT_EMAIL_NOT_CONFIGURED: "Passwort konnte nicht zurückgesetzt werden, da diese Seite nicht zum Versenden von E-Mails konfiguriert worden ist"
+ FORGOT_EMAIL_SUBJECT: "Zurücksetzen des Passwortes von %s"
+ FORGOT_EMAIL_BODY: "Passwort zurücksetzen Hallo %1$s,
Auf %4$s wurde die Zurücksetzung Ihres Passwortes angefordert.
Klicken Sie hier um Ihr Passwort zurückzusetzen.
Alternativ können Sie auch die folgende Adresse in die Adresszeile Ihres Browsers kopieren:
%2$s
Viele Grüße, %3$s
"
+ MANAGE_PAGES: "Seiten verwalten"
+ CONFIGURATION: "Einstellungen"
+ PAGES: "Seiten"
+ PLUGINS: "Plugins"
+ PLUGIN: "Plugin"
+ THEMES: "Themes"
+ LOGOUT: "Abmelden"
+ BACK: "Zurück"
+ NEXT: "Nächste"
+ PREVIOUS: "Vorherige"
+ ADD_PAGE: "Seite hinzufügen"
+ ADD_MODULAR: "Modulare Seite hinzufügen"
+ MOVE: "Verschieben"
+ DELETE: "Löschen"
+ UNSET: "Zurücksetzen"
+ VIEW: "Anzeigen"
+ SAVE: "Speichern"
+ NORMAL: "Normal"
+ EXPERT: "Expertenansicht"
+ EXPAND_ALL: "Alle ausklappen"
+ COLLAPSE_ALL: "Alle einklappen"
+ ERROR: "Fehler"
+ CLOSE: "Schließen"
+ CANCEL: "Abbrechen"
+ CONTINUE: "Weiter"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Bestätigung erforderlich"
+ MODAL_CHANGED_DETECTED_TITLE: "Änderungen erkannt"
+ MODAL_CHANGED_DETECTED_DESC: "Einige Änderungen wurden noch nicht gespeichert. Wollen Sie diese Seite wirklich verlassen?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Bestätigung erforderlich"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Wollen Sie diese Datei wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden."
+ ADD_FILTERS: "Filter hinzufügen"
+ SEARCH_PAGES: "Seiten durchsuchen"
+ VERSION: "Version"
+ WAS_MADE_WITH: "Erstellt mit"
+ BY: "Von"
+ UPDATE_THEME: "Theme aktualisieren"
+ UPDATE_PLUGIN: "Plugins aktualisieren"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "dieses Themes ist verfügbar"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "dieses Plugins ist verfügbar"
+ AUTHOR: "Autor"
+ HOMEPAGE: "Homepage"
+ DEMO: "Demo"
+ BUG_TRACKER: "Bug-Tracker"
+ KEYWORDS: "Schlagwörter"
+ LICENSE: "Lizenz"
+ DESCRIPTION: "Beschreibung"
+ README: "Readme"
+ REMOVE_THEME: "Theme löschen"
+ INSTALL_THEME: "Theme installieren"
+ THEME: "Theme"
+ BACK_TO_THEMES: "Zurück zu den Themes"
+ BACK_TO_PLUGINS: "Zurück zu den Plugins"
+ CHECK_FOR_UPDATES: "Updates suchen"
+ ADD: "Hinzufügen"
+ CLEAR_CACHE: "Cache leeren"
+ CLEAR_CACHE_ALL_CACHE: "Alle Caches"
+ CLEAR_CACHE_ASSETS_ONLY: "Nur Assets"
+ CLEAR_CACHE_IMAGES_ONLY: "Nur Bilder"
+ CLEAR_CACHE_CACHE_ONLY: "Nur Cache"
+ CLEAR_CACHE_TMP_ONLY: "nur temporär"
+ DASHBOARD: "Übersicht"
+ UPDATES_AVAILABLE: "Updates verfügbar"
+ DAYS: "Tage"
+ UPDATE: "Aktualisieren"
+ BACKUP: "Sicherung"
+ BACKUPS: "Sicherungen"
+ BACKUP_NOW: "Sicherung jetzt erstellen"
+ BACKUPS_STATS: "Sicherungsstatistiken"
+ BACKUPS_HISTORY: "Sicherungsverlauf"
+ BACKUPS_PROFILES: "Sicherungsprofile"
+ BACKUPS_COUNT: "Anzahl an Sicherungen"
+ BACKUPS_PROFILES_COUNT: "Anzahl der Profile"
+ BACKUPS_TOTAL_SIZE: "Belegter Speicherplatz"
+ BACKUPS_NEWEST: "Neuste Sicherung"
+ BACKUPS_OLDEST: "Älteste Sicherung"
+ BACKUPS_PURGE: "Bereinigen"
+ BACKUPS_NOT_GENERATED: "Bisher wurden keine Backups generiert..."
+ BACKUPS_PURGE_NUMBER: "Sie nutzen %s von insgesamt %s möglichen Backups"
+ BACKUPS_PURGE_TIME: "%s Tage an Sicherungen übrig"
+ BACKUPS_PURGE_SPACE: "%s von %s verwendet"
+ BACKUP_DELETED: "Backup erfolgreich gelöscht"
+ BACKUP_NOT_FOUND: "Sicherung wurde nicht gefunden"
+ BACKUP_DATE: "Sicherungsdatum"
+ STATISTICS: "Statistiken"
+ TODAY: "Heute"
+ WEEK: "Woche"
+ MONTH: "Monat"
+ LATEST_PAGE_UPDATES: "Zuletzt bearbeitete Seiten"
+ MAINTENANCE: "Wartung"
+ UPDATED: "Aktualisiert"
+ MON: "Mo"
+ TUE: "Di"
+ WED: "Mi"
+ THU: "Do"
+ FRI: "Fr"
+ SAT: "Sa"
+ SUN: "So"
+ COPY: "Kopieren"
+ EDIT: "Bearbeiten"
+ CREATE: "Erstellen"
+ GRAV_ADMIN: "Grav-Admin"
+ GRAV_OFFICIAL_PLUGIN: "Offizielles Grav Plugin"
+ GRAV_OFFICIAL_THEME: "Offizielles Grav Theme"
+ PLUGIN_SYMBOLICALLY_LINKED: "Dieses Plugin basiert auf einer symbolischen Verknüpfung. Updates werden daher nicht erkannt."
+ THEME_SYMBOLICALLY_LINKED: "Dieses Theme basiert auf einer symbolischen Verknüpfung. Updates werden daher nicht erkannt."
+ REMOVE_PLUGIN: "Plugin entfernen"
+ INSTALL_PLUGIN: "Plugin installieren"
+ AVAILABLE: "Verfügbar"
+ INSTALLED: "Installiert"
+ INSTALL: "Installieren"
+ ACTIVE_THEME: "Ausgewähltes Theme"
+ SWITCHING_TO: "Wechseln zu"
+ SWITCHING_TO_DESCRIPTION: "Durch das Wechseln des Themes kann nicht garantiert werden, dass das Layout aller Seiten unterstützt wird. Daher können beim Aufrufen der Seiten Fehler entstehen."
+ SWITCHING_TO_CONFIRMATION: "Möchten Sie fortfahren und zum Theme wechseln"
+ CREATE_NEW_USER: "Neuen Benutzer erstellen"
+ REMOVE_USER: "Benutzer entfernen"
+ ACCESS_DENIED: "Zugriff verweigert"
+ ACCOUNT_NOT_ADMIN: "Ihr Account verfügt über keine Administrationsberechtigungen"
+ PHP_INFO: "PHP-Info"
+ INSTALLER: "Installationsprogramm"
+ AVAILABLE_THEMES: "Verfügbare Themes"
+ AVAILABLE_PLUGINS: "Verfügbare Plugins"
+ INSTALLED_THEMES: "Installierte Themes"
+ INSTALLED_PLUGINS: "Installierte Plugins"
+ BROWSE_ERROR_LOGS: "Error Logs anzeigen"
+ SITE: "Site"
+ INFO: "Info"
+ SYSTEM: "System"
+ USER: "Benutzer"
+ ADD_ACCOUNT: "Benutzer hinzufügen"
+ SWITCH_LANGUAGE: "Sprache wechseln"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Plugin erfolgreich aktiviert"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Plugin erfolgreich deaktiviert"
+ SUCCESSFULLY_CHANGED_THEME: "Standard Theme erfolgreich geändert"
+ INSTALLATION_FAILED: "Installation fehlgeschlagen"
+ INSTALLATION_SUCCESSFUL: "Installation erfolgreich"
+ UNINSTALL_FAILED: "Deinstallation fehlgeschlagen"
+ UNINSTALL_SUCCESSFUL: "Deinstallation erfolgreich"
+ SUCCESSFULLY_SAVED: "Speichern erfolgreich"
+ SUCCESSFULLY_COPIED: "Kopieren erfolgreich"
+ REORDERING_WAS_SUCCESSFUL: "Umsortieren war erfolgreich"
+ SUCCESSFULLY_DELETED: "Erfolgreich gelöscht"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Sprache erfolgreich gewechselt"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Sie haben unzureichende Berechtigungen für diese Aktion"
+ CACHE_CLEARED: "Cache geleert"
+ METHOD: "Methode"
+ ERROR_CLEARING_CACHE: "Fehler beim leeren des Cache"
+ AN_ERROR_OCCURRED: "Ein Fehler ist aufgetreten"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Ihr Backup steht zum Download bereit"
+ DOWNLOAD_BACKUP: "Backup herunterladen"
+ PAGES_FILTERED: "Seiten gefiltert"
+ NO_PAGE_FOUND: "Keine Seite gefunden"
+ INVALID_PARAMETERS: "Ungültige Eingabe"
+ NO_FILES_SENT: "Keine Dateien übertragen"
+ EXCEEDED_FILESIZE_LIMIT: "Dateigrößenbeschränkung aus PHP-Konfiguration überschritten"
+ EXCEEDED_POSTMAX_LIMIT: "PHP-Einstellung post_max_size überschritten"
+ UNKNOWN_ERRORS: "Unbekannte Fehler"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Dateigrößenbeschränkung in Grav-Konfiguration überschritten"
+ UNSUPPORTED_FILE_TYPE: "Dateityp nicht unterstützt"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Hochgeladene Datei konnte nicht verschoben werden."
+ FILE_UPLOADED_SUCCESSFULLY: "Datei erfolgreich hochgeladen"
+ FILE_DELETED: "Datei gelöscht"
+ FILE_COULD_NOT_BE_DELETED: "Datei konnte nicht gelöscht werden"
+ FILE_NOT_FOUND: "Datei nicht gefunden"
+ NO_FILE_FOUND: "Keine Datei gefunden"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav wurde erfolgreich aktualisiert auf"
+ GRAV_UPDATE_FAILED: "Grav Update fehlgeschlagen"
+ EVERYTHING_UPDATED: "Alles aktualisiert"
+ UPDATES_FAILED: "Updates fehlgeschlagen"
+ AVATAR_BY: "Avatar von"
+ AVATAR_UPLOAD_OWN: "Oder laden Sie Ihr eigenes hoch..."
+ LAST_BACKUP: "Letztes Backup"
+ FULL_NAME: "Voller Name"
+ USERNAME: "Benutzername"
+ EMAIL: "E-Mail"
+ USERNAME_EMAIL: "Benutzername oder E-Mail"
+ PASSWORD: "Passwort"
+ PASSWORD_CONFIRM: "Passwort bestätigen"
+ TITLE: "Titel"
+ LANGUAGE: "Sprache"
+ ACCOUNT: "Benutzer"
+ EMAIL_VALIDATION_MESSAGE: "Muss eine gültige E-Mail Adresse sein"
+ PASSWORD_VALIDATION_MESSAGE: "Das Passwort muss mindestens eine Zahl, einen Groß- und einen Kleinbuchstaben enthalten sowie mindestens 8 Zeichen lang sein."
+ LANGUAGE_HELP: "Bevorzugte Sprache einstellen"
+ MEDIA: "Medien"
+ DEFAULTS: "Standards"
+ SITE_TITLE: "Website Titel"
+ SITE_TITLE_PLACEHOLDER: "Titel der Website"
+ SITE_TITLE_HELP: "Titel der Site. Wird häufig von Themes verwendet."
+ SITE_DEFAULT_LANG: "Standardsprache"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Standardsprache die im Theme Tag benutzt werden soll"
+ SITE_DEFAULT_LANG_HELP: "Standardsprache die im Theme Tag benutzt werden soll"
+ DEFAULT_AUTHOR: "Hauptautor"
+ DEFAULT_AUTHOR_HELP: "Name des Hauptautors, häufig verwendet in Themes und Inhalten"
+ DEFAULT_EMAIL: "Standard E-Mail Adresse"
+ DEFAULT_EMAIL_HELP: "Standard E-Mail Adresse, die in Plugins und Themes verwendet wird"
+ TAXONOMY_TYPES: "Tag-Typen"
+ TAXONOMY_TYPES_HELP: "Wenn sie verschiedene Tag-Typen verwenden wollen, müssen Sie hier die verschiedenen Typen angeben."
+ PAGE_SUMMARY: "Seitenzusammenfassung"
+ ENABLED: "Aktiviert"
+ ENABLED_HELP: "Aktiviert die Seitenzusammenfasung (die Seitenzusammenfassung entspricht dem Inhalt der Seite)"
+ 'YES': "Ja"
+ 'NO': "Nein"
+ SUMMARY_SIZE: "Zusammenfassungslänge"
+ SUMMARY_SIZE_HELP: "Länge der Zusammenfassung in Buchstaben"
+ FORMAT: "Format"
+ FORMAT_HELP: "kurz = Kürzung der Zusammenfassung bis zum Trennzeichen oder der Zusammenfassungslänge; lang = Trennzeichen wird ignoriert"
+ SHORT: "Kurz"
+ LONG: "Lang"
+ DELIMITER: "Trennzeichen"
+ DELIMITER_HELP: "Das Trennzeichen für die Zusammenfassung (Standard '===')"
+ METADATA: "Metadaten"
+ METADATA_HELP: "Standardmetadaten, die auf jeder Seite angezeigt werden, es sei denn sie werden von einer Seite überschrieben"
+ NAME: "Name"
+ CONTENT: "Inhalt"
+ SIZE: "Größe"
+ ACTION: "Aktion"
+ REDIRECTS_AND_ROUTES: "Weiterleitungen & Routen"
+ CUSTOM_REDIRECTS: "Eigene Weiterleitungen"
+ CUSTOM_REDIRECTS_HELP: "Routen, die auf andere Seiten weiterleiten. Standard Regex Ersetzungen sind erlaubt"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/dein/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/deine/umleitung"
+ CUSTOM_ROUTES: "Eigene Routen"
+ CUSTOM_ROUTES_HELP: "Routen, die auf andere Seiten verweisen. Standard Regex Ersetzungen sind erlaubt"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/dein/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/deine/route"
+ FILE_STREAMS: "Datei Streams"
+ DEFAULT: "Standard"
+ PAGE_MEDIA: "Seitenmedien"
+ OPTIONS: "Optionen"
+ PUBLISHED: "Veröffentlicht"
+ PUBLISHED_HELP: "Standardmäßig ist eine Seite veröffentlicht, es sei denn sie wird explizit auf nicht veröffentlicht gesetzt, das Veröffentlichungsdatum liegt in der Zukunft oder das Ablaufdatum wurde erreicht."
+ DATE: "Datum"
+ DATE_HELP: "Die Datumsangabe erlaubt es diese Seite mit einem Datum zu versehen."
+ PUBLISHED_DATE: "Veröffentlichungsdatum"
+ PUBLISHED_DATE_HELP: "Datum ab dem die Seite automatisch veröffentlicht wird."
+ UNPUBLISHED_DATE: "Ablaufdatum"
+ UNPUBLISHED_DATE_HELP: "Datum ab dem die Veröffentlichung der Seite zurückgenommen wird."
+ ROBOTS: "Robots"
+ TAXONOMIES: "Tags"
+ TAXONOMY: "Tag"
+ ADVANCED: "Erweitert"
+ SETTINGS: "Einstellungen"
+ FOLDER_NUMERIC_PREFIX: "Numerischer Ordnerprefix"
+ FOLDER_NUMERIC_PREFIX_HELP: "Numerischer Prefix der die Reihenfolge und Sichtbarkeit der Seiten beeinflusst"
+ FOLDER_NAME: "Ordnername"
+ FOLDER_NAME_HELP: "Name des Ordners, der für diese Seite angelegt werden sollen"
+ PARENT: "Übergeordnet"
+ DEFAULT_OPTION_ROOT: "- Ursprung -"
+ DEFAULT_OPTION_SELECT: "- Auswählen -"
+ DISPLAY_TEMPLATE: "Template anzeigen"
+ DISPLAY_TEMPLATE_HELP: "Der Seitentyp der zur Wahl des richtigen Twig Templates verwendet wird."
+ BODY_CLASSES: "Body CSS Klassen"
+ ORDERING: "Sortierung"
+ PAGE_ORDER: "Seitensortierung"
+ OVERRIDES: "Überschreibungen"
+ MENU: "Menü"
+ MENU_HELP: "Beschriftung zur Anzeige im Menu. Wenn nicht gesetzt, wird der Titel als Standard genutzt."
+ SLUG: "Slug"
+ SLUG_HELP: "Der Slug bestimmt den Namen der Seite innerhalb der URL."
+ SLUG_VALIDATE_MESSAGE: "Ein Slug darf nur aus Kleinbuchstaben und Bindestrichen bestehen"
+ PROCESS: "Verarbeitung"
+ PROCESS_HELP: "Steuert die Verarbeitung einer Seite. Sollte pro Seite und nicht global gesetzt werden."
+ DEFAULT_CHILD_TYPE: "Standardtyp für Unterseiten"
+ USE_GLOBAL: "Globale Einstellungen verwenden"
+ ROUTABLE: "Aufrufbar"
+ ROUTABLE_HELP: "Ob diese Seite mit einer URL aufgerufen werden kann"
+ CACHING: "Caching"
+ VISIBLE: "Sichtbar"
+ VISIBLE_HELP: "Steuert die Sichtbarkeit einer Seite im Menu."
+ DISABLED: "Deaktiviert"
+ ITEMS: "Elemente"
+ ORDER_BY: "Sortieren nach"
+ ORDER: "Sortierung"
+ FOLDER: "Ordner"
+ ASCENDING: "Aufsteigend"
+ DESCENDING: "Absteigend"
+ ADD_MODULAR_CONTENT: "Modularen Inhalt hinzufügen"
+ PAGE_TITLE: "Seitentitel"
+ PAGE_TITLE_HELP: "Titel der Seite"
+ PAGE: "Seite"
+ MODULAR_TEMPLATE: "Modulares Template"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Dateiname"
+ PARENT_PAGE: "Übergeordnete Seite"
+ HOME_PAGE: "Startseite"
+ HOME_PAGE_HELP: "Seite die von Grav als Startseite genutzt werden soll"
+ DEFAULT_THEME: "Standard Theme"
+ DEFAULT_THEME_HELP: "Setzt den Standard Theme von Grav (Standard ist Antimatter)"
+ TIMEZONE: "Zeitzone"
+ TIMEZONE_HELP: "Überschreibt die Zeitzone des Servers"
+ SHORT_DATE_FORMAT: "Kurzes Datumsformat"
+ SHORT_DATE_FORMAT_HELP: "Setzt das von Themes genutzte Kurzdatumsformat"
+ LONG_DATE_FORMAT: "Langes Datumsformat"
+ LONG_DATE_FORMAT_HELP: "Setzt das von Themes genutzte Langdatumsformat"
+ DEFAULT_ORDERING: "Standard Sortierung"
+ DEFAULT_ORDERING_HELP: "Seiten in einer Liste werden in der Standard Sortierung angezeigt, falls diese nicht überschrieben wird."
+ DEFAULT_ORDERING_DEFAULT: "Standard - basierend auf dem Ordnernamen"
+ DEFAULT_ORDERING_FOLDER: "Ordner - basierend auf dem Ordnernamen ohne Prefix"
+ DEFAULT_ORDERING_TITLE: "Titel - basieren auf dem Seitentitel"
+ DEFAULT_ORDERING_DATE: "Datum - basierend auf dem Datum der Seite"
+ DEFAULT_ORDER_DIRECTION: "Standard Sortierrichtung"
+ DEFAULT_ORDER_DIRECTION_HELP: "Sortierrichtung von Seiten in einer Liste"
+ DEFAULT_PAGE_COUNT: "Standard Seitenzahl"
+ DEFAULT_PAGE_COUNT_HELP: "Maximale Anzahl von Seiten in einer Liste"
+ DATE_BASED_PUBLISHING: "Datumsbasierte Veröffentlichungen"
+ DATE_BASED_PUBLISHING_HELP: "Seiten mit Veröffentlichungs- und Ablaufdatum automatisch veröffentlichen oder zurückziehen"
+ EVENTS: "Ereignisse"
+ EVENTS_HELP: "Von Grav gesteuerte Ereignisse aktivieren. Ein Deaktivieren kann Plugins in ihrer Funktionsweise stören."
+ REDIRECT_DEFAULT_ROUTE: "Weiterleiten auf Standardroute"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Automatisches Weiterleiten auf die Route einer Seite"
+ LANGUAGES: "Sprachen"
+ SUPPORTED: "Unterstützt"
+ SUPPORTED_HELP: "Durch Kommata getrennte Liste mit 2-stelligen Sprachcodes (zum Beispiel 'en,fr,de')"
+ TRANSLATIONS_ENABLED: "Übersetzungen aktivieren"
+ TRANSLATIONS_ENABLED_HELP: "Aktiviert Übersetzungen in Grav, Plugins und Erweiterungen"
+ TRANSLATIONS_FALLBACK: "Fallbacksprache"
+ TRANSLATIONS_FALLBACK_HELP: "Falls Übersetzungen in einer Sprache nicht vorhanden sind, wird auf diese Sprache zurückgegriffen"
+ ACTIVE_LANGUAGE_IN_SESSION: "Aktive Sprache in Session speichern"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Speichert die aktive Sprache in der Session"
+ HTTP_HEADERS: "HTTP-Header"
+ EXPIRES: "Läuft ab"
+ EXPIRES_HELP: "Ändert den Expires-Header (Cache-Ablaufdatum). Angabe in Sekunden"
+ CACHE_CONTROL: "HTTP-Cache-Control"
+ CACHE_CONTROL_HELP: "Muss auf einen validen Cache-Control-Wert gesetzt werden, wie zum Beispiel 'no-cache, no-store, must-revalidate\""
+ LAST_MODIFIED: "Zuletzt geändert"
+ LAST_MODIFIED_HELP: "Setzt das Datum der letzten Änderung um Caching zu optimieren"
+ ETAG: "ETag"
+ ETAG_HELP: "Setzt den ETag-Header um Änderungen der Seite für Proxies erkenntlich zu machen"
+ VARY_ACCEPT_ENCODING: "Vary Accept Encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Setzt den `Vary: Accept Encoding`-Header um Proxies und CDNs zu optimieren"
+ MARKDOWN: "Markdown"
+ MARKDOWN_EXTRA: "Markdown extra"
+ MARKDOWN_EXTRA_HELP: "Aktiviert Markdown Extra Unterstützung - https://michelf.ca/projects/php-markdown/extra/"
+ AUTO_LINE_BREAKS: "Automatische Zeilenumbrüche"
+ AUTO_LINE_BREAKS_HELP: "Aktiviert Unterstützung für automatische Zeilenumbrüche in Markdown"
+ AUTO_URL_LINKS: "URLs zu Links verwandeln"
+ AUTO_URL_LINKS_HELP: "Verwandelt jede URL in einen HTML-Link"
+ ESCAPE_MARKUP: "HTML Ausgabe unterdrücken"
+ ESCAPE_MARKUP_HELP: "Ersetzt Markup-Tags durch HTML-Zeichen"
+ CACHING_HELP: "Globaler an/aus Schalter um Caching in Grav zu aktivieren/deaktivieren"
+ CACHE_CHECK_METHOD: "Cacheprüfungsmethode"
+ CACHE_CHECK_METHOD_HELP: "Methode die Grav verwendet um den Cache zu prüfen."
+ CACHE_DRIVER: "Cache-Treiber"
+ CACHE_DRIVER_HELP: "Cache-Treiber den Grav verwendet um Dateien vorzuhalten. 'Automatisch' versucht selbstständig den besten Treiber zu ermitteln."
+ CACHE_PREFIX: "Cache-Prefix"
+ CACHE_PREFIX_HELP: "Cache-Prefix der verwendet wird um Dateien zu cachen. Sollte nicht verändert werden, wenn man nicht genau weiß, was man tut."
+ CACHE_PREFIX_PLACEHOLDER: "Abgeleitet von der Basis-URL (überschreiben durch einen zufälligen Text)"
+ CACHE_PURGE_JOB: "Geplante Bereinigung ausführen"
+ CACHE_PURGE_JOB_HELP: "Mit dem Zeitplaner können Sie die alten Ordner für den Cache der Doctrine-Datei mit diesem Job regelmäßig löschen"
+ CACHE_CLEAR_JOB: "Geplante Bereinigung ausführen"
+ CACHE_CLEAR_JOB_HELP: "Mit dem Scheduler können Sie den Grav-Cache periodisch löschen"
+ CACHE_JOB_TYPE: "Cache Job Typ"
+ CACHE_PURGE: "Alten Cache bereinigen"
+ LIFETIME: "Cache-Lebensdauer"
+ LIFETIME_HELP: "Setzt die Dauer des Caches in Sekunden. 0 = unendlich"
+ GZIP_COMPRESSION: "Gzip-Komprimierung"
+ GZIP_COMPRESSION_HELP: "Aktiviert Gzip-Komprimierung um Grav schneller an Browser senden zu können."
+ TWIG_TEMPLATING: "Twig Templates"
+ TWIG_CACHING: "Twig Caching"
+ TWIG_CACHING_HELP: "Twig Cache-Einstellungen. Sollte aktiviert bleiben um die beste Performance zu gewährleisten."
+ TWIG_DEBUG: "Twig debuggen"
+ TWIG_DEBUG_HELP: "Deaktiviert die Twig-Debug-Extension"
+ DETECT_CHANGES: "Änderungen erkennen"
+ DETECT_CHANGES_HELP: "Twig erkennt automatisch, wenn Themes geändert werden und baut den Cache neu auf"
+ AUTOESCAPE_VARIABLES: "Variablen automatisch escapen"
+ AUTOESCAPE_VARIABLES_HELP: "Alle Veriablen escapen. Ihre Seite könnte dadurch nicht korrekt angezeigt werden"
+ ASSETS: "Assets"
+ CSS_PIPELINE: "CSS Pipeline"
+ CSS_PIPELINE_HELP: "Die CSS Pipeline fasst alle Ihre CSS Dateien in einer einzigen Datei zusammen"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Externe CSS-Dateien mit in die Pipeline aufnehmen"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Externe Resourcen haben evtl. relative Pfadangaben und sollten deshalb nicht mit in die Pipeline aufgenommen werden"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "CSS Pipeline zuerst"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Führt die CSS Pipeline vor ausgeschlossen CSS-Verweisen aus"
+ CSS_MINIFY: "CSS minifizieren"
+ CSS_MINIFY_HELP: "Komprimiert ihre CSS Dateien nach dem pipelining"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "CSS minify Windows"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Verwendet eine andere minify Version, die auf Windows-Betriebssystemen besser funktioniert"
+ CSS_REWRITE: "CSS Umschreiben"
+ CSS_REWRITE_HELP: "Ersetze alle relativen CSS URLs beim pipelining"
+ JAVASCRIPT_PIPELINE: "Javascript Pipeline"
+ JAVASCRIPT_PIPELINE_HELP: "Kombiniert alle JavaScript Dateien zu einer"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Externe Javascript-Dateien mit in die Pipeline aufnehmen"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Externe Resourcen haben evtl. relative Pfadangaben und sollten deshalb nicht mit in die Pipeline aufgenommen werden"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Javascript Pipeline zuerst"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Bindet zuerst die Pipline und dann die ausgenommenen Skripte ein"
+ JAVASCRIPT_MINIFY: "JavaScript minifizieren"
+ JAVASCRIPT_MINIFY_HELP: "Komprimiert die JavaScript Dateien nach dem pipelining"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Zeitstempel für Assets aktivieren"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Aktviert die Zeitstempel für Assets"
+ COLLECTIONS: "Sammlungen"
+ ERROR_HANDLER: "Fehlerbehandlung"
+ DISPLAY_ERRORS: "Fehler Anzeigen"
+ DISPLAY_ERRORS_HELP: "Volle Backtrace-Fehler-Seite anzeigen"
+ LOG_ERRORS: "Fehler protokollieren"
+ LOG_ERRORS_HELP: "Fehler im /logs Ordner speichern"
+ LOG_HANDLER: "Log-Handler"
+ LOG_HANDLER_HELP: "Ausgabeort von Logs"
+ DEBUGGER: "Debugger"
+ DEBUGGER_HELP: "Aktiviert den Grav Debugger und die folgenden Einstellungen"
+ DEBUG_TWIG: "Twig debuggen"
+ DEBUG_TWIG_HELP: "Twig Templates debuggen"
+ SHUTDOWN_CLOSE_CONNECTION: "Verbindung trennen"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Trennt die Verbindung vor dem Aufruf von onShutdown(). Deaktivieren für Debugging"
+ DEFAULT_IMAGE_QUALITY: "Standard Bildqualität"
+ DEFAULT_IMAGE_QUALITY_HELP: "Standardbildqualität beim Speichern von Bildern (85%)"
+ CACHE_ALL: "Alle Bilder cachen"
+ CACHE_ALL_HELP: "Alle Bilder, auch ohne Bearbeitung, im Grav Cache ablegen"
+ IMAGES_DEBUG: "Bild Debug Wasserzeichen"
+ IMAGES_DEBUG_HELP: "Wasserzeichen mit der Auflösung der Bilder anzeigen um Retina-Displays zu testen"
+ IMAGES_SEOFRIENDLY: "SEO freundliche Bildnamen"
+ UPLOAD_LIMIT: "Datei Upload Limit"
+ UPLOAD_LIMIT_HELP: "Maximale Dateigröße in Bytes (0 = ohne Beschränkung)"
+ ENABLE_MEDIA_TIMESTAMP: "Zeitstempel für Mediadateien"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Fügt jedem Medienelement einen Zeitstempel der letzten Bearbeitung hinzu"
+ SESSION: "Sitzung"
+ SESSION_ENABLED_HELP: "Sessions in Grav aktivieren"
+ TIMEOUT: "Zeitüberschreitung"
+ TIMEOUT_HELP: "Session Timeout in Sekunden"
+ SESSION_NAME_HELP: "Name für den Session-Cookie"
+ SESSION_UNIQUENESS: "Primärschlüssel"
+ ABSOLUTE_URLS: "Absolute URLs"
+ ABSOLUTE_URLS_HELP: "Absolute oder Relative Pfadangaben für `base_url`"
+ PARAMETER_SEPARATOR: "Parameter Trennzeichen"
+ PARAMETER_SEPARATOR_HELP: "Trennzeichen für Parameter in der URL"
+ TASK_COMPLETED: "Aufgabe erledigt"
+ EVERYTHING_UP_TO_DATE: "Keine Aktualisierungen verfügbar"
+ UPDATES_ARE_AVAILABLE: "Aktualisierung(en) verfügbar"
+ IS_AVAILABLE_FOR_UPDATE: "ist zur Aktualisierung verfügbar"
+ IS_NOW_AVAILABLE: "ist jetzt verfügbar"
+ CURRENT: "Aktuell"
+ UPDATE_GRAV_NOW: "Grav jetzt aktualisieren"
+ GRAV_SYMBOLICALLY_LINKED: "Grav ist symbolisch Verknüpft und kann daher nicht aktualisiert werden"
+ UPDATING_PLEASE_WAIT: "Aktualisiere, bitte warten…"
+ OF_THIS: "von diesem"
+ OF_YOUR: "von deinem"
+ HAVE_AN_UPDATE_AVAILABLE: "hat ein Update verfügbar"
+ SAVE_AS: "Speichern als"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Sind Sie sicher, dass Sie diese Seite und all deren Kinder löschen wollen? Wenn diese Seite noch in anderen Sprachen vorliegt, werden diese Übersetzungen behalten & müssen separat gelöscht werden. Ansonsten wird die Seite samt Ordner mit allen Unterseiten gelöscht. Diese Aktion kann nicht rückgängig gemacht werden."
+ AND: "und"
+ UPDATE_AVAILABLE: "Aktualisierung verfügbar"
+ METADATA_KEY: "Schlüssel (z.B. 'Stichwort')"
+ METADATA_VALUE: "Wert (z.B. 'Blog, Grav')"
+ USERNAME_HELP: "Der Nutzername sollte zwischen 3 bis 16 Zeichen lang sein und darf Kleinbuchstaben, Zahlen, Unterstrichen und Bindestrichen enthalten. Großbuchstaben, Leerzeichen und Sonderzeichen sind nicht erlaubt"
+ FULLY_UPDATED: "Vollständig aktualisiert"
+ SAVE_LOCATION: "Gespeichert unter"
+ PAGE_FILE: "Seiten-Template"
+ PAGE_FILE_HELP: "Seiten-Template Dateiname, und als Standard das Anzeige-Template für diese Seite"
+ NO_USER_ACCOUNTS: "Keine Benutzerkonten gefunden, bitte erstellen Sie zuerst eines..."
+ NO_USER_EXISTS: "Für dieses Konto existiert kein lokaler Benutzer, es kann nicht gespeichert werden..."
+ REDIRECT_TRAILING_SLASH: "Weiterleiten von nachgestellten '/'"
+ REDIRECT_TRAILING_SLASH_HELP: "Eine 301 Weiterleitung durchführen anstatt nachgestellte '/' transparent zu verarbeiten."
+ DEFAULT_DATE_FORMAT: "Seiten-Datumsformat"
+ DEFAULT_DATE_FORMAT_HELP: "Das von Grav für die Seite verwendete Datumsformat. Standardmässig versucht Grav das Datum anhand Ihrer Herkunft festzulegen. Sie können das Datumsformat mithilfe der PHP Syntax festlegen (z.B. Y-m-d-H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Automatisch vorschlagen"
+ IGNORE_FILES: "Dateien ignorieren"
+ IGNORE_FILES_HELP: "Dateien die beim Verarbeiten von Seiten ignoriert werden sollen"
+ IGNORE_FOLDERS: "Ignoriere Ordner"
+ IGNORE_FOLDERS_HELP: "Verzeichnisse die beim Verarbeiten von Seiten ignoriert werden sollen"
+ HIDE_EMPTY_FOLDERS: "Leere Ordner verstecken"
+ HIDE_EMPTY_FOLDERS_HELP: "Wenn der Ordner keine .md-Datei hat, sollte er in der Navigation versteckt werden und nicht routbar sein"
+ HTTP_ACCEPT_LANGUAGE: "Sprache vom Browser übernehmen"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Sie können sich entscheiden die Sprache automatisch über den 'http_accept_language' Header einzustellen"
+ OVERRIDE_LOCALE: "Spracheinstellung überschreiben"
+ OVERRIDE_LOCALE_HELP: "Lokale PHP Spracheinstellung mit aktueller Sprache überschreiben"
+ REDIRECT: "Seiten-Weiterleitung"
+ REDIRECT_HELP: "Geben Sie eine Seiten-Adresse oder externe URL ein, auf welche diese Seite weiterleiten soll - z.B. '/eine/adresse' oder 'http://www.seite.de'"
+ PLUGIN_STATUS: "Plugin Status"
+ INCLUDE_DEFAULT_LANG: "Standardsprache hinzufügen"
+ INCLUDE_DEFAULT_LANG_HELP: "Dies wird alle URLs der Standardsprache um die Sprache ergänzen z.B. '/en/blog/post' oder '/de/blog/post'"
+ ALLOW_URL_TAXONOMY_FILTERS: "URL Kategoriefilter"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Seiten-basierende Sammlungen erlauben das Filtern via '/taxonomy:value'."
+ REDIRECT_DEFAULT_CODE: "Standard Weiterleitungscode"
+ REDIRECT_DEFAULT_CODE_HELP: "HTTP Statuscode für Weiterleitungen"
+ IGNORE_HIDDEN: "Versteckte ignorieren"
+ IGNORE_HIDDEN_HELP: "Alle Dateien und Ordner ignorieren, die mit einem Punkt beginnen"
+ WRAPPED_SITE: "Eingefasste Seite"
+ WRAPPED_SITE_HELP: "Damit Themes/Plugins wissen ob Grav in eine andere Plattform eingebunden ist"
+ FALLBACK_TYPES: "Zugelassene Fallback-Typen"
+ FALLBACK_TYPES_HELP: "Erlaubte Datei-Typen auf die über Seiten-Routen zugegriffen werden kann. Standardmäßig alle unterstützen Media Typen."
+ INLINE_TYPES: "Inline Ersatz Typ"
+ INLINE_TYPES_HELP: "Eine Liste an Datei Typen die Inline angezeigt werden sollte, statt heruntergeladen zu werden"
+ APPEND_URL_EXT: "URL Endung hinzufügen"
+ APPEND_URL_EXT_HELP: "Wird eine eigene Erweiterung zu der URL der Seite hinzufügen. Damit wird Grav nach `..twig` suchen"
+ PAGE_MODES: "Seitenmodus"
+ PAGE_TYPES: "Seitentyp"
+ ACCESS_LEVELS: "Zugriffslevels"
+ GROUPS: "Gruppen"
+ GROUPS_HELP: "Liste aller Gruppen in denen der Benutzer Mitglied ist"
+ ADMIN_ACCESS: "Admin Zugriff"
+ SITE_ACCESS: "Website Zugriff"
+ INVALID_SECURITY_TOKEN: "Ungültiger Sicherheitstoken"
+ ACTIVATE: "Aktivieren"
+ TWIG_UMASK_FIX: "Umask Fix"
+ TWIG_UMASK_FIX_HELP: "Twig erstellt Cache Dateien standardmäßig mit 0755, Fix setzt dass auf 0775"
+ CACHE_PERMS: "Cache Berechtigungen"
+ CACHE_PERMS_HELP: "Standardberechtigungen für den Cache-Ordner. In der Regel 0755 oder 0775 je nach Setup"
+ REMOVE_SUCCESSFUL: "Entfernen erfolgreich"
+ REMOVE_FAILED: "Entfernen fehlgeschlagen"
+ HIDE_HOME_IN_URLS: "Startseiten Adresse in URLs ausblenden"
+ HIDE_HOME_IN_URLS_HELP: "Wird sicherstellen, dass die Standard-Adresse für alle Seiten unterhalb der Startseite nicht auf die Standard-Adresse der Startseite verweisen"
+ TWIG_FIRST: "Twig-Verarbeitung zuerst ausführen"
+ TWIG_FIRST_HELP: "Falls Sie die Seiten-Generierung mittels Twig aktiviert haben, können Sie einstellen, ob diese vor oder nach der Markdown-Verarbeitung geschehen soll"
+ SESSION_SECURE: "Sicher"
+ SESSION_SECURE_HELP: "Wenn aktiviert muss die Kommunikation für Cookies über eine verschlüsselte Verbindung stattfinden. Warnung: Aktivieren Sie diese Option nur auf Seiten, die ausschließlich auf HTTPS laufen"
+ SESSION_HTTPONLY: "Nur HTTP"
+ SESSION_HTTPONLY_HELP: "Wenn aktiv, werden Cookies nur über HTTP genutzt. Eine Änderung per JavaScript ist nicht erlaubt"
+ REVERSE_PROXY: "Reverse Proxy"
+ REVERSE_PROXY_HELP: "Aktivieren Sie dies, wenn sie sich hinter einem Reverse Proxy befinden und Probleme mit URLs und inkorrekten Ports haben"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Ungültiger Frontmatter, speichern nicht möglich"
+ ADD_FOLDER: "Ordner hinzufügen"
+ PROXY_URL: "Proxy-URL"
+ PROXY_URL_HELP: "Geben Sie den Proxy-HOST oder IP und PORT ein"
+ NOTHING_TO_SAVE: "Nichts zu speichern"
+ FILE_ERROR_ADD: "Beim Versuch, die Dateien hinzuzufügen, ist ein Fehler aufgetreten"
+ FILE_ERROR_UPLOAD: "Beim Versuch, die Dateien hochzuladen, ist ein Fehler aufgetreten"
+ FILE_UNSUPPORTED: "Nicht unterstützter Dateityp"
+ ADD_ITEM: "Eintrag hinzufügen"
+ FILE_TOO_LARGE: "Die Datei ist zu groß für den Upload, maximal zulässig sind %s entsprechend Ihrer PHP-Einstellungen. Erhöhen Sie die Einstellung von 'post_max_size'"
+ INSTALLING: "Installiere"
+ LOADING: "Laden..."
+ DEPENDENCIES_NOT_MET_MESSAGE: "Die folgenden Abhängigkeiten müssen zuerst erfüllt sein:"
+ ERROR_INSTALLING_PACKAGES: "Fehler während der Paketinstallation"
+ INSTALLING_DEPENDENCIES: "Installiere Abhängigkeiten…"
+ INSTALLING_PACKAGES: "Installiere Paket(e)…"
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Paket(e) erfolgreich installiert."
+ READY_TO_INSTALL_PACKAGES: "Bereit das/die Paket(e) zu installieren"
+ PACKAGES_NOT_INSTALLED: "Pakete nicht installiert"
+ PACKAGES_NEED_UPDATE: "Pakete bereits installiert, aber zu alt"
+ PACKAGES_SUGGESTED_UPDATE: "Pakete bereits installiert und die Version ist ok, werden aber trotzdem installiert um sie aktuell zu halten"
+ REMOVE_THE: "Entferne %s"
+ CONFIRM_REMOVAL: "Sind sie sicher, dass sie %s löschen möchten?"
+ REMOVED_SUCCESSFULLY: "%s erfolgreich entfernt"
+ ERROR_REMOVING_THE: "Fehler beim Entfernen der %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "Das %s benötigt die folgenden Abhängigkeiten, die nicht von anderen installierten Pakete benötigt werden. Wenn Sie diese nicht verwenden, können Sie direkt hier entfernt werden."
+ READY_TO_UPDATE_PACKAGES: "Bereit das/die Paket(e) zu aktualisieren"
+ ERROR_UPDATING_PACKAGES: "Fehler beim Aktualisieren des/der Pakete(s)"
+ UPDATING_PACKAGES: "Aktualisiere Paket(e) ..."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Paket(e) erfolgreich aktualisiert."
+ UPDATING: "Aktualisiere"
+ GPM_RELEASES: "GPM-Releases"
+ GPM_RELEASES_HELP: "Wählen Sie 'Testbetrieb' um Beta- oder Test-Versionen zu installieren"
+ GPM_METHOD: "Methode um entfernte Inhalte zu laden"
+ GPM_METHOD_HELP: "Wenn auf Auto gestellt, wird Grav feststellen ob PHP fopen Funktion verfügbar ist und diese nutzen, ansonsten cURL. Um die Nutzung einer der beiden Optionen zu erzwingen bitten den Schalter umstellen."
+ GPM_VERIFY_PEER: "Zertifikatsüberprüfung externer Server (SSL)"
+ GPM_VERIFY_PEER_HELP: "Einige Provider scheinen an der Überprüfung des getgrav.org SSL Zertifikates zu scheitern. Dies führt dazu das GPM nicht funktioniert. Ist dies der Fall, hilft vielleicht das abstellen dieser Einstellung"
+ AUTO: "Automatisch"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stabil"
+ TESTING: "Testbetrieb"
+ FRONTMATTER_PROCESS_TWIG: "Verarbeite Frontmatter Twig"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Wenn aktiviert können Twig Konfigurationsvariablen im Seiten Frontmatter verwendet werden"
+ FRONTMATTER_IGNORE_FIELDS: "Ignoriere Frontmatter Felder"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Bestimmte Frontmatter Felder können Twig enthalten sollten aber möglicherweise nicht verarbeitet werden, z. B. \"Formulare\""
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Paket %s erfolgreich installiert"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Übergeordnete Sortiereinstellung, Sortierung deaktiviert"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Seite ist nicht sichtbar, Sortierung deaktiviert"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "Sortierung via Admin wird nicht unterstützt, weil es mehr als 200 Elemente auf gleicher Ebene gibt"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Die Sortierung ist für diese Seite deaktiviert, weil Numerisches Ordner Präfix nicht aktiviert ist"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "Hinweis: Sie können keine Medien-Dateien hochladen bis die Seite gespeichert ist. Klicken Sie dazu \"Speichern\" oben rechts."
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "Hinweis: Seite muss gespeichert werden, bevor Sie Dateien hochladen können."
+ DROP_FILES_HERE_TO_UPLOAD: "Ziehen Sie Dateien hierauf oder klicken Sie in diesem Bereich "
+ INSERT: "Einfügen"
+ UNDO: "Rückgängigmachen"
+ REDO: "Wiederholen"
+ HEADERS: "Überschriften"
+ BOLD: "Fett gedruckt"
+ ITALIC: "Kursiv"
+ STRIKETHROUGH: "Durchgestrichen"
+ SUMMARY_DELIMITER: "Trennzeichen für Zusammenfassung"
+ LINK: "Link"
+ IMAGE: "Bild"
+ BLOCKQUOTE: "Zitat"
+ UNORDERED_LIST: "Ungeordnete Liste"
+ ORDERED_LIST: "Geordnete Liste"
+ EDITOR: "Editor"
+ PREVIEW: "Vorschau"
+ FULLSCREEN: "Vollbild"
+ MODULAR: "Modular"
+ MODULAR_SETUP: "Modularer Aufbau"
+ NON_ROUTABLE: "Nicht aufrufbar"
+ NON_MODULAR: "Nicht modular"
+ NON_VISIBLE: "Nicht sichtbar"
+ NON_PUBLISHED: "Nicht veröffentlicht"
+ CHARACTERS: "Zeichen"
+ PUBLISHING: "Veröffentlichen"
+ NOTIFICATIONS: "Benachrichtigungen"
+ MEDIA_TYPES: "Medien-Typen"
+ IMAGE_OPTIONS: "Bildoptionen"
+ MIME_TYPE: "MIME-Typ"
+ THUMB: "Vorschaubild"
+ TYPE: "Typ"
+ FILE_EXTENSION: "Dateiendung"
+ LEGEND: "Seitenlegende"
+ MEMCACHE_SERVER: "Memcache Server"
+ MEMCACHE_SERVER_HELP: "Die Memcache-Server-Adresse"
+ MEMCACHE_PORT: "Memcache Port"
+ MEMCACHE_PORT_HELP: "Der Memcache-Server Port"
+ MEMCACHED_SERVER: "Memcached server"
+ MEMCACHED_SERVER_HELP: "Die Memcache-Server-Adresse"
+ MEMCACHED_PORT: "Memcache Port"
+ MEMCACHED_PORT_HELP: "Der Memcache-Server Port"
+ REDIS_SERVER: "Redis server"
+ REDIS_SERVER_HELP: "Die Redis-Server-Adresse"
+ REDIS_PORT: "Redis port"
+ REDIS_PORT_HELP: "Der Redis-Server Port"
+ REDIS_PASSWORD: "Redis Passwort/Geheimnis"
+ ALL: "Alle"
+ FROM: "von"
+ TO: "an"
+ RELEASE_DATE: "Veröffentlichungsdatum"
+ SORT_BY: "Sortieren nach"
+ RESOURCE_FILTER: "Filtere..."
+ FORCE_SSL: "SSL erzwingen"
+ FORCE_SSL_HELP: "Globales SSL erzwingen, wenn aktiviert sendet Grav für eine HTTP Anfrage eine Weiterleitung zur HTTPS-Seite"
+ NEWS_FEED: "Neuigkeiten"
+ EXTERNAL_URL: "Externe URL"
+ SESSION_PATH: "Der Session-Pfad"
+ SESSION_PATH_HELP: "Nur verwenden, wenn Sie eine benutzerdefinierte Base-URL nutzen (rewrite der Site-Domain / Unterordner)"
+ CUSTOM_BASE_URL: "Benutzerdefinierte Base-URL"
+ CUSTOM_BASE_URL_HELP: "Verwenden Sie diese Einstellung, wenn ein Rewrite der Site-Domain durchgeführt oder ein anderer Unterordner als der von Grav verwendete genutzt werden soll. Beispiel: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'Kann "%s" nicht außerhalb von Seiten verwenden.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Kann Datei nicht hochladen %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'Kann Datei %s nicht nach "%s" verschieben'
+ DROPZONE_CANCEL_UPLOAD: 'Upload abbrechen'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Sind Sie sicher, dass Sie den Upload abbrechen wollen?'
+ DROPZONE_DEFAULT_MESSAGE: 'Ziehen Sie Dateien hierher oder klicken Sie in diesem Bereich '
+ DROPZONE_FALLBACK_MESSAGE: 'Ihr Browser unterstützt Drag und Drop Datei-Uploads nicht.'
+ DROPZONE_FALLBACK_TEXT: 'Bitte nutzen Sie das untenstehende Formular um Dateien wie in alten Zeiten hochzuladen.'
+ DROPZONE_FILE_TOO_BIG: 'Die Datei ist zu groß ({{filesize}}MiB). Maximale Dateigröße: {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "Sie können Dateien dieses Typs nicht hochladen."
+ DROPZONE_MAX_FILES_EXCEEDED: "Sie können keine weiteren Dateien hochladen."
+ DROPZONE_REMOVE_FILE: "Datei entfernen"
+ DROPZONE_RESPONSE_ERROR: "Server antwortete mit Statuscode {{statusCode}}."
+ PREMIUM_PRODUCT: "Premium"
+ ADMIN_CACHING: "Aktivere Admin Caching"
+ ADMIN_CACHING_HELP: "Caching für den Admin-Bereich kann unabhängig vom Front-End kontrolliert werden"
+ DESTINATION_NOT_SPECIFIED: "Ziel nicht angegeben"
+ UPLOAD_ERR_NO_TMP_DIR: "Ordner für temporäre Dateien fehlt"
+ SESSION_SPLIT: "Geteilte Session"
+ SESSION_SPLIT_HELP: "Unabhängig geteilte Sessions zwischen der Site und anderen Plugins (wie zum Beispiel Admin)"
+ ERROR_FULL_BACKTRACE: "Voller Backtrace Fehler"
+ ERROR_SIMPLE: "Einfacher Fehler"
+ ERROR_SYSTEM: "Systemfehler"
+ IMAGES_AUTO_FIX_ORIENTATION: "Bildausrichtung automatisch beheben"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Bildausrichtung anhand der Exif-Daten automatisch beheben"
+ REDIS_SOCKET: "Redis Socket"
+ REDIS_SOCKET_HELP: "Der Redis Socket"
+ NOT_SET: "Nicht eingestellt"
+ PERMISSIONS: "Zugriffsrechte"
+ NEVER_CACHE_TWIG: "Twig niemals chachen"
+ NEVER_CACHE_TWIG_HELP: "Nur Inhalte zwischenspeichern und Twig für Seiten jedes Mal verarbeiten. Ignoriert die Einstellung twig_first."
+ ALLOW_WEBSERVER_GZIP: "Webserver Gzip erlauben"
+ ALLOW_WEBSERVER_GZIP_HELP: "Ist standardmäßig deaktiviert. Wenn diese Option aktiviert ist, wird die WebServer konfigurierte Gzip/Deflate-Komprimierung verwendet, aber die HTTP-Verbindung wird nicht vor dem onShutDown() Ereignis geschlossen, was ein langsameres laden der Seite bedeutet"
+ OFFLINE_WARNING: "Es kann keine Verbindung zu GPM hergestellt werden"
+ CLEAR_IMAGES_BY_DEFAULT: "Standardmässig wird der image cache gelöscht"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "Standardmäßig werden bearbeitete Bilder bei alle Cache-Löschungen gelöscht, dies kann deaktiviert werden"
+ CLI_COMPATIBILITY: "CLI Kompatibilität"
+ CLI_COMPATIBILITY_HELP: "Sorgt dafür, dass nur nicht-flüchtige Cache Treiber verwendet werden (Datei, Redis, Memcache, etc.)"
+ REINSTALL_PLUGIN: "Plugin erneut installieren"
+ REINSTALL_THEME: "Theme erneut installieren"
+ REINSTALL_THE: "%s erneut installieren"
+ CONFIRM_REINSTALL: "Sind sie sicher, dass sie %s erneut installieren möchten?"
+ REINSTALLED_SUCCESSFULLY: "%s erfolgreich erneut installiert"
+ ERROR_REINSTALLING_THE: "Fehler bei der erneuten Installation von %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Paket %s erfolgreich erneut installiert"
+ REINSTALLATION_FAILED: "Erneute Installation fehlgeschlagen"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "Die installierte Version ist nicht die neueste verfügbare Version. Durch Klicken auf Weiter, entfernen Sie die aktuelle Version und installieren Sie die neueste verfügbare Version"
+ TOOLS: "Werkzeuge"
+ DIRECT_INSTALL: "Direkte Installation"
+ NO_PACKAGE_NAME: "Name des Pakets nicht angegeben"
+ PACKAGE_EXTRACTION_FAILED: "Paket konnte nicht ausgepackt werden"
+ NOT_VALID_GRAV_PACKAGE: "Kein gültiges Grav-Paket"
+ NAME_COULD_NOT_BE_DETERMINED: "Name konnte nicht ermittelt werden"
+ CANNOT_OVERWRITE_SYMLINKS: "Symbolische Links können nicht überschrieben werden"
+ ZIP_PACKAGE_NOT_FOUND: "ZIP Archiv wurde nicht gefunden"
+ GPM_OFFICIAL_ONLY: "Nur offizielle GPM Server"
+ GPM_OFFICIAL_ONLY_HELP: "Erlauben Sie direkte Installationen nur aus dem offiziellen GPM-Repository."
+ NO_CHILD_TYPE: "Kein Unterseitentyp für diese Rawroute"
+ SORTABLE_PAGES: "Sortierbare Seiten:"
+ UNSORTABLE_PAGES: "Nicht sortierbare Seiten"
+ ADMIN_SPECIFIC_OVERRIDES: "Administrative Anpassungen"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Sortierung der Unterseiten"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "Die Reihenfolge, die vom Admin Plugin verwendet werden soll um Unterseiten in der \"Seiten\" Ansicht anzuzeigen"
+ PWD_PLACEHOLDER: "komplexe Zeichenfolge mindestens 8 Zeichen lang"
+ PWD_REGEX: "Regulärer Ausdruck für Passwörter"
+ PWD_REGEX_HELP: "Standard: Das Passwort muss mindestens eine Zahl, einen Groß- und einen Kleinbuchstaben enthalten sowie mindestens 8 Zeichen lang sein"
+ USERNAME_PLACEHOLDER: "nur Kleinbuchstaben, z.B. 'admin'"
+ USERNAME_REGEX: "Regulärer Ausdruck für Benutzernamen"
+ USERNAME_REGEX_HELP: "Standard: nur Kleinbuchstaben, Ziffern, Bindestriche und Unterstriche. 3 - 16 Zeichen"
+ CONTENT_PADDING: "Abstand um Inhalte"
+ CONTENT_PADDING_HELP: "Abstand um Inhalte aktivieren/deaktivieren um mehr Platz anzubieten"
+ ENABLE_AUTO_METADATA: "Auto-Metadaten aus Exif"
+ ENABLE_AUTO_METADATA_HELP: "Automatische Generierung von Metadaten-Dateien für Bilder mit Exif-Informationen"
+ 2FA_TITLE: "2-Faktor-Authentifizierung"
+ 2FA_INSTRUCTIONS: "##### 2-Faktor-Authentifizierung\nSie haben **2FA** für dieses Konto aktiviert. Bitte benutzen Sie Ihre **2FA** App, um den aktuellen **6-stelligen Code** einzugeben, um den Anmeldevorgang abzuschließen."
+ 2FA_LABEL: "Admin Zugriff"
+ 2FA_FAILED: "Ungültiger 2-Faktor-Authentifizierungscode, bitte versuchen Sie es erneut..."
+ 2FA_ENABLED: "Zwei-Faktor-Authentifizierung aktiviert"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "Zwei-Faktor-Authentifizierung Geheimnis"
+ 2FA_REGENERATE: "Erneuern"
+ FORCE_LOWERCASE_URLS: "Erzwingen von Kleinbuchstaben-URLs"
+ INTL_ENABLED: "Intl Modulintegration"
+ VIEW_SITE_TIP: "Seite anzeigen"
+ TOOLS_DIRECT_INSTALL_TITLE: "Direktinstallation von Grav-Paketen"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "Paket mittels \"Direct ZIP Upload\" installieren"
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "Paket mittels \"Remote URL Reference\" installieren"
+ TOOLS_DIRECT_INSTALL_URL_DESC: "Alternativ können Sie auch eine vollständige URL zu der gezippten Paketdatei angeben um das Paket über eine Fremd-URL zu installieren."
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Hochladen und installieren"
+ ROUTE_DEFAULT: "Standardroute"
+ ROUTE_ALIASES: "Routen-Aliase"
+ OPEN_NEW_TAB: "In neuem Tab öffnen"
+ SESSION_INITIALIZE: "Session initialisieren"
+ STRICT_YAML_COMPAT: "YAML Kompatibilität"
+ STRICT_TWIG_COMPAT: "Twig Kompatibilität"
+ SCHEDULER: "Planer"
+ SCHEDULER_INSTALL_INSTRUCTIONS: "Installationsanleitung"
+ SCHEDULER_INSTALLED_READY: "Installiert und bereit"
+ SCHEDULER_CRON_NA: "Cron ist nicht verfügbar"
+ SCHEDULER_NOT_ENABLED: "Nicht aktiviert"
+ SCHEDULER_SETUP: "Planer-Setup"
+ SCHEDULER_STATUS: "Planer-Status"
+ SCHEDULER_RUNAT: "Starten um"
+ SCHEDULER_EMAIL: "E-Mail"
+ SECURITY: "Sicherheit"
+ UPLOADS_DANGEROUS_EXTENSIONS: "Gefährliche Erweiterungen"
+ REPORTS: "Berichte"
+ LOGS: "Logs"
+ BACKUPS_MAX_COUNT: "Maximale Anzahl an Sicherungen"
+ BACKUPS_MAX_RETENTION_TIME: "Maximale Gesamtzeit"
+ BACKUPS_MAX_RETENTION_TIME_APPEND: "in Tagen"
+ BACKUPS_PROFILE_NAME: "Sicherungsname"
+ BACKUPS_PROFILE_ROOT_FOLDER: "Stammverzeichnis"
+ BACKUPS_PROFILE_ROOT_FOLDER_HELP: "Kann ein absoluter Pfad oder ein Stream sein"
+ BACKUPS_PROFILE_EXCLUDE_PATHS: "Pfade ausschließen"
+ BACKUPS_PROFILE_EXCLUDE_PATHS_HELP: "Absolute Pfade zum Ausschließen, einer pro Zeile"
+ BACKUPS_PROFILE_EXCLUDE_FILES: "Dateien ausschließen"
+ BACKUPS_PROFILE_EXCLUDE_FILES_HELP: "Bestimmte Dateien oder Ordner, die ausgeschlossen werden sollen, eine pro Zeile"
+ BACKUPS_PROFILE_SCHEDULE: "Geplanten Job aktivieren"
+ BACKUPS_PROFILE_SCHEDULE_AT: "Geplanten Job ausführen"
+ COMMAND: "Befehl"
+ EXTRA_ARGUMENTS: "Zusätzliche Argumente"
diff --git a/user/plugins/admin/languages/el.yaml b/user/plugins/admin/languages/el.yaml
new file mode 100644
index 0000000..6f18d94
--- /dev/null
+++ b/user/plugins/admin/languages/el.yaml
@@ -0,0 +1,710 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Αυτή είναι δοκιμαστική έκδοση (beta)! Χρησιμοποιήστε την στην παραγωγή με δική σας ευθύνη..."
+ ADMIN_REPORT_ISSUE: "Βρήκατε κάποιο πρόβλημα; Παρακαλείστε να το αναφέρετε στο GitHub."
+ EMAIL_FOOTER: " με την ισχύ του Grav - Το μοντέρνο CMS χωρίς τη χρήση Βάσης Δεδομένων"
+ LOGIN_BTN: "Σύνδεση"
+ LOGIN_BTN_FORGOT: "Το ξέχασα"
+ LOGIN_BTN_RESET: "Επαναφορά συνθηματικού"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Αποστολή οδηγιών επαναφοράς"
+ LOGIN_BTN_CLEAR: "Καθαρισμός Φόρμας"
+ LOGIN_BTN_CREATE_USER: "Δημιουργία χρήστη"
+ LOGIN_LOGGED_IN: "Έχετε συνδεθεί με επιτυχία"
+ LOGIN_FAILED: "Αποτυχία Σύνδεσης"
+ LOGGED_OUT: "Έχετε αποσυνδεθεί"
+ RESET_NEW_PASSWORD: "Παρακαλούμε εισαγάγετε νέο συνθηματικό …"
+ RESET_LINK_EXPIRED: "Ο σύνδεσμος επαναφοράς έχει λήξει, παρακαλώ προσπαθήστε ξανά"
+ RESET_PASSWORD_RESET: "Έχει γίνει επαναφορά του συνθηματικού"
+ RESET_INVALID_LINK: "Ο σύνδεσμος γιά την επαναφορά δεν είναι έγκυρος, παρακαλώ προσπαθήστε πάλι"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Οι οδηγίες για να επαναφέρετε το συνθηματικό σας έχουν σταλεί στη διεύθυνση ηλεκτρονικού σας ταχυδρομείου"
+ FORGOT_FAILED_TO_EMAIL: "Απέτυχε η αποστολή οδηγιών με email, παρακαλώ προσπαθήστε ξανά αργότερα"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Δεν είναι δυνατή η επαναφορά του συνθηματικού για %s, δεν έχει καθοριστεί διεύθυνση ηλεκτρονικού ταχυδρομείου"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Χρήστης με όνομα χρήστη %s δεν υπάρχει"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Δεν είναι δυνατή η επαναφορά του συνθηματικού σας. Αυτός ο ιστότοπος δεν έχει ρυθμιστεί για αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου"
+ FORGOT_EMAIL_SUBJECT: "%s Αίτηση Επαναφοράς Κωδικού Πρόσβασης"
+ FORGOT_EMAIL_BODY: "Επαναφορά συνθηματικού Αγαπητέ/ή %1$s,
Έγινε μία αίτηση στις %4$s για επαναφορά του συνθηματικού σας.
< br / > Κάντε κλικ εδώ για να επαναφέρετε το συνθηματικό σας < br / > < br / >
Εναλλακτικά, αντιγράψτε την ακόλουθη διεύθυνση URL στη γραμμή διευθύνσεων του προγράμματος περιήγησής σας:
%2$s
< br / > Με εκτίμηση, < br / > < br / >%3$s
"
+ MANAGE_PAGES: "Διαχείριση σελίδων"
+ CONFIGURATION: "Διαμόρφωση"
+ PAGES: "Σελίδες"
+ PLUGINS: "Πρόσθετα"
+ PLUGIN: "Πρόσθετο"
+ THEMES: "Θέματα"
+ LOGOUT: "Αποσύνδεση"
+ BACK: "Επιστροφή"
+ NEXT: "Επόμενο"
+ PREVIOUS: "Προηγούμενο"
+ ADD_PAGE: "Προσθήκη σελίδας"
+ ADD_MODULAR: "Προσθέστε Modular"
+ MOVE: "Μετακίνηση"
+ DELETE: "Διαγραφή"
+ VIEW: "Προβολή"
+ SAVE: "Αποθήκευση"
+ NORMAL: "Κανονικό"
+ EXPERT: "Ειδικός"
+ EXPAND_ALL: "Ανάπτυξη Όλων"
+ COLLAPSE_ALL: "Σύμπτυξη όλων"
+ ERROR: "Σφάλμα"
+ CLOSE: "Κλείσιμο"
+ CANCEL: "Ακύρωση"
+ CONTINUE: "Συνέχεια"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Απαιτείται Επιβεβαίωση"
+ MODAL_CHANGED_DETECTED_TITLE: "Εντοπίστηκαν Αλλαγές"
+ MODAL_CHANGED_DETECTED_DESC: "Έχετε μη αποθηκευμένες αλλαγές. Είστε βέβαιοι ότι θέλετε να φύγετε χωρίς να γίνει αποθήκευση;"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Απαιτείται Επιβεβαίωση"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το αρχείο; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί."
+ ADD_FILTERS: "Προσθήκη φίλτρων"
+ SEARCH_PAGES: "Αναζήτηση στις σελίδες"
+ VERSION: "Έκδοση"
+ WAS_MADE_WITH: "Δημιουργήθηκε με"
+ BY: "Από"
+ UPDATE_THEME: "Ενημέρωση Θέματος"
+ UPDATE_PLUGIN: "Ενημέρωση Προσθέτου"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "αυτού του θέματος είναι τώρα διαθέσιμη"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "αυτού του πρόσθετου είναι τώρα διαθέσιμη"
+ AUTHOR: "Συντάκτης"
+ HOMEPAGE: "Αρχική σελίδα"
+ DEMO: "Επίδειξη"
+ BUG_TRACKER: "Παρακολούθηση Σφαλμάτων"
+ KEYWORDS: "Λέξεις κλειδιά"
+ LICENSE: "Άδεια Χρήσης"
+ DESCRIPTION: "Περιγραφή"
+ README: "Διάβασέ με"
+ REMOVE_THEME: "Κατάργηση Θέματος"
+ INSTALL_THEME: "Εγκατάσταση Θέματος"
+ THEME: "Θέμα"
+ BACK_TO_THEMES: "Πίσω στα Θέματα"
+ BACK_TO_PLUGINS: "Πίσω στα Πρόσθετα"
+ CHECK_FOR_UPDATES: "Ελέγχος για Ενημερώσεις"
+ ADD: "Προσθήκη"
+ CLEAR_CACHE: "Εκκαθάριση προσωρινής μνήμης"
+ CLEAR_CACHE_ALL_CACHE: "Όλης της Cache"
+ CLEAR_CACHE_ASSETS_ONLY: "Μόνον οι διαθέσιμοι πόροι"
+ CLEAR_CACHE_IMAGES_ONLY: "Μόνον των Εικόνων"
+ CLEAR_CACHE_CACHE_ONLY: "Μόνον τη μνήμη cache"
+ CLEAR_CACHE_TMP_ONLY: "Tmp μόνο"
+ DASHBOARD: "Πίνακας Ελέγχου"
+ UPDATES_AVAILABLE: "Υπάρχουν Διαθέσιμες Ενημερώσεις"
+ DAYS: "Ημέρες"
+ UPDATE: "Ενημέρωση"
+ BACKUP: "Δημιουργία αντιγράφου ασφαλείας"
+ STATISTICS: "Στατιστικά"
+ TODAY: "Σήμερα"
+ WEEK: "Εβδομάδα"
+ MONTH: "Μήνας"
+ LATEST_PAGE_UPDATES: "Πιο πρόσφατες ενημερώσεις της σελίδας"
+ MAINTENANCE: "Συντήρηση"
+ UPDATED: "Ενημερωμένο"
+ MON: "Δευ"
+ TUE: "Τρί"
+ WED: "Τετ"
+ THU: "Πέμ"
+ FRI: "Παρ"
+ SAT: "Σάβ"
+ SUN: "Κυρ"
+ COPY: "Αντιγραφή"
+ EDIT: "Επεξεργασία"
+ CREATE: "Δημιουργία"
+ GRAV_ADMIN: "Διαχειριστής του Grav"
+ GRAV_OFFICIAL_PLUGIN: "Επίσημο πρόσθετο του Grav"
+ GRAV_OFFICIAL_THEME: "Επίσημο Θέμα του Grav"
+ PLUGIN_SYMBOLICALLY_LINKED: "Αυτό το πρόσθετο είναι συμβολικά συνδεδεμένο. Οποιεσδήποτε ενημερώσεις δεν θα εντοπιστούν."
+ THEME_SYMBOLICALLY_LINKED: "Αυτό το θέμα είναι συμβολικά συνδεδεμένο. Οποιεσδήποτε ενημερώσεις δεν θα εντοπιστούν"
+ REMOVE_PLUGIN: "Αφαιρέστε το Πρόσθετο"
+ INSTALL_PLUGIN: "Εγκατάσταση Πρόσθετου"
+ AVAILABLE: "Διαθέσιμα"
+ INSTALLED: "Εγκατεστημένα"
+ INSTALL: "Εγκατάσταση"
+ ACTIVE_THEME: "Ενεργό Θέμα"
+ SWITCHING_TO: "Μετάβαση σε"
+ SWITCHING_TO_DESCRIPTION: "Εάν αλλάξετε το θέμα, δεν υπάρχει καμία εγγύηση ότι θα υποστηρίζονται όλα τα layouts σελίδων, ενδεχομένως προκαλώντας σφάλματα όταν θα φορτώσουν οι σελίδες αυτές."
+ SWITCHING_TO_CONFIRMATION: "Θέλετε να συνεχίσετε και να μεταβείτε στο θέμα"
+ CREATE_NEW_USER: "Δημιουργία νέου χρήστη"
+ REMOVE_USER: "Διαγραφή χρήστη"
+ ACCESS_DENIED: "Απαγορεύεται η Πρόσβαση"
+ ACCOUNT_NOT_ADMIN: "ο λογαριασμός σας δεν έχει δικαιώματα διαχειριστή"
+ PHP_INFO: "Πληροφορίες της PHP"
+ INSTALLER: "Πρόγραμμα εγκατάστασης"
+ AVAILABLE_THEMES: "Διαθέσιμα Θέματα"
+ AVAILABLE_PLUGINS: "Διαθέσιμα πρόσθετα"
+ INSTALLED_THEMES: "Εγκατεστημένα Θέματα"
+ INSTALLED_PLUGINS: "Εγκατεστημένα πρόσθετα"
+ BROWSE_ERROR_LOGS: "Περιήγηση στα αρχεία σφαλμάτων"
+ SITE: "Ιστότοπος"
+ INFO: "Πληροφορίες"
+ SYSTEM: "Σύστημα"
+ USER: "Χρήστης"
+ ADD_ACCOUNT: "Προσθήκη λογαριασμού"
+ SWITCH_LANGUAGE: "Εναλλαγή γλώσσας"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Το πρόσθετο ενεργοποιήθηκε με επιτυχία"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Το πρόσθετο απενεργοποιήθηκε με επιτυχία"
+ SUCCESSFULLY_CHANGED_THEME: "Έγινε επιτυχής αλλαγή του προεπιλεγμένου θέματος"
+ INSTALLATION_FAILED: "Η εγκατάσταση απέτυχε"
+ INSTALLATION_SUCCESSFUL: "Επιτυχής εγκατάσταση"
+ UNINSTALL_FAILED: "Η απεγκατάσταση έχει αποτύχει"
+ UNINSTALL_SUCCESSFUL: "Η απεγκατάσταση έγινε με επιτυχία"
+ SUCCESSFULLY_SAVED: "Επιτυχής αποθήκευση"
+ SUCCESSFULLY_COPIED: "Επιτυχής αντιγραφή"
+ REORDERING_WAS_SUCCESSFUL: "Η αναδιάταξη ήταν επιτυχής"
+ SUCCESSFULLY_DELETED: "Επιτυχής διαγραφή"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Επιτυχία στην αλλαγή γλώσσας"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Έχετε ανεπαρκή δικαιώματα για την εργασία"
+ CACHE_CLEARED: "Η προσωρινή μνήμη καθάρισε"
+ METHOD: "Μέθοδος"
+ ERROR_CLEARING_CACHE: "Σφάλμα κατά την εκκαθάριση της μνήμης cache"
+ AN_ERROR_OCCURRED: "Παρουσιάστηκε ένα σφάλμα"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Το αντίγραφο ασφαλείας είναι έτοιμο για λήψη"
+ DOWNLOAD_BACKUP: "Λήψη αντιγράφου ασφαλείας"
+ PAGES_FILTERED: "Σελίδες που φιλτραρίστηκαν"
+ NO_PAGE_FOUND: "Δεν βρέθηκαν σελίδες"
+ INVALID_PARAMETERS: "Μη έγκυρες παράμετροι"
+ NO_FILES_SENT: "Δεν απεστάλη κανένα αρχείο"
+ EXCEEDED_FILESIZE_LIMIT: "Υπέρβαση ορίου μεγέθους αρχείων ρύθμισης παραμέτρων της PHP"
+ UNKNOWN_ERRORS: "Άγνωστο Σφάλμα"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Το αρχείο υπερβαίνει το προκαθορισμένο όριο μεγέθους"
+ UNSUPPORTED_FILE_TYPE: "Μη υποστηριζόμενος τύπος αρχείου"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Απέτυχε η μετακίνηση του μεταφορτωμένου αρχείου."
+ FILE_UPLOADED_SUCCESSFULLY: "Το αρχείο μεταφορτώθηκε με επιτυχία"
+ FILE_DELETED: "Το αρχείο διεγράφη"
+ FILE_COULD_NOT_BE_DELETED: "Δεν ήταν δυνατή η διαγραφή του αρχείου"
+ FILE_NOT_FOUND: "Το αρχείο δεν βρέθηκε"
+ NO_FILE_FOUND: "Δεν βρέθηκαν αρχεία"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Το Grav ενημερώθηκε επιτυχώς σε"
+ GRAV_UPDATE_FAILED: "Η ενημέρωση του Grav απέτυχε"
+ EVERYTHING_UPDATED: "Τα πάντα έχουν ενημερωθεί"
+ UPDATES_FAILED: "Αποτυχία ενημέρωσης"
+ AVATAR_BY: "Avatar από"
+ AVATAR_UPLOAD_OWN: "Ή ανέβασε το δικό σου avatar..."
+ LAST_BACKUP: "Τελευταία δημιουργία αντιγράφων ασφαλείας"
+ FULL_NAME: "Ονοματεπώνυμο"
+ USERNAME: "Όνομα χρήστη"
+ EMAIL: "Διεύθυνση ηλεκτρονικού ταχυδρομείου"
+ USERNAME_EMAIL: "Όνομα χρήστη ή διεύθυνση ηλεκτρονικού ταχυδρομείου"
+ PASSWORD: "Συνθηματικό"
+ PASSWORD_CONFIRM: "Επιβεβαίωση συνθηματικού"
+ TITLE: "Τίτλος"
+ LANGUAGE: "Γλώσσα"
+ ACCOUNT: "Λογαριασμός"
+ EMAIL_VALIDATION_MESSAGE: "Πρέπει να είναι μια έγκυρη διεύθυνση email"
+ PASSWORD_VALIDATION_MESSAGE: "Το συνθηματικό πρέπει να περιέχει τουλάχιστον έναν αριθμό, ένα κεφαλαίο και ένα πεζό γράμμα και τουλάχιστον 8 ή περισσότερους χαρακτήρες"
+ LANGUAGE_HELP: "Ορίστε την αγαπημένη γλώσσα"
+ MEDIA: "Πολυμέσα"
+ DEFAULTS: "Προεπιλογές"
+ SITE_TITLE: "Τίτλος Ιστοχώρου"
+ SITE_TITLE_PLACEHOLDER: "Τίτλος σε όλο τον ιστοχώρο"
+ SITE_TITLE_HELP: "Προεπιλεγμένος τίτλος για το site σας, χρησιμοποιείται συχνά στα θέματα"
+ SITE_DEFAULT_LANG: "Προεπιλεγμένη γλώσσα"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Προεπιλεγμένη γλώσσα που θα χρησιμοποιείται από tag του θέματος"
+ SITE_DEFAULT_LANG_HELP: "Προεπιλεγμένη γλώσσα που θα χρησιμοποιείται από tag του θέματος"
+ DEFAULT_AUTHOR: "Προεπιλεγμένος συντάκτης"
+ DEFAULT_AUTHOR_HELP: "Ένα προεπιλεγμένο όνομα συντάκτη, που χρησιμοποιείται συχνά στα θέματα ή στο περιεχόμενο της σελίδας"
+ DEFAULT_EMAIL: "Προεπιλεγμένη διεύθυνση ηλεκτρονικού ταχυδρομείου"
+ DEFAULT_EMAIL_HELP: "Μία ηλεκτρονική διεύθυνση email για να χρησιμοποιηθεί σε θέματα ή σελίδες"
+ TAXONOMY_TYPES: "Τύποι ταξινομίας"
+ TAXONOMY_TYPES_HELP: "Τύποι ταξινόμησης πρέπει να οριστούν εδώ εάν θέλετε να τους χρησιμοποιήσετε σε σελίδες"
+ PAGE_SUMMARY: "Περίληψη Σελίδας"
+ ENABLED: "Ενεργοποιημένη"
+ ENABLED_HELP: "Ενεργοποίηση σύνοψης σελίδας (η σύνοψη επιστρέφει το ίδιο με το περιεχόμενο της σελίδας)"
+ 'YES': "Ναι"
+ 'NO': "Όχι"
+ SUMMARY_SIZE: "Μέγεθος Περίληψης"
+ SUMMARY_SIZE_HELP: "Το πλήθος των χαρακτήρων μίας σελίδας που θα χρησιμοποιηθεί για τη σύνοψη της σελίδας"
+ FORMAT: "Μορφοποίηση"
+ FORMAT_HELP: "short = χρησιμοποίησε την πρώτη εμφάνιση του delimiter ή την υπέρβαση του πλήθους χαρακτήρων; long = η σύνοψη θα αγνοηθεί"
+ SHORT: "Σύντομη"
+ LONG: "Εκτεταμένη"
+ DELIMITER: "Διαχωριστικό"
+ DELIMITER_HELP: "Ο οριοθέτης σύνοψης (προεπιλογή ' ===')"
+ METADATA: "Μεταδεδομένα"
+ METADATA_HELP: "Προεπιλεγμένες τιμές μεταδεδομένων που θα εμφανίζονται σε κάθε σελίδα εκτός εάν παρακάμπτονται από την εκάστοτε σελίδα"
+ NAME: "Όνομα"
+ CONTENT: "Περιεχόμενο"
+ REDIRECTS_AND_ROUTES: "Ανακατευθύνσεις και Διαδρομές"
+ CUSTOM_REDIRECTS: "Προσαρμοσμένες Ανακατευθύνσεις"
+ CUSTOM_REDIRECTS_HELP: "διαδρομές για την ανακατεύθυνση σε άλλες σελίδες. Η αντικατάσταση με μία κανονική παράσταση είναι έγκυρη"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/your/redirect"
+ CUSTOM_ROUTES: "Προσαρμοσμένες Διαδρομές"
+ CUSTOM_ROUTES_HELP: "διαδρομές για την ανακατεύθυνση σε άλλες σελίδες. Η αντικατάσταση με μία κανονική παράσταση είναι έγκυρη"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/your/route"
+ FILE_STREAMS: "File Streams"
+ DEFAULT: "Προεπιλογή"
+ PAGE_MEDIA: "Σελίδα Media"
+ OPTIONS: "Επιλογές"
+ PUBLISHED: "Δημοσιευμένο"
+ PUBLISHED_HELP: "Από προεπιλογή, μια σελίδα δημοσιεύεται κατευθείαν, εκτός αν ορίσετε την επιλογή published: false, εάν το publish_date αναφέρεται σε μελλοντική στιγμή, ή αν το unpublish_date αναφέρεται στο παρελθόν"
+ DATE: "Ημερομηνία"
+ DATE_HELP: "Η μεταβλητή date σας επιτρέπει να ορίσετε συγκεκριμένα μία ημερομηνία που να σχετίζεται με τη σελίδα αυτή."
+ PUBLISHED_DATE: "Ημερομηνία δημοσίευσης"
+ PUBLISHED_DATE_HELP: "Μπορεί να δοθεί μία ημερομηνία για την αυτόματη δημοσίευση της ανάρτησης."
+ UNPUBLISHED_DATE: "Ημερομηνία παύσης δημοσίευσης"
+ UNPUBLISHED_DATE_HELP: "Μπορεί να δοθεί μία ημερομηνία για την αυτόματη ιδιωτικοποίηση της ανάρτησης."
+ ROBOTS: "Ρομπότ"
+ TAXONOMIES: "Ταξινομίες"
+ TAXONOMY: "Ταξινομία"
+ ADVANCED: "Γιά Προχωρημένους"
+ SETTINGS: "Ρυθμίσεις"
+ FOLDER_NUMERIC_PREFIX: "Αριθμητικό Πρόθεμα Φακέλου"
+ FOLDER_NUMERIC_PREFIX_HELP: "Αριθμητικό πρόθεμα που παρέχει χειροκίνητη ταξινόμηση και υπονοεί την ορατότητα"
+ FOLDER_NAME: "Όνομα φακέλου"
+ FOLDER_NAME_HELP: "Το όνομα του φακέλου που θα χρησιμοποιηθεί στο σύστημα αρχείων γι αυτή τη σελίδα"
+ PARENT: "Γονικό"
+ DEFAULT_OPTION_ROOT: "-Ρίζα-"
+ DEFAULT_OPTION_SELECT: "-Επιλέξτε-"
+ DISPLAY_TEMPLATE: "Εμφάνιση Προτύπου"
+ DISPLAY_TEMPLATE_HELP: "Το τύπος της σελίδας που ορίζει ποιο twig template θα χρησιμοποιηθεί για να αποδοθεί η σελίδα"
+ BODY_CLASSES: "Body Classes"
+ ORDERING: "Ταξινόμηση"
+ PAGE_ORDER: "Σειρά Σελίδων"
+ OVERRIDES: "Παρακάμψεις"
+ MENU: "Μενού"
+ MENU_HELP: "Η συμβολοσειρά που θα χρησιμοποιηθεί σε ένα μενού. Εάν δεν έχει καθοριστεί, θα χρησιμοποιηθεί ο τίτλος."
+ SLUG: "Slug"
+ SLUG_HELP: "Η μεταβλητή slug σας επιτρέπει να ορίσετε συγκεκριμένα το sub-URL μίας σελίδας"
+ SLUG_VALIDATE_MESSAGE: "Ένα slug πρέπει να περιέχει μόνο πεζούς αλφαριθμητικούς χαρακτήρες και παύλες"
+ PROCESS: "Διαδικασία"
+ PROCESS_HELP: "Ελέγχει τον τρόπο που οι σελίδες δέχονται επεξεργασία. Μπορεί να ρυθμιστεί για την εκάστοτε σελίδα ή καθολικά"
+ DEFAULT_CHILD_TYPE: "Προεπιλεγμένος τύπος Child"
+ USE_GLOBAL: "Χρησιμοποίησε τη Global τιμή"
+ ROUTABLE: "Μπορεί να δρομολογηθεί"
+ ROUTABLE_HELP: "Εάν η σελίδα είναι προσβάσιμα από μια διεύθυνση URL"
+ CACHING: "Προσωρινή μνήμη αποθήκευσης"
+ VISIBLE: "Ορατή"
+ VISIBLE_HELP: "Προσδιορίζει αν μια σελίδα είναι ορατή στην πλοήγηση."
+ DISABLED: "Ανενεργή"
+ ITEMS: "Αντικείμενα"
+ ORDER_BY: "Ταξινόμιση κατά"
+ ORDER: "Ταξινόμηση"
+ FOLDER: "Φάκελος"
+ ASCENDING: "Αύξουσα"
+ DESCENDING: "Φθίνουσα"
+ ADD_MODULAR_CONTENT: "Προσθήκη σπονδυλωτού περιεχομένου"
+ PAGE_TITLE: "Τίτλος σελίδας"
+ PAGE_TITLE_HELP: "Ο τίτλος της σελίδας"
+ PAGE: "Σελίδα"
+ MODULAR_TEMPLATE: "Σπονδυλωτό πρότυπο"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Όνομα αρχείου"
+ PARENT_PAGE: "Γονική σελίδα"
+ HOME_PAGE: "Αρχική σελίδα"
+ HOME_PAGE_HELP: "Η σελίδα που το Grav θα χρησιμοποιήσει ως προεπιλεγμένη σελίδα προσγείωσης"
+ DEFAULT_THEME: "Προεπιλεγμένο Θέμα"
+ DEFAULT_THEME_HELP: "Ορίσετε το προεπιλεγμένο θέμα που θα χρησιμοποιεί το Grav (η προεπιλογή είναι το θέμα Antimatter)"
+ TIMEZONE: "Ζώνη ώρας"
+ TIMEZONE_HELP: "Παράκαμψη της προεπιλεγμένης ζώνης ώρας του διακομιστή"
+ SHORT_DATE_FORMAT: "Σύντομη μορφή ημερομηνίας"
+ SHORT_DATE_FORMAT_HELP: "Ρύθμισε τη σύντομη μορφή ημερομηνίας που θα χρησιμοποιείται από τα θέματα"
+ LONG_DATE_FORMAT: "Εκτενής μορφή ημερομηνίας"
+ LONG_DATE_FORMAT_HELP: "Ρύθμισε την εκτενή μορφή ημερομηνίας που θα χρησιμοποιείται από τα θέματα"
+ DEFAULT_ORDERING: "Προεπιλεγμένη Ταξινόμηση"
+ DEFAULT_ORDERING_HELP: "Οι σελίδες μίας λίστας θα εμφανίζονται χρησιμοποιώντας την σειρά εκτός αν αυτό παρακαμφθεί"
+ DEFAULT_ORDERING_DEFAULT: "Default - βάσει του όνομα φακέλου"
+ DEFAULT_ORDERING_FOLDER: "Folder - βάσει του όνομα φακέλου"
+ DEFAULT_ORDERING_TITLE: "Title - βάσει του τίτλου που έχει οριστεί στο header"
+ DEFAULT_ORDERING_DATE: "Date - βάσει της ημερομηνίας που έχει οριστεί στο header"
+ DEFAULT_ORDER_DIRECTION: "Προεπιλεγμένη σειρά εμφάνισης"
+ DEFAULT_ORDER_DIRECTION_HELP: "Η κατεύθυνση των σελίδων σε μια λίστα"
+ DEFAULT_PAGE_COUNT: "Προεπιλεγμένο πλήθος σελίδων"
+ DEFAULT_PAGE_COUNT_HELP: "Προεπιλεγμένο μέγιστο πλήθος σελίδων σε μία λίστα"
+ DATE_BASED_PUBLISHING: "Δημοσίευση βάσει ημερομηνίας"
+ DATE_BASED_PUBLISHING_HELP: "Αυτόματη δημοσίευση ή ιδιωτικοποίηση των αναρτήσεων βασισμένη στην ημερομηνία τους"
+ EVENTS: "Συμβάντα"
+ EVENTS_HELP: "Ενεργοποίησε ή απενεργοποίησε συγκεκριμένα events. Απενεργοποιώντας τα ίσως διακοπεί η λειτουργία κάποιου προσθέτου"
+ REDIRECT_DEFAULT_ROUTE: "Ανακατεύθυνση της προεπιλεγμένης διαδρομής"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Αυτόματη ανακατεύθυνση στην προεπιλεγμένη διαδρομή μιας σελίδας"
+ LANGUAGES: "Γλώσσες"
+ SUPPORTED: "Υποστηρίζεται"
+ SUPPORTED_HELP: "Λίστα χωρισμένη με κόμματα κωδικών γλωσσών 2 γραμμάτων (π.χ. 'en,fr,de')"
+ TRANSLATIONS_ENABLED: "Οι μεταφράσεις είναι ενεργοποιημένες"
+ TRANSLATIONS_ENABLED_HELP: "Υποστήριξη μεταφράσεων του Grav, πρόσθετων και επεκτάσεων"
+ TRANSLATIONS_FALLBACK: "Εναλλακτική μετάφραση"
+ TRANSLATIONS_FALLBACK_HELP: "Fallback σε κάποια άλλη μετάφραση εάν η επιλεγμένη γλώσσα δεν έχει μεταφραστεί"
+ ACTIVE_LANGUAGE_IN_SESSION: "Ενεργή γλώσσα κατά τη συνεδρία"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Αποθήκευση της ενεργής γλώσσας στη συνεδρία"
+ HTTP_HEADERS: "Κεφαλίδες HTTP"
+ EXPIRES: "Λήξη"
+ EXPIRES_HELP: "Ορίζει την κεφαλίδα λήξης. Η τιμή είναι σε δευτερόλεπτα."
+ CACHE_CONTROL: "HTTP Cache-Control"
+ CACHE_CONTROL_HELP: "Ορίστε μια έγκυρη τιμή cache-ελέγχου όπως 'όχι-cache, όχι-αποθήκευση, να-επανεπικυρωθεί'"
+ LAST_MODIFIED: "Τελευταία τροποποίηση"
+ LAST_MODIFIED_HELP: "Ορίζει την τελευταία τροποποιημένη κεφαλίδα που μπορεί να σας βοηθήσει να βελτιστοποιήσετε τον διακομιστή μεσολάβησης και την προσωρινή αποθήκευση του προγράμματος περιήγησης"
+ ETAG: "ETag"
+ ETAG_HELP: "Καθορίζει την κεφαλίδα etag για να βοηθήσει στον εντοπισμό όταν μια σελίδα έχει τροποποιηθεί"
+ VARY_ACCEPT_ENCODING: "Ποικίλλει η δεκτή κωδικοποίηση"
+ VARY_ACCEPT_ENCODING_HELP: "Ορίζει την κεφαλίδα «Ποικίλλει: Δεκτή Κωδικοποίηση» για να βοηθήσει με το διακομιστή μεσολάβησης και την προσωρινή αποθήκευση CDN"
+ MARKDOWN_EXTRA: "Markdown extra"
+ MARKDOWN_EXTRA_HELP: "Ενεργοποιήσετε την προεπιλεγμένη υποστήριξη για Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ MARKDOWN_EXTRA_ESCAPE_FENCES: "Διάφυγε τα HTML στοιχεία σε markdown extra fences"
+ MARKDOWN_EXTRA_ESCAPE_FENCES_HELP: "Διαφεύγει τα HTML στοιχεία σε markdown extra fences"
+ AUTO_LINE_BREAKS: "Αυτόματες αλλαγές γραμμών"
+ AUTO_LINE_BREAKS_HELP: "Ενεργοποιήσετε την υποστήριξη για αυτόματη αλλαγή γραμμής στη markdown"
+ AUTO_URL_LINKS: "Αυτόματες συνδέσεις URL"
+ AUTO_URL_LINKS_HELP: "Ενεργοποιήσετε την αυτόματη μετατροπή των διευθύνσεων URL σε HTML υπερ-συνδέσεις"
+ ESCAPE_MARKUP: "Σημειοθέτηση αποσπάσματος"
+ ESCAPE_MARKUP_HELP: "Ετικέτες σήμανσης διαφυγής σε HTML οντότητες"
+ CACHING_HELP: "Γενικός διακόπτης ενεργοποίησης/απενεργοποίησης της προσωρινής αποθήκευσης του Grav"
+ CACHE_CHECK_METHOD: "Μέθοδος ελέγχου μνήμης προσωρινής αποθήκευσης (cache)"
+ CACHE_CHECK_METHOD_HELP: "Επιλέξτε τη μέθοδο που χρησιμοποιεί το Grav για να ελέγξει αν έχουν τροποποιηθεί αρχεία σελίδων."
+ CACHE_DRIVER: "Πρόγραμμα οδήγησης της μνήμης cache"
+ CACHE_DRIVER_HELP: "Επιλέξτε ποιο πρόγραμμα οδήγησης της προσωρινής αποθήκευσης να χρησιμοποιήσει το Grav. Ο 'Αυτόματος εντοπισμός' προσπαθεί να βρει το καλύτερο για εσάς"
+ CACHE_PREFIX: "Πρόθεμα cache"
+ CACHE_PREFIX_HELP: "Ένα αναγνωριστικό για το μέρος του κλειδιού Grav. Μην το αλλάξετε εκτός και αν γνωρίζετε τι κάνετε."
+ CACHE_PREFIX_PLACEHOLDER: "Προέρχεται από τη βασική διεύθυνση URL (παράκαμψη εισάγοντας τυχαία συμβολοσειρά)"
+ LIFETIME: "Διάρκεια ζωής"
+ LIFETIME_HELP: "Ορίζει τη διάρκεια ζωής της cache σε δευτερόλεπτα. 0 = άπειρο"
+ GZIP_COMPRESSION: "Συμπίεση Gzip"
+ GZIP_COMPRESSION_HELP: "Ενεργοποιήστε τη GZip συμπίεση της σελίδας του Grav για αυξημένη απόδοση."
+ TWIG_TEMPLATING: "Πρότυπα Twig"
+ TWIG_CACHING: "Προσωρινή αποθήκευση Twig"
+ TWIG_CACHING_HELP: "Ελέγχει το μηχανισμό προσωρινής αποθήκευσης του Twig. Αφήστε το ενεργοποιημένο για καλύτερη απόδοση."
+ TWIG_DEBUG: "Εντοπισμός σφαλμάτων Twig"
+ TWIG_DEBUG_HELP: "Επιτρέπει την επιλογή της μη φόρτωσης της επέκτασης Αποσφαλμάτωση Twig"
+ DETECT_CHANGES: "Ανίχνευση μεταβολών"
+ DETECT_CHANGES_HELP: "Το Twig θα μεταγλωττίσει αυτόματα την προσωρινή μνήμη του αν εντοπίσει αλλαγές σε πρότυπα Twig"
+ AUTOESCAPE_VARIABLES: "Εφαρμογή Autoescape στις μεταβλητές"
+ AUTOESCAPE_VARIABLES_HELP: "Κάνει autoescape όλες τις μεταβλητές. Αυτό πιθανότατα θα σπάσει το site σας"
+ ASSETS: "Αντικείμενα"
+ CSS_PIPELINE: "Αγωγός CSS"
+ CSS_PIPELINE_HELP: "Ο αγωγός CSS είναι η ενοποίηση πολλαπλών CSS πόρων σε ένα αρχείο"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Συμπεριλάβετε εξωτερικούς αγωγούς CSS"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Οι εξωτερικές διευθύνσεις URL μερικές φορές έχουν σχετικές αναφορές αρχείων και δεν θα έπρεπε να διοχετεύονται"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "Πρώτα επεξεργασία CSS αγωγού"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Επεξεργάζεται τον CSS αγωγό πριν από οποιαδήποτε άλλη αναφορά CSS που δεν περιλαμβάνεται"
+ CSS_MINIFY: "Συμπίεση της CSS"
+ CSS_MINIFY_HELP: "Ελαχιστοποίησε το CSS κατά τη διοχέτευση"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "Παράκαψη ελαχιστοποίησης CSS για Windows"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Παράκαμψη Ελαχιστοποίησης για Windows. False από προεπιλογή λόγω ThreadStackSize"
+ CSS_REWRITE: "Επανεγγραφή CSS"
+ CSS_REWRITE_HELP: "Επανεγγραφή κάθε αναφορικού CSS URL κατά τη διοχέτευση"
+ JAVASCRIPT_PIPELINE: "Αγωγός JavaScript"
+ JAVASCRIPT_PIPELINE_HELP: "Ο αγωγός JS είναι η ενοποίηση πολλαπλών JS πόρων σε ένα αρχείο"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Συμπεριλάβετε εξωτερικούς αγωγούς JS"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Οι εξωτερικές διευθύνσεις URL μερικές φορές έχουν σχετικές αναφορές αρχείων και δεν θα έπρεπε να διοχετεύονται"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Πρώτα επεξεργασία JS αγωγού"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Επεξεργάζεται τον JS αγωγό πριν από οποιαδήποτε άλλη αναφορά JS που δεν περιλαμβάνεται"
+ JAVASCRIPT_MINIFY: "Συμπίεση του κώδικα JavaScript"
+ JAVASCRIPT_MINIFY_HELP: "Ελαχιστοποίησε το JS κατά τη διοχέτευση"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Ενεργοποιήστε τις χρονοσημάνσεις στους διαθέσιμους πόρους"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Ενεργοποίηση χρονοσημάνσεων στους διαθέσιμους πόρους"
+ COLLECTIONS: "Συλλογές"
+ ERROR_HANDLER: "Πρόγραμμα χειρισμού σφαλμάτων"
+ DISPLAY_ERRORS: "Εμφάνιση των μηνυμάτων λάθους"
+ DISPLAY_ERRORS_HELP: "Εμφάνιση πλήρους backtrace στιλ σελίδας σφάλματος"
+ LOG_ERRORS: "Καταγραφή σφαλμάτων σε αρχείο"
+ LOG_ERRORS_HELP: "Καταγραφή σφαλμάτων στο φάκελο /logs"
+ DEBUGGER: "Πρόγραμμα εντοπισμού σφαλμάτων"
+ DEBUGGER_HELP: "Ενεργοποιήσετε το πρόγραμμα εντοπισμού σφαλμάτων του Grav και τις ακόλουθες ρυθμίσεις"
+ DEBUG_TWIG: "Εκσφαλμάτωση του Twig"
+ DEBUG_TWIG_HELP: "Ενεργοποίηση του εντοπισμού σφαλμάτων στα πρότυπα του Twig"
+ SHUTDOWN_CLOSE_CONNECTION: "Τερματισμός στενής σύνδεσης"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Κλείσε τη σύνδεση πριν από την κλήση onShutdown(). False για εντοπισμό σφαλμάτων"
+ DEFAULT_IMAGE_QUALITY: "Προεπιλεγμένη ποιότητα εικόνας"
+ DEFAULT_IMAGE_QUALITY_HELP: "Η προεπιλεγμένη ποιότητα εικόνας που θα χρησιμοποιείται όταν γίνεται αναδειγματοληψία ή προσωρινή αποθήκευση εικόνων (85%)"
+ CACHE_ALL: "Προσωρινή αποθήκευση όλων των εικόνων"
+ CACHE_ALL_HELP: "Πέρασε όλες τις εικόνες μέσω του Grav cache συστήματος ακόμη και αν δεν έχουν επεξεργασία μέσων"
+ IMAGES_DEBUG: "Υδατογράφημα αποσφαλμάτωσης εικόνας"
+ IMAGES_DEBUG_HELP: "Εμφάνιση μιας επικάλυψης πάνω από τις εικόνες που δείχνει το βάθος pixel της εικόνας, όταν εργάζεστε με retina για παράδειγμα"
+ UPLOAD_LIMIT: "Όριο μεταφόρτωσης αρχείων"
+ UPLOAD_LIMIT_HELP: "Ορίσετε το μέγιστο μέγεθος μεταφόρτωσης σε byte (0 είναι απεριόριστο)"
+ ENABLE_MEDIA_TIMESTAMP: "Ενεργοποιήστε τις χρονοσημάνσεις στα μέσα"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Προσθέτει μια χρονική σήμανση με βάση την τελευταία ημερομηνία τροποποίησης σε κάθε στοιχείο πολυμέσων"
+ SESSION: "Συνεδρία"
+ SESSION_ENABLED_HELP: "Ενεργοποιήσετε την υποστήριξη συνεδριών λειτουργίας εντός Grav"
+ TIMEOUT: "Λήξη χρονικού ορίου"
+ TIMEOUT_HELP: "Ορίζει το χρονικό όριο της συνεδρίας σε δευτερόλεπτα"
+ SESSION_NAME_HELP: "Ένα αναγνωριστικό που χρησιμοποιείται για το σχηματισμό του όνόματος του cookie της συνεδρίας"
+ ABSOLUTE_URLS: "Απόλυτες διευθύνσεις URL"
+ ABSOLUTE_URLS_HELP: "Απόλυτες ή σχετικές διευθύνσεις URL για «base_url»"
+ PARAMETER_SEPARATOR: "Διαχωριστικό παραμέτρων"
+ PARAMETER_SEPARATOR_HELP: "Διαχωριστικό για τις παραμέτρους που διαβιβάστηκαν, που μπορούν να αλλάξουν για τον Apache στα Windows"
+ TASK_COMPLETED: "Η εργασία ολοκληρώθηκε"
+ EVERYTHING_UP_TO_DATE: "Τα πάντα είναι ενημερωμένα"
+ UPDATES_ARE_AVAILABLE: "υπάρχουν διαθέσιμες ενημερώσεις"
+ IS_AVAILABLE_FOR_UPDATE: "είναι διαθέσιμη για ενημέρωση"
+ IS_NOW_AVAILABLE: "είναι τώρα διαθέσιμη"
+ CURRENT: "Τρέχουσα έκδοση"
+ UPDATE_GRAV_NOW: "Αναβαθμίστε το Grav τώρα"
+ GRAV_SYMBOLICALLY_LINKED: "Το Grav είναι symlinked. Δε θα υπάρχει διαθέσιμη αναβάθμιση"
+ UPDATING_PLEASE_WAIT: "Ενημέρωση... παρακαλώ περιμένετε, λήψη"
+ OF_THIS: "αυτό"
+ OF_YOUR: "σας"
+ HAVE_AN_UPDATE_AVAILABLE: "έχει διαθέσιμη ενημέρωση"
+ SAVE_AS: "Αποθήκευση ως"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή τη σελίδα και όλα τα τέκνα; Εάν η σελίδα είναι μεταφρασμένη σε άλλες γλώσσες, αυτές οι μεταφράσεις θα κρατηθούν και πρέπει να διαγραφούν ξεχωριστά. Αλλιώς ο φάκελος σελίδας θα διαγραφεί μαζί με τις υποσελίδες. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί."
+ AND: "και"
+ UPDATE_AVAILABLE: "Υπάρχει διαθέσιμη ενημέρωση"
+ METADATA_KEY: "Κλειδί (π.χ. «λέξεις-κλειδιά»)"
+ METADATA_VALUE: "Τιμή (π.χ. «Blog, Grav»)"
+ USERNAME_HELP: "Το όνομα χρήστη πρέπει να είναι μεταξύ 3 και 16 χαρακτήρες, με πεζά, αριθμούς, κάτω παύλες και ενωτικά. Κεφαλαία γράμματα, διαστήματα και ειδικοί χαρακτήρες δεν επιτρέπονται"
+ FULLY_UPDATED: "Πλήρως Ενημερωμένο"
+ SAVE_LOCATION: "Τοποθεσία Αποθήκευσης"
+ PAGE_FILE: "Πρότυπο σελίδας"
+ PAGE_FILE_HELP: "Ονομασία αρχείου προτύπου σελίδας, και από προεπιλογή το πρότυπο εμφάνισης για αυτή τη σελίδα"
+ NO_USER_ACCOUNTS: "Δεν βρέθηκε κανένας λογαριασμός χρήστη, παρακαλώ δημιουργήστε έναν πρώτα..."
+ REDIRECT_TRAILING_SLASH: "Ανακατευθύνει την τελική κάθετο"
+ REDIRECT_TRAILING_SLASH_HELP: "Εκτέλεσε μια ανακατεύθυνση 301 αντί για διαφανή χειρισμό των καταληκτικών καθέτων URIs."
+ DEFAULT_DATE_FORMAT: "Μορφή ημερομηνίας σελίδας"
+ DEFAULT_DATE_FORMAT_HELP: "Μορφή ημερομηνίας σελίδας που χρησιμοποιείται από το Grav. Από προεπιλογή, το Grav επιχειρεί να μαντέψει τη μορφή, εντούτοις μπορείτε να ορίσετε μορφή σύμφωνα με τη σύνταξη PHP"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Μάντεψε αυτόματα"
+ IGNORE_FILES: "Αγνόηση αρχείων"
+ IGNORE_FILES_HELP: "Συγκεκριμένα αρχεία που θα αγνοηθούν κατά την επεξεργασία σελίδων"
+ IGNORE_FOLDERS: "Αγνόηση φακέλων"
+ IGNORE_FOLDERS_HELP: "Συγκεκριμένοι φάκελοι που θα αγνοηθούν κατά την επεξεργασία σελίδων"
+ HTTP_ACCEPT_LANGUAGE: "Όρισε την γλώσσα βάσει του φυλλομετρητή"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Μπορείτε να επιλέξετε να δοκιμάσετε να ορίσετε τη γλώσσα που βασίζεται στην ετικέτα κεφαλίδας «http_accept_language» στο πρόγραμμα περιήγησης"
+ OVERRIDE_LOCALE: "Παράκαμψη των Τοπικών Ρυθμίσεων"
+ OVERRIDE_LOCALE_HELP: "Αντικαθιστά τις ρυθμίσεις τοπικότητας στην PHP με βάση την τρέχουσα γλώσσα"
+ REDIRECT: "Ανακατεύθυνση σελίδας"
+ REDIRECT_HELP: "Εισαγάγετε μια διαδρομή σελίδας ή εξωτερική διεύθυνση URL για αυτήν τη σελίδα για να ανακατευθύνετε σε. π.χ. «/κάποια/διαδρομή» ή «http://somesite.com»"
+ PLUGIN_STATUS: "Κατάσταση πρόσθετου"
+ INCLUDE_DEFAULT_LANG: "Περιλαμβάνει την προεπιλεγμένη γλώσσα"
+ INCLUDE_DEFAULT_LANG_HELP: "Αυτό θα προτάξει όλες τις διευθύνσεις URL στην προεπιλεγμένη γλώσσα με την προεπιλεγμένη γλώσσα. π.χ. «/en/blog/my-post»"
+ ALLOW_URL_TAXONOMY_FILTERS: "Φίλτρα Ταξινόμησης URL"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Συλλογές βασισμένες σε σελίδα σας επιτρέπουν να φιλτράρετε μέσω «/taxonomy:value»."
+ REDIRECT_DEFAULT_CODE: "Προεπιλεγμένος κωδικός ανακατεύθυνσης"
+ REDIRECT_DEFAULT_CODE_HELP: "Ο κωδικός κατάστασης HTTP που θα χρησιμοποιηθεί για ανακατευθύνσεις"
+ IGNORE_HIDDEN: "Παράβλεψη των κρυφών"
+ IGNORE_HIDDEN_HELP: "Παράβλεψη όλων των αρχείων και των φακέλων που ξεκινούν με μια ΤΕΛΕΙΑ"
+ WRAPPED_SITE: "Wrapped site"
+ WRAPPED_SITE_HELP: "Για να γνωρίζουν τα θέματα/plugins αν το Grav είναι wrapped από άλλη πλατφόρμα"
+ FALLBACK_TYPES: "Επιτρεπόμενοι εναλλακτικοί τύποι"
+ FALLBACK_TYPES_HELP: "Επιτρεπτοί τύποι αρχείων που μπορούν να βρεθούν αν έχετε αποκτήσει πρόσβαση μέσω της οδού Σελίδα. Προεπιλέγει οποιοδήποτε τύπο μέσων που υποστηρίζονται."
+ INLINE_TYPES: "Ένθετοι εναλλακτικοί τύποι"
+ INLINE_TYPES_HELP: "Μια λίστα των τύπων αρχείων που πρέπει να εμφανίζονται ένθετα αντί να γίνεται λήψη αυτών"
+ APPEND_URL_EXT: "Προσάρτηση επέκτασης URL"
+ APPEND_URL_EXT_HELP: "Θα προσθέσει μια προσαρμοσμένη επέκταση στο URL της Σελίδας. Σημείωση, αυτό θα σημαίνει ότι το Grav θα αναζητήσει ' . . twig ' πρότυπο"
+ PAGE_MODES: "Λειτουργίες Σελίδας"
+ PAGE_TYPES: "Τύποι σελίδας"
+ ACCESS_LEVELS: "Επίπεδα πρόσβασης"
+ GROUPS: "Ομάδες"
+ GROUPS_HELP: "Λίστα των ομάδων που ο χρήστης είναι μέλος"
+ ADMIN_ACCESS: "Πρόσβαση Διαχειριστή"
+ SITE_ACCESS: "Πρόσβαση στον Ιστοχώρο"
+ INVALID_SECURITY_TOKEN: "Το διακριτικό ασφαλείας δεν είναι έγκυρο"
+ ACTIVATE: "Eνεργοποίηση"
+ TWIG_UMASK_FIX: "Unmask Fix"
+ TWIG_UMASK_FIX_HELP: "Από προεπιλογή το Twig δημιουργεί προσωρινά αρχεία ως 0755, η διόρθωση τα αλλάζει σε 0775"
+ CACHE_PERMS: "Δικαιώματα μνήμης cache"
+ CACHE_PERMS_HELP: "Προεπιλεγμένα δικαιώματα φακέλων cache. Συνήθως 0755 ή 0775"
+ REMOVE_SUCCESSFUL: "Επιτυχής Κατάργηση"
+ REMOVE_FAILED: "Η Κατάργηση Απέτυχε"
+ HIDE_HOME_IN_URLS: "Απόκρυψη της αρχικής διαδρομής στις διευθύνσεις URL"
+ HIDE_HOME_IN_URLS_HELP: "Θα εξασφαλίσει ότι οι προεπιλεγμένες διαδρομές για όλες τις σελίδες κάτω από την αρχική δε θα αναφέρουν την κανονική διαδρομή της"
+ TWIG_FIRST: "Επεξεργασία πρώτα των αρχείων του Twig"
+ TWIG_FIRST_HELP: "Εάν έχετε ενεργοποιήσει την επεξεργασία σελίδων Twig, τότε μπορείτε να ρυθμίσετε το Twig να επεξεργαστεί πριν ή μετά από τη markdown"
+ SESSION_SECURE: "Ασφαλής"
+ SESSION_SECURE_HELP: "Εάν είναι αληθές, δείχνει ότι η επικοινωνία για αυτό το cookie πρέπει να γίνει πάνω από μιά κρυπτογραφημένη μετάδοση. Προειδοποίηση: Ενεργοποιήστε την μόνο στις τοποθεσίες που εκτελούνται αποκλειστικά σε HTTPS"
+ SESSION_HTTPONLY: "HTTP μόνο"
+ SESSION_HTTPONLY_HELP: "Εάν είναι αληθές, δείχνει ότι τα cookies πρέπει να χρησιμοποιούνται μόνο μέσω HTTP, και δεν επιτρέπεται η τροποποίηση μέσω JavaScript"
+ REVERSE_PROXY: "Αντίστροφoς Διακομιστής Μεσολάβησης"
+ REVERSE_PROXY_HELP: "Ενεργοποιήστε το άν είστε πίσω από έναν διακομιστή αντίστροφης μεσολάβηση αντιμετωπίζετε δυσκολία με τις διευθύνσεις URL που περιέχουν εσφαλμένες θύρες"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Μη έγκυρος πρόλογος (frontmatter), αδύνατη η αποθήκευση"
+ ADD_FOLDER: "Προσθήκη φακέλου"
+ PROXY_URL: "Διεύθυνση URL του διακομιστή μεσολάβησης"
+ PROXY_URL_HELP: "Εισάγετε το Διακομιστή proxy ή τη διεύθυνση IP και τη ΘΥΡΑ (PORT)"
+ NOTHING_TO_SAVE: "Δεν υπάρχει τίποτα γιά αποθήκευση"
+ FILE_ERROR_ADD: "Παρουσιάστηκε σφάλμα κατά την προσθήκη του αρχείου"
+ FILE_ERROR_UPLOAD: "Παρουσιάστηκε σφάλμα κατά τη μεταφόρτωση του αρχείου"
+ FILE_UNSUPPORTED: "Μη υποστηριζόμενος τύπος αρχείου"
+ ADD_ITEM: "Προσθήκη στοιχείου"
+ FILE_TOO_LARGE: "Το αρχείο είναι πολύ μεγάλο για να φορτωθεί, το μέγιστο επιτρεπόμενο είναι %s σύμφωνα με τις ρυθμίσεις της PHP. Αυξήστε τη ρύθμιση PHP σας `post_max_size`"
+ INSTALLING: "Γίνεται εγκατάσταση"
+ LOADING: "Φόρτωση…"
+ DEPENDENCIES_NOT_MET_MESSAGE: "Οι ακόλουθες εξαρτήσεις πρέπει να εκπληρωθούν πρώτα:"
+ ERROR_INSTALLING_PACKAGES: "Σφάλμα κατά την εγκατάσταση πακέτων"
+ INSTALLING_DEPENDENCIES: "Εγκατάσταση εξαρτήσεων..."
+ INSTALLING_PACKAGES: "Εγκατάσταση πακέτων..."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Τα πακέτα εγκαταστάθηκαν με επιτυχία."
+ READY_TO_INSTALL_PACKAGES: "Είστε έτοιμοι να εγκαταστήσετε τα πακέτα"
+ PACKAGES_NOT_INSTALLED: "Τα πακέτα δεν εγκαταστάθηκαν"
+ PACKAGES_NEED_UPDATE: "Πακέτα που έχουν ήδη εγκατασταθεί, αλλά πολύ παλιά"
+ PACKAGES_SUGGESTED_UPDATE: "Πακέτα που είναι ήδη εγκατεστημένα, καθώς και η έκδοση είναι εντάξει, αλλά θα ενημερώνονται"
+ REMOVE_THE: "Αφαιρέστε το %s"
+ CONFIRM_REMOVAL: "Είστε βέβαιοι ότι θέλετε να διαγράψετε το %s; ?"
+ REMOVED_SUCCESSFULLY: "Το %s καταργήθηκε με επιτυχία"
+ ERROR_REMOVING_THE: "Σφάλμα κατά την αφαίρεση του %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "Το %s απαιτεί τις ακόλουθες εξαρτήσεις, που δεν απαιτούνται από άλλα εγκατεστημένα πακέτα. Εάν δεν τις χρησιμοποιείτε, μπορείτε να τις καταργήσετε απευθείας από εδώ."
+ READY_TO_UPDATE_PACKAGES: "Είστε έτοιμοι να ενημερώσετε τα πακέτα"
+ ERROR_UPDATING_PACKAGES: "Σφάλμα κατά την ενημέρωση πακέτων."
+ UPDATING_PACKAGES: "Ενημέρωση πακέτων..."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Τα πακέτα ενημερώθηκαν με επιτυχία."
+ UPDATING: "Ενημέρωση"
+ GPM_RELEASES: "GPM κυκλοφορίες"
+ GPM_RELEASES_HELP: "Επιλέξτε «Testing» για να εγκαταστήσετε beta ή testing εκδόσεις"
+ GPM_METHOD: "Μέθοδος Remote Fetch"
+ GPM_METHOD_HELP: "Όταν οριστεί σε αυτόματο, το Grav θα καθορίσει αν το fopen είναι διαθέσιμο και θα το χρησιμοποιήσει, αλλιώς θα επιστρέψει στο cURL. Για να επιβάλετε τη χρήση του ενός ή του άλλου αλλάξτε τη ρύθμιση."
+ GPM_VERIFY_PEER: "Εξ αποστάσεως επαλήθευση Peer (SSL)"
+ GPM_VERIFY_PEER_HELP: "Ορισμένες υπηρεσίες παροχής φαίνεται να αποτυγχάνουν στην επαλήθευση πιστοποιητικύ SSL του getgrav.org, κάνοντας το GPM να μην λειτουργεί. Αν αυτή είναι η περίπτωση για εσάς, η απενεργοποίηση αυτής της ρύθμισης μπορεί να βοηθήσει"
+ AUTO: "Αυτόματο"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stable"
+ TESTING: "Testing"
+ FRONTMATTER_PROCESS_TWIG: "Επεξεργασία frontmatter Twig"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Όταν ενεργοποιηθεί μπορείτε να χρησιμοποιήσετε μεταβλητές Twig config στο frontmatter"
+ FRONTMATTER_IGNORE_FIELDS: "Παράβλεψη frontmatter πεδίων"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Ορισμένα πεδία frontmatter μπορεί να περιέχουν Twig, αλλά δεν πρέπει να υποστούν επεξεργασία, όπως οι «φόρμες»"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Το πακέτο %s εγκαταστάθηκε με επιτυχία"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Γονική ρύθμιση ταξινόμησης, η ταξινόμηση απενεργοποιήθηκε"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Η σελίδα δεν είναι ορατή, η ταξινόμηση απενεργοποιήθηκε"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "Η ταξινόμηση μέσω διαχειριστή δεν υποστηρίζεται γιατί υπάρχουν περισσότερα από 200 παράγωγα"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Η ταξινόμηση σελίδων είναι απενεργοποιημένη για αυτήν τη σελίδα, επειδή το Αριθμητικό Πρόθεμα Φακέλου δεν είναι ενεργοποιημένο"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "ΣΗΜΕΙΩΣΗ: Δεν μπορείτε να προσθέσετε αρχεία πολυμέσων, μέχρι να αποθηκεύσετε τη σελίδα. Απλά κάντε κλικ «Αποθήκευση» στην κορυφή"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "ΣΗΜΕΙΩΣΗ: Η σελίδα πρέπει να αποθηκευτεί πριν να μπορείτε να ανεβάσετε αρχεία σε αυτή."
+ DROP_FILES_HERE_TO_UPLOAD: "Ρίξτε τα αρχεία σας εδώ ή κάντε κλικ σε αυτόν τον τομέα "
+ INSERT: "Εισαγωγή"
+ UNDO: "Αναίρεση"
+ REDO: "Ακύρωση Αναίρεσης"
+ HEADERS: "Επικεφαλίδες"
+ BOLD: "Έντονα"
+ ITALIC: "Πλάγια"
+ STRIKETHROUGH: "Διακριτή διαγραφή"
+ SUMMARY_DELIMITER: "Περίληψη οριοθέτη"
+ LINK: "Σύνδεσμος"
+ IMAGE: "Εικόνα"
+ BLOCKQUOTE: "Μπλοκ κειμένου παράθεσης"
+ UNORDERED_LIST: "Μη ταξινομημένη λίστα"
+ ORDERED_LIST: "Ταξινομημένη λίστα"
+ EDITOR: "Επεξεργαστής"
+ PREVIEW: "Προεπισκόπιση"
+ FULLSCREEN: "Πλήρης οθόνη"
+ MODULAR: "Σπονδυλωτή"
+ NON_ROUTABLE: "Απροσπέλαστη"
+ NON_MODULAR: "Μη σπονδυλωτή"
+ NON_VISIBLE: "Αόρατη"
+ NON_PUBLISHED: "Μη δημοσιευμένες"
+ CHARACTERS: "χαρακτήρες"
+ PUBLISHING: "Δημοσίευση"
+ NOTIFICATIONS: "Ειδοποιήσεις"
+ MEDIA_TYPES: "Τύποι μέσων"
+ IMAGE_OPTIONS: "Επιλογές εικόνας"
+ MIME_TYPE: "Τύπος MIME"
+ THUMB: "Μικρογραφία"
+ TYPE: "Τύπος"
+ FILE_EXTENSION: "Επέκταση αρχείου"
+ LEGEND: "Υπόμνημα σελίδων"
+ MEMCACHE_SERVER: "Διακομιστής Memcache"
+ MEMCACHE_SERVER_HELP: "Η διεύθυνση του διακομιστή Memcache"
+ MEMCACHE_PORT: "Θύρα Memcache"
+ MEMCACHE_PORT_HELP: "Η θύρα του διακομιστή Memcache"
+ MEMCACHED_SERVER: "Διακομιστής Memcached"
+ MEMCACHED_SERVER_HELP: "Η διεύθυνση του διακομιστή Memcached"
+ MEMCACHED_PORT: "Θύρα Memcached"
+ MEMCACHED_PORT_HELP: "Η θύρα του διακομιστή Memcached"
+ REDIS_SERVER: "Διακομιστής Redis"
+ REDIS_SERVER_HELP: "Η διεύθυνση του διακομιστή Redis"
+ REDIS_PORT: "Θύρα Redis"
+ REDIS_PORT_HELP: "Η θύρα του διακομιστή Redis"
+ REDIS_PASSWORD: "Συνθηματικό Redis"
+ ALL: "Όλα"
+ FROM: "από"
+ TO: "έως"
+ RELEASE_DATE: "Ημερομηνία κυκλοφορίας"
+ SORT_BY: "Ταξινόμηση κατά"
+ RESOURCE_FILTER: "Φίλτρο..."
+ FORCE_SSL: "Εξαναγκασμός SSL"
+ FORCE_SSL_HELP: "Γενικός εξαναγκασμός SSL, αν ενεργοποιηθεί όταν η σελίδα είναι προσβάσιμη μέσω HTTP, το Grav αποστέλλει ανακατεύθυνση στη σελίδα HTTPS"
+ NEWS_FEED: "Ροή ειδήσεων"
+ EXTERNAL_URL: "Εξωτερική διεύθυνση URL"
+ SESSION_PATH: "Η διαδρομή συνεδρίας"
+ SESSION_PATH_HELP: "Χρησιμοποιήστε το μόνο εάν επιλέξετε μια προσαρμοσμένη διεύθυνση URL βάσης (ξαναγράφετε το site domain / υποφάκελο)"
+ CUSTOM_BASE_URL: "Προσαρμοσμένο URL βάσης"
+ CUSTOM_BASE_URL_HELP: "Χρησιμοποιήστε το αν θέλετε να ξαναγράψετε τον τομέα του ιστότοπου ή να χρησιμοποιήσετε κάποιον υποφάκελο, διαφορετικό από εκείνο που χρησιμοποιείται από το Grav. Παράδειγμα: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'Δεν είναι δυνατό να χρησιμοποιήσετε «%s» εκτός σελίδων.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Δεν μπορείτε να ανεβάσετε το αρχείο %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'Δεν μπορείτε να μετακινήσετε το αρχείο %s σε «%s»'
+ DROPZONE_CANCEL_UPLOAD: 'Ακύρωση ανεβάσματος'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Είστε βέβαιοι ότι θέλετε να ακυρώσετε αυτό το ανέβασμα;'
+ DROPZONE_DEFAULT_MESSAGE: 'Ρίξτε τα αρχεία σας εδώ ή κάντε κλικ σε αυτόν τον τομέα '
+ DROPZONE_FALLBACK_MESSAGE: 'Το πρόγραμμα περιήγησής σας δεν υποστηρίζει drag and drop ανέβασμα αρχείων.'
+ DROPZONE_FALLBACK_TEXT: 'Παρακαλούμε χρησιμοποιήστε την παρακάτω φόρμα για να φορτώσετε τα αρχεία σας όπως γινόταν παλαιότερα.'
+ DROPZONE_FILE_TOO_BIG: 'Το αρχείο είναι πολύ μεγάλο ({{filesize}}MiB). Μέγιστο μέγεθος αρχείου: {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "Δεν μπορείτε να ανεβάσετε αρχεία αυτού του τύπου."
+ DROPZONE_MAX_FILES_EXCEEDED: "Δεν μπορείτε να ανεβάσετε άλλα αρχεία."
+ DROPZONE_REMOVE_FILE: "Αφαίρεση αρχείου"
+ DROPZONE_RESPONSE_ERROR: "Ο διακομιστής ανταποκρίθηκε με κωδικό {{statusCode}}."
+ PREMIUM_PRODUCT: "Premium"
+ ADMIN_CACHING: "Ενεργοποιήσετε την προσωρινή αποθήκευση Admin"
+ ADMIN_CACHING_HELP: "Η προσωρινή αποθήκευση στο admin μπορεί να ελέγχεται αυτόνομα από τη front-end ιστοσελίδα"
+ DESTINATION_NOT_SPECIFIED: "Δεν ορίστηκε προορισμός"
+ UPLOAD_ERR_NO_TMP_DIR: "Λείπει ένας προσωρινός φάκελος"
+ SESSION_SPLIT: "Συνεδρία Σπλιτ"
+ SESSION_SPLIT_HELP: "Ανεξάρτητες split συνεδρίες μεταξύ σελίδας και άλλων plugins (όπως admin)"
+ ERROR_FULL_BACKTRACE: "Πλήρες Backtrace σφάλμα"
+ ERROR_SIMPLE: "Απλό σφάλμα"
+ ERROR_SYSTEM: "Σφάλμα συστήματος"
+ IMAGES_AUTO_FIX_ORIENTATION: "Αυτόματη επιδιόρθωση του προσανατολισμού"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Διορθώσετε αυτόματα τον προσανατολισμό της εικόνας που βασίζεται στα δεδομένα Exif"
+ REDIS_SOCKET: "Υποδοχή Redis"
+ REDIS_SOCKET_HELP: "Η υποδοχή Redis"
+ NOT_SET: "Μη ορισμένο"
+ PERMISSIONS: "Δικαιώματα"
+ NEVER_CACHE_TWIG: "Ποτέ Twig Cache"
+ NEVER_CACHE_TWIG_HELP: "Μόνο cache περιεχόμενου και επεξεργασια του Twig κάθε φορά για τις σελίδες. Παραβλέπει τη ρύθμιση twig_first."
+ ALLOW_WEBSERVER_GZIP: "Επίτρεψε WebServer Gzip"
+ ALLOW_WEBSERVER_GZIP_HELP: "Απενεργοποιημένη από προεπιλογή. Όταν είναι ενεργοποιημένη, η WebServer-configured Gzip/Deflate συμπίεση θα λειτουργήσει, αλλά δεν θα κλείσει πριν από το onShutDown() προκαλώντας αργή φόρτωση της σελίδας"
+ OFFLINE_WARNING: "Αδύνατη η σύνδεση με το GPM"
+ CLEAR_IMAGES_BY_DEFAULT: "Καθαρισμός cache εικόνων από προεπιλογή"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "Από προεπιλογή, οι επεξεργασμένες εικόνες καθαρίζονται για κάθε καθαρισμό cache, αυτό μπορεί να απενεργοποιηθεί"
+ CLI_COMPATIBILITY: "CLI Συμβατότητα"
+ CLI_COMPATIBILITY_HELP: "Εξασφαλίζει ότι χρησιμοποιούνται μόνο non-volatile Cache οδηγοί (αρχείο, redis, memcache, κλπ.)"
+ REINSTALL_PLUGIN: "Επανεγκατάσταση Πρόσθετου"
+ REINSTALL_THEME: "Εγκατάσταση Θέματος"
+ REINSTALL_THE: "Εγκαταστήστε ξανά το %s"
+ CONFIRM_REINSTALL: "Είστε βέβαιοι ότι θέλετε να επανεγκαταστήσετε το %s;"
+ REINSTALLED_SUCCESSFULLY: "Το %s επανεγκαταστάθηκε με επιτυχία"
+ ERROR_REINSTALLING_THE: "Σφάλμα κατά την επανεγκατάσταση του %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Το πακέτο %s επανεγκαταστάθηκε με επιτυχία"
+ REINSTALLATION_FAILED: "Η επανεγκατάσταση απέτυχε"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "Η εγκατεστημένη έκδοση δεν είναι η τελευταία έκδοση. Κάνοντας κλικ στο κουμπί Συνέχεια, θα αφαιρέσετε την τρέχουσα έκδοση και θα εγκαταστήσετε την πιο πρόσφατη διαθέσιμη έκδοση"
+ TOOLS: "Εργαλεία"
+ DIRECT_INSTALL: "Άμεση Εγκατάσταση"
+ NO_PACKAGE_NAME: "Δεν επιλέχθηκε όνομα πακέτου"
+ PACKAGE_EXTRACTION_FAILED: "Η εξαγωγή πακέτου απέτυχε"
+ NOT_VALID_GRAV_PACKAGE: "Δεν είναι ένα έγκυρο πακέτο Grav"
+ NAME_COULD_NOT_BE_DETERMINED: "Δεν μπορούσε να προσδιοριστεί το όνομα"
+ CANNOT_OVERWRITE_SYMLINKS: "Δεν μπορεί να αντικαταστήσει symlinks"
+ ZIP_PACKAGE_NOT_FOUND: "Δεν βρέθηκε ZIP"
+ GPM_OFFICIAL_ONLY: "Επίσημo GPM μόνο"
+ GPM_OFFICIAL_ONLY_HELP: "Επιτρέπει μόνο άμεση εγκατάσταση από το επίσημο αποθετήριο GPM."
+ NO_CHILD_TYPE: "Κανένα θυγατρικό τύπο για αυτό το rawroute"
+ SORTABLE_PAGES: "Ταξινομήσιμες σελίδες:"
+ UNSORTABLE_PAGES: "Μη Ταξινομήσιμες Σελίδες"
+ ADMIN_SPECIFIC_OVERRIDES: "Συγκεκριμένες Παρακάμψεις Διαχειριστή"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Σειρά Εμφάνισης Θυγατρικών"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "Η σειρά με την οποία οι θυγατρικές αυτής της σελίδας θα πρέπει να εμφανίζονται στην προβολή 'Σελίδες' του Admin plugin"
+ PWD_PLACEHOLDER: "πολύπλοκη συμβολοσειρά μήκους τουλάχιστον 8 χαρακτήρων"
+ PWD_REGEX: "Κανονική έκφραση (regex) συνθηματικού"
+ PWD_REGEX_HELP: "Από προεπιλογή: Το συνθηματικό πρέπει να περιέχει τουλάχιστον έναν αριθμό, ένα κεφαλαίο και ένα πεζό γράμμα και τουλάχιστον 8 ή περισσότερους χαρακτήρες"
+ USERNAME_PLACEHOLDER: "πεζούς χαρακτήρες μόνο, π.χ. «admin»"
+ USERNAME_REGEX: "Όνομα χρήστη Regex"
+ USERNAME_REGEX_HELP: "Από προεπιλογή: πεζούς μόνο χαρακτήρες, αριθμούς, παύλες και κάτω παύλες. 3 - 16 χαρακτήρες"
+ CONTENT_PADDING: "Περιθώριο περιεχομένου"
+ CONTENT_PADDING_HELP: "Ενεργοποίηση/απενεργοποίηση του περιθωρίου περιεχομένου γύρω από την περιοχή περιεχομένου για να παρέχει περισσότερο χώρο"
+ ENABLE_AUTO_METADATA: "Αυτόματα μεταδεδομένα από Exif"
+ ENABLE_AUTO_METADATA_HELP: "Δημιουργεί αυτόματα αρχεία μεταδεδομένων για εικόνες με exif πληροφορίες"
+ 2FA_TITLE: "Έλεγχος ταυτότητας δύο παραγόντων"
+ 2FA_INSTRUCTIONS: "##### Έλεγχος Ταυτότητας Δύο Παραγόντων\nΈχετε ενεργοποιημένο τον **ΕΤΔΠ** σε αυτόν το λογαριασμό. Παρακαλώ χρησιμοποιήστε την **ΕΤΔΠ** εφαρμογή σας για να εισαγάγετε τον τρέχοντα **6-ψήφιο κωδικό** ώστε να ολοκληρώσετε τη διαδικασία σύνδεσης."
+ 2FA_REGEN_HINT: "Η αναδημιουργία του κωδικού θα απαιτήσει να ενημερώσετε την εφαρμογή επαλήθευσής σας"
+ 2FA_LABEL: "Πρόσβαση Διαχειριστή"
+ 2FA_FAILED: "Μη έγκυρος κωδικός ελέγχου ταυτότητας δύο παραγόντων, παρακαλούμε προσπαθήστε ξανά..."
+ 2FA_ENABLED: "ΕΤΔΠ Ενεργοποιημένος"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "Μυστικό ΕΤΔΠ"
+ 2FA_SECRET_HELP: "Σαρώστε αυτόν τον κωδικό QR με την [εφαρμογή ελέγχου ταυτότητας] (https://learn.getgrav.org/admin-panel/2fa#apps) της επιλογής σας. Επίσης είναι καλή ιδέα να κρατήσετε αντίγραφο ασφαλείας του μυστικού κωδικού σε μια ασφαλή τοποθεσία, σε περίπτωση που χρειαστεί να επανεγκαταστήσετε την εφαρμογή σας. Ελέγξτε τα [εγχειρίδια του Grav] (https://learn.getgrav.org/admin-panel/2fa) για περισσότερες πληροφορίες "
+ 2FA_REGENERATE: "Επαναδημιουργία"
+ FORCE_LOWERCASE_URLS: "Εξαναγκασμός πεζών URLs"
+ FORCE_LOWERCASE_URLS_HELP: "Ως προεπιλογή το Grav θα ορίσει τις διαδρομές με πεζά. Αν αυτό είναι απενεργοποιημένο, μπορούν να χρησιμοποιηθούν κεφαλαία"
+ INTL_ENABLED: "Ενσωμάτωση intl ενότητας"
+ INTL_ENABLED_HELP: "Χρησιμοποιήστε την Intl PHP ενότητα και διαταξινόμηση για να ταξινομήσετε τις συλλογές που βασίζονται στο UTF8"
+ VIEW_SITE_TIP: "Δείτε τη σελίδα"
+ TOOLS_DIRECT_INSTALL_TITLE: "Απευθείας Εγκατάσταση των Grav Πακέτων"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "Εγκατάσταση Πακέτου με Απευθείας Μεταφόρτωση ZIP"
+ TOOLS_DIRECT_INSTALL_UPLOAD_DESC: "Μπορείτε να εγκαταστήσετε εύκολα ένα έγκυρο θέμα , πρόσθετο , ή ακόμα και πακέτο zip αναβάθμισης του Grav με αυτή τη μέθοδο. Αυτό το πακέτο δε χρειάζεται να είναι καταχωρημένο στο GPM και σας επιτρέπει να επιστρέψετε εύκολα σε προηγούμενη έκδοση ή να εγκαταστήσετε για δοκιμή."
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "Εγκατάσταση Πακέτου μέσω Απομακρυσμένης Αναφοράς URL"
+ TOOLS_DIRECT_INSTALL_URL_DESC: "Εναλλακτικά, μπορείτε επίσης να αναφέρετε το πλήρες URL για το αρχείο ZIP του πακέτου και να το εγκαταστήσετε μέσω απομακρυσμένου URL."
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Μεταφόρτωση και εγκατάσταση"
+ ROUTE_OVERRIDES: "Παρακάμψεις Διαδρομών"
+ ROUTE_DEFAULT: "Προεπιλεγμένη Διαδρομή"
+ ROUTE_CANONICAL: "Κανονική Διαδρομή"
+ ROUTE_ALIASES: "Ψευδώνυμα Διαδρομών"
diff --git a/user/plugins/admin/languages/en.yaml b/user/plugins/admin/languages/en.yaml
new file mode 100644
index 0000000..c851ad6
--- /dev/null
+++ b/user/plugins/admin/languages/en.yaml
@@ -0,0 +1,836 @@
+PLUGIN_ADMIN:
+ ADMIN_NOSCRIPT_MSG: "Please enable JavaScript in your browser."
+ ADMIN_BETA_MSG: "This is a Beta release! Use this in production at your own risk..."
+ ADMIN_REPORT_ISSUE: "Found an issue? Please report it on GitHub."
+ EMAIL_FOOTER: "Powered by Grav - The Modern Flat File CMS"
+ LOGIN_BTN: "Login"
+ LOGIN_BTN_FORGOT: "Forgot"
+ LOGIN_BTN_RESET: "Reset Password"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Send Reset Instructions"
+ LOGIN_BTN_CLEAR: "Clear Form"
+ LOGIN_BTN_CREATE_USER: "Create User"
+ LOGIN_LOGGED_IN: "You have been successfully logged in"
+ LOGIN_FAILED: "Login failed"
+ LOGGED_OUT: "You have been logged out"
+ RESET_NEW_PASSWORD: "Please enter a new password …"
+ RESET_LINK_EXPIRED: "Reset link has expired, please try again"
+ RESET_PASSWORD_RESET: "Password has been reset"
+ RESET_INVALID_LINK: "Invalid reset link used, please try again"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Instructions to reset your password have been sent to your email address"
+ FORGOT_FAILED_TO_EMAIL: "Failed to email instructions, please try again later"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Cannot reset password for %s, no email address is set"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "User with username %s does not exist"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Cannot reset password. This site is not configured to send emails"
+ FORGOT_EMAIL_SUBJECT: "%s Password Reset Request"
+ FORGOT_EMAIL_BODY: "Password Reset Dear %1$s,
A request was made on %4$s to reset your password.
Click this to reset your password
Alternatively, copy the following URL into your browser's address bar:
%2$s
Kind regards, %3$s
"
+ MANAGE_PAGES: "Manage Pages"
+ CONFIGURATION: "Configuration"
+ PAGES: "Pages"
+ PLUGINS: "Plugins"
+ PLUGIN: "Plugin"
+ THEMES: "Themes"
+ LOGOUT: "Logout"
+ BACK: "Back"
+ NEXT: "Next"
+ PREVIOUS: "Previous"
+ ADD_PAGE: "Add Page"
+ ADD_MODULAR: "Add Modular"
+ MOVE: "Move"
+ DELETE: "Delete"
+ UNSET: "Unset"
+ VIEW: "View"
+ SAVE: "Save"
+ NORMAL: "Normal"
+ EXPERT: "Expert"
+ EXPAND_ALL: "Expand All"
+ COLLAPSE_ALL: "Collapse All"
+ SECURE_DELETE_DESC: "Type DELETE and confirm"
+ ERROR: "Error"
+ CLOSE: "Close"
+ CANCEL: "Cancel"
+ CONTINUE: "Continue"
+ CONFIRM: "Confirm"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Confirmation Required"
+ MODAL_CHANGED_DETECTED_TITLE: "Changes Detected"
+ MODAL_CHANGED_DETECTED_DESC: "You have unsaved changes. Are you sure you want to leave without saving?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Confirmation Required"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Are you sure you want to delete this file? This action cannot be undone."
+ ADD_FILTERS: "Add Filters"
+ SEARCH_PAGES: "Search Pages"
+ VERSION: "Version"
+ WAS_MADE_WITH: "Was made with"
+ BY: "By"
+ UPDATE_THEME: "Update Theme"
+ UPDATE_PLUGIN: "Update Plugin"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "of this theme is now available"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "of this plugin is now available"
+ AUTHOR: "Author"
+ HOMEPAGE: "Homepage"
+ DEMO: "Demo"
+ BUG_TRACKER: "Bug Tracker"
+ KEYWORDS: "Keywords"
+ LICENSE: "License"
+ DESCRIPTION: "Description"
+ README: "Readme"
+ REMOVE_THEME: "Remove Theme"
+ INSTALL_THEME: "Install Theme"
+ THEME: "Theme"
+ BACK_TO_THEMES: "Back to Themes"
+ BACK_TO_PLUGINS: "Back to Plugins"
+ CHECK_FOR_UPDATES: "Check for Updates"
+ ADD: "Add"
+ CLEAR_CACHE: "Clear Cache"
+ CLEAR_CACHE_ALL_CACHE: "All Cache"
+ CLEAR_CACHE_ASSETS_ONLY: "Assets Only"
+ CLEAR_CACHE_IMAGES_ONLY: "Images Only"
+ CLEAR_CACHE_CACHE_ONLY: "Cache Only"
+ CLEAR_CACHE_TMP_ONLY: "Tmp Only"
+ DASHBOARD: "Dashboard"
+ UPDATES_AVAILABLE: "Updates Available"
+ DAYS: "Days"
+ UPDATE: "Update"
+ BACKUP: "Backup"
+ BACKUPS: "Backups"
+ BACKUP_NOW: "Backup Now"
+ BACKUPS_STATS: "Backup Statistics"
+ BACKUPS_HISTORY: "Backup History"
+ BACKUPS_PURGE_CONFIG: "Backup Purge Configuration"
+ BACKUPS_PROFILES: "Backup Profiles"
+ BACKUPS_COUNT: "Number of Backups"
+ BACKUPS_PROFILES_COUNT: "Number of Profiles"
+ BACKUPS_TOTAL_SIZE: "Space Used"
+ BACKUPS_NEWEST: "Newest Backup"
+ BACKUPS_OLDEST: "Oldest Backup"
+ BACKUPS_PURGE: "Purge"
+ BACKUPS_NOT_GENERATED: "No backups have been generated yet..."
+ BACKUPS_PURGE_NUMBER: "Using %s of %s backup slots"
+ BACKUPS_PURGE_TIME: "%s days of backups left"
+ BACKUPS_PURGE_SPACE: "Using %s of %s"
+ BACKUP_DELETED: "Backup Successfully Deleted"
+ BACKUP_NOT_FOUND: "Backup Not Found"
+ BACKUP_DATE: "Backup Date"
+
+ STATISTICS: "Statistics"
+ TODAY: "Today"
+ WEEK: "Week"
+ MONTH: "Month"
+ LATEST_PAGE_UPDATES: "Latest Page Updates"
+ MAINTENANCE: "Maintenance"
+ UPDATED: "Updated"
+ MON: "Mon"
+ TUE: "Tue"
+ WED: "Wed"
+ THU: "Thu"
+ FRI: "Fri"
+ SAT: "Sat"
+ SUN: "Sun"
+ COPY: "Copy"
+ EDIT: "Edit"
+ CREATE: "Create"
+ GRAV_ADMIN: "Grav Admin"
+ GRAV_OFFICIAL_PLUGIN: "Grav Official Plugin"
+ GRAV_OFFICIAL_THEME: "Grav Official Theme"
+ PLUGIN_SYMBOLICALLY_LINKED: "This plugin is symbolically linked. Updates won't be detected."
+ THEME_SYMBOLICALLY_LINKED: "This theme is symbolically linked. Updates won't be detected"
+ REMOVE_PLUGIN: "Remove Plugin"
+ INSTALL_PLUGIN: "Install Plugin"
+ AVAILABLE: "Available"
+ INSTALLED: "Installed"
+ INSTALL: "Install"
+ ACTIVE_THEME: "Active Theme"
+ SWITCHING_TO: "Switching to"
+ SWITCHING_TO_DESCRIPTION: "By switching to a different theme, there is no guarantee that all the layout pages are supported, potentially causing errors when trying to load said pages."
+ SWITCHING_TO_CONFIRMATION: "Do you want to continue and switch to the theme"
+ CREATE_NEW_USER: "Create New User"
+ REMOVE_USER: "Remove User"
+ ACCESS_DENIED: "Access denied"
+ ACCOUNT_NOT_ADMIN: "your account does not have administrator permissions"
+ PHP_INFO: "PHP Info"
+ INSTALLER: "Installer"
+ AVAILABLE_THEMES: "Available Themes"
+ AVAILABLE_PLUGINS: "Available Plugins"
+ INSTALLED_THEMES: "Installed Themes"
+ INSTALLED_PLUGINS: "Installed Plugins"
+ BROWSE_ERROR_LOGS: "Browse Error Logs"
+ SITE: "Site"
+ INFO: "Info"
+ SYSTEM: "System"
+ USER: "User"
+ ADD_ACCOUNT: "Add Account"
+ SWITCH_LANGUAGE: "Switch Language"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Successfully enabled plugin"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Successfully disabled plugin"
+ SUCCESSFULLY_CHANGED_THEME: "Successfully changed default theme"
+ INSTALLATION_FAILED: "Installation failed"
+ INSTALLATION_SUCCESSFUL: "Installation successful"
+ UNINSTALL_FAILED: "Uninstall failed"
+ UNINSTALL_SUCCESSFUL: "Uninstall successful"
+ SUCCESSFULLY_SAVED: "Successfully saved"
+ SUCCESSFULLY_COPIED: "Successfully copied"
+ REORDERING_WAS_SUCCESSFUL: "Reordering was successful"
+ SUCCESSFULLY_DELETED: "Successfully deleted"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Successfully switched language"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "You have insufficient permissions for task"
+ CACHE_CLEARED: "Cache cleared"
+ METHOD: "Method"
+ ERROR_CLEARING_CACHE: "Error clearing cache"
+ AN_ERROR_OCCURRED: "An error occurred"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Your backup is ready for download"
+ DOWNLOAD_BACKUP: "Download backup"
+ PAGES_FILTERED: "Pages filtered"
+ NO_PAGE_FOUND: "No Page found"
+ INVALID_PARAMETERS: "Invalid Parameters"
+ NO_FILES_SENT: "No files sent"
+ EXCEEDED_FILESIZE_LIMIT: "Exceeded PHP configuration upload_max_filesize"
+ EXCEEDED_POSTMAX_LIMIT: "Exceeded PHP configuration post_max_size"
+ UNKNOWN_ERRORS: "Unknown errors"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Exceeded Grav configuration file size limit"
+ UNSUPPORTED_FILE_TYPE: "Unsupported file type"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Failed to move uploaded file"
+ FILE_UPLOADED_SUCCESSFULLY: "File uploaded successfully"
+ FILE_DELETED: "File deleted"
+ FILE_COULD_NOT_BE_DELETED: "File could not be deleted"
+ FILE_NOT_FOUND: "File not found"
+ NO_FILE_FOUND: "No file found"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav was successfully updated to"
+ GRAV_UPDATE_FAILED: "Grav update failed"
+ EVERYTHING_UPDATED: "Everything updated"
+ UPDATES_FAILED: "Updates failed"
+ AVATAR_BY: "Avatar by"
+ AVATAR_UPLOAD_OWN: "Or upload your own..."
+ LAST_BACKUP: "Last Backup"
+ FULL_NAME: "Full name"
+ USERNAME: "Username"
+ EMAIL: "Email"
+ USERNAME_EMAIL: "Username or Email"
+ PASSWORD: "Password"
+ PASSWORD_CONFIRM: "Confirm Password"
+ TITLE: "Title"
+ LANGUAGE: "Language"
+ ACCOUNT: "Account"
+ EMAIL_VALIDATION_MESSAGE: "Must be a valid email address"
+ PASSWORD_VALIDATION_MESSAGE: "Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters"
+ LANGUAGE_HELP: "Set the favorite language"
+ MEDIA: "Media"
+ DEFAULTS: "Defaults"
+ SITE_TITLE: "Site Title"
+ SITE_TITLE_PLACEHOLDER: "Site wide title"
+ SITE_TITLE_HELP: "Default title for your site, often used in themes"
+ SITE_DEFAULT_LANG: "Default language"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Default language to be used by theme's tag"
+ SITE_DEFAULT_LANG_HELP: "Default language to be used by theme's tag"
+ DEFAULT_AUTHOR: "Default Author"
+ DEFAULT_AUTHOR_HELP: "A default author name, often used in themes or page content"
+ DEFAULT_EMAIL: "Default Email"
+ DEFAULT_EMAIL_HELP: "A default email to reference in themes or pages"
+ TAXONOMY_TYPES: "Taxonomy Types"
+ TAXONOMY_TYPES_HELP: "Taxonomy types must be defined here if you wish to use them in pages"
+ PAGE_SUMMARY: "Page Summary"
+ ENABLED: "Enabled"
+ ENABLED_HELP: "Enable page summary (the summary returns the same as the page content)"
+ 'YES': "Yes"
+ 'NO': "No"
+ SUMMARY_SIZE: "Summary Size"
+ SUMMARY_SIZE_HELP: "The amount of characters of a page to use as a content summary"
+ FORMAT: "Format"
+ FORMAT_HELP: "short = use the first occurrence of delimiter or size; long = summary delimiter will be ignored"
+ SHORT: "Short"
+ LONG: "Long"
+ DELIMITER: "Delimiter"
+ DELIMITER_HELP: "The summary delimiter (default '===')"
+ METADATA: "Metadata"
+ METADATA_HELP: "Default metadata values that will be displayed on every page unless overridden by the page"
+ NAME: "Name"
+ CONTENT: "Content"
+ SIZE: "Size"
+ ACTION: "Action"
+ REDIRECTS_AND_ROUTES: "Redirects & Routes"
+ CUSTOM_REDIRECTS: "Custom Redirects"
+ CUSTOM_REDIRECTS_HELP: "routes to redirect to other pages. Standard Regex replacement is valid"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/your/redirect"
+ CUSTOM_ROUTES: "Custom Routes"
+ CUSTOM_ROUTES_HELP: "routes to alias to other pages. Standard Regex replacement is valid"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/your/route"
+ FILE_STREAMS: "File Streams"
+ DEFAULT: "Default"
+ PAGE_MEDIA: "Page Media"
+ OPTIONS: "Options"
+ PUBLISHED: "Published"
+ PUBLISHED_HELP: "By default, a page is published unless you explicitly set published: false or via a publish_date being in the future, or unpublish_date in the past"
+ DATE: "Date"
+ DATE_HELP: "The date variable allows you to specifically set a date associated with this page."
+ PUBLISHED_DATE: "Published Date"
+ PUBLISHED_DATE_HELP: "Can provide a date to automatically trigger publication."
+ UNPUBLISHED_DATE: "Unpublished Date"
+ UNPUBLISHED_DATE_HELP: "Can provide a date to automatically trigger un-publication."
+ ROBOTS: "Robots"
+ TAXONOMIES: "Taxonomies"
+ TAXONOMY: "Taxonomy"
+ ADVANCED: "Advanced"
+ SETTINGS: "Settings"
+ FOLDER_NUMERIC_PREFIX: "Folder Numeric Prefix"
+ FOLDER_NUMERIC_PREFIX_HELP: "Numeric prefix that provides manual ordering and implies visibility"
+ FOLDER_NAME: "Folder Name"
+ FOLDER_NAME_HELP: "The folder name that will be stored in the filesystem for this page"
+ PARENT: "Parent"
+ DEFAULT_OPTION_ROOT: "- Root -"
+ DEFAULT_OPTION_SELECT: "- Select -"
+ DISPLAY_TEMPLATE: "Display Template"
+ DISPLAY_TEMPLATE_HELP: "The page type that translates into which twig template renders the page"
+ BODY_CLASSES: "Body Classes"
+ ORDERING: "Ordering"
+ PAGE_ORDER: "Page Order"
+ OVERRIDES: "Overrides"
+ MENU: "Menu"
+ MENU_HELP: "The string to be used in a menu. If not set, Title will be used."
+ SLUG: "Slug"
+ SLUG_HELP: "The slug variable allows you to specifically set the page's portion of the URL"
+ SLUG_VALIDATE_MESSAGE: "A slug must contain only lowercase alphanumeric characters and dashes"
+ PROCESS: "Process"
+ PROCESS_HELP: "Control how pages are processed. Can be set per-page rather than globally"
+ DEFAULT_CHILD_TYPE: "Default Child Type"
+ USE_GLOBAL: "Use Global"
+ ROUTABLE: "Routable"
+ ROUTABLE_HELP: "If this page is reachable by a URL"
+ CACHING: "Caching"
+ VISIBLE: "Visible"
+ VISIBLE_HELP: "Determines if a page is visible in the navigation."
+ DISABLED: "Disabled"
+ ITEMS: "Items"
+ ORDER_BY: "Order By"
+ ORDER: "Order"
+ FOLDER: "Folder"
+ ASCENDING: "Ascending"
+ DESCENDING: "Descending"
+ ADD_MODULAR_CONTENT: "Add Modular Content"
+ PAGE_TITLE: "Page Title"
+ PAGE_TITLE_HELP: "The title of the page"
+ PAGE: "Page"
+ MODULAR_TEMPLATE: "Modular Template"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Filename"
+ PARENT_PAGE: "Parent Page"
+ HOME_PAGE: "Home page"
+ HOME_PAGE_HELP: "The page that Grav will use as the default landing page"
+ DEFAULT_THEME: "Default theme"
+ DEFAULT_THEME_HELP: "Set the default theme for Grav to use (default is Antimatter)"
+ TIMEZONE: "Timezone"
+ TIMEZONE_HELP: "Override the default timezone the server"
+ SHORT_DATE_FORMAT: "Short display date format"
+ SHORT_DATE_FORMAT_HELP: "Set the short date format that can be used by themes"
+ LONG_DATE_FORMAT: "Long display date format"
+ LONG_DATE_FORMAT_HELP: "Set the long date format that can be used by themes"
+ DEFAULT_ORDERING: "Default ordering"
+ DEFAULT_ORDERING_HELP: "Pages in a list will render using this order unless it is overridden"
+ DEFAULT_ORDERING_DEFAULT: "Default - based on folder name"
+ DEFAULT_ORDERING_FOLDER: "Folder - based on prefix-less folder name"
+ DEFAULT_ORDERING_TITLE: "Title - based on title field in header"
+ DEFAULT_ORDERING_DATE: "Date - based on date field in header"
+ DEFAULT_ORDER_DIRECTION: "Default order direction"
+ DEFAULT_ORDER_DIRECTION_HELP: "The direction of pages in a list"
+ DEFAULT_PAGE_COUNT: "Default page count"
+ DEFAULT_PAGE_COUNT_HELP: "Default maximum pages count in a list"
+ DATE_BASED_PUBLISHING: "Date-based publishing"
+ DATE_BASED_PUBLISHING_HELP: "Automatically (un)publish posts based on their date"
+ EVENTS: "Events"
+ EVENTS_HELP: "Enable or Disable specific events. Disabling these can break plugins"
+ REDIRECT_DEFAULT_ROUTE: "Redirect default route"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Automatically redirect to a page's default route"
+ LANGUAGES: "Languages"
+ SUPPORTED: "Supported"
+ SUPPORTED_HELP: "Comma separated list of 2 letter language codes (for example 'en,fr,de')"
+ TRANSLATIONS_ENABLED: "Translations enabled"
+ TRANSLATIONS_ENABLED_HELP: "Support translations in Grav, plugins and extensions"
+ TRANSLATIONS_FALLBACK: "Translations fallback"
+ TRANSLATIONS_FALLBACK_HELP: "Fallback through supported translations if active language doesn't exist"
+ ACTIVE_LANGUAGE_IN_SESSION: "Active language in session"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Store the active language in the session"
+ HTTP_HEADERS: "HTTP Headers"
+ EXPIRES: "Expires"
+ EXPIRES_HELP: "Sets the expires header. The value is in seconds."
+ CACHE_CONTROL: "HTTP Cache-Control"
+ CACHE_CONTROL_HELP: "Set to a valid cache-control value such as `no-cache, no-store, must-revalidate`"
+ LAST_MODIFIED: "Last modified"
+ LAST_MODIFIED_HELP: "Sets the last modified header that can help optimize proxy and browser caching"
+ ETAG: "ETag"
+ ETAG_HELP: "Sets the etag header to help identify when a page has been modified"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Sets the `Vary: Accept Encoding` header to help with proxy and CDN caching"
+ MARKDOWN: "Markdown"
+ MARKDOWN_EXTRA: "Markdown extra"
+ MARKDOWN_EXTRA_HELP: "Enable default support for Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ MARKDOWN_EXTRA_ESCAPE_FENCES: "Escape HTML elements in markdown extra fences"
+ MARKDOWN_EXTRA_ESCAPE_FENCES_HELP: "Escapes HTML elements in markdown extra fences"
+ AUTO_LINE_BREAKS: "Auto line breaks"
+ AUTO_LINE_BREAKS_HELP: "Enable support for automatic line breaks in markdown"
+ AUTO_URL_LINKS: "Auto URL links"
+ AUTO_URL_LINKS_HELP: "Enable automatic conversion of URLs into HTML hyperlinks"
+ ESCAPE_MARKUP: "Escape markup"
+ ESCAPE_MARKUP_HELP: "Escape markup tags into HTML entities"
+ CACHING_HELP: "Global ON/OFF switch to enable/disable Grav caching"
+ CACHE_CHECK_METHOD: "Cache check method"
+ CACHE_CHECK_METHOD_HELP: "Select the method that Grav uses to check if page files have been modified."
+ CACHE_DRIVER: "Cache driver"
+ CACHE_DRIVER_HELP: "Choose which cache driver Grav should use. 'Auto Detect' attempts to find the best for you"
+ CACHE_PREFIX: "Cache prefix"
+ CACHE_PREFIX_HELP: "An identifier for part of the Grav key. Don't change unless you know what your doing."
+ CACHE_PREFIX_PLACEHOLDER: "Derived from base URL (override by entering random string)"
+ CACHE_PURGE_JOB: "Run Scheduled Purge Job"
+ CACHE_PURGE_JOB_HELP: "With the scheduler you can periodically clear out old Doctrine file cache folders with this job"
+ CACHE_CLEAR_JOB: "Run Scheduled Clear Job"
+ CACHE_CLEAR_JOB_HELP: "With the scheduler you can periodically clear the Grav Cache"
+ CACHE_JOB_TYPE: "Cache Job Type"
+ CACHE_JOB_TYPE_HELP: "Either clear with the 'standard' folders cache clear, or with 'all' folders"
+ CACHE_PURGE: "Purge Old Cache"
+ LIFETIME: "Lifetime"
+ LIFETIME_HELP: "Sets the cache lifetime in seconds. 0 = infinite"
+ GZIP_COMPRESSION: "Gzip compression"
+ GZIP_COMPRESSION_HELP: "Enable GZip compression of the Grav page for increased performance."
+ TWIG_TEMPLATING: "Twig Templating"
+ TWIG_CACHING: "Twig caching"
+ TWIG_CACHING_HELP: "Control the Twig caching mechanism. Leave this enabled for best performance."
+ TWIG_DEBUG: "Twig debug"
+ TWIG_DEBUG_HELP: "Allows the option of not loading the Twig Debugger extension"
+ DETECT_CHANGES: "Detect changes"
+ DETECT_CHANGES_HELP: "Twig will automatically recompile the Twig cache if it detects any changes in Twig templates"
+ AUTOESCAPE_VARIABLES: "Autoescape variables"
+ AUTOESCAPE_VARIABLES_HELP: "Autoescapes all variables. This will break your site most likely"
+ ASSETS: "Assets"
+ CSS_PIPELINE: "CSS pipeline"
+ CSS_PIPELINE_HELP: "The CSS pipeline is the unification of multiple CSS resources into one file"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Include externals in CSS pipeline"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "External URLs sometimes have relative file references and shouldn't be pipelined"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "CSS pipeline render first"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Render the CSS pipeline before any other CSS references that are not included"
+ CSS_MINIFY: "CSS minify"
+ CSS_MINIFY_HELP: "Minify the CSS during pipelining"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "CSS minify Windows override"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Minify Override for Windows platforms. False by default due to ThreadStackSize"
+ CSS_REWRITE: "CSS rewrite"
+ CSS_REWRITE_HELP: "Rewrite any CSS relative URLs during pipelining"
+ JAVASCRIPT_PIPELINE: "JavaScript pipeline"
+ JAVASCRIPT_PIPELINE_HELP: "The JS pipeline is the unification of multiple JS resources into one file"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Include externals in JS pipeline"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "External URLs sometimes have relative file references and shouldn't be pipelined"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "JS pipeline render first"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Render the JS pipeline before any other JS references that are not included"
+ JAVASCRIPT_MINIFY: "JavaScript minify"
+ JAVASCRIPT_MINIFY_HELP: "Minify the JS during pipelining"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Enable timestamps on assets"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Enable asset timestamps"
+ COLLECTIONS: "Collections"
+ ERROR_HANDLER: "Error handler"
+ DISPLAY_ERRORS: "Display errors"
+ DISPLAY_ERRORS_HELP: "Display full backtrace-style error page"
+ LOG_ERRORS: "Log errors"
+ LOG_ERRORS_HELP: "Log errors to /logs folder"
+ LOG_HANDLER: "Log handler"
+ LOG_HANDLER_HELP: "Where to output the logs"
+ SYSLOG_FACILITY: "Syslog facility"
+ SYSLOG_FACILITY_HELP: "Syslog facility for output"
+ DEBUGGER: "Debugger"
+ DEBUGGER_HELP: "Enable Grav debugger and following settings"
+ DEBUG_TWIG: "Debug Twig"
+ DEBUG_TWIG_HELP: "Enable debugging of Twig templates"
+ SHUTDOWN_CLOSE_CONNECTION: "Shutdown close connection"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Close the connection before calling onShutdown(). false for debugging"
+ DEFAULT_IMAGE_QUALITY: "Default image quality"
+ DEFAULT_IMAGE_QUALITY_HELP: "Default image quality to use when resampling or caching images (85%)"
+ CACHE_ALL: "Cache all images"
+ CACHE_ALL_HELP: "Run all images through Grav's cache system even if they have no media manipulations"
+ IMAGES_DEBUG: "Image debug watermark"
+ IMAGES_DEBUG_HELP: "Show an overlay over images indicating the pixel depth of the image when working with retina for example"
+ IMAGES_SEOFRIENDLY: "SEO-Friendly Image names"
+ IMAGES_SEOFRIENDLY_HELP: "When enabled, the image name is displayed first, then a smaller hash to reflect processed operations"
+ UPLOAD_LIMIT: "File upload limit"
+ UPLOAD_LIMIT_HELP: "Set maximum upload size in bytes (0 is unlimited)"
+ ENABLE_MEDIA_TIMESTAMP: "Enable timestamps on media"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Appends a timestamp based on last modified date to each media item"
+ SESSION: "Session"
+ SESSION_ENABLED_HELP: "Enable session support within Grav"
+ TIMEOUT: "Timeout"
+ TIMEOUT_HELP: "Sets the session timeout in seconds"
+ SESSION_NAME_HELP: "An identifier used to form the name of the session cookie"
+ SESSION_UNIQUENESS: "Unique string"
+ SESSION_UNIQUENESS_HELP: "MD5 hash of Grav's root path, ie `GRAV_ROOT` (default) or a based on the random `security.salt` string."
+ ABSOLUTE_URLS: "Absolute URLs"
+ ABSOLUTE_URLS_HELP: "Absolute or relative URLs for `base_url`"
+ PARAMETER_SEPARATOR: "Parameter separator"
+ PARAMETER_SEPARATOR_HELP: "Separator for passed parameters that can be changed for Apache on Windows"
+ TASK_COMPLETED: "Task completed"
+ EVERYTHING_UP_TO_DATE: "Everything is up to date"
+ UPDATES_ARE_AVAILABLE: "update(s) are available"
+ IS_AVAILABLE_FOR_UPDATE: "is available for update"
+ IS_NOW_AVAILABLE: "is now available"
+ CURRENT: "Current"
+ UPDATE_GRAV_NOW: "Update Grav Now"
+ GRAV_SYMBOLICALLY_LINKED: "Grav is symbolically linked. Upgrade won't be available"
+ UPDATING_PLEASE_WAIT: "Updating... please wait, downloading"
+ OF_THIS: "of this"
+ OF_YOUR: "of your"
+ HAVE_AN_UPDATE_AVAILABLE: "have an update available"
+ SAVE_AS: "Save as"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Are you sure you want to delete this page and all its children? If the page is translated in other languages, those translations will be kept and must be deleted separately. Otherwise the page folder will be deleted along with its subpages. This action cannot be undone."
+ AND: "and"
+ UPDATE_AVAILABLE: "Update available"
+ METADATA_KEY: "Key (e.g. 'Keywords')"
+ METADATA_VALUE: "Value (e.g. 'Blog, Grav')"
+ USERNAME_HELP: "Username should be between 3 and 16 characters, including lowercase letters, numbers, underscores, and hyphens. Uppercase letters, spaces, and special characters are not allowed"
+ FULLY_UPDATED: "Fully Updated"
+ SAVE_LOCATION: "Save location"
+ PAGE_FILE: "Page Template"
+ PAGE_FILE_HELP: "Page template file name, and by default the display template for this page"
+ NO_USER_ACCOUNTS: "No user accounts found, please create one first..."
+ NO_USER_EXISTS: "No local user exists for this account, cannot save..."
+ REDIRECT_TRAILING_SLASH: "Redirect trailing slash"
+ REDIRECT_TRAILING_SLASH_HELP: "Perform a 301 redirect rather than transparently handling trailing slash URIs."
+ DEFAULT_DATE_FORMAT: "Page date format"
+ DEFAULT_DATE_FORMAT_HELP: "Page date format used by Grav. By default, Grav attempts to guess your date format, however you can specifiy a format using PHP's date syntax (e.g.: Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Guess automatically"
+ IGNORE_FILES: "Ignore files"
+ IGNORE_FILES_HELP: "Specific files to ignore when processing pages"
+ IGNORE_FOLDERS: "Ignore folders"
+ IGNORE_FOLDERS_HELP: "Specific folders to ignore when processing pages"
+ HIDE_EMPTY_FOLDERS: "Hide empty folders"
+ HIDE_EMPTY_FOLDERS_HELP: "If folder has no .md file, should it be hidden in navigation as well as being unroutable"
+ HTTP_ACCEPT_LANGUAGE: "Set language from browser"
+ HTTP_ACCEPT_LANGUAGE_HELP: "You can opt to try to set the language based on `http_accept_language` header tag in the browser"
+ OVERRIDE_LOCALE: "Override locale"
+ OVERRIDE_LOCALE_HELP: "Override the locale setting in PHP based on the current language"
+ REDIRECT: "Page redirect"
+ REDIRECT_HELP: "Enter a page route or external URL for this page to redirect to. e.g. `/some/route` or `http://somesite.com`"
+ PLUGIN_STATUS: "Plugin status"
+ INCLUDE_DEFAULT_LANG: "Include default language"
+ INCLUDE_DEFAULT_LANG_HELP: "This will prepend all URLs in the default language with the default language. e.g. `/en/blog/my-post`"
+ PAGES_FALLBACK_ONLY: "Pages fallback only"
+ PAGES_FALLBACK_ONLY_HELP: "Only 'fallback' to find page content through supported languages, default behavior is to display any language found if active language is missing"
+ ALLOW_URL_TAXONOMY_FILTERS: "URL Taxonomy Filters"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Page-based collections allow you to filter via `/taxonomy:value`."
+ REDIRECT_DEFAULT_CODE: "Default redirect code"
+ REDIRECT_DEFAULT_CODE_HELP: "The HTTP status code to use for redirects"
+ IGNORE_HIDDEN: "Ignore hidden"
+ IGNORE_HIDDEN_HELP: "Ignore all files and folders that begin with a DOT"
+ WRAPPED_SITE: "Wrapped site"
+ WRAPPED_SITE_HELP: "For themes/plugins to know if Grav is wrapped by another platform"
+ FALLBACK_TYPES: "Allowed fallback types"
+ FALLBACK_TYPES_HELP: "Allowed file types that can be found if accessed via Page route. Defaults to any supported media type."
+ INLINE_TYPES: "Inline fallback types"
+ INLINE_TYPES_HELP: "A list of file types that should be displayed inline rather than downloaded"
+ APPEND_URL_EXT: "Append URL extension"
+ APPEND_URL_EXT_HELP: "Will add a custom extension to the Page's URL. Note, this will mean Grav will look for `..twig` template"
+ PAGE_MODES: "Page Modes"
+ PAGE_TYPES: "Page Types"
+ PAGE_TYPES_HELP: "Determines the page types that Grav supports and the order determines which type to fall back to if the request is ambiguous"
+ ACCESS_LEVELS: "Access Levels"
+ GROUPS: "Groups"
+ GROUPS_HELP: "List of groups the user is part of"
+ ADMIN_ACCESS: "Admin Access"
+ SITE_ACCESS: "Site Access"
+ INVALID_SECURITY_TOKEN: "Invalid Security Token"
+ ACTIVATE: "Activate"
+ TWIG_UMASK_FIX: "Umask Fix"
+ TWIG_UMASK_FIX_HELP: "By default Twig creates cached files as 0755, fix switches this to 0775"
+ CACHE_PERMS: "Cache Permissions"
+ CACHE_PERMS_HELP: "Default cache folder perms. Usually 0755 or 0775 depending on setup"
+ REMOVE_SUCCESSFUL: "Remove Successful"
+ REMOVE_FAILED: "Remove Failed"
+ HIDE_HOME_IN_URLS: "Hide home route in URLs"
+ HIDE_HOME_IN_URLS_HELP: "Will ensure the default routes for any pages under home do not reference home's regular route"
+ TWIG_FIRST: "Process Twig First"
+ TWIG_FIRST_HELP: "If you enabled Twig page processing, then you can configure Twig to process before or after markdown"
+ SESSION_SECURE: "Secure"
+ SESSION_SECURE_HELP: "If true, indicates that communication for this cookie must be over an encrypted transmission. WARNING: Enable this only on sites that run exclusively on HTTPS"
+ SESSION_HTTPONLY: "HTTP Only"
+ SESSION_HTTPONLY_HELP: "If true, indicates that cookies should be used only over HTTP, and JavaScript modification is not allowed"
+ REVERSE_PROXY: "Reverse Proxy"
+ REVERSE_PROXY_HELP: "Enable this if you are behind a reverse proxy and you are having trouble with URLs containing incorrect ports"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Invalid frontmatter, could not save"
+ ADD_FOLDER: "Add Folder"
+ PROXY_URL: "Proxy URL"
+ PROXY_URL_HELP: "Enter the proxy HOST or IP and PORT"
+ NOTHING_TO_SAVE: "Nothing to Save"
+ FILE_ERROR_ADD: "An error occurred while trying to add the file"
+ FILE_ERROR_UPLOAD: "An error occurred while trying to upload the file"
+ FILE_UNSUPPORTED: "Unsupported file type"
+ ADD_ITEM: "Add item"
+ FILE_TOO_LARGE: "The file is too large to be uploaded, maximum allowed is %s according to your PHP settings. Increase your `post_max_size` PHP setting"
+ INSTALLING: "Installing"
+ LOADING: "Loading.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "The following dependencies need to be fulfilled first:"
+ ERROR_INSTALLING_PACKAGES: "Error while installing the package(s)"
+ INSTALLING_DEPENDENCIES: "Installing dependencies..."
+ INSTALLING_PACKAGES: "Installing package(s).."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Package(s) successfully installed."
+ READY_TO_INSTALL_PACKAGES: "Ready to install the package(s)"
+ PACKAGES_NOT_INSTALLED: "Packages not installed"
+ PACKAGES_NEED_UPDATE: "Packages already installed, but too old"
+ PACKAGES_SUGGESTED_UPDATE: "Packages already installed, and version is ok, but will be updated to keep you up to date"
+ REMOVE_THE: "Remove the %s"
+ CONFIRM_REMOVAL: "Are you sure you want to delete this %s?"
+ REMOVED_SUCCESSFULLY: "%s removed successfully"
+ ERROR_REMOVING_THE: "Error removing the %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "The %s required the following dependencies, which are not required by other installed packages. If you are not using them, you can remove them directly from here."
+ READY_TO_UPDATE_PACKAGES: "Ready to update the package(s)"
+ ERROR_UPDATING_PACKAGES: "Error while updating the package(s)"
+ UPDATING_PACKAGES: "Updating package(s).."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Package(s) successfully updated."
+ UPDATING: "Updating"
+ GPM_RELEASES: "GPM Releases"
+ GPM_RELEASES_HELP: "Choose 'Testing' to install beta or testing versions"
+ GPM_METHOD: "Remote Fetch Method"
+ GPM_METHOD_HELP: "When set to Auto, Grav will determine if fopen is available and use it, otherwise fall back to cURL. To force the use of one or the other switch the setting."
+ GPM_VERIFY_PEER: "Remote Verify Peer (SSL)"
+ GPM_VERIFY_PEER_HELP: "Some providers seems to fail verifying getgrav.org SSL certificate, causing GPM to not work. If this is the case for you, turning off this setting might help"
+ AUTO: "Auto"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stable"
+ TESTING: "Testing"
+ FRONTMATTER_PROCESS_TWIG: "Process frontmatter Twig"
+ FRONTMATTER_PROCESS_TWIG_HELP: "When enabled you can use Twig config variables in page front matter"
+ FRONTMATTER_IGNORE_FIELDS: "Ignore frontmatter fields"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Certain frontmatter fields may contain Twig but should not be processed, such as 'forms'"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Package %s installed successfully"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Parent setting order, ordering disabled"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Page is not visible, ordering disabled"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "Ordering via the admin is unsupported because there are more than 200 siblings"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Page ordering is disabled for this page because Folder Numeric Prefix is not enabled"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "NOTE: You cannot add media files until you save the page. Just click 'Save' on top"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "NOTE: Page must be saved before you can upload files to it."
+ DROP_FILES_HERE_TO_UPLOAD: "Drop your files here or click in this area "
+ INSERT: "Insert"
+ UNDO: "Undo"
+ REDO: "Redo"
+ HEADERS: "Headers"
+ BOLD: "Bold"
+ ITALIC: "Italic"
+ STRIKETHROUGH: "Strikethrough"
+ SUMMARY_DELIMITER: "Summary Delimiter"
+ LINK: "Link"
+ IMAGE: "Image"
+ BLOCKQUOTE: "Blockquote"
+ UNORDERED_LIST: "Unordered List"
+ ORDERED_LIST: "Ordered List"
+ EDITOR: "Editor"
+ PREVIEW: "Preview"
+ FULLSCREEN: "Fullscreen"
+ MODULAR: "Modular"
+ MODULAR_SETUP: "Modular Setup"
+ NON_ROUTABLE: "Non-Routable"
+ NON_MODULAR: "Non-Modular"
+ NON_VISIBLE: "Non-Visible"
+ NON_PUBLISHED: "Non-Published"
+ CHARACTERS: "characters"
+ PUBLISHING: "Publishing"
+ NOTIFICATIONS: "Notifications"
+ MEDIA_TYPES: "Media Types"
+ IMAGE_OPTIONS: "Image options"
+ MIME_TYPE: "Mime Type"
+ THUMB: "Thumb"
+ TYPE: "Type"
+ FILE_EXTENSION: "File Extension"
+ LEGEND: "Page Legend"
+ MEMCACHE_SERVER: "Memcache server"
+ MEMCACHE_SERVER_HELP: "The Memcache server address"
+ MEMCACHE_PORT: "Memcache port"
+ MEMCACHE_PORT_HELP: "The Memcache server port"
+ MEMCACHED_SERVER: "Memcached server"
+ MEMCACHED_SERVER_HELP: "The Memcached server address"
+ MEMCACHED_PORT: "Memcached port"
+ MEMCACHED_PORT_HELP: "The Memcached server port"
+ REDIS_SERVER: "Redis server"
+ REDIS_SERVER_HELP: "The Redis server address"
+ REDIS_PORT: "Redis port"
+ REDIS_PORT_HELP: "The Redis server port"
+ REDIS_PASSWORD: "Redis password/secret"
+ ALL: "All"
+ FROM: "from"
+ TO: "to"
+ RELEASE_DATE: "Release Date"
+ SORT_BY: "Sort By"
+ RESOURCE_FILTER: "Filter..."
+ FORCE_SSL: "Force SSL"
+ FORCE_SSL_HELP: "Globally force SSL, if enabled when the site is reached through HTTP, Grav sends a redirect to the HTTPS page"
+ NEWS_FEED: "News Feed"
+ EXTERNAL_URL: "External URL"
+ SESSION_PATH: "The session path"
+ SESSION_PATH_HELP: "Use only if you choose a custom base URL (you rewrite the site domain / subfolder)"
+ CUSTOM_BASE_URL: "Custom base URL"
+ CUSTOM_BASE_URL_HELP: "Use if you want to rewrite the site domain or use a different subfolder than the one used by Grav. Example: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'Cannot use "%s" outside of pages.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Unable to upload file %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'Unable to move file %s to "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'Cancel upload'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Are you sure you want to cancel this upload?'
+ DROPZONE_DEFAULT_MESSAGE: 'Drop your files here or click in this area '
+ DROPZONE_FALLBACK_MESSAGE: 'Your browser does not support drag and drop file uploads.'
+ DROPZONE_FALLBACK_TEXT: 'Please use the fallback form below to upload your files like in the older days.'
+ DROPZONE_FILE_TOO_BIG: 'File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "You can't upload files of this type."
+ DROPZONE_MAX_FILES_EXCEEDED: "You can not upload any more files."
+ DROPZONE_REMOVE_FILE: "Remove file"
+ DROPZONE_RESPONSE_ERROR: "Server responded with {{statusCode}} code."
+ PREMIUM_PRODUCT: "Premium"
+ ADMIN_CACHING: "Enable Admin Caching"
+ ADMIN_CACHING_HELP: "Caching in the admin can be controlled independently from the front-end site"
+ DESTINATION_NOT_SPECIFIED: "Destination not specified"
+ UPLOAD_ERR_NO_TMP_DIR: "Missing a temporary folder"
+ SESSION_SPLIT: "Session Split"
+ SESSION_SPLIT_HELP: "Independent split sessions between site and other plugins (such as admin)"
+ ERROR_FULL_BACKTRACE: "Full Backtrace Error"
+ ERROR_SIMPLE: "Simple Error"
+ ERROR_SYSTEM: "System Error"
+ IMAGES_AUTO_FIX_ORIENTATION: "Fix orientation automatically"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Automatically fix the image orientation based on the Exif data"
+ REDIS_SOCKET: "Redis socket"
+ REDIS_SOCKET_HELP: "The Redis socket"
+ NOT_SET: "Not set"
+ PERMISSIONS: "Permissions"
+ NEVER_CACHE_TWIG: "Never Cache Twig"
+ NEVER_CACHE_TWIG_HELP: "Only cache content and process Twig every time for pages. Ignores twig_first setting."
+ ALLOW_WEBSERVER_GZIP: "Allow WebServer Gzip"
+ ALLOW_WEBSERVER_GZIP_HELP: "Off by default. When enabled, WebServer-configured Gzip/Deflate compression will work, but http connection will not be closed before onShutDown() event causing slower page loading"
+ OFFLINE_WARNING: "The connection to the GPM cannot be established"
+ CLEAR_IMAGES_BY_DEFAULT: "Clear image cache by default"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "By default processed images are cleared for all cache clears, this can be disabled"
+ CLI_COMPATIBILITY: "CLI Compatibility"
+ CLI_COMPATIBILITY_HELP: "Ensures that only non-volatile Cache drivers are used (file, redis, memcache, etc.)"
+ REINSTALL_PLUGIN: "Reinstall Plugin"
+ REINSTALL_THEME: "Reinstall Theme"
+ REINSTALL_THE: "Reinstall the %s"
+ CONFIRM_REINSTALL: "Are you sure you want to reinstall this %s?"
+ REINSTALLED_SUCCESSFULLY: "%s reinstalled successfully"
+ ERROR_REINSTALLING_THE: "Error reinstalling the %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Package %s reinstalled successfully"
+ REINSTALLATION_FAILED: "Reinstallation failed"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "The installed version is not the latest release. By clicking Continue, you'll remove the current version and install the latest available release"
+ TOOLS: "Tools"
+ DIRECT_INSTALL: "Direct Install"
+ NO_PACKAGE_NAME: "Package name not specified"
+ PACKAGE_EXTRACTION_FAILED: "Package extraction failed"
+ NOT_VALID_GRAV_PACKAGE: "Not a valid Grav package"
+ NAME_COULD_NOT_BE_DETERMINED: "Name could not be determined"
+ CANNOT_OVERWRITE_SYMLINKS: "Cannot overwrite symlinks"
+ ZIP_PACKAGE_NOT_FOUND: "ZIP package could not be found"
+ GPM_OFFICIAL_ONLY: "Official GPM Only"
+ GPM_OFFICIAL_ONLY_HELP: "Only allow direct installs from the official GPM repository only."
+ NO_CHILD_TYPE: "No child type for this rawroute"
+ SORTABLE_PAGES: "Sortable Pages:"
+ UNSORTABLE_PAGES: "Unsortable Pages"
+ ADMIN_SPECIFIC_OVERRIDES: "Admin Specific Overrides"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Children Display Order"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "The order that children of this page should be displayed in the 'Pages' view of Admin plugin"
+ PWD_PLACEHOLDER: "complex string at least 8 chars long"
+ PWD_REGEX: "Password Regex"
+ PWD_REGEX_HELP: "By default: Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters"
+ USERNAME_PLACEHOLDER: "lowercase chars only, e.g. 'admin'"
+ USERNAME_REGEX: "Username Regex"
+ USERNAME_REGEX_HELP: "By default: Only lowercase chars, digits, dashes, and underscores. 3 - 16 chars"
+ CONTENT_PADDING: "Content padding"
+ CONTENT_PADDING_HELP: "Enable/Disable content padding around content area to provide more space"
+ ENABLE_AUTO_METADATA: "Auto metadata from Exif"
+ ENABLE_AUTO_METADATA_HELP: "Automatically generate metadata files for images with exif information"
+ 2FA_TITLE: "2-Factor Authentication"
+ 2FA_INSTRUCTIONS: "##### 2-Factor Authentication\nYou have **2FA** enabled on this account. Please use your **2FA** app to enter the current **6-digit code** to complete the login process."
+ 2FA_REGEN_HINT: "Regenerating the secret will require you to update your authenticator app"
+ 2FA_LABEL: "Admin Access"
+ 2FA_FAILED: "Invalid 2-Factor Authentication code, please try again..."
+ 2FA_ENABLED: "2FA Enabled"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "2FA Secret"
+ 2FA_SECRET_HELP: "Scan this QR code into your [Authenticator App](https://learn.getgrav.org/admin-panel/2fa#apps). Also it's a good idea to backup the secret in a safe location, in case you need to reinstall your app. Check the [Grav docs](https://learn.getgrav.org/admin-panel/2fa) for more information "
+ 2FA_REGENERATE: "Regenerate"
+ FORCE_LOWERCASE_URLS: "Force lowercase URLs"
+ FORCE_LOWERCASE_URLS_HELP: "By default Grav will set all slugs and routes to be lowercase. With this set to false, Uppercase slugs and routes can be used"
+ INTL_ENABLED: "Intl module integration"
+ INTL_ENABLED_HELP: "Use Intl PHP module and collate to sort UTF8 based collections"
+ VIEW_SITE_TIP: "View site"
+ TOOLS_DIRECT_INSTALL_TITLE: "Direct Install of Grav Packages"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "Install Package via Direct ZIP Upload"
+ TOOLS_DIRECT_INSTALL_UPLOAD_DESC: "You can easily install a valid Grav theme , plugin , or even Grav update Zip package via this method. This package does not have to be registered via GPM and allows you to easily roll back to a prior version or install for testing."
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "Install Package via Remote URL Reference"
+ TOOLS_DIRECT_INSTALL_URL_DESC: "Alternatively, you can also reference a full URL to the package ZIP file and install it via this remote URL."
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Upload and install"
+ ROUTE_OVERRIDES: "Route Overrides"
+ ROUTE_DEFAULT: "Default Route"
+ ROUTE_CANONICAL: "Canonical Route"
+ ROUTE_ALIASES: "Route Aliases"
+ OPEN_NEW_TAB: "Open in new tab"
+ PAGEMEDIA_RESIZER: "> The following settings apply to images uploaded through the page media. Resize Width / Height will automatically resize down and proportionally an image if exceeds the limits set in the inputs. Resolution min and max values define the size ranges for uploaded images. Set the fields to 0 to prevent any manipulation."
+ SESSION_INITIALIZE: "Initialize Session"
+ SESSION_INITIALIZE_HELP: "Makes Grav to start a session. This feature is needed to make any user interaction to work, such as login, forms etc. Admin plugin isn't affected by this setting."
+ STRICT_YAML_COMPAT: "YAML Compatibility"
+ STRICT_YAML_COMPAT_HELP: "Falls back to Symfony 2.4 YAML parser if Native or 3.4 parser fails"
+ STRICT_TWIG_COMPAT: "Twig Compatibility"
+ STRICT_TWIG_COMPAT_HELP: "Enables deprecated Twig autoescape setting. When disabled, |raw filter is required to output HTML as Twig will autoescape output"
+ SCHEDULER: "Scheduler"
+ SCHEDULER_INSTALL_INSTRUCTIONS: "Install Instructions"
+ SCHEDULER_INSTALLED_READY: "Installed and Ready"
+ SCHEDULER_CRON_NA: "Cron Not Available"
+ SCHEDULER_NOT_ENABLED: "Not Enabled"
+ SCHEDULER_SETUP: "Scheduler Setup"
+ SCHEDULER_INSTRUCTIONS: "The Grav Scheduler allows you to create and schedule custom jobs. It also provides a method for Grav plugins to integrate programmatically and dynamically add jobs to be run periodically."
+ SCHEDULER_POST_INSTRUCTIONS: "To enable the Scheduler's functionality, you must add the Grav Scheduler to your system's crontab file. Run the command above from the terminal to add it automatically. Once saved, refresh this page to see the status Ready."
+ SCHEDULER_JOBS: "Custom Scheduler Jobs"
+ SCHEDULER_STATUS: "Scheduler Status"
+ SCHEDULER_RUNAT: "Run At"
+ SCHEDULER_RUNAT_HELP: "Cron formatted 'at' syntax"
+ SCHEDULER_OUTPUT: "Output File"
+ SCHEDULER_OUTPUT_HELP: "The path/filename of the output file (from the root of the Grav installation)"
+ SCHEDULER_OUTPUT_TYPE: "Output Type"
+ SCHEDULER_OUTPUT_TYPE_HELP: "Either append to the same file each run, or overwrite the file with each run"
+ SCHEDULER_EMAIL: "Email"
+ SCHEDULER_EMAIL_HELP: "Email to send output to. NOTE: requires output file to be set"
+ SECURITY: "Security"
+ XSS_SECURITY: "XSS Security for Content"
+ XSS_WHITELIST_PERMISSIONS: "Whitelist Permissions"
+ XSS_WHITELIST_PERMISSIONS_HELP: "Users with these permissions will skip the XSS rules when saving content"
+ XSS_ON_EVENTS: "Filter On-events"
+ XSS_INVALID_PROTOCOLS: "Filter Invalid protocols"
+ XSS_INVALID_PROTOCOLS_LIST: "Invalid protocols list"
+ XSS_MOZ_BINDINGS: "Filter Moz bindings"
+ XSS_HTML_INLINE_STYLES: "Filter HTML inline styles"
+ XSS_DANGEROUS_TAGS: "Filter Dangerous HTML tags"
+ XSS_DANGEROUS_TAGS_LIST: "Dangerous HTML tags list"
+ XSS_ONSAVE_ISSUE: "Save failed: XSS issue detected..."
+ XSS_ISSUE: "NOTICE: Grav found potential XSS issues in %s "
+ UPLOADS_SECURITY: "Uploads Security"
+ UPLOADS_DANGEROUS_EXTENSIONS: "Dangerous Extensions"
+ UPLOADS_DANGEROUS_EXTENSIONS_HELP: "Block these extensions from being uploaded no matter the accepted MIME types"
+ REPORTS: "Reports"
+ LOGS: "Logs"
+ LOG_VIEWER_FILES: "Log Viewer Files"
+ LOG_VIEWER_FILES_HELP: "Files in /logs/ that will be available to view in Tools - Logs. e.g. 'grav' = /logs/grav.log"
+ BACKUPS_STORAGE_PURGE_TRIGGER: "Backup Storage Purge Trigger"
+ BACKUPS_MAX_COUNT: "Maximum Number of Backups"
+ BACKUPS_MAX_COUNT_HELP: "0 is unlimited"
+ BACKUPS_MAX_SPACE: "Maximum Backups Space"
+ BACKUPS_MAX_RETENTION_TIME: "Maximum Retention Time"
+ BACKUPS_MAX_RETENTION_TIME_APPEND: "in Days"
+ BACKUPS_PROFILE_NAME: "Backup Name"
+ BACKUPS_PROFILE_ROOT_FOLDER: "Root Folder"
+ BACKUPS_PROFILE_ROOT_FOLDER_HELP: "Can be an absolute path or a stream"
+ BACKUPS_PROFILE_EXCLUDE_PATHS: "Exclude Paths"
+ BACKUPS_PROFILE_EXCLUDE_PATHS_HELP: "Absolute paths to exclude, one per line"
+ BACKUPS_PROFILE_EXCLUDE_FILES: "Exclude Files"
+ BACKUPS_PROFILE_EXCLUDE_FILES_HELP: "Specific Files or Folders to exclude, one per line"
+ BACKUPS_PROFILE_SCHEDULE: "Enable Scheduled Job"
+ BACKUPS_PROFILE_SCHEDULE_AT: "Run Scheduled Job"
+ COMMAND: "Command"
+ EXTRA_ARGUMENTS: "Extra Arguments"
+ DEFAULT_LANG: "Override Default Language"
+ DEFAULT_LANG_HELP: "Default is the first supported language. This can be overridden by setting this option but it must be one of the supported languages"
+ DEBUGGER_PROVIDER: "Debugger Provider"
+ DEBUGGER_PROVIDER_HELP: "Default is PHP Debug Bar, but Clockwork browser extension provides for a less intrusive approach"
+ DEBUGGER_DEBUGBAR: "PHP Debug Bar"
+ DEBUGGER_CLOCKWORK: "Clockwork Browser Extension"
+ SHOW_SENSITIVE: "Show Sensitive Data"
+ SHOW_SENSITIVE_HELP: "Clockwork Provider ONLY: Censor potentially sensitive information (POST parameters, cookies, files, configuration and most array/object data in log messages)"
+ VALID_LINK_ATTRIBUTES: "Valid Link Attributes"
+ VALID_LINK_ATTRIBUTES_HELP: "Attributes that will be automatically added to the media HTML element"
+ PAGE_ROUTE_NOT_FOUND: "Page route not found"
+ PAGE_ROUTE_FOUND: "Page route found"
+ NO_ROUTE_PROVIDED: "No route provided"
+
diff --git a/user/plugins/admin/languages/es.yaml b/user/plugins/admin/languages/es.yaml
new file mode 100644
index 0000000..ef0418e
--- /dev/null
+++ b/user/plugins/admin/languages/es.yaml
@@ -0,0 +1,758 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "¡Ésta es una versión Beta! Utilízala en producción bajo tu propio riesgo..."
+ ADMIN_REPORT_ISSUE: "¿Encontraste algún problema de funcionamiento? Por favor, repórtalo en GitHub."
+ EMAIL_FOOTER: "Generado con Grav - El CMS moderno de archivos planos"
+ LOGIN_BTN: "Acceder"
+ LOGIN_BTN_FORGOT: "La olvidé"
+ LOGIN_BTN_RESET: "Restablecer contraseña"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Enviar instrucciones de restablecimiento"
+ LOGIN_BTN_CLEAR: "Limpiar formulario"
+ LOGIN_BTN_CREATE_USER: "Crear Usuario"
+ LOGIN_LOGGED_IN: "Has conseguido iniciar sesión con éxito"
+ LOGIN_FAILED: "El inicio de sesión falló"
+ LOGGED_OUT: "Te has desconectado"
+ RESET_NEW_PASSWORD: "Introduce una contraseña nueva …"
+ RESET_LINK_EXPIRED: "El enlace de restablecimiento ha caducado, por favor, inténtalo de nuevo"
+ RESET_PASSWORD_RESET: "La contraseña ha sido restablecida"
+ RESET_INVALID_LINK: "El enlace de restablecimiento no es válido, por favor, inténtalo otra vez"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Las instrucciones para restablecer tu contraseña han sido enviadas por email a %s"
+ FORGOT_FAILED_TO_EMAIL: "Falló al enviar las instrucciones por correo electrónico, por favor inténtalo de nuevo más tarde"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "No se puede restablecer la contraseña para %s, no se ha establecido una dirección de email"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "El usuario %s no existe"
+ FORGOT_EMAIL_NOT_CONFIGURED: "No se puede restablecer contraseña. Este sitio no está configurado para enviar emails"
+ FORGOT_EMAIL_SUBJECT: "%s Petición de restablecimiento de contraseña"
+ FORGOT_EMAIL_BODY: "Restablecimiento de contraseña Estimado %1$s,
Se ha realizado una petición el %4$s para restablecer tu contraseña.
Pincha aquí para restablecer tu contraseña
Alternativamente, copia la siguiente URL en la barra de direcciones de tu navegador:
%2$s
Atentamente, %3$s
"
+ MANAGE_PAGES: "Administrar Páginas"
+ CONFIGURATION: "Configuración"
+ PAGES: "Páginas"
+ PLUGINS: "Plugins"
+ PLUGIN: "Plugin"
+ THEMES: "Temas"
+ LOGOUT: "Salir"
+ BACK: "Atrás"
+ NEXT: "Siguiente"
+ PREVIOUS: "Anterior"
+ ADD_PAGE: "Agregar Página"
+ ADD_MODULAR: "Agregar Módulo"
+ MOVE: "Mover"
+ DELETE: "Borrar"
+ UNSET: "Anular"
+ VIEW: "Vista"
+ SAVE: "Guardar"
+ NORMAL: "Normal"
+ EXPERT: "Experto"
+ EXPAND_ALL: "Expandir Todo"
+ COLLAPSE_ALL: "Cerrar Todo"
+ ERROR: "Error"
+ CLOSE: "Cerrar"
+ CANCEL: "Cancelar"
+ CONTINUE: "Continuar"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Confirmación Requerida"
+ MODAL_CHANGED_DETECTED_TITLE: "Cambios detectados"
+ MODAL_CHANGED_DETECTED_DESC: "Usted tiene cambios sin guardar. ¿Está seguro que desea salir sin guardar?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Confirmación Requerida"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "¿Está seguro que desea eliminar este archivo? Esta acción no se puede deshacer."
+ ADD_FILTERS: "Agregar Filtros"
+ SEARCH_PAGES: "Buscar Páginas"
+ VERSION: "Versión"
+ WAS_MADE_WITH: "Fue hecho con"
+ BY: "Por"
+ UPDATE_THEME: "Actualizar Tema"
+ UPDATE_PLUGIN: "Actualizar Plugin"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "de este tema ya está disponible"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "de este plugin ya está disponible"
+ AUTHOR: "Autor"
+ HOMEPAGE: "Página de inicio"
+ DEMO: "Demo"
+ BUG_TRACKER: "Bug Tracker"
+ KEYWORDS: "Palabras Clave"
+ LICENSE: "Licencia"
+ DESCRIPTION: "Descripción"
+ README: "Léame"
+ REMOVE_THEME: "Eliminar Tema"
+ INSTALL_THEME: "Instalar Tema"
+ THEME: "Tema"
+ BACK_TO_THEMES: "Volver a Temas"
+ BACK_TO_PLUGINS: "Volver a Plugins"
+ CHECK_FOR_UPDATES: "Buscar Actualizaciones"
+ ADD: "Agregar"
+ CLEAR_CACHE: "Limpiar Cache"
+ CLEAR_CACHE_ALL_CACHE: "Limpiar Toda la Cache"
+ CLEAR_CACHE_ASSETS_ONLY: "Solo Assets"
+ CLEAR_CACHE_IMAGES_ONLY: "Solo Imágenes"
+ CLEAR_CACHE_CACHE_ONLY: "Solo Cache"
+ CLEAR_CACHE_TMP_ONLY: "Tempo solamente"
+ DASHBOARD: "Panel"
+ UPDATES_AVAILABLE: "Actualizaciones disponibles"
+ DAYS: "Días"
+ UPDATE: "Actualizar"
+ BACKUP: "Respaldo"
+ BACKUPS: "Copias de seguridad"
+ BACKUP_NOW: "Realizar copia de seguridad ahora"
+ BACKUPS_STATS: "Estadísticas de copia de seguridad"
+ BACKUPS_HISTORY: "Historial de copia de seguridad"
+ BACKUPS_PURGE_CONFIG: "Configuración de depuración de copia de seguridad"
+ BACKUPS_PROFILES: "Perfiles de copia de seguridad"
+ BACKUPS_COUNT: "Número de copias de seguridad"
+ BACKUPS_PROFILES_COUNT: "Número de perfiles"
+ BACKUPS_TOTAL_SIZE: "Espacio utilizado"
+ BACKUPS_NEWEST: "Copia de seguridad más reciente"
+ BACKUPS_OLDEST: "Copia de seguridad más antigua"
+ BACKUPS_PURGE: "Purgar"
+ BACKUPS_NOT_GENERATED: "No se han generado copias de seguridad aún..."
+ BACKUPS_PURGE_NUMBER: "Usando %s de %s ranuras de respaldo"
+ BACKUPS_PURGE_TIME: "%s días de copias de seguridad restantes"
+ BACKUPS_PURGE_SPACE: "Usando %s de %s"
+ BACKUP_DELETED: "Copia de Seguridad Eliminada Correctamente"
+ BACKUP_NOT_FOUND: "Copia de seguridad no encontrada"
+ BACKUP_DATE: "Fecha de respaldo"
+ STATISTICS: "Estadísticas"
+ TODAY: "Hoy"
+ WEEK: "Semana"
+ MONTH: "Mes"
+ LATEST_PAGE_UPDATES: "Últimas Páginas Actualizadas"
+ MAINTENANCE: "Mantenimiento"
+ UPDATED: "Actualizado"
+ MON: "Lun"
+ TUE: "Mar"
+ WED: "Mie"
+ THU: "Jue"
+ FRI: "Vie"
+ SAT: "Sab"
+ SUN: "Dom"
+ COPY: "Copiar"
+ EDIT: "Editar"
+ CREATE: "Crear"
+ GRAV_ADMIN: "Grav Admin"
+ GRAV_OFFICIAL_PLUGIN: "Plugin Oficial de Grav"
+ GRAV_OFFICIAL_THEME: "Tema Oficial de Grav"
+ PLUGIN_SYMBOLICALLY_LINKED: "Este plugin esta simbólicamente vinculado. No se podrá actualizar."
+ THEME_SYMBOLICALLY_LINKED: "Este tema esta simbólicamente vinculado. Las actualizaciones no se podrán eliminar."
+ REMOVE_PLUGIN: "Eliminar Plugin"
+ INSTALL_PLUGIN: "Instalar Plugin"
+ AVAILABLE: "Disponibile"
+ INSTALLED: "Instalado"
+ INSTALL: "Instalar"
+ ACTIVE_THEME: "Tema activo"
+ SWITCHING_TO: "Cambiando a"
+ SWITCHING_TO_DESCRIPTION: "Al cambiar de tema, no hay garantía que las estructuras de las páginas serán soportadas, potencialmente causará errores cuando se traten de cargaar las páginas."
+ SWITCHING_TO_CONFIRMATION: "Desea continuar y cambiar el tema"
+ CREATE_NEW_USER: "Crear Nuevo Usuario"
+ REMOVE_USER: "Eliminar Usuario"
+ ACCESS_DENIED: "Acceso denegado"
+ ACCOUNT_NOT_ADMIN: "su cuenta no tiene privilegios de administrador"
+ PHP_INFO: "PHP Info"
+ INSTALLER: "Instalador"
+ AVAILABLE_THEMES: "Temas Disponibles"
+ AVAILABLE_PLUGINS: "Plugins Disponibles"
+ INSTALLED_THEMES: "Temas Instalados"
+ INSTALLED_PLUGINS: "Plugins Instalados"
+ BROWSE_ERROR_LOGS: "Ver registros de errores"
+ SITE: "Sitio"
+ INFO: "Info"
+ SYSTEM: "Sistema"
+ USER: "Usuario"
+ ADD_ACCOUNT: "Añadir Cuenta"
+ SWITCH_LANGUAGE: "Cambiar Idioma"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Plugin activado exitosamente"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Plugin activado exitosamente"
+ SUCCESSFULLY_CHANGED_THEME: "El tema por defecto se cambió exitosamente"
+ INSTALLATION_FAILED: "Instalación fallida"
+ INSTALLATION_SUCCESSFUL: "Instalación exitosa"
+ UNINSTALL_FAILED: "Desinstalación fallida"
+ UNINSTALL_SUCCESSFUL: "Desinstalación exitosa"
+ SUCCESSFULLY_SAVED: "Guardado Exitosamente"
+ SUCCESSFULLY_COPIED: "Copiado Exitosamente"
+ REORDERING_WAS_SUCCESSFUL: "El cambio de orden fue exitoso"
+ SUCCESSFULLY_DELETED: "Eliminado Exitosamente"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Se cambió el idioma exitosamente"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Usted no tiene suficientes permisos para la tarea"
+ CACHE_CLEARED: "Cache borrado"
+ METHOD: "Método"
+ ERROR_CLEARING_CACHE: "Error al borrar el cache"
+ AN_ERROR_OCCURRED: "Ocurrió un error"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Su respaldo esta listo para descargar"
+ DOWNLOAD_BACKUP: "Descargar respaldo"
+ PAGES_FILTERED: "Páginas filtradas"
+ NO_PAGE_FOUND: "No se encontraron páginas"
+ INVALID_PARAMETERS: "Parámetros Inválidos"
+ NO_FILES_SENT: "No se enviaron archivos"
+ EXCEEDED_FILESIZE_LIMIT: "El límite de tamaño de archivo de la configuración de PHP fue exedido"
+ UNKNOWN_ERRORS: "Errores desconocidos"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "El límite de tamaño de archivo de configuración de Grav fue excedido"
+ UNSUPPORTED_FILE_TYPE: "Tipo de archivo no soportado"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Falló al mover archivo cargado."
+ FILE_UPLOADED_SUCCESSFULLY: "El archivo se cargó exitosamente"
+ FILE_DELETED: "Archivo Eliminado"
+ FILE_COULD_NOT_BE_DELETED: "El archivo no pudo ser eliminado"
+ FILE_NOT_FOUND: "Archivo no encontrado"
+ NO_FILE_FOUND: "No se encontraron archivos"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav fué exitosamente actualizado a"
+ GRAV_UPDATE_FAILED: "Falló la actualización de Grav"
+ EVERYTHING_UPDATED: "Todo está actualizado"
+ UPDATES_FAILED: "Fallaron actualizaciones"
+ AVATAR_BY: "Avatar por"
+ AVATAR_UPLOAD_OWN: "O subir tus propios..."
+ LAST_BACKUP: "Último Respaldo"
+ FULL_NAME: "Nombre completo"
+ USERNAME: "Usuario"
+ EMAIL: "Correo electrónico"
+ USERNAME_EMAIL: "Nombre de usuario o correo electrónico"
+ PASSWORD: "Contraseña"
+ PASSWORD_CONFIRM: "Confirmar Contraseña"
+ TITLE: "Título"
+ LANGUAGE: "Idioma"
+ ACCOUNT: "Cuenta"
+ EMAIL_VALIDATION_MESSAGE: "Debe ser una cuenta de correo electrónico válida"
+ PASSWORD_VALIDATION_MESSAGE: "La contraseña debe contener al menos un número y una letra mayúscula y minúscula, y al menos 8 o más caracteres"
+ LANGUAGE_HELP: "Establecer el idioma favorito"
+ MEDIA: "Multimedia"
+ DEFAULTS: "Defaults"
+ SITE_TITLE: "Título del sitio"
+ SITE_TITLE_PLACEHOLDER: "Título en todo el sitio"
+ SITE_TITLE_HELP: "Título default para el sitio, algunas veces utilizados en los temas"
+ SITE_DEFAULT_LANG: "Idioma predeterminado"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Idioma predeterminado que se usará con la etiqueta del tema"
+ SITE_DEFAULT_LANG_HELP: "Idioma predeterminado que se usará con la etiqueta del tema"
+ DEFAULT_AUTHOR: "Autor por defecto"
+ DEFAULT_AUTHOR_HELP: "Un nombre de autor por defecto, algunas veces usado en temas o en contenido de las páginas"
+ DEFAULT_EMAIL: "Correo electrónico por defecto"
+ DEFAULT_EMAIL_HELP: "Un correo electrónico para referenciar en temas o páginas"
+ TAXONOMY_TYPES: "Tipos de taxonomía"
+ TAXONOMY_TYPES_HELP: "Los tipos de taxonomía deben definirse aquí si desea utilizarlos en las páginas"
+ PAGE_SUMMARY: "Resumen de página"
+ ENABLED: "Habilitado"
+ ENABLED_HELP: "Habilitar resumen de página (el resumen regresará lo mismo que el contenido de página)"
+ 'YES': "Sí"
+ 'NO': "No"
+ SUMMARY_SIZE: "Tamaño del resumen"
+ SUMMARY_SIZE_HELP: "La cantidad de caracteres de la página para utilizarse como resumen del contenido"
+ FORMAT: "Formato"
+ FORMAT_HELP: "corto = utiliza la primera ocurrencia del delimitador o tamaño; largo = el delimitador será ignorado"
+ SHORT: "Corto"
+ LONG: "Largo"
+ DELIMITER: "Delimitador"
+ DELIMITER_HELP: "El delimitador del resumen (por defecto es '===')"
+ METADATA: "Metadatos"
+ METADATA_HELP: "Metadatos por defecto que serán mostrados en cada página, a menos que se sobrepongan en la página"
+ NAME: "Nombre"
+ CONTENT: "Contenido"
+ SIZE: "Tamaño"
+ ACTION: "Acción"
+ REDIRECTS_AND_ROUTES: "Redirecciones y Ruteos"
+ CUSTOM_REDIRECTS: "Redirecciones personalizadas"
+ CUSTOM_REDIRECTS_HELP: "rutas para direccionar a otras páginas. El reemplazo con Regex Estandar es válido"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/su/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/su/redireccion"
+ CUSTOM_ROUTES: "Rutas Personalizadas"
+ CUSTOM_ROUTES_HELP: "rutas de alias a otras páginas. El reemplazo con Regex Estandar es válido"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/su/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/su/ruta"
+ FILE_STREAMS: "Flujos de archivo"
+ DEFAULT: "Por defecto"
+ PAGE_MEDIA: "Multimedia de página"
+ OPTIONS: "Opciones"
+ PUBLISHED: "Publicado"
+ PUBLISHED_HELP: "Por default, las páginas son publicadas, a menos que explicitamente se establescan como published: false o que la fecha de publicación sea en el futuro con published_date, o que la fecha de despublicación sea en el pasado con unpublish_date"
+ DATE: "Fecha"
+ DATE_HELP: "La variable de fecha permite especificamente establecer una fecha asociada con esta página."
+ PUBLISHED_DATE: "Fecha de publicación"
+ PUBLISHED_DATE_HELP: "Puede proveer una fecha para disparar la publicación automáticamente."
+ UNPUBLISHED_DATE: "Fecha de Despublicación"
+ UNPUBLISHED_DATE_HELP: "Puedes proporcionar una fecha para ejecutar directamente la des-publicación."
+ ROBOTS: "Rotobs"
+ TAXONOMIES: "Taxonomías"
+ TAXONOMY: "Taxonomía"
+ ADVANCED: "Avanzado"
+ SETTINGS: "Parámetros"
+ FOLDER_NUMERIC_PREFIX: "Prefijo numérico de carpeta"
+ FOLDER_NUMERIC_PREFIX_HELP: "Prefijo numérico que proporciona orden manual e implica visibilidad"
+ FOLDER_NAME: "Nombre de la Carpeta"
+ FOLDER_NAME_HELP: "El nombre de la carpeta que se almacenará en el sistema de archivos de esta página"
+ PARENT: "Padre"
+ DEFAULT_OPTION_ROOT: "-Raíz-"
+ DEFAULT_OPTION_SELECT: "-Seleccione-"
+ DISPLAY_TEMPLATE: "Mostrar Plantilla"
+ DISPLAY_TEMPLATE_HELP: "El tipo de página, que se traduce en que plantilla twig genera la página"
+ BODY_CLASSES: "Clases del Body"
+ ORDERING: "Orden"
+ PAGE_ORDER: "Ordenar páginas"
+ OVERRIDES: "Sobreponer"
+ MENU: "Menu"
+ MENU_HELP: "El texto a utilizar en un menú. Si no se establece, se utilizará Título."
+ SLUG: "Slug"
+ SLUG_HELP: "La variable slug permite definir específicamente la porción del URL que corresponde a la página"
+ SLUG_VALIDATE_MESSAGE: "El slug debe contener sólo caracteres alfanuméricos en minúsculas y guiones"
+ PROCESS: "Procesar"
+ PROCESS_HELP: "Control de cómo se procesan páginas. Configurable por página en lugar de global"
+ DEFAULT_CHILD_TYPE: "Tipo de hijo por defecto"
+ USE_GLOBAL: "Uso Global"
+ ROUTABLE: "Enrutable"
+ ROUTABLE_HELP: "Si esta página es accesible desde una URL"
+ CACHING: "Almacenando en caché"
+ VISIBLE: "Visible"
+ VISIBLE_HELP: "Determina si una página es visible en la navegación."
+ DISABLED: "Deshabilitado"
+ ITEMS: "Elementos"
+ ORDER_BY: "Ordenar por"
+ ORDER: "Ordenar"
+ FOLDER: "Carpeta"
+ ASCENDING: "Ascendente"
+ DESCENDING: "Descendente"
+ ADD_MODULAR_CONTENT: "Adicioinar Contenido Modular"
+ PAGE_TITLE: "Título de Página"
+ PAGE_TITLE_HELP: "El título de la página"
+ PAGE: "Página"
+ MODULAR_TEMPLATE: "Plantilla Modular"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Nombre de Archivo"
+ PARENT_PAGE: "Página Padre"
+ HOME_PAGE: "Pagina Inicial"
+ HOME_PAGE_HELP: "La página que utilizará Grav como aterrizaje por defecto"
+ DEFAULT_THEME: "Tema por defecto"
+ DEFAULT_THEME_HELP: "Establecer el tema por defecto que utilizará Grav (default es Antimatter)"
+ TIMEZONE: "Zona Horaria"
+ TIMEZONE_HELP: "Sobreponer la zona horaria por defecto del servidor"
+ SHORT_DATE_FORMAT: "Short date format"
+ SHORT_DATE_FORMAT_HELP: "Establecer el formato de fecha corta que puede ser utilizado por los temas"
+ LONG_DATE_FORMAT: "Formato de fecha largo"
+ LONG_DATE_FORMAT_HELP: "Establecer el formato de fecha larga que puede ser utilizado por los temas"
+ DEFAULT_ORDERING: "Orden por defecto"
+ DEFAULT_ORDERING_HELP: "Las páginas de la lista serán generadas utilizando este orden a menos que sea sobrepuesto"
+ DEFAULT_ORDERING_DEFAULT: "Por defecto - basado en el nombre de la carpeta"
+ DEFAULT_ORDERING_FOLDER: "Carpeta - basado en el nombre de la carpeta sin prefijo"
+ DEFAULT_ORDERING_TITLE: "Título - basado en el campo title del encabezado"
+ DEFAULT_ORDERING_DATE: "Fecha - basado en el campo date del encabezado"
+ DEFAULT_ORDER_DIRECTION: "Dirección del orden por defecto"
+ DEFAULT_ORDER_DIRECTION_HELP: "La dirección de las páginas en una lista"
+ DEFAULT_PAGE_COUNT: "Cuenta de página por defecto"
+ DEFAULT_PAGE_COUNT_HELP: "Número máximo de cuenta de páginas en una lista"
+ DATE_BASED_PUBLISHING: "Publicación basado en fecha"
+ DATE_BASED_PUBLISHING_HELP: "Automáticamente (des)publicar páginas basado en la fecha"
+ EVENTS: "Eventos"
+ EVENTS_HELP: "Habilitar o Deshabilitar eventos específicos. Dehabilitarlos puede dañar plugins"
+ REDIRECT_DEFAULT_ROUTE: "Ruta de redirección por defecto"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Automáticamente redirigir a la ruta de páginas por defecto"
+ LANGUAGES: "Idiomas"
+ SUPPORTED: "Soportados"
+ SUPPORTED_HELP: "Lista de códigos de idioma de 2 letras separados por comas (por ejemplo 'en,fr,es')"
+ TRANSLATIONS_ENABLED: "Traducciones habilitadas"
+ TRANSLATIONS_ENABLED_HELP: "Soportar traduccioines en Grav, plugins y extensiones"
+ TRANSLATIONS_FALLBACK: "Traducciones de apoyo"
+ TRANSLATIONS_FALLBACK_HELP: "Retroceder a la traducción de apoyo si el idioma activo no existe"
+ ACTIVE_LANGUAGE_IN_SESSION: "Idioma activo en la sesión"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Almacenar el idioma activo en la sesión"
+ HTTP_HEADERS: "Encabezados HTTP"
+ EXPIRES: "Expira"
+ EXPIRES_HELP: "Establecer la expiración del encabezado. El valor en es en segundos."
+ CACHE_CONTROL: "Control-Cache HTTP"
+ CACHE_CONTROL_HELP: "Establece un control-cache válido como 'no-cache, no-almacenar, debe-revalidar'"
+ LAST_MODIFIED: "Último modificado"
+ LAST_MODIFIED_HELP: "Establece el úlimo encabezado modificado que puede optimizar el proxy el cache del navegador"
+ ETAG: "Etiqueta ETag"
+ ETAG_HELP: "Establece el encabezado etag para ayudar a identificar cuando una página ha sido modificada"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Establecer el encabezado `Vary: Accept Encoding` para ayudar con el proxy y el cache del CDN"
+ MARKDOWN: "Markdown"
+ MARKDOWN_EXTRA: "Markdown extra"
+ MARKDOWN_EXTRA_HELP: "Habilitar el soporte de Markdown Extra por defecto - https://michelf.ca/projects/php-markdown/extra/"
+ MARKDOWN_EXTRA_ESCAPE_FENCES: "Escapar elementos HTML en vallas adicionales de markdown"
+ MARKDOWN_EXTRA_ESCAPE_FENCES_HELP: "Escapa elementos HTML en vallas adicionales de markdown"
+ AUTO_LINE_BREAKS: "Saltos de línea automáticos"
+ AUTO_LINE_BREAKS_HELP: "Habilita el soporte de saltos de línea automáticos en markdown"
+ AUTO_URL_LINKS: "Enlaces URL Automáticos"
+ AUTO_URL_LINKS_HELP: "Habilita la conversión automática de URLs a hyperlinks HTML"
+ ESCAPE_MARKUP: "Marcas de escape"
+ ESCAPE_MARKUP_HELP: "Etiquetas de marcas de escape en entidades HTML"
+ CACHING_HELP: "Interruptor de encendido/apagado global para habilitar/deshabilitar almacenamiento en caché de Grav"
+ CACHE_CHECK_METHOD: "Método de comprobación de la caché"
+ CACHE_CHECK_METHOD_HELP: "Seleccione el método que Grav utiliza para comprobar si se han modificado los archivos de página."
+ CACHE_DRIVER: "Manejador de Cache"
+ CACHE_DRIVER_HELP: "Elegir qué controlador de caché debe usar Grav. 'Detectar Automáticamente' intenta encontrar el mejor para usted"
+ CACHE_PREFIX: "Prefijo de caché"
+ CACHE_PREFIX_HELP: "Es un identificador de parte escencial de Grav. No cambiar a menos que sepa lo que esta haciendo."
+ CACHE_PREFIX_PLACEHOLDER: "Derivado desde la URL base (anulado por ingreso de una cadena aleatoria)"
+ CACHE_PURGE_JOB: "Ejecutar trabajo de purga programado"
+ CACHE_CLEAR_JOB: "Ejecutar tarea de limpieza programada"
+ CACHE_CLEAR_JOB_HELP: "Con el planificador puedes borrar periódicamente la caché de Grav"
+ CACHE_PURGE: "Vaciar caché antigua"
+ LIFETIME: "Tiempo de vida"
+ LIFETIME_HELP: "Establece la duración del caché en segundos. 0 = infinito"
+ GZIP_COMPRESSION: "Compresión gzip"
+ GZIP_COMPRESSION_HELP: "Permitir la compresión GZip de Grav (paginas) para un mejor rendimiento."
+ TWIG_TEMPLATING: "Plantillas Twig"
+ TWIG_CACHING: "Caché de Twig"
+ TWIG_CACHING_HELP: "Controla el mecanismo de almacenamiento en caché de Twig. Deje este habilitado para un mejor rendimiento."
+ TWIG_DEBUG: "Depuración de Twig"
+ TWIG_DEBUG_HELP: "Habilita la opción de no cargar la extensión del depurador de Twig"
+ DETECT_CHANGES: "Detectar cambios"
+ DETECT_CHANGES_HELP: "Twig recompilará automáticamente el caché de Twig si detecta cambios en las plantillas de Twig"
+ AUTOESCAPE_VARIABLES: "Variables de autoescape"
+ AUTOESCAPE_VARIABLES_HELP: "Autoescapa todas las variables. Esto probablemente romperá su sitio"
+ ASSETS: "Activos"
+ CSS_PIPELINE: "Canalización de CSS"
+ CSS_PIPELINE_HELP: "La canalización de CSS es la unificación de múltiples recursos CSS en un archivo"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Incluir externos en el desarrollo CSS"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Las URLs externas algunas veces tienen referencias relativas de archivo y no deberían ser conectadas"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "Interpretar primero el flujo CSS"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Interpretar el flujo CSS antes de cualquier otra referencia CSS que no estén incluidas"
+ CSS_MINIFY: "Minificar CSS"
+ CSS_MINIFY_HELP: "Minificar CSS durante la canalización"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "Anulación de minificación de CSS en Windows"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Anula la minificación en plataformas Windows. Falso por defecto debido a ThreadStackSize"
+ CSS_REWRITE: "Reescritura CSS"
+ CSS_REWRITE_HELP: "Reescribir cualquier URL relativa de CSS durante la canalización"
+ JAVASCRIPT_PIPELINE: "Canalización de JavaScript"
+ JAVASCRIPT_PIPELINE_HELP: "La canalización JS es la unificación de múltiples recursos JS en un archivo"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Incluir recursos externos en el flujo de JS"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Las URLs externas algunas veces tienen referencias relativas de archivo y no deberían colocarse en el flujo"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Interpretar primero el flujo JS"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Interpretar el flujo JS antes de cualquier otra referencia JS que no esté incluida"
+ JAVASCRIPT_MINIFY: "Minificar JavaScript"
+ JAVASCRIPT_MINIFY_HELP: "Minificar JS en canalización"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Habilitar las marcas de tiempo en activos"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Habilitar las marcas de tiempo en los recursos"
+ COLLECTIONS: "Colecciones"
+ ERROR_HANDLER: "Manejador de Errores"
+ DISPLAY_ERRORS: "Mostrar errores"
+ DISPLAY_ERRORS_HELP: "Mostrar página de error con el estilo de traza inversa completo"
+ LOG_ERRORS: "Registro de Errores"
+ LOG_ERRORS_HELP: "Errores de registro a la carpeta /logs"
+ LOG_HANDLER: "Gestor de registro"
+ LOG_HANDLER_HELP: "Dónde enviar los registros"
+ DEBUGGER: "Depurador"
+ DEBUGGER_HELP: "Habilitar depurador de Grav y ajustes siguientes"
+ DEBUG_TWIG: "Depuración de twig"
+ DEBUG_TWIG_HELP: "Habilitar la depuración de plantillas Twig"
+ SHUTDOWN_CLOSE_CONNECTION: "Al apagar cierra la conexión"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Cerrar la conexión antes de llamar al onShutdown(). Falso para la depuración"
+ DEFAULT_IMAGE_QUALITY: "Calidad de imagen por defecto"
+ DEFAULT_IMAGE_QUALITY_HELP: "Calidad de imagen por defecto para ser utilizada cuando se muestruea o se almacenan en caché las imágenes (85%)"
+ CACHE_ALL: "Guardar imágenes en caché"
+ CACHE_ALL_HELP: "Guardar todas las imágenes en el caché de Grav, aun en los casos cundo no se han manipulado"
+ IMAGES_DEBUG: "Marca de agua de imágen de depuración"
+ IMAGES_DEBUG_HELP: "Cubrir las imágenes con un texto indicando la profundidad de pixeles cuando se trabaja con retina por ejemplo"
+ UPLOAD_LIMIT: "Tamaño límite para carga de archivos"
+ UPLOAD_LIMIT_HELP: "Establecer el máximo tamaño de archivo para ser cargado (0 es ilimitado)"
+ ENABLE_MEDIA_TIMESTAMP: "Habilitar marcas de tiempo en multimedia"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Agregar marcas de tiempo basadas en la última fecha de modificación en cada elemento multimedia"
+ SESSION: "Sesión"
+ SESSION_ENABLED_HELP: "Habilitar el soporte de sesión dentro de Grav"
+ TIMEOUT: "Fuera de tiempo"
+ TIMEOUT_HELP: "Establece el tiempo de espera de sesión en segundos"
+ SESSION_NAME_HELP: "Un identificador que se utiliza para formar el nombre de la cookie de sesión"
+ SESSION_UNIQUENESS: "Cadena única"
+ ABSOLUTE_URLS: "URL absoluta"
+ ABSOLUTE_URLS_HELP: "Direcciones URL absolutas o relativas para 'base_url'"
+ PARAMETER_SEPARATOR: "Separador de parámetro"
+ PARAMETER_SEPARATOR_HELP: "Separater for passed parameters that can be changed for Apache on Windows"
+ TASK_COMPLETED: "Tarea completada"
+ EVERYTHING_UP_TO_DATE: "Todo está actualizado"
+ UPDATES_ARE_AVAILABLE: "hay actualizaciones disponibles"
+ IS_AVAILABLE_FOR_UPDATE: "está disponible para su actualización"
+ IS_NOW_AVAILABLE: "está ahora disponible"
+ CURRENT: "Actual"
+ UPDATE_GRAV_NOW: "Actualizar Grav Ahora"
+ GRAV_SYMBOLICALLY_LINKED: "Grav está symbolicamente vinculado. No podrá ser actualizado"
+ UPDATING_PLEASE_WAIT: "Actualizado... por favor espere, descargando"
+ OF_THIS: "de esta"
+ OF_YOUR: "de su"
+ HAVE_AN_UPDATE_AVAILABLE: "Tiene una actualización disponible"
+ SAVE_AS: "Guardar como"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "¿Seguro que deseas eliminar esta página y todos sus hijos? Si la página está traducida en otros idiomas, se conservarán esas traducciones y deben ser eliminadas por separado. De otra manera, la carpeta de la página será eliminada con todas sus sub páginas. Esta acción no puede ser revertida."
+ AND: "y"
+ UPDATE_AVAILABLE: "Actualización disponible"
+ METADATA_KEY: "Key (e.g. 'Palabra clave')"
+ METADATA_VALUE: "Valor (e.g. 'Blog, Grav')"
+ USERNAME_HELP: "El nombre de usuario debe tener entre 3 y 16, incluyendo letras en minúsculas, números, guiones bajos y guiones. Las mayusculas, espacios y careacteres especiales no están permitidos"
+ FULLY_UPDATED: "Totalmente Actualizado"
+ SAVE_LOCATION: "Guardar ubicación"
+ PAGE_FILE: "Plantillas de Página"
+ PAGE_FILE_HELP: "Nombre de archivo de plantilla de página y plantilla de visualización por defecto"
+ NO_USER_ACCOUNTS: "No se encuentraron cuentas de usuario, por favor crear una..."
+ NO_USER_EXISTS: "No existe ningún usuario local en esta cuenta, no se puede guardar..."
+ REDIRECT_TRAILING_SLASH: "Redirigir barra final"
+ REDIRECT_TRAILING_SLASH_HELP: "Realice una redirección 301 en lugar de manejo transparente barra final"
+ DEFAULT_DATE_FORMAT: "Formato de fecha de página"
+ DEFAULT_DATE_FORMAT_HELP: "Formato de fecha de página utilizado por Grav. Por defecto, Grav intenta adivinar la fecha de formato, sin embargo se puede especificar un formato usando sintaxis de fecha de PHP (por ejemplo: H:i Y-m-d)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Adivinar automáticamente"
+ IGNORE_FILES: "Ignorar archivos"
+ IGNORE_FILES_HELP: "Archivos específicos para ignorar al procesar páginas"
+ IGNORE_FOLDERS: "Ignorar carpetas"
+ IGNORE_FOLDERS_HELP: "Archivos específicos para ignorar al procesar páginas"
+ HIDE_EMPTY_FOLDERS: "Ocultar carpetas vacías"
+ HTTP_ACCEPT_LANGUAGE: "Establece el idioma desde el navegador"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Usted puede optar por intentar establecer el idioma basado en etiquetas de cabecera de 'http_accept_language' en el navegador"
+ OVERRIDE_LOCALE: "Sobreponer la configuración regional"
+ OVERRIDE_LOCALE_HELP: "Sobreponer la configuración regional en PHP basado en el idioma actual"
+ REDIRECT: "Redirección de página"
+ REDIRECT_HELP: "Escriba una ruta de la página o URL externa para esta página para redirigir a por ejemplo, '/alguna/ruta' o 'http://algunsitio.com'"
+ PLUGIN_STATUS: "Estado plugin"
+ INCLUDE_DEFAULT_LANG: "Incluir idioma predeterminado"
+ INCLUDE_DEFAULT_LANG_HELP: "Esto antepondrá todas las URLs en el idioma por defecto con el idioma predeterminado. por ejemplo, '/es/blog/mi post'"
+ ALLOW_URL_TAXONOMY_FILTERS: "Filtros de URL por taxonomía"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Colecciones basadas en páginas le permiten filtrar por ' / taxonomía: valor '."
+ REDIRECT_DEFAULT_CODE: "Código de redirección por defecto"
+ REDIRECT_DEFAULT_CODE_HELP: "El código de Estado HTTP para redirecciones"
+ IGNORE_HIDDEN: "Ignorar ocultos"
+ IGNORE_HIDDEN_HELP: "Ignorar todos los archivos y carpetas que comienzan con un punto"
+ WRAPPED_SITE: "Sitio envuelto"
+ WRAPPED_SITE_HELP: "Para que los plugins/temas sepan si Grav esta envuelta en otra plataforma"
+ FALLBACK_TYPES: "Tipos alternativos permitidos"
+ FALLBACK_TYPES_HELP: "Tipos de archivos permitidos que se pueden encontrar si accede por la ruta de la página. Por defecto cualquier tipo de medios compatibles."
+ INLINE_TYPES: "Tipos alternativos en línea"
+ INLINE_TYPES_HELP: "Una lista de tipos de archivos que deben ser mostrados en línea en lugar de descargar"
+ APPEND_URL_EXT: "Añadir extensión de dirección URL"
+ APPEND_URL_EXT_HELP: "Agregará una extensión personalizada a URL de la página. Tenga en cuenta, que esto significa que Grav buscará la plantilla ' . . twig '"
+ PAGE_MODES: "Modo de Página"
+ PAGE_TYPES: "Tipos de páginas"
+ ACCESS_LEVELS: "Niveles de acceso"
+ GROUPS: "Grupos"
+ GROUPS_HELP: "Lista de grupos a los que el usuario forma parte de"
+ ADMIN_ACCESS: "Acceso de Administrador"
+ SITE_ACCESS: "Acceso al Sitio"
+ INVALID_SECURITY_TOKEN: "Clave de seguridad inválida"
+ ACTIVATE: "Activar"
+ TWIG_UMASK_FIX: "Correción Umask"
+ TWIG_UMASK_FIX_HELP: "Por defecto twig crea archivos caché como 0755, la corrección cambia esto a 0775"
+ CACHE_PERMS: "Permisos de Cache"
+ CACHE_PERMS_HELP: "Permisos de carpeta por defecto. Generalmente 0755 o 0775 según la configuración del servidor"
+ REMOVE_SUCCESSFUL: "Eliminación exitosa"
+ REMOVE_FAILED: "Eliminación fallar"
+ HIDE_HOME_IN_URLS: "Ocultar la ruta de la pagina inicial en las URL"
+ HIDE_HOME_IN_URLS_HELP: "Se asegurará que las rutas pdefecto para las páginas debajo de la pagina inicial no hacen referencia a la ruta regular de la pagina inicial"
+ TWIG_FIRST: "Tratar Twig Primero"
+ TWIG_FIRST_HELP: "Si usted ha habilitado el tratamiento Twig de la pagina, entones usted puedes configurar el tratamiento Twig suceder antes o después de markdown"
+ SESSION_SECURE: "Seguro"
+ SESSION_SECURE_HELP: "Si es Verdadero, indica que la comunicación para esta cookie debe ser sobre una transmisión encriptada. ALERTA: Habilitar esta opción solo en sitios que corran exclusivamente con HTTPS"
+ SESSION_HTTPONLY: "Sólo HTTP"
+ SESSION_HTTPONLY_HELP: "Si es verdadero, indica que las cookies deberían ser usadas solo sobre HTTP, y las modificaciones de JavaScript no son permitidas"
+ REVERSE_PROXY: "Proxy Reverso"
+ REVERSE_PROXY_HELP: "Habilitar esta opción si esta usted detras de un proxy reverso y usted tiene problemas con URLs que contienen puertos incorrectos"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Frontmater no válido, no se pudo guardar"
+ ADD_FOLDER: "Agregar carpeta"
+ PROXY_URL: "Dirección URL del Proxy"
+ PROXY_URL_HELP: "Escribir el host o IP y puerto del Proxy"
+ NOTHING_TO_SAVE: "Nada que Guardar"
+ FILE_ERROR_ADD: "Ocurrió un error intentando agregar el archivo"
+ FILE_ERROR_UPLOAD: "Ocurrió un error intentando subir el archivo"
+ FILE_UNSUPPORTED: "Tipo de archivo no permitido"
+ ADD_ITEM: "Agregar elemento"
+ FILE_TOO_LARGE: "El archivo es demasiado grande para subirse, el máximo permitido es %s de acuerdo a tus ajustes en PHP. Incrementa el ajuste `post_max_size` en PHP"
+ INSTALLING: "Instalando"
+ LOADING: "Cargando…"
+ DEPENDENCIES_NOT_MET_MESSAGE: "Las siguientes dependencias necesitan ser cubiertas primero:"
+ ERROR_INSTALLING_PACKAGES: "Error mientras se instalaban el o los paquetes"
+ INSTALLING_DEPENDENCIES: "Instalando dependencias…"
+ INSTALLING_PACKAGES: "Instalando paquete(s)…"
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Paquete(s) instalado(s) satisfactoriamente."
+ READY_TO_INSTALL_PACKAGES: "Listo para instalar los paquetes"
+ PACKAGES_NOT_INSTALLED: "Los paquetes no se instalaron"
+ PACKAGES_NEED_UPDATE: "Los paquetes ya estaban instalados, pero demasiado anteriores"
+ PACKAGES_SUGGESTED_UPDATE: "Los paquetes ya estaba instalados y la versión está bien, pero serán actualizados para que estés al día"
+ REMOVE_THE: "Quitar el %s"
+ CONFIRM_REMOVAL: "¿Seguro que deseas eliminar este %s?"
+ REMOVED_SUCCESSFULLY: "%s quitado satisfactoriamente"
+ ERROR_REMOVING_THE: "Error quitando el %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "El %s requería las siguientes dependencias, que no son requeridas por otros paquetes instalados. Si no las estás usando, puedes quitarlas directamente desde ahí."
+ READY_TO_UPDATE_PACKAGES: "Listo para actualizar los paquetes"
+ ERROR_UPDATING_PACKAGES: "Error al actualizar los paquetes"
+ UPDATING_PACKAGES: "Actualizando los paquetes…"
+ PACKAGES_SUCCESSFULLY_UPDATED: "Los paquetes se actualizaron satisfactoriamente."
+ UPDATING: "Actualizando"
+ GPM_RELEASES: "Lanzamientos GPM"
+ GPM_RELEASES_HELP: "Elige 'Probando' para instalar versiones beta o en prueba"
+ GPM_METHOD: "Método de recuperación remota"
+ GPM_METHOD_HELP: "Cuando se pone en Automático, Grav determinará si está disponible fopen y lo usará, de otra forma se utilizará cURL. Para forzar el uso de uno u otro, cambiar la configuración."
+ GPM_VERIFY_PEER: "Verificación a distancia de peer (SSL)"
+ GPM_VERIFY_PEER_HELP: "Algunos proveedores parecen fallar en la verificación del certificado SSL de getgrav.org, lo cual causa que GPM no funcione. Si este es su caso, apagar esta configuración puede ayudar"
+ AUTO: "Automático"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Estable"
+ TESTING: "Pruebas"
+ FRONTMATTER_PROCESS_TWIG: "Procesar el Twig de texto preliminar"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Cuando esté habilitado, puedes usar las variables de configuración de Twig en la página inicial"
+ FRONTMATTER_IGNORE_FIELDS: "Ignorar campos de texto preliminar"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Ciertos campos de texto preliminar podrían contener Twig pero no deberían ser procesados, como los 'formularios'"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "El paquete %s se instaló satisfactoriamente"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "El orden esta deshabilitado debido a que ha sido establecido en el padre"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "El orden esta deshabilitado debido a que la página no es visible"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "El orden a través del admin no esta soportado debido a que contiene mas de 200 elementos hermanos"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Página de pedido está deshabilitada para esta página porque el prefijo numérico de carpeta no está activada"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "Nota: Usted no puede agregar archivos multimedia hasta que la página sea guardada. Solo haga clic en el botón de Guardar arriba"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "Nota: La página debe ser guardada antes de subir archivos."
+ DROP_FILES_HERE_TO_UPLOAD: "Suelta tus archivos aquí o dale clic a esta área "
+ INSERT: "Insertar"
+ UNDO: "Deshacer"
+ REDO: "Rehacer"
+ HEADERS: "Encabezados"
+ BOLD: "Negrita"
+ ITALIC: "Itálica"
+ STRIKETHROUGH: "Tachado"
+ SUMMARY_DELIMITER: "Delimitador de resumen"
+ LINK: "Enlace"
+ IMAGE: "Imagen"
+ BLOCKQUOTE: "Cita"
+ UNORDERED_LIST: "Lista sin órden"
+ ORDERED_LIST: "Lista ordenada"
+ EDITOR: "Editor"
+ PREVIEW: "Vista previa"
+ FULLSCREEN: "Pantalla completa"
+ MODULAR: "Modular"
+ NON_ROUTABLE: "No-Enrutable"
+ NON_MODULAR: "No modular"
+ NON_VISIBLE: "No-Visible"
+ NON_PUBLISHED: "No-Publicada"
+ CHARACTERS: "caracteres"
+ PUBLISHING: "Publicar"
+ NOTIFICATIONS: "Notificaciones"
+ MEDIA_TYPES: "Tipos de multimedia"
+ IMAGE_OPTIONS: "Opciones de imágenes"
+ MIME_TYPE: "Tipo Mime"
+ THUMB: "Miniatura"
+ TYPE: "Tipo"
+ FILE_EXTENSION: "Extensión de Archivo"
+ LEGEND: "Leyenda de página"
+ MEMCACHE_SERVER: "Servidor memcache"
+ MEMCACHE_SERVER_HELP: "Dirección del servidor Memcache"
+ MEMCACHE_PORT: "Puerto de memcache"
+ MEMCACHE_PORT_HELP: "Puerto del servidor Memcache"
+ MEMCACHED_SERVER: "Servidor Memcached"
+ MEMCACHED_SERVER_HELP: "Dirección del servidor Memcached"
+ MEMCACHED_PORT: "Puerto de memcached"
+ MEMCACHED_PORT_HELP: "Puerto del servidor Memcached"
+ REDIS_SERVER: "Servidor de Redis"
+ REDIS_SERVER_HELP: "Dirección del servidor Redis"
+ REDIS_PORT: "Puerto de Redis"
+ REDIS_PORT_HELP: "Puerto del servidor Redis"
+ REDIS_PASSWORD: "Contraseña/secreto Redis"
+ ALL: "Todo"
+ FROM: "de"
+ TO: "para"
+ RELEASE_DATE: "Fecha de lanzamiento"
+ SORT_BY: "Ordenar por"
+ RESOURCE_FILTER: "Filtrar..."
+ FORCE_SSL: "Forzar SSL"
+ FORCE_SSL_HELP: "Forzar SSL globalmente, si esta habilitado, cuando el sitio es requerido por medio de HTTP, Grav envía un direccionamiento a la página HTTPS"
+ NEWS_FEED: "Noticias"
+ EXTERNAL_URL: "URL externo"
+ SESSION_PATH: "La ruta de la sesión"
+ SESSION_PATH_HELP: "Úselo solo si ha elegido una URL base personalizada (usted ha reescrito el dominio del sitio /subcarpeta)"
+ CUSTOM_BASE_URL: "URL base personalizada"
+ CUSTOM_BASE_URL_HELP: "Úselo si usted quiere reescribir el dominio del sitio, o utiliza una subcarpeta diferente a la que Grav utiliza. Ejemplo: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'No puede utilizar "%s" fuera de las páginas.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'No puede subir archivo %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'No puede mover archivo %s a "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'Cancelar carga'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: '¿Está seguro que quiere cancelar esta carga?'
+ DROPZONE_DEFAULT_MESSAGE: 'Suelte aquí sus archivos o click en esta área '
+ DROPZONE_FALLBACK_MESSAGE: 'Su navegador no soporta arrastrar y soltar para subir archivos.'
+ DROPZONE_FALLBACK_TEXT: 'Por favor utilice el formulario opcional de abajo para cargar sus archivos, como se hacía antes.'
+ DROPZONE_FILE_TOO_BIG: 'El archivo es muy grande ({{filesize}}MiB). Tamaño máximo permitido: {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "Usted no puede subir archivos de este tipo."
+ DROPZONE_MAX_FILES_EXCEEDED: "Usted no puede subir mas archivos."
+ DROPZONE_REMOVE_FILE: "Remover archivo"
+ DROPZONE_RESPONSE_ERROR: "El servidor respondió con el código: {{statusCode}}."
+ PREMIUM_PRODUCT: "Premium"
+ ADMIN_CACHING: "Habilitar Caché del Admin"
+ ADMIN_CACHING_HELP: "El caché del admin puede ser controlado independientemente de la página frontal"
+ DESTINATION_NOT_SPECIFIED: "Destino no especificado"
+ UPLOAD_ERR_NO_TMP_DIR: "No se encuentra la carpeta temporal"
+ SESSION_SPLIT: "Separación de sesión"
+ SESSION_SPLIT_HELP: "Separar sesiones independientes entre el sitio y otros plugins (como el Admin)"
+ ERROR_FULL_BACKTRACE: "Error Backtrace completo"
+ ERROR_SIMPLE: "Error simple"
+ ERROR_SYSTEM: "Error de sistema"
+ IMAGES_AUTO_FIX_ORIENTATION: "Corregir la orientación automáticamente"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Corrección automática de la orientación de la imagen basada en los datos Exif"
+ REDIS_SOCKET: "Socket Redis"
+ REDIS_SOCKET_HELP: "El Socket Redis"
+ NOT_SET: "No establecido"
+ PERMISSIONS: "Permisos"
+ NEVER_CACHE_TWIG: "Nunca Cachear Twig"
+ NEVER_CACHE_TWIG_HELP: "Solo cachear el contenido y procesar Twig cada ver para las páginas. Ingorar configuración twig_first."
+ ALLOW_WEBSERVER_GZIP: "Permitir Webserver Gzip"
+ ALLOW_WEBSERVER_GZIP_HELP: "Desactivado de forma predeterminada. Cuando esté activado, Webserver-configured compresión Gzip/Deflate funcionará, pero las conexiones http no serán cerradas antes del evento onShutDown() causando una carga más lenta de la página"
+ OFFLINE_WARNING: "No se puede establecer la conexión con GPM"
+ CLEAR_IMAGES_BY_DEFAULT: "Borrar cache de imágenes por default"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "Por default las imágenes procesadas son eliminadas en todas las limpiezas de cache, esto puede ser deshabilitado"
+ CLI_COMPATIBILITY: "Compatibilidad de CLI"
+ CLI_COMPATIBILITY_HELP: "Asegura que se utilicen conductores de caché sólo no-volátil (archivo, redis, memcache, etcetera)."
+ REINSTALL_PLUGIN: "Reinstalar plugin"
+ REINSTALL_THEME: "Vuelva a instalar el tema"
+ REINSTALL_THE: "Vuelva a instalar el %s"
+ CONFIRM_REINSTALL: "¿Está seguro que desea volver a instalar este %s?"
+ REINSTALLED_SUCCESSFULLY: "%s instalado correctamente"
+ ERROR_REINSTALLING_THE: "Error de reinstalación del %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "El paquete %s se instaló satisfactoriamente"
+ REINSTALLATION_FAILED: "Reinstalación de error"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "La versión instalada no es la última. Al pulsar continuar, quitará la versión actual e instalará la última versión disponible"
+ TOOLS: "Herramientas"
+ DIRECT_INSTALL: "Instalación Directa"
+ NO_PACKAGE_NAME: "Nombre de paquete no especificado"
+ PACKAGE_EXTRACTION_FAILED: "Extracción de paquete fallido"
+ NOT_VALID_GRAV_PACKAGE: "No un paquete válido de Grav"
+ NAME_COULD_NOT_BE_DETERMINED: "No se pudo determinar el nombre"
+ CANNOT_OVERWRITE_SYMLINKS: "No pudieron sobre escribir los enlaces simbólicos"
+ ZIP_PACKAGE_NOT_FOUND: "No se pudo encontrar el paquete ZIP"
+ GPM_OFFICIAL_ONLY: "Sólo GPM Oficial"
+ GPM_OFFICIAL_ONLY_HELP: "Sólo permite la instalación directa desde el repositorio oficial de GPM."
+ NO_CHILD_TYPE: "Ningún \"child type\" para este \"rawroute\""
+ SORTABLE_PAGES: "Páginas que se puede ordenar:"
+ UNSORTABLE_PAGES: "Páginas sin orden"
+ ADMIN_SPECIFIC_OVERRIDES: "Reemplazos Especificos del Administrador"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Orden de visualización de los hijos"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "El orden en que los hijos de esta página deben mostrarse en la vista de 'Páginas' del plugin de Administración"
+ PWD_PLACEHOLDER: "compleja cadena de al menos 8 caracteres de largo"
+ PWD_REGEX: "Contraseña Regex"
+ PWD_REGEX_HELP: "La contraseña debe contener al menos un número y una letra mayúscula y minúscula, y al menos 8 o más caracteres"
+ USERNAME_PLACEHOLDER: "minúsculas, sólo caracteres. ej. 'admin'"
+ USERNAME_REGEX: "Nombre de usuario Regex"
+ USERNAME_REGEX_HELP: "Por defecto: sólo caracteres en minúsculas, dígitos, guiones y guiones bajos. 3 - 16 caracteres"
+ CONTENT_PADDING: "Acolchado para el contenido"
+ CONTENT_PADDING_HELP: "Habilitar o deshabilitar el acolchado del contenido alrededor del área de contenido para proporcionar mayor espacio"
+ ENABLE_AUTO_METADATA: "Auto metadata de Exif"
+ ENABLE_AUTO_METADATA_HELP: "Genera automáticamente los archivos de metadata para las imágenes con información exif"
+ 2FA_TITLE: "Autenticación de 2 Factores"
+ 2FA_INSTRUCTIONS: "##### Autenticación de 2 Factores (2FA)\nTienes **2FA** habilitado en esta cuenta. Por favor usa tu aplicación **2FA** para ingresar el **código de 6 dígitos** para completar el proceso de acceso."
+ 2FA_REGEN_HINT: "Regenerar el secreto requiere que actualice su app de autenticación"
+ 2FA_LABEL: "Acceso al Administrador"
+ 2FA_FAILED: "Código de Autentificación de 2-Factores inválido, por favor intente de nuevo..."
+ 2FA_ENABLED: "2FA Habilitado"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "Secreto 2FA"
+ 2FA_SECRET_HELP: "Escanee este código QR con su [App de Autenticación](https://learn.getgrav.org/admin-panel/2fa#apps). Además es una buena idea resguardar el secreto en una ubicación segura, en caso de que necesite reinstalar la app. Revise los [documentos Grav](https://learn.getgrav.org/admin-panel/2fa) para más información "
+ 2FA_REGENERATE: "Regenerar"
+ FORCE_LOWERCASE_URLS: "Forzar URLs en minúsculas"
+ FORCE_LOWERCASE_URLS_HELP: "De manera predeterminada Grav establecerá todos los slugs y rutas en minúsculas. Si se establece en falso, se podrán utilizar slugs y rutas en mayúsculas"
+ INTL_ENABLED: "Integración de modulo Intl"
+ INTL_ENABLED_HELP: "Utilizar el módulo PHP Intl y cotejar para ordenar colecciones basadas en UTF8"
+ VIEW_SITE_TIP: "Ver sitio"
+ TOOLS_DIRECT_INSTALL_TITLE: "Instalación Directa de Paquetes Grav"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "Instalar Paquete a través de Carga Directa de ZIP"
+ TOOLS_DIRECT_INSTALL_UPLOAD_DESC: "Puede instalar fácilmente un tema , plugin Grav o incluso un paquete de actualización Grav con este método. El paquete no debe estar registrado via GPM y le permite fácilmente volver atrás a una versión anterior o instalarlo para pruebas."
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "Instalar Paquete a través de Referencia URL Remota"
+ TOOLS_DIRECT_INSTALL_URL_DESC: "Alternativamente, puede referenciar una URL completa al archivo ZIP de paquete e instalarlo desde esta URL remota."
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Cargar e instalar"
+ ROUTE_OVERRIDES: "Anulaciones de Ruta"
+ ROUTE_DEFAULT: "Ruta Predeterminada"
+ ROUTE_CANONICAL: "Ruta Canónica"
+ ROUTE_ALIASES: "Alias de Ruta"
+ OPEN_NEW_TAB: "Abrir en una pestaña nueva"
+ STRICT_YAML_COMPAT: "Compatibilidad de YAML"
+ STRICT_TWIG_COMPAT: "Compatibilidad de Twig"
+ SCHEDULER: "Planificador"
+ SCHEDULER_INSTALL_INSTRUCTIONS: "Instrucciones de instalación"
+ SCHEDULER_INSTALLED_READY: "Instalado y listo"
+ SCHEDULER_CRON_NA: "Cron no disponible"
+ SCHEDULER_NOT_ENABLED: "Desactivado"
+ SCHEDULER_SETUP: "Configuración del planificador"
+ SCHEDULER_INSTRUCTIONS: "El planificador de Grav te permite crear y programar tareas personalizadas. También proporciona un método para integrar plugins de Grav de forma programada y añadir dinámicamente tareas para que se ejecuten de forma periódica."
+ SCHEDULER_JOBS: "Tareas programadas personalizadas"
+ SCHEDULER_STATUS: "Estado del planificador"
+ SECURITY: "Seguridad"
+ XSS_SECURITY: "Seguridad XSS para el contenido"
+ XSS_WHITELIST_PERMISSIONS: "Permisos de lista blanca"
+ XSS_WHITELIST_PERMISSIONS_HELP: "Los usuarios con estos permisos omitirán las reglas XSS al guardar contenido"
diff --git a/user/plugins/admin/languages/et.yaml b/user/plugins/admin/languages/et.yaml
new file mode 100644
index 0000000..dc884d3
--- /dev/null
+++ b/user/plugins/admin/languages/et.yaml
@@ -0,0 +1,760 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Tegemist on beeta-versiooniga! Kasutamine töötaval veebilehel oma vastutusel..."
+ ADMIN_REPORT_ISSUE: "Leidsid mõne vea? Palun anna sellest teada GitHub-i vahendusel."
+ EMAIL_FOOTER: " Veebileht kasutab Grav'i - kaasaegset failipõhist CMS-i"
+ LOGIN_BTN: "Logi sisse"
+ LOGIN_BTN_FORGOT: "Unusta"
+ LOGIN_BTN_RESET: "Lähtesta salasõna"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Saada lähtestamise juhised"
+ LOGIN_BTN_CLEAR: "Tühjenda vorm"
+ LOGIN_BTN_CREATE_USER: "Loo kasutaja"
+ LOGIN_LOGGED_IN: "Oled edukalt sisse logitud"
+ LOGIN_FAILED: "Sisselogimine ebaõnnestus"
+ LOGGED_OUT: "Sind logiti välja"
+ RESET_NEW_PASSWORD: "Palun sisesta uus salasõna …"
+ RESET_LINK_EXPIRED: "Salasõna lähtestamine on aegunud, palun proovi uuesti"
+ RESET_PASSWORD_RESET: "Salasõna on lähtestatud"
+ RESET_INVALID_LINK: "Kehtetu lähtestamise link, palun proovi uuesti"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Salasõna lähtestamise juhised on saadeti sinu e-posti aadressile"
+ FORGOT_FAILED_TO_EMAIL: "Juhiste e-postiga saatmine ebaõnnestus, palun proovi hiljem uuesti"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Kasutaja %s salasõna ei saa lähtestada, puudub e-posti aadress"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Puudub kasutaja kasutajanimega %s "
+ FORGOT_EMAIL_NOT_CONFIGURED: "Salasõna ei saa lähtestada. See veebileht ei ole seadistatud e-kirju saatma"
+ FORGOT_EMAIL_SUBJECT: "%s salasõna lähtestamise taotlus"
+ FORGOT_EMAIL_BODY: "Salasõna lähtestamine Hea %1$s,
veebilehelt %4$s tehti taotlus sinu salasõna lähtestamiseks.
Salasõna lähtestamiseks klõpsa siia
Võid ka järgneva aadressi kopeerida oma veebilehitseja aadressiribale:
%2$s
Parimate soovidega %3$s
"
+ MANAGE_PAGES: "Halda lehti"
+ CONFIGURATION: "Seadistused"
+ PAGES: "Lehed"
+ PLUGINS: "Lisamoodulid"
+ PLUGIN: "Lisamoodul"
+ THEMES: "Kujundused"
+ LOGOUT: "Logi välja"
+ BACK: "Tagasi"
+ NEXT: "Edasi"
+ PREVIOUS: "Eelmine"
+ ADD_PAGE: "Lisa leht"
+ ADD_MODULAR: "Lisa modulaarne"
+ MOVE: "Liiguta"
+ DELETE: "Kustuta"
+ VIEW: "Vaata"
+ SAVE: "Salvesta"
+ NORMAL: "Tavaline"
+ EXPERT: "Ekspert"
+ EXPAND_ALL: "Laienda kõik"
+ COLLAPSE_ALL: "Ahenda kõik"
+ ERROR: "Viga"
+ CLOSE: "Sulge"
+ CANCEL: "Tühista"
+ CONTINUE: "Jätka"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Kinnitamine on nõutav"
+ MODAL_CHANGED_DETECTED_TITLE: "Tehtud on muudatusi"
+ MODAL_CHANGED_DETECTED_DESC: "Sul on salvestamata muudatusi. Kas soovid kindlasti lahkuda ilma salvestamata?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Kinnitamine on nõutav"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Kas soovid kindlasti seda faili kustutada? Seda toimingut ei saa tagasi võtta."
+ ADD_FILTERS: "Lisa filtreid"
+ SEARCH_PAGES: "Otsi lehti"
+ VERSION: "Versioon"
+ WAS_MADE_WITH: "valmistas"
+ BY: "-ga"
+ UPDATE_THEME: "Uuenda kujundust"
+ UPDATE_PLUGIN: "Uuenda lisamoodulit"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "on sellele kujundusele nüüd saadaval"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "on sellele lisamoodulile nüüd saadaval"
+ AUTHOR: "Autor"
+ HOMEPAGE: "Koduleht"
+ DEMO: "Esitlus"
+ BUG_TRACKER: "Vigadest teavitamine"
+ KEYWORDS: "Võtmesõnad"
+ LICENSE: "Litsents"
+ DESCRIPTION: "Kirjeldus"
+ README: "Loe mind"
+ REMOVE_THEME: "Eemalda kujundus"
+ INSTALL_THEME: "Paigalda kujundus"
+ THEME: "Kujundus"
+ BACK_TO_THEMES: "Tagasi kujundustenimekirja"
+ BACK_TO_PLUGINS: "Tagasi lisamoodulite nimekirja"
+ CHECK_FOR_UPDATES: "Kontrolli uuendusi"
+ ADD: "Lisa"
+ CLEAR_CACHE: "Tühjenda puhver"
+ CLEAR_CACHE_ALL_CACHE: "Kogu puhver"
+ CLEAR_CACHE_ASSETS_ONLY: "Ainult väärtused"
+ CLEAR_CACHE_IMAGES_ONLY: "Ainult pildid"
+ CLEAR_CACHE_CACHE_ONLY: "Ainult puhver"
+ CLEAR_CACHE_TMP_ONLY: "Ainult tmp"
+ DASHBOARD: "Töölaud"
+ UPDATES_AVAILABLE: "Uuendamist vajavad"
+ DAYS: "Päeva tagasi"
+ UPDATE: "Uuenda"
+ BACKUP: "Varunda"
+ BACKUPS: "Varukoopiad"
+ BACKUP_NOW: "Loo kohe varukoopia"
+ BACKUPS_STATS: "Varukoopia statistika"
+ BACKUPS_HISTORY: "Varukoopiate ajalugu"
+ BACKUPS_PURGE_CONFIG: "Varukoopiate kustutamise seaded"
+ BACKUPS_PROFILES: "Varukoopia profiil"
+ BACKUPS_COUNT: "Varukoopiate arv"
+ BACKUPS_PROFILES_COUNT: "Profiilide arv"
+ BACKUPS_TOTAL_SIZE: "Kasutatud kettaruum"
+ BACKUPS_NEWEST: "Uusim varukoopia"
+ BACKUPS_OLDEST: "Vanim varukoopia"
+ BACKUPS_PURGE: "Kustuta"
+ BACKUPS_NOT_GENERATED: "Ühtegi varukoopiat pole veel loodud..."
+ BACKUP_NOT_FOUND: "Varukoopiat ei leitud"
+ BACKUP_DATE: "Varukoopia kuupäev"
+ STATISTICS: "Statistika"
+ TODAY: "Täna"
+ WEEK: "Nädal"
+ MONTH: "Kuu"
+ LATEST_PAGE_UPDATES: "Viimased lehtede uuendused"
+ MAINTENANCE: "Hooldus"
+ UPDATED: "Uuendatud"
+ MON: "E"
+ TUE: "T"
+ WED: "K"
+ THU: "N"
+ FRI: "R"
+ SAT: "L"
+ SUN: "P"
+ COPY: "Kopeeri"
+ EDIT: "Muuda"
+ CREATE: "Loo"
+ GRAV_ADMIN: "Grav'i admin"
+ GRAV_OFFICIAL_PLUGIN: "Grav-i ametlik lisamoodul"
+ GRAV_OFFICIAL_THEME: "Grav-i ametlik kujundus"
+ PLUGIN_SYMBOLICALLY_LINKED: "See lisamoodul on sümboolselt lingitud (symlink). Värskendusi ei leitud."
+ THEME_SYMBOLICALLY_LINKED: "See kujundus on sümboolselt lingitud (symlink) Värskendusi ei leitud"
+ REMOVE_PLUGIN: "Eemalda lisamoodul"
+ INSTALL_PLUGIN: "Paigalda lisamoodul"
+ AVAILABLE: "Saadaval"
+ INSTALLED: "Paigaldatud"
+ INSTALL: "Paigalda"
+ ACTIVE_THEME: "Aktiivne kujundus"
+ SWITCHING_TO: "Üleminek"
+ SWITCHING_TO_DESCRIPTION: "Valides mõne muu kujundus, ei ole mingit garantiid, et elementide paigutus lehtedel jääb samaks. Lehtede laadimisel võivad tekkida tõrked."
+ SWITCHING_TO_CONFIRMATION: "Kas soovite jätkata ja kujundust muuta?"
+ CREATE_NEW_USER: "Uue kasutaja loomine"
+ REMOVE_USER: "Eemalda kasutaja"
+ ACCESS_DENIED: "Juurdepääs keelatud!"
+ ACCOUNT_NOT_ADMIN: "Teie kontol pole on administraatori õiguseid"
+ PHP_INFO: "PHP teave"
+ INSTALLER: "Paigaldaja"
+ AVAILABLE_THEMES: "Saadaolevad kujundused"
+ AVAILABLE_PLUGINS: "Saadaolevad lisamoodulid"
+ INSTALLED_THEMES: "Paigaldatud kujundused"
+ INSTALLED_PLUGINS: "Paigaldatud lisamoodulid"
+ BROWSE_ERROR_LOGS: "Uuri vealogisid"
+ SITE: "Veebileht"
+ INFO: "Teave"
+ SYSTEM: "Süsteem"
+ USER: "Kasutaja"
+ ADD_ACCOUNT: "Lisa konto"
+ SWITCH_LANGUAGE: "Vaheta keelt"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Lisamoodul on edukalt lubatud"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Lisamoodul on edukalt keelatud"
+ SUCCESSFULLY_CHANGED_THEME: "Vaikimisi kujundus on edukalt muudetud"
+ INSTALLATION_FAILED: "Paigaldamine ebaõnnestus"
+ INSTALLATION_SUCCESSFUL: "Paigaldamine õnnestus"
+ UNINSTALL_FAILED: "Eemaldamine ebaõnnestus"
+ UNINSTALL_SUCCESSFUL: "Eemaldamine õnnestus"
+ SUCCESSFULLY_SAVED: "Edukalt salvestatud"
+ SUCCESSFULLY_COPIED: "Edukalt kopeeritud"
+ REORDERING_WAS_SUCCESSFUL: "Järjekord on muudetud"
+ SUCCESSFULLY_DELETED: "Kustutatud"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Keel on edukalt vahetatud"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Teil pole selle tegevuse jaoks piisavalt õigusi"
+ CACHE_CLEARED: "Puhver tühejndatud"
+ METHOD: "Meetod"
+ ERROR_CLEARING_CACHE: "Viga puhvri kustutamisel"
+ AN_ERROR_OCCURRED: "Tekkis viga"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Teie varukoopia n allalaadimiseks valmis"
+ DOWNLOAD_BACKUP: "Laadi varukopia alla"
+ PAGES_FILTERED: "Filtreeritud lehed"
+ NO_PAGE_FOUND: "Leheküõlge ei leitud"
+ INVALID_PARAMETERS: "Vigased parameetrid"
+ NO_FILES_SENT: "Faile ei saadetud ära"
+ EXCEEDED_FILESIZE_LIMIT: "PHP seadete faili suuruse limiit on ületatud"
+ UNKNOWN_ERRORS: "Tundmatud vead"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Grav seadefaili limiit on ületatud"
+ UNSUPPORTED_FILE_TYPE: "Failitüüp ei ole toetatud"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Üleslaaditud faili ei saanud liigutada"
+ FILE_UPLOADED_SUCCESSFULLY: "Fail on edukalt üles laaditud"
+ FILE_DELETED: "Fail on kustutatud"
+ FILE_COULD_NOT_BE_DELETED: "Faili ei saa kustutada"
+ FILE_NOT_FOUND: "Faili ei leitud"
+ NO_FILE_FOUND: "Ühtegi faili ei leitud"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav uuendati edukalt versioonile"
+ GRAV_UPDATE_FAILED: "Grav'i uuendamine nurjus"
+ EVERYTHING_UPDATED: "Kõik on uuendatud"
+ UPDATES_FAILED: "Uuendamine nurjus"
+ AVATAR_BY: "Avatar lehelt"
+ AVATAR_UPLOAD_OWN: "Või laadi üles enda oma..."
+ LAST_BACKUP: "Viimane varundus"
+ FULL_NAME: "Täisnimi"
+ USERNAME: "Kasutajanimi"
+ EMAIL: "E-post"
+ USERNAME_EMAIL: "Kasutajanimi või e-post"
+ PASSWORD: "Salasõna"
+ PASSWORD_CONFIRM: "Kinnita salasõna"
+ TITLE: "Pealkiri"
+ LANGUAGE: "Keel"
+ ACCOUNT: "Konto"
+ EMAIL_VALIDATION_MESSAGE: "Palun sisesta kehtiv e-posti aadress"
+ PASSWORD_VALIDATION_MESSAGE: "Salasõna peab sisaldama vähemalt ühte numbrit, ühte suur- ja väiketähte ja koosnema vähemalt 8 märgist"
+ LANGUAGE_HELP: "Määra sobiv keel"
+ MEDIA: "Meedia"
+ DEFAULTS: "Vaikimisi"
+ SITE_TITLE: "Veebilehe nimi"
+ SITE_TITLE_PLACEHOLDER: "Pealkiri kogu saidile"
+ SITE_TITLE_HELP: "Vaikimisi pealkiri saidile - sageli kasutatakse kujundustes"
+ SITE_DEFAULT_LANG: "Vaikimisi keel"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Vaikimisi keel tag-ides kasutamiseks"
+ SITE_DEFAULT_LANG_HELP: "Vaikimisi keel kujunduste tag-ides kasutamiseks"
+ DEFAULT_AUTHOR: "Vaikimisi autor"
+ DEFAULT_AUTHOR_HELP: "Vaikimisi autori nimi, kasutatakse sageli kujundustes või lehe sisus"
+ DEFAULT_EMAIL: "Vaikimisi e-post"
+ DEFAULT_EMAIL_HELP: "Vaikimisi e-mail kujundustes või lehekülgedel kasutamiseks"
+ TAXONOMY_TYPES: "Taksonoomia tüübid"
+ TAXONOMY_TYPES_HELP: "Taksonoomia liigid tuleb määratleda siin kui soovid kasutada neid lehtedel"
+ PAGE_SUMMARY: "Lehekülje kokkuvõte"
+ ENABLED: "Lubatud"
+ ENABLED_HELP: "Lülita kokkuvõte sisse (see kokkuvõte on sama mis lehekülje sisu)"
+ 'YES': "Jah"
+ 'NO': "Ei"
+ SUMMARY_SIZE: "Kokkuvõtte maht"
+ SUMMARY_SIZE_HELP: "Tähemärkide arv, mida kasutatakse sisu kokkuvõtte genereerimisel"
+ FORMAT: "Formaat"
+ FORMAT_HELP: "lühike = kasuta esimest leitud eraldajat või suurust; pikk = ignoreeri kokkuvõte eraldajat"
+ SHORT: "Lühike"
+ LONG: "Pikk"
+ DELIMITER: "Eraldaja"
+ DELIMITER_HELP: "Lehekülje kokkuvõtte eraldaja (vaikimisi ' === \")"
+ METADATA: "Metaandmed"
+ METADATA_HELP: "Metaandmete vaikeväärtused, mida kuvatakse vaikimisi igal leheküljel. Vajadusel saab seda igal lehel eraldi muuta."
+ NAME: "Nimi"
+ CONTENT: "Sisu"
+ SIZE: "Suurus"
+ ACTION: "Tegevus"
+ REDIRECTS_AND_ROUTES: "Ümbersuunamised ja marsruudid"
+ CUSTOM_REDIRECTS: "Kohandatud ümbersuunamised"
+ CUSTOM_REDIRECTS_HELP: "Marsruudid teistele lehekülgedele suunamiseks. Saab kasutada ka standardsed regex-lausendeid."
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/sinu/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/sinu/suunamine"
+ CUSTOM_ROUTES: "Kohandatud suunamised"
+ CUSTOM_ROUTES_HELP: "marsruudid aliaste teistele lehekülgedele suunamiseks. Saab kasutada ka standardsed regex-lausendeid"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/sinu/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/your/route"
+ FILE_STREAMS: "Failivood"
+ DEFAULT: "Vaikimisi"
+ PAGE_MEDIA: "Lehe meedia"
+ OPTIONS: "Valikud"
+ PUBLISHED: "Avaldatud"
+ PUBLISHED_HELP: "Vaikimisi on leht avaldatud, kui sa ei määra avaldamise staatuseks 'Ei' või kui avaldamise kuupäev on tulevikus või privaatseks muutmise kuupäev on minevikus"
+ DATE: "Kuupäev"
+ DATE_HELP: "Kuupäeva muutuja lubab sul määrata just selle lehega seotud kuupäeva"
+ PUBLISHED_DATE: "Avaldamise aeg"
+ PUBLISHED_DATE_HELP: "Saab määrata kuupäeva ja kellaaja, millal leht automaatselt avaldatakse"
+ UNPUBLISHED_DATE: "Privaatseks muutmise aeg"
+ UNPUBLISHED_DATE_HELP: "Saab määrata kuupäeva ja kellaaja, millal leht automaatselt privaatseks muudetakse."
+ ROBOTS: "Robotid"
+ TAXONOMIES: "Taksonoomiad"
+ TAXONOMY: "Taksonoomia"
+ ADVANCED: "Edasijõudnud"
+ SETTINGS: "Seaded"
+ FOLDER_NUMERIC_PREFIX: "Kataloogi numbriline eesliide"
+ FOLDER_NUMERIC_PREFIX_HELP: "Numbriline eesliide, mis võimaldab käsitsi järjestamist ja tähistab nähtavust"
+ FOLDER_NAME: "Kataloogi nimi"
+ FOLDER_NAME_HELP: "Kataloogi nimi, mis salvestatakse failisüsteemi selle lehe jaoks"
+ PARENT: "Peamine"
+ DEFAULT_OPTION_ROOT: "- Juurkaust -"
+ DEFAULT_OPTION_SELECT: "- Vali -"
+ DISPLAY_TEMPLATE: "Näita malli"
+ DISPLAY_TEMPLATE_HELP: "Lehe tüüp, mis määrab, millist Twigi malli kasutatakse lehe visualiseerimiseks"
+ BODY_CLASSES: "Body klassid"
+ ORDERING: "Järjestamine"
+ PAGE_ORDER: "Lehekülgede järjekord"
+ OVERRIDES: "Ülekirjutused"
+ MENU: "Menüü"
+ MENU_HELP: "Menüüs kasutatav sõne. Kui ei ole seatud, kasutatakse Pealkirja."
+ SLUG: "Slug"
+ SLUG_HELP: "Slug muutuja võimaldab spetsiifiliselt seada lehe nimetuse osa URL-s"
+ SLUG_VALIDATE_MESSAGE: "Slug peab koosnema ainult väiketähtedest, numbritest ja kriipsudest"
+ PROCESS: "Töötlemine"
+ PROCESS_HELP: "Saab määrata, kuidas lehti töödeldakse. Lisaks üldisele seadistusele saab muuta ka iga lehe puhul eraldi"
+ DEFAULT_CHILD_TYPE: "Vaikimisi tütre tüüp"
+ USE_GLOBAL: "Kasuta vaikeväärtust"
+ ROUTABLE: "Suunatav"
+ ROUTABLE_HELP: "Kui see leht on URLi kaudu kättesaadav"
+ CACHING: "Vahemälu"
+ VISIBLE: "Nähtav"
+ VISIBLE_HELP: "Määrab, kas leht on navigatsioonis nähtav."
+ DISABLED: "Keelatud"
+ ITEMS: "Kirjed"
+ ORDER_BY: "Järjesta"
+ ORDER: "Järjekord"
+ FOLDER: "Kataloog"
+ ASCENDING: "Kasvav"
+ DESCENDING: "Kahanev"
+ ADD_MODULAR_CONTENT: "Lisa modulaarne sisu"
+ PAGE_TITLE: "Lehe pealkiri"
+ PAGE_TITLE_HELP: "Lehekülje pealkiri"
+ PAGE: "Leht"
+ MODULAR_TEMPLATE: "Modulaarne mall"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Failinimi"
+ PARENT_PAGE: "Peamine lehekülg"
+ HOME_PAGE: "Avaleht"
+ HOME_PAGE_HELP: "Leht, mida Grav kasutab vaikimisi avalehena"
+ DEFAULT_THEME: "Vaikmisi kujundus"
+ DEFAULT_THEME_HELP: "Määra Grav'i vaikimisi kujundus (algne vaikimisi kujundus on Antimatter)"
+ TIMEZONE: "Ajavöönd"
+ TIMEZONE_HELP: "Tühista serveri vaikimisi ajavöönd"
+ SHORT_DATE_FORMAT: "Lühike kuupäevavorming"
+ SHORT_DATE_FORMAT_HELP: "Määra lühike kuupäevavorming, mida kujundused saavad kasutada"
+ LONG_DATE_FORMAT: "Pikk kuupäevavorming"
+ LONG_DATE_FORMAT_HELP: "Määra pikk kuupäevavorming, mida kujundused saavad kasutada"
+ DEFAULT_ORDERING: "Vaikimisi järjestamine"
+ DEFAULT_ORDERING_HELP: "Lehtede nimekirja esitatakse seda järjestust kasutades, kui ei ole käsitsi ümber järjestatud"
+ DEFAULT_ORDERING_DEFAULT: "Vaikimisi - põhineb kausta nimel"
+ DEFAULT_ORDERING_FOLDER: "Kataloog - põhineb eesliiteta kataloogi nimel"
+ DEFAULT_ORDERING_TITLE: "Pealkiri - põhineb päise pealkirjaväljal"
+ DEFAULT_ORDERING_DATE: "Kuupäev - põhineb päise kuupäevaväljal"
+ DEFAULT_ORDER_DIRECTION: "Vaikimisi järjestamise suund"
+ DEFAULT_ORDER_DIRECTION_HELP: "Lehtede nimekirja järjestamise suund"
+ DEFAULT_PAGE_COUNT: "Vaikimisi lehtede arv"
+ DEFAULT_PAGE_COUNT_HELP: "Vaikimisi maksimaalne lehtede arv nimekirjas"
+ DATE_BASED_PUBLISHING: "Kuupäevapõhine avaldamine"
+ DATE_BASED_PUBLISHING_HELP: "Muuda lehed kuupäevapõhiselt automaatselt avaldatuks või privaatseks"
+ EVENTS: "Sündmused"
+ EVENTS_HELP: "Luba või keela eriomased sündmused. Keelamine võib osad lisamoodulid katki teha"
+ REDIRECT_DEFAULT_ROUTE: "Suuna vaikimisi teekonnale"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Suuna automaatselt lehe vaikimisi teekonnale"
+ LANGUAGES: "Keeled"
+ SUPPORTED: "Toetatud"
+ SUPPORTED_HELP: "Komadega eraldatud nimekiri 2-tähelistest keelekoodidest (näiteks 'et,en,de')"
+ TRANSLATIONS_ENABLED: "Tõlkimine on lubatud"
+ TRANSLATIONS_ENABLED_HELP: "Võimaldab Gravi, lisamoodulite ja laienduste tõlgete kasutamise"
+ TRANSLATIONS_FALLBACK: "Tõlke varukeel"
+ TRANSLATIONS_FALLBACK_HELP: "Otsib tõlget järjestikuselt teistest keeltest, kui valitud keeles tõlget ei ole"
+ ACTIVE_LANGUAGE_IN_SESSION: "Sessiooni aktiivne keel"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Säilita sessioonis aktiivne keel"
+ HTTP_HEADERS: "HTTP päised"
+ EXPIRES: "Aegub"
+ EXPIRES_HELP: "Seab aegumise (`expires`) päise. Väärtus on sekundites."
+ CACHE_CONTROL: "HTTP vahemälu juhtimine"
+ CACHE_CONTROL_HELP: "Seab puhvri juhtimise käsundi, nagu `no-cache, no-store, must-revalidate`"
+ LAST_MODIFIED: "Viimati muudetud"
+ LAST_MODIFIED_HELP: "Seab viimati muudetud (`last modified`) päise, mis võib aidata optimeerida puhverserveri ja veebilehitseja puhverdamist"
+ ETAG: "ETag"
+ ETAG_HELP: "Seab `etag` päise, mis võib aidata määrata, millal lehte on muudetud"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Seab `Vary: Accept Encoding` päise, mis võib aidata puhverserveri ja CDN-i töös"
+ MARKDOWN_EXTRA: "Markdown Extra"
+ MARKDOWN_EXTRA_HELP: "Lülita sisse vaikimisi tugi Markdown Extra-le - https://michelf.ca/projects/php-markdown/extra/"
+ AUTO_LINE_BREAKS: "Automaatsed reavahetused"
+ AUTO_LINE_BREAKS_HELP: "Lülita markdown-s sisse tugi automaatsetele reavahetustele"
+ AUTO_URL_LINKS: "Automaatsed URL lingid"
+ AUTO_URL_LINKS_HELP: "Lülita sisse URL-de automaatne muutmine HTML linkideks"
+ CACHING_HELP: "Globaalne ON/OFF lüliti Grav puhverdamise sisse- ja väljalülitamiseks"
+ CACHE_CHECK_METHOD: "Vahemälu kontrollimise meetod"
+ CACHE_CHECK_METHOD_HELP: "Vali meetod, mida Grav kasutab kontrollimaks, kas lehefaile on muudetud."
+ CACHE_DRIVER: "Vahemälu draiver"
+ CACHE_DRIVER_HELP: "Vali millist puhverdamisvahendit Grav peaks kasutama. 'Auto Detect' proovib ise leida parima võimaluse"
+ CACHE_PREFIX: "Vahemälu eesliide"
+ CACHE_PREFIX_HELP: "Grav võtme tunnus. Ära muuda, kui sa ei ta, mida sa teed."
+ CACHE_PREFIX_PLACEHOLDER: "Tuletatud baas-URL-st (asenda suvalise sõne sisestamisega)"
+ LIFETIME: "Eluiga"
+ LIFETIME_HELP: "Seab puhvri eluea sekundites. 0 = lõpmatu"
+ GZIP_COMPRESSION: "Gzip pakkimine"
+ GZIP_COMPRESSION_HELP: "Lülita sisse või välja Grav lehe GZip pakkimine. Sisselülitamine võib parandada jõudlust."
+ TWIG_TEMPLATING: "Twig mallid"
+ TWIG_CACHING: "Twig vahemälu"
+ TWIG_CACHING_HELP: "Lülita sisse või välja Twigi puhverdamine. Sisselülitamine vajalik parima jõudluse saamiseks."
+ TWIG_DEBUG: "Twigi veatuvastus"
+ TWIG_DEBUG_HELP: "Võimaldab mitte laadida Twig Debugger laiendust"
+ DETECT_CHANGES: "Tuvasta muudatused"
+ DETECT_CHANGES_HELP: "Twig rekompileerib automaatselt Twigi puhvri, kui tuvastab muudatuse Twigi mallides"
+ AUTOESCAPE_VARIABLES: "Autoescape-i muutujad"
+ AUTOESCAPE_VARIABLES_HELP: "Autoescapes kõik muutujad. See teeb väga tõenäoliselt sinu saidi katki"
+ ASSETS: "Ressursid"
+ CSS_PIPELINE: "CSS Pipeline"
+ CSS_PIPELINE_HELP: "CSS pipeline tähendab mitmete CSS ressursside ühendamist ühte faili"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Kaasa välised URL-d CSS pipeline-i"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Välistel URL-del on vahel relatiivsed failiviited ja neid ei peaks pipeline lisama"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "Töötle CSS pipeline esimesena"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Töötle CSS pipeline enne muid CSS viiteid, mis ei ole pipeline lisatud"
+ CSS_MINIFY: "CSS-i vähendamine"
+ CSS_MINIFY_HELP: "Minimeeri CSS pipeline lisamise ajal"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "CSS minimeerimise Windowsi serveritel"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "CSS minimeerimise olek Windows serverite puhul. Vaikimisi välja lülitatud ThreadStackSize tõttu"
+ CSS_REWRITE: "CSS-i ülekirjutamine"
+ CSS_REWRITE_HELP: "Asenda kõik CSS relatiivsed URL-d pipeline lisamise ajal"
+ JAVASCRIPT_PIPELINE: "JavaScript pipeline"
+ JAVASCRIPT_PIPELINE_HELP: "JS pipeline tähendab mitmete JS ressursside ühendamist ühte faili"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Kaasa välised URL-d JS pipeline"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Välistel URL-del on vahel relatiivsed failiviited ja neid ei peaks pipeline lisama"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Töötle JS pipeline esimesena"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Töötle JS pipeline enne muid JS viiteid, mis ei ole pipeline lisatud"
+ JAVASCRIPT_MINIFY: "JavaSkripti minimeerimine"
+ JAVASCRIPT_MINIFY_HELP: "Minimeeri JS pipeline lisamise ajal"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Luba ressurssidele loomisaja lisamine"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Luba ressurssidele loomisaja lisamine"
+ COLLECTIONS: "Kogud"
+ ERROR_HANDLER: "Vigade käsitlemine"
+ DISPLAY_ERRORS: "Näita veateateid"
+ DISPLAY_ERRORS_HELP: "Vigade kuvamise mall"
+ LOG_ERRORS: "Logi veateated"
+ LOG_ERRORS_HELP: "Logi vead /logs kataloogi"
+ DEBUGGER: "Veatuvastus"
+ DEBUGGER_HELP: "Lülita sisse Gravi veatuvastus ja järgnevad seaded"
+ DEBUG_TWIG: "Twigi veatuvastus"
+ DEBUG_TWIG_HELP: "Lülita sisse Twigi silumine"
+ SHUTDOWN_CLOSE_CONNECTION: "Shutdown-il ühenduse sulgemine"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Sulge ühendus enne onShutdown()-i kutsumist. 'Ei' silumise jaoks"
+ DEFAULT_IMAGE_QUALITY: "Piltide vaikimisi kvaliteet"
+ DEFAULT_IMAGE_QUALITY_HELP: "Vaikimisi pildikvaliteet piltide töötlemisel või puhverdamisel (85%)"
+ CACHE_ALL: "Loe kõik pildid vahemällu"
+ CACHE_ALL_HELP: "Juhi kõik pildid läbi Gravi puhvrisüsteemi isegi juhul, kui neid ei töödelda"
+ IMAGES_DEBUG: "Piltide silumise vesimärk"
+ IMAGES_DEBUG_HELP: "Näita piltide kohal kattekihti, mis näitab pildi pikslisügavust näiteks retina-ga töötamisel"
+ UPLOAD_LIMIT: "Failide üleslaadimise limiit"
+ UPLOAD_LIMIT_HELP: "Määra maksimaalne üleslaadimise suurus baitides (0 tähendab, et piirangut pole)"
+ ENABLE_MEDIA_TIMESTAMP: "Luba loomisaja lisamine meediale"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Lisab igale meediafailile ajatempli viimase muutmise ajaga"
+ SESSION: "Sessioon"
+ SESSION_ENABLED_HELP: "Luba Gravi sessioonide tugi"
+ TIMEOUT: "Aegumine"
+ TIMEOUT_HELP: "Määrab sessiooni aegumise sekundites"
+ SESSION_NAME_HELP: "Identifikaator, mida kasutatakse sessiooniküpsise nime koostamisel"
+ ABSOLUTE_URLS: "Absoluutsed URL-id"
+ ABSOLUTE_URLS_HELP: "Absoluutsed või relatiivsed URL-d `base_url`-i jaoks"
+ PARAMETER_SEPARATOR: "Parameetrite eraldaja"
+ PARAMETER_SEPARATOR_HELP: "Saadetud parameetrite eraldaja, mida saab muuta Windowsil jooksva Apache jaoks"
+ TASK_COMPLETED: "Ülesanne on lõpetatud"
+ EVERYTHING_UP_TO_DATE: "Kõik on ajakohane"
+ UPDATES_ARE_AVAILABLE: "uuendus(t) on saadaval"
+ IS_AVAILABLE_FOR_UPDATE: "on saadaval uuendamiseks"
+ IS_NOW_AVAILABLE: "on nüüd saadaval"
+ CURRENT: "Praegune"
+ UPDATE_GRAV_NOW: "Uuenda kohe Grav-i"
+ GRAV_SYMBOLICALLY_LINKED: "Grav on sümboolselt lingitud. Uuendamine ei ole võimalik"
+ UPDATING_PLEASE_WAIT: "Uuendamine... palun oota, käimas on allalaadimine"
+ OF_THIS: "selle"
+ OF_YOUR: "sinu"
+ HAVE_AN_UPDATE_AVAILABLE: "on uuendus saadaval"
+ SAVE_AS: "Salvesta - "
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Oled kindel, et soovid antud lehe ja kõik selle alamlehed kustutada? Kui leht on teistesse keeltesse tõlgitud, jäävad tõlked alles ja need tuleb eraldi kustutada. Jätkamisel lehe kaust koos alamlehtedega kustutatakse. Seda tegevust ei saa tagasi võtta."
+ AND: "ja"
+ UPDATE_AVAILABLE: "Uuendus on saadaval"
+ METADATA_KEY: "Võti (näiteks 'keywords')"
+ METADATA_VALUE: "Väärtus (näiteks 'Ajaveeb, Grav')"
+ USERNAME_HELP: "Kasutajanimi peaks olema 3 kuni 16 märki, võib sisaldada väikeseid tähti, numbreid, alakriipse ja kriipse. Suured tähed, tühikud ja spets-märgid ei ole lubatud"
+ FULLY_UPDATED: "Täielikult uuendatud"
+ SAVE_LOCATION: "Salvestamise asukoht"
+ PAGE_FILE: "Lehekülje mallid"
+ PAGE_FILE_HELP: "Lehe malli faili nimi ja vaikimisi kuvamall sellele lehele"
+ NO_USER_ACCOUNTS: "Ühtegi kasutajakontot ei leitud. Palun loo alustuseks üks konto..."
+ REDIRECT_TRAILING_SLASH: "Lõpukaldjoone ümbersuunamine"
+ REDIRECT_TRAILING_SLASH_HELP: "Tee 301 ümbersuunamine selle asemel, et lõpukaldjoonega URI-sid nähtamatult käsitleda."
+ DEFAULT_DATE_FORMAT: "Lehe kuupäevavorming"
+ DEFAULT_DATE_FORMAT_HELP: "Gravi'i poolt kasutatav lehe kuupäevavorming. Vaikimisi üritab Grav ise sobivat kuupäevavormingut valida, kuid saad seda täpsustada PHP kuupäevasüntaksit kasutades (näiteks: Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Vali automaatselt"
+ IGNORE_FILES: "Ignoreeri faile"
+ IGNORE_FILES_HELP: "Spetsiifilised failid, mida lehekülgde töötlemisel ignoreerida"
+ IGNORE_FOLDERS: "Ignoreeri katalooge"
+ IGNORE_FOLDERS_HELP: "Spetsiifilised kataloogid, mida ignoreeritakse lehtede töötlemisel"
+ HTTP_ACCEPT_LANGUAGE: "Vali keel veebilehitsejast"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Proovi seada keel veebilehitseja `http_accept_language` päise alusel"
+ OVERRIDE_LOCALE: "Kirjuta keel üle"
+ OVERRIDE_LOCALE_HELP: "Kirjuta keele seadede PHP-s üle kasutadest valitud keelt"
+ REDIRECT: "Lehe ümbersuunamine"
+ REDIRECT_HELP: "Sisesta marsruut või väline URL, kuhu see leht ümbersuunata. Nt. `/some/route` või `http://somesite.com`"
+ PLUGIN_STATUS: "Lisamooduli staatus"
+ INCLUDE_DEFAULT_LANG: "Kaasa vaikimisi keel"
+ INCLUDE_DEFAULT_LANG_HELP: "See seade lisab ka vaikimisi keeles olevate URL-de algusesse keeletähise. Nt. `/et/blog/postitus`"
+ ALLOW_URL_TAXONOMY_FILTERS: "URL Taksonoomia filtrid"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Lehepõhised kollektsioonid võimaldavad filtreerimist taksonoomia põhjal. Nt. `/taxonomy:value`."
+ REDIRECT_DEFAULT_CODE: "Vaikimisi ümbersuunamiskood"
+ REDIRECT_DEFAULT_CODE_HELP: "HTTP olekukood ümbersuunamiste puhul"
+ IGNORE_HIDDEN: "Ignoreeri peidetuid"
+ IGNORE_HIDDEN_HELP: "Ignoreeri kõiki faile ja katalooge, mis algavad PUNKTIGA"
+ WRAPPED_SITE: "Põimitud sait"
+ WRAPPED_SITE_HELP: "Et kujundused ja lisamoodulid teaksid, kas Grav on põimitud mõne teise platformi sisse"
+ FALLBACK_TYPES: "Lubatud failitüübid"
+ FALLBACK_TYPES_HELP: "Lubatud failitüübid, mida on võimalik leida läbi lehe marsruudi. Vaikimisi on lubatud kõik toetatud meediatüübid."
+ INLINE_TYPES: "Tekstisisesed failitüübid"
+ INLINE_TYPES_HELP: "Nimekiri failitüüpidest, mida peaks pigem näitama teksti sees kui allalaadima"
+ APPEND_URL_EXT: "Lisa aadressilaiend"
+ APPEND_URL_EXT_HELP: "Lisab lehe aadressile laiendi. Pane tähele, et sellisel juhul järgib Grav `..twig`malli."
+ PAGE_MODES: "Lehe moodulid"
+ PAGE_TYPES: "Lehe liigid"
+ ACCESS_LEVELS: "Juurdepääs"
+ GROUPS: "Grupid"
+ GROUPS_HELP: "Nimekiri gruppidest, kuhu kasutaja kuulub"
+ ADMIN_ACCESS: "Admini ligipääs"
+ SITE_ACCESS: "Saidi ligipääs"
+ INVALID_SECURITY_TOKEN: "Vigane turvakood"
+ ACTIVATE: "Aktiveeri"
+ TWIG_UMASK_FIX: "Umask parandus"
+ TWIG_UMASK_FIX_HELP: "Twig loob vaikimisi puhverfailid õigustega 0755, parandus muudab selle 0775-ks"
+ CACHE_PERMS: "Vahemälu õigused"
+ CACHE_PERMS_HELP: "Vaikimisi õigused puhvri (cache) kataloogile. Tavaliselt 0755 või 0775 olenevalt seadistusest"
+ REMOVE_SUCCESSFUL: "Eemaldatud"
+ REMOVE_FAILED: "Eemaldamine ebaõnnestus"
+ HIDE_HOME_IN_URLS: "Peida aadressis avalehe teekond"
+ HIDE_HOME_IN_URLS_HELP: "Peidab kõikide avalehe alamlehtede aadressis avalehe vaikimisi teekonna"
+ TWIG_FIRST: "Töötle esmalt Twigi"
+ TWIG_FIRST_HELP: "Kui sa oled sisse lülitanud Twigi töötlemise lehel, saad konfigureerida Twigi töötlemise enne või pärast markdowni"
+ SESSION_SECURE: "Turvaline"
+ SESSION_SECURE_HELP: "Kui sisse lülitatud, siis suhtlus selle küpsisega peab olema ainult üle krüpteeritud ühenduse. HOIATUS: lülita sisse ainult saitidel, mis jooksevad täielikult HTTPSi peal"
+ SESSION_HTTPONLY: "Ainult HTTP"
+ SESSION_HTTPONLY_HELP: "Kui sisse lülitatud, siis seab HTTPOnly lipu, mis ei luba küpsise muutmist JavaScripti vms poolt kasutaja veebilehitsejas"
+ REVERSE_PROXY: "Pöördproksi"
+ REVERSE_PROXY_HELP: "Lülita see sisse, kui sait on pöördproksi taga ja tekivad probleemid URL-dega, mis sisaldavad ebaõigeid porte"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Vigane frontmatter, ei saanud salvestada"
+ ADD_FOLDER: "Lisa kataloog"
+ PROXY_URL: "Proksi URL"
+ PROXY_URL_HELP: "Sisesta proksi HOSTINIMI või IP ja PORT"
+ NOTHING_TO_SAVE: "Pole midagi salvestada"
+ FILE_ERROR_ADD: "Faili lisamisel tekkis viga"
+ FILE_ERROR_UPLOAD: "Faili üleslaadimisel tekkis viga"
+ FILE_UNSUPPORTED: "Failitüüp pole toetatud"
+ ADD_ITEM: "Lisa kirje"
+ FILE_TOO_LARGE: "See fail on üles laadimiseks luuga suur. Maksmiaalne lubatud suurus on vastavalt sinu PHP seadetele %s. Suurenda PHP seadetes väärtust `post_max_size`"
+ INSTALLING: "Paigaldamine"
+ LOADING: "Laadimine.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "Eelnevalt tuleb täita järgmised nõuded:"
+ ERROR_INSTALLING_PACKAGES: "Viga paki (pakkide) paigaldamisek"
+ INSTALLING_DEPENDENCIES: "Sõltuvuste paigaldamine..."
+ INSTALLING_PACKAGES: "Pakkide paigaldamine.."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Pakk (Pakid) on paigaldatud."
+ READY_TO_INSTALL_PACKAGES: "Valmis paki (pakkide) paigaldamiseks"
+ PACKAGES_NOT_INSTALLED: "Pakke ei paigaldatud"
+ PACKAGES_NEED_UPDATE: "Pakid on juba paigaldatud, aga need on liiga vanad"
+ PACKAGES_SUGGESTED_UPDATE: "Pakid on juba paigaldatud ning versioon on korras, aga seda uuendatakse, et sul oleks kõige viimane versioon"
+ REMOVE_THE: "Eemalda %s"
+ CONFIRM_REMOVAL: "Kas oled kindel, et soovid selle %s kustutada?"
+ REMOVED_SUCCESSFULLY: "%s on eemaldatud"
+ ERROR_REMOVING_THE: "Viga %s eemaldamisel"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "%s vajas järgnevaid sõltuvusi, mis ei ole vajalikud teiste installeeritud pakettide jaoks. Kui sa neid ei kasuta, võid need otse siit eemaldada."
+ READY_TO_UPDATE_PACKAGES: "Valmis paki (pakkide) uuendamiseks"
+ ERROR_UPDATING_PACKAGES: "Viga paki (pakkide) uuendamisel"
+ UPDATING_PACKAGES: "Paki (Pakkide) uuendamine.."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Pakk (Pakid) on uuendatud."
+ UPDATING: "Uuendamine"
+ GPM_RELEASES: "GPM versioonid"
+ GPM_RELEASES_HELP: "Vali 'Testimine' beta ja test-versioonide installeerimiseks"
+ GPM_METHOD: "Mujalt tõmbamise meetod"
+ GPM_METHOD_HELP: "Kui valitud Automaatne, siis Grav tuvastab, kas fopen on saadaval ning kasutab seda. Kui see pole saadaval, siis kasutatakse cURL-i. Kui tahad jõuga sundida peale mingit valikut, siis tee siin see valik."
+ GPM_VERIFY_PEER_HELP: "Tundub, et mõned teenusepakkujad ei suuda verifitseerida getgrav.org SSL sertifikaate, põhjustades GPMi mittetöötamise. Kui sul on see olukord, siis selle seade väljalülitamine võib aidata"
+ AUTO: "Automaatne"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stabiilne"
+ TESTING: "Testimine"
+ FRONTMATTER_PROCESS_TWIG: "Töötle frontmatter-i Twigi"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Kui sisselülitatud, saad kasutada Twigi konfiguratsioonimuutujaid lehe frontmatter-s"
+ FRONTMATTER_IGNORE_FIELDS: "Ignoreeri frontmatter-i välju"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Teatud frontmatteri väljad võivad sisaldada Twigi, kuid ei tohiks saada töödeldud, nagu näiteks 'forms'"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Pakk %s on paigaldatud"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Vanem defineerib järjestuse, järjestamine väljalülitatud"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Lehekülg pole nähtav, järjekorra muutmine on välja lülitatud"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "Admini kaudu pole järjekorra muutmine võimalik, kuna siin on üle 200 alamkirje"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Lehtede järjestamine on väljalülitatud kuna kataloogi numbriline eesliide on välja lülitatud"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "MÄRKUS: Sa ei saa lisada meediafaile enne lehe salvestamist. Lihtsalt kliki 'Salvesta' üleval ääres"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "MÄRKUS: Leht peab olema salvestatud enne kui sa saad sellele faile üleslaadida."
+ DROP_FILES_HERE_TO_UPLOAD: "Lohista failid siia või klõpsa sellele alale "
+ INSERT: "Sisesta"
+ UNDO: "Võta tagagi"
+ REDO: "Tee uuesti"
+ HEADERS: "Päised"
+ BOLD: "Rasvane kiri"
+ ITALIC: "Kaldkiri"
+ STRIKETHROUGH: "Läbikriipsutamine"
+ SUMMARY_DELIMITER: "Kokkuvõtte eraldaja"
+ LINK: "Link"
+ IMAGE: "Pilt"
+ BLOCKQUOTE: "Plokk-tsitaat"
+ UNORDERED_LIST: "Järjestamata loend"
+ ORDERED_LIST: "Järjestatud loend"
+ EDITOR: "Redaktor"
+ PREVIEW: "Eelvaade"
+ FULLSCREEN: "Täisekraan"
+ MODULAR: "Modulaarne"
+ NON_ROUTABLE: "Privaatne"
+ NON_MODULAR: "Mittemodulaarne"
+ NON_VISIBLE: "Nähtamatu"
+ NON_PUBLISHED: "Avaldamata"
+ CHARACTERS: "tähemärki"
+ PUBLISHING: "Avaldamine"
+ NOTIFICATIONS: "Teated"
+ MEDIA_TYPES: "Meedia tüübid"
+ IMAGE_OPTIONS: "Pildi valikud"
+ MIME_TYPE: "Failitüübid"
+ THUMB: "Pisipilt"
+ TYPE: "Tüüp"
+ FILE_EXTENSION: "Faililaiendid"
+ LEGEND: "Lehe legend"
+ MEMCACHE_SERVER: "Memcache server"
+ MEMCACHE_SERVER_HELP: "Memcache serveri aadress"
+ MEMCACHE_PORT: "Memcache port"
+ MEMCACHE_PORT_HELP: "Memcache serveri port"
+ MEMCACHED_SERVER: "Memcache server"
+ MEMCACHED_SERVER_HELP: "Memcache serveri aadress"
+ MEMCACHED_PORT: "Memcache port"
+ MEMCACHED_PORT_HELP: "Memcache serveri port"
+ REDIS_SERVER: "Redis server"
+ REDIS_SERVER_HELP: "Redis serveri aadress"
+ REDIS_PORT: "Redis port"
+ REDIS_PORT_HELP: "Redis serveri port"
+ REDIS_PASSWORD: "Redis parool/salasõna"
+ ALL: "kõik"
+ FROM: "alates"
+ TO: "kuni"
+ RELEASE_DATE: "Väljalaske kuupäev"
+ SORT_BY: "Sorteerimise alus"
+ RESOURCE_FILTER: "Filter..."
+ FORCE_SSL: "SSL-i jõuga kasutamine"
+ FORCE_SSL_HELP: "Kogu saidil kasutatakse SSL-i. Kui see on sisse lülitatud, siis HHTP ühendusega lehele saabunud kasutajad suunatakse HTTPS lehele"
+ NEWS_FEED: "Uudised"
+ EXTERNAL_URL: "Väline URL"
+ SESSION_PATH: "Sessiooni asukoht"
+ SESSION_PATH_HELP: "Kasuta ainult juhul, kui oled valinud modifitseeritud base URL-i (suunad ümber saidi domeeni / alamkataloogi)"
+ CUSTOM_BASE_URL: "Kohandatud baas URL"
+ CUSTOM_BASE_URL_HELP: "Kasuta, kui soovid ülekirjutada saidi domeeni või kasutada teistsugust alamkataloogi kui Grav. Nt: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'Väljaspool lehti ei saa "%s" kasutada.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Faili üleslaadimine ebaõnnestus %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'Ei saa liigutada faili %s asukohta "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'Tühista üleslaadimine'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Kas sa oled kindel, et soovid selle üleslaadimise tühistada?'
+ DROPZONE_DEFAULT_MESSAGE: 'Lohista failid siia või klõpsa sellele alale '
+ DROPZONE_FALLBACK_MESSAGE: 'Sinu brauser ei toeta failide lohistamisega üleslaadimist.'
+ DROPZONE_FALLBACK_TEXT: 'Palun kasuta allpool olevat vormi failide üleslaadimiseks nagu vanal ajal.'
+ DROPZONE_FILE_TOO_BIG: 'Fail on liiga suur ({{filesize}}MiB). Maks failisuurus: {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "Sa ei saa laadida üles seda tüüpi faile."
+ DROPZONE_MAX_FILES_EXCEEDED: "Sa ei saa rohkem faile üles laadida."
+ DROPZONE_REMOVE_FILE: "Eemalda fail"
+ DROPZONE_RESPONSE_ERROR: "Server vastas {{statusCode}} koodiga."
+ PREMIUM_PRODUCT: "Tasuline"
+ ADMIN_CACHING: "Luba adminiliidese vahemälu kasutamine"
+ ADMIN_CACHING_HELP: "Adminiliidese vahemällu lugemist saab kasutada sõltumata saidi avalikust poolest"
+ DESTINATION_NOT_SPECIFIED: "Sihtkoht on määramata"
+ UPLOAD_ERR_NO_TMP_DIR: "Ajutiste failide kataloog puudub"
+ SESSION_SPLIT: "Sessiooni jagamine"
+ SESSION_SPLIT_HELP: "Iseseisvad eraldatud sessioonid saidi ja lisamoodulite (nagu Admin) vahel"
+ ERROR_FULL_BACKTRACE: "Täieliku infoga viga"
+ ERROR_SIMPLE: "Lihtne viga"
+ ERROR_SYSTEM: "Süsteemi viga"
+ IMAGES_AUTO_FIX_ORIENTATION: "Paranda suund automaatselt"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Paranda pildi suund automaatselt vastavalt EXIF-andmetele"
+ REDIS_SOCKET: "Redis pesa"
+ REDIS_SOCKET_HELP: "Redis pesa"
+ NOT_SET: "Määramata"
+ PERMISSIONS: "Õigused"
+ NEVER_CACHE_TWIG: "Ära kasuta Twigi jaoks kunagi vahemälu"
+ NEVER_CACHE_TWIG_HELP: "Loe vahemällu ainult sisu ning töötle Twigi iga lehe loomisel. Ignoreerib sätet twig_first."
+ ALLOW_WEBSERVER_GZIP: "Luba veebiserveri Gzipi kasutamine"
+ ALLOW_WEBSERVER_GZIP_HELP: "Vaikimisi väljalülitatud. Kui sisse lülitada, siis veebiserveris konfigureeritud Gzip/Deflate pakkimine töötab, aga http ühendust ei suleta enne onShutDown() sündmust, mis põhjustab aeglasema lehe laadimise"
+ OFFLINE_WARNING: "Ühenduse loomine GMP-ga ebaõnnestus"
+ CLEAR_IMAGES_BY_DEFAULT: "Tühjenda vaikimisi piltide vahemälu"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "Vaikimisi tühjendatakse töödeldud piltide puhver kõikide puhvritühjenduste käigus, siin saab selle välja lülitada"
+ CLI_COMPATIBILITY: "CLI ühilduvus"
+ CLI_COMPATIBILITY_HELP: "Tagab, et kasutatakse ainult mitte-volatiilsed puhverdamisvahendid ( fail, redis, memcache, etc.)"
+ REINSTALL_PLUGIN: "Paigalda plugin uuesti"
+ REINSTALL_THEME: "Paigalda kujundus uuesti"
+ REINSTALL_THE: "Paigalda %s uuesti"
+ CONFIRM_REINSTALL: "Kas oled kindel, et soovid %s uuesti paigaldada?"
+ REINSTALLED_SUCCESSFULLY: "%s on uuesti paigaldatud"
+ ERROR_REINSTALLING_THE: "Viga %s uuesti paigaldamisel"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Pakk %s on uuesti paigaldatud"
+ REINSTALLATION_FAILED: "Uuesti paigaldamine ebaõnnestus"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "Installeeritud versioon ei ole uusim redaktsioon. Kui klikid Jätka, eemaldada praeguse versiooni ja installeerid uusima kättesaadava redaktsiooni"
+ TOOLS: "Tööriistad"
+ DIRECT_INSTALL: "Failist paigaldamine"
+ NO_PACKAGE_NAME: "Paketi nime ei ole kindlaks määratud"
+ PACKAGE_EXTRACTION_FAILED: "Paketi lahtipakkimine ebaõnnestus"
+ NOT_VALID_GRAV_PACKAGE: "Tegemist ei ole korrektse Grav-i paketiga"
+ NAME_COULD_NOT_BE_DETERMINED: "Nime ei ole võimalik kindlaks määrata"
+ CANNOT_OVERWRITE_SYMLINKS: "Symlinkide ülekirjutamine ebaõnnestus"
+ ZIP_PACKAGE_NOT_FOUND: "ZIP-paketti ei leitud"
+ GPM_OFFICIAL_ONLY: "Ainult ametlik GPM"
+ GPM_OFFICIAL_ONLY_HELP: "Luba otse installeerida ainult ametlikust GPM repositooriumist."
+ NO_CHILD_TYPE: "Puudub tütre tüüp sellele marsruudile"
+ SORTABLE_PAGES: "Sorteeritavad lehed:"
+ UNSORTABLE_PAGES: "Mittesorteeritavad lehed"
+ ADMIN_SPECIFIC_OVERRIDES: "Admini-spetsiifilised erisused"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Tütarde kuvamisjärjestus"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "Järjestus, milles selle lehe tütred peaks olema kuvatud Admin lisamooduli 'Lehed' vaates"
+ PWD_PLACEHOLDER: "keeruline vähemalt 8 märgi pikkune sõne"
+ PWD_REGEX: "Salasõna Regex"
+ PWD_REGEX_HELP: "Vaikeväärtus: Salasõna peab sisaldama vähemalt ühte numbrit, ühte suur- ja väiketähte ja koosnema vähemalt 8 märgist"
+ USERNAME_PLACEHOLDER: "ainult väiketähed, nt. 'admin'"
+ USERNAME_REGEX: "Kasutajanime Regex"
+ USERNAME_REGEX_HELP: "Vaikimisi: Ainult väikesed tähed, numbrid, kriipsud ja alakriipsud. 3 - 16 märki"
+ CONTENT_PADDING: "Sisu padding"
+ CONTENT_PADDING_HELP: "Lülita sisse või välja sisu padding sisuala ümber suurema ruumi tekitamiseks"
+ ENABLE_AUTO_METADATA: "Auto metadata Exif-st"
+ ENABLE_AUTO_METADATA_HELP: "Genereeri automaatselt metaandmete failid Exif infoga piltidele"
+ 2FA_TITLE: "Kahe etapiline autentimine"
+ 2FA_INSTRUCTIONS: "##### 2-faktoriline autentimine\nSellel kontol on **2FA** sisse lülitatud. Palun kasuta oma **2FA** äppi ja sisesta **6-kohaline kood** sisselogimise lõpuleviimiseks."
+ 2FA_REGEN_HINT: "Salakoodi regenereerimine nõuab, et sa uuendaksid koodi ka oma autentimise äpis"
+ 2FA_LABEL: "Admini ligipääs"
+ 2FA_FAILED: "Vale 2-etapilise autentimise kood, palun proovi uuesti..."
+ 2FA_ENABLED: "2-etapiline autentimine on sees"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "2FA salasõna"
+ 2FA_SECRET_HELP: "Skaneeri see QR kood oma [autentimise äppi](https://learn.getgrav.org/admin-panel/2fa#apps). Samuti on hea idee varundada salakood turvalises kohas, kui sa peaksid vajama äppi uuesti installimist. Tutvu [Grav dokumentatsiooniga](https://learn.getgrav.org/admin-panel/2fa) lisainformatsiooni saamiseks "
+ 2FA_REGENERATE: "Loo uuesti"
+ FORCE_LOWERCASE_URLS: "URL-ides kasutatakse ainult väiketähti"
+ FORCE_LOWERCASE_URLS_HELP: "Vaikimisi paneb Grav URL-idesse kõik väiketähtedega. Kui see välja lülitada, siis saab URL-ides kasutada ka suurtähti"
+ INTL_ENABLED: "Intl mooduli integreerimine"
+ INTL_ENABLED_HELP: "Kasuta Intl PHP moodulit sorteerimiseks UTF8 põhistes kollektsioonides"
+ VIEW_SITE_TIP: "Vaata Saiti"
+ TOOLS_DIRECT_INSTALL_TITLE: "Grav pakkide otse paigaldamine"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "Paigalda pakke ZIP-faili üleslaadimisega"
+ TOOLS_DIRECT_INSTALL_UPLOAD_DESC: "Saad lihtsasti installeerida valideeritud Gravi kujunduse , lisamooduli või isegi Grav uuenduse Zip paketi. Paketti ei pea registreerima GPM-s ja see võimaldab sul lihtsalt minna tagasi eelnevale versioonile või installerida ainult testimiseks."
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "Paigalda pakke kasutades installipakkide URL-e"
+ TOOLS_DIRECT_INSTALL_URL_DESC: "Alternatiivselt, saad lisada täispika URL-i paketi ZIP faili asukohaga ja installeerida sealtkaudu."
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Laadi üles ja paigalda"
+ ROUTE_OVERRIDES: "Marsuutide ülekirjutused"
+ ROUTE_DEFAULT: "Vaikimisi marsruut"
+ ROUTE_CANONICAL: "Kanooniline marsruut"
+ ROUTE_ALIASES: "Marsruudi aliased"
+ OPEN_NEW_TAB: "Ava uuel kaardil"
+ SESSION_INITIALIZE: "Käivita sessioon"
+ STRICT_YAML_COMPAT: "YAML ühilduvus"
+ STRICT_TWIG_COMPAT: "Twig ühilduvus"
+ SCHEDULER: "Ajastamine"
+ SCHEDULER_INSTALL_INSTRUCTIONS: "Paigaldamise juhendid"
+ SCHEDULER_INSTALLED_READY: "Paigaldatud ja valmis"
+ SCHEDULER_CRON_NA: "Cron pole saadaval"
+ SCHEDULER_NOT_ENABLED: "Pole sisse lülitatud"
+ SCHEDULER_SETUP: "Ajastamise seadistamine"
+ SCHEDULER_STATUS: "Ajastaja olek"
+ SCHEDULER_RUNAT: "Käivita"
+ SCHEDULER_OUTPUT: "Väljundfail"
+ SCHEDULER_OUTPUT_TYPE: "Väljundi tüüp"
+ SCHEDULER_EMAIL: "E-post"
+ SECURITY: "Turvalisus"
+ XSS_WHITELIST_PERMISSIONS: "Valge nimekirja õigused"
+ XSS_INVALID_PROTOCOLS_LIST: "Vigane protokollide nimekiri"
+ UPLOADS_SECURITY: "Üleslaadimiste turvalisus"
+ UPLOADS_DANGEROUS_EXTENSIONS: "Ohtlikud faililaiendid"
+ UPLOADS_DANGEROUS_EXTENSIONS_HELP: "Keela nende faililaienditega failide üleslaadimine hoolimata lubatud failitüüpidest"
+ REPORTS: "Aruanded"
+ LOGS: "Logid"
+ BACKUPS_MAX_COUNT: "Maksimaalne varukoopiate arv"
+ BACKUPS_MAX_COUNT_HELP: "0 tähendab piiramatut"
+ BACKUPS_MAX_SPACE: "Maksimaalne ruum varukoopiatele"
+ BACKUPS_MAX_RETENTION_TIME: "Maksimaalne säilitamise aeg"
+ BACKUPS_MAX_RETENTION_TIME_APPEND: "päeva jooksul"
+ BACKUPS_PROFILE_NAME: "Varukoopia nimi"
+ BACKUPS_PROFILE_ROOT_FOLDER: "Juurkaust"
+ BACKUPS_PROFILE_EXCLUDE_PATHS: "Jäta välja asukohad"
+ BACKUPS_PROFILE_EXCLUDE_PATHS_HELP: "Täielikud kaustateed, mida välja jätta. Üks rea kohta."
+ BACKUPS_PROFILE_EXCLUDE_FILES: "Jäta välja failid"
+ BACKUPS_PROFILE_EXCLUDE_FILES_HELP: "Konkreetsed failid või kaustad, mida välja jätta. Üks rea kohta."
+ BACKUPS_PROFILE_SCHEDULE: "Luba ajastatud töö"
+ BACKUPS_PROFILE_SCHEDULE_AT: "Käivita ajastatud töö"
+ COMMAND: "Käsk"
+ EXTRA_ARGUMENTS: "Lisaargumendid"
diff --git a/user/plugins/admin/languages/eu.yaml b/user/plugins/admin/languages/eu.yaml
new file mode 100644
index 0000000..923fa0d
--- /dev/null
+++ b/user/plugins/admin/languages/eu.yaml
@@ -0,0 +1,182 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Beta bertsio bat da hau! Produkzioan erabili ezazu zure ardurapean..."
+ ADMIN_REPORT_ISSUE: "Arazoren bat topatu duzu? Mesedez, horren berri eman GitHub-en."
+ EMAIL_FOOTER: "Grav-ekin eginda - Fitxategi lauzko CMS modernoa"
+ LOGIN_BTN: "Sartu"
+ LOGIN_BTN_FORGOT: "Ahaztu"
+ LOGIN_BTN_RESET: "Berrezarri pasahitza"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Bidali reset egiteko argibideak"
+ LOGIN_BTN_CLEAR: "Garbitu inprimakia"
+ LOGIN_BTN_CREATE_USER: "Sortu erabiltzailea"
+ LOGIN_LOGGED_IN: "Saioa arrakastaz hastea lortu duzu"
+ LOGIN_FAILED: "Akatsa sartzerakoan"
+ LOGGED_OUT: "Saiotik irten zara"
+ RESET_NEW_PASSWORD: "Mesedez sartu pasahitz berri bat …"
+ RESET_LINK_EXPIRED: "Reset egiteko esteka iraungi da, mesedez saiatu berriro"
+ RESET_PASSWORD_RESET: "Pasahitza berrezarri da"
+ RESET_INVALID_LINK: "Reset egiteko esteka baliogabea, mesedez saiatu berriro"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Zure pasahitza berriz ezartzeko argibideak zure helbide elektronikora bidali dira"
+ FORGOT_FAILED_TO_EMAIL: "Akatsa argibideak posta elektroniko bidez bidaltzean, mesedez saiatu berriro beranduago"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Ezinezkoa da pasahitza berriro ezartzea %s-rentzat, ez da helbide elektronikorik ezarri"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "%s erabiltzailea ez da existitzen"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Ezin da pasahitza berrezarri. Gune hau ez dago konfiguratuta mezu elektronikoak bidaltzeko"
+ FORGOT_EMAIL_SUBJECT: "%s Pasahitza Berrezartzeko Eskaera"
+ FORGOT_EMAIL_BODY: "Pazahitza berrezarketa %1$s estimatua,
Eskaera egin da %4$s zure pasahitza berrezartzeko.
Klik egin hemen zure pasahitza berrezartzeko
Edo bestela kopiatu hurrengo URLa zure nabigatzailearen helbide barran
%2$s
Begirunez, %3$s
"
+ MANAGE_PAGES: "Orrialdeak Kudeatu"
+ CONFIGURATION: "Konfigurazioa"
+ PAGES: "Orrialdeak"
+ PLUGINS: "Pluginak"
+ PLUGIN: "Plugina"
+ THEMES: "Itxurak"
+ LOGOUT: "Irten"
+ BACK: "Itzuli"
+ ADD_PAGE: "Gehitu orrialdea"
+ ADD_MODULAR: "Gehitu modulua"
+ MOVE: "Mugitu"
+ DELETE: "Ezabatu"
+ SAVE: "Gorde"
+ NORMAL: "Normala"
+ EXPERT: "Aditua"
+ EXPAND_ALL: "Guztia Zabaldu"
+ COLLAPSE_ALL: "Tolestu dena"
+ ERROR: "Errorea"
+ CLOSE: "Itxi"
+ CANCEL: "Utzi"
+ CONTINUE: "Jarraitu"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Egiaztapena Beharrezkoa da"
+ MODAL_CHANGED_DETECTED_TITLE: "Aldaketak Aurkituta"
+ MODAL_CHANGED_DETECTED_DESC: "Gorde gabeko aldaketak dauzkazu. Ziur al zaude irten nahi duzula?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Egiaztapena Beharrezkoa da"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Ziur al zaude fitxategi hau ezabatu nahi duzula? Ekintza hau ezingo da desegin."
+ ADD_FILTERS: "Iragazkiak gehitu"
+ SEARCH_PAGES: "Orrialdeak Bilatu"
+ VERSION: "Bertsioa"
+ UPDATE_THEME: "Itxura Eguneratu"
+ UPDATE_PLUGIN: "Plugina Eguneratu"
+ AUTHOR: "Egilea"
+ HOMEPAGE: "Hasiera Orrialdea"
+ DEMO: "Demo"
+ BUG_TRACKER: "Akatsen Jarraitzailea"
+ KEYWORDS: "Hitz gakoak"
+ LICENSE: "Lizentzia"
+ DESCRIPTION: "Deskribapena"
+ README: "Irakur nazazu"
+ REMOVE_THEME: "Itxura Ezabatu"
+ INSTALL_THEME: "Itxura instalatu"
+ THEME: "Itxura"
+ BACK_TO_THEMES: "Itxuretara Itzuli"
+ BACK_TO_PLUGINS: "Pluginetara Itzuli"
+ CHECK_FOR_UPDATES: "Begiratu Eguneraketak"
+ ADD: "Gehitu"
+ CLEAR_CACHE: "Garbitu Katxea"
+ CLEAR_CACHE_ALL_CACHE: "Katxe Guztia"
+ CLEAR_CACHE_ASSETS_ONLY: "Asset-ak Bakarrik"
+ CLEAR_CACHE_IMAGES_ONLY: "Irudiak Bakarrik"
+ CLEAR_CACHE_CACHE_ONLY: "Katxea Bakarrik"
+ CLEAR_CACHE_TMP_ONLY: "Tmp Bakarrik"
+ DASHBOARD: "Arbela"
+ UPDATES_AVAILABLE: "Eguneraketak Eskuragarri"
+ DAYS: "Egun"
+ UPDATE: "Eguneratu"
+ BACKUP: "Segurtasun kopia"
+ STATISTICS: "Estatistikak"
+ TODAY: "Gaur"
+ WEEK: "Astea"
+ MONTH: "Hilabetea"
+ LATEST_PAGE_UPDATES: "Eguneratutako Azken Orrialdeak"
+ MAINTENANCE: "Mantenua"
+ UPDATED: "Eguneratuta"
+ MON: "Al."
+ TUE: "Ar."
+ WED: "Az."
+ THU: "Og."
+ FRI: "Ol."
+ SAT: "Lr."
+ SUN: "Ig."
+ COPY: "Kopiatu"
+ EDIT: "Editatu"
+ CREATE: "Sortu"
+ GRAV_ADMIN: "Grav Admin"
+ GRAV_OFFICIAL_PLUGIN: "Grav-en Plugin Ofiziala"
+ GRAV_OFFICIAL_THEME: "Grav-en Itxura Ofiziala"
+ PLUGIN_SYMBOLICALLY_LINKED: "Plugin hau sinbolikoki estekatuta dago. Eguneraketak ezingo dira aurkitu."
+ THEME_SYMBOLICALLY_LINKED: "Plugin hau sinbolikoki estekatuta dago. Eguneraketak ezingo dira aurkitu"
+ REMOVE_PLUGIN: "Plugina Ezabatu"
+ INSTALL_PLUGIN: "Plugina instalatu"
+ AVAILABLE: "Eskuragarri"
+ INSTALLED: "Instalatuta"
+ INSTALL: "Instalatu"
+ ACTIVE_THEME: "Itxura Aktiboa"
+ SWITCHING_TO_DESCRIPTION: "Itxura ezberdin batera aldatuz gero, ezingo da ziurtatu orrialdeen egiturak ondo eutsiko dutenik, potentzialki erroreak eragingo dituzte orrialdeak kargatzen saiatzean."
+ SWITCHING_TO_CONFIRMATION: "Jarraitu eta Itxura aldatu nahi al duzu"
+ CREATE_NEW_USER: "Erabiltzaile Berri Bat Sortu"
+ REMOVE_USER: "Erabiltzailea Ezabatu"
+ ACCESS_DENIED: "Sarbide debekatua"
+ ACCOUNT_NOT_ADMIN: "zure kontuak ez dauka administratzaile baimenik"
+ PHP_INFO: "PHP Info"
+ INSTALLER: "Instalatzaile"
+ AVAILABLE_THEMES: "Eskuragarri Dauden Itxurak"
+ AVAILABLE_PLUGINS: "Eskuragarri Dauden Pluginak"
+ INSTALLED_THEMES: "Instalatutako Itxurak"
+ INSTALLED_PLUGINS: "Instalatutako Pluginak"
+ BROWSE_ERROR_LOGS: "Begiratu Errore Erregistroa"
+ SITE: "Gunea"
+ INFO: "Info"
+ SYSTEM: "Sistema"
+ USER: "Erabiltzailea"
+ ADD_ACCOUNT: "Gehitu Kontua"
+ SWITCH_LANGUAGE: "Aldatu Hizkuntza"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Plugina ondo gaitu da"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Plugina ondo desgaitu da"
+ SUCCESSFULLY_CHANGED_THEME: "Itxura lehenetsia ondo aldatu da"
+ INSTALLATION_FAILED: "Instalazioak huts egin du"
+ INSTALLATION_SUCCESSFUL: "Instalazioa ondo egin da"
+ UNINSTALL_FAILED: "Desinstalazioak huts egin du"
+ UNINSTALL_SUCCESSFUL: "Desinstalazioa ondo egin da"
+ SUCCESSFULLY_SAVED: "Ondo gorde da"
+ SUCCESSFULLY_COPIED: "Ondo kopiatu da"
+ REORDERING_WAS_SUCCESSFUL: "Ordenaren aldaketa ondo egin da"
+ SUCCESSFULLY_DELETED: "Ondo ezabatu da"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Hizkuntza ondo aldatu da"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Ez daukazu bahimen nahikorik zereginerako"
+ CACHE_CLEARED: "Katxea garbituta"
+ METHOD: "Metodoa"
+ ERROR_CLEARING_CACHE: "Errorea katxea garbitzean"
+ AN_ERROR_OCCURRED: "Errore bat gertatu da"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Zure segurtasun kopia deskargatzeko prest dago"
+ DOWNLOAD_BACKUP: "Segurtasun kopia deskargatu"
+ PAGES_FILTERED: "Orrialdeak iragazita"
+ NO_PAGE_FOUND: "Ez da Orrialderik aurkitu"
+ INVALID_PARAMETERS: "Baliogabeko Parametroak"
+ NO_FILES_SENT: "Fitxategirik ez da bidali"
+ EXCEEDED_FILESIZE_LIMIT: "PHP konfigurazio fitxategiaren tamainaren muga gainditu da"
+ UNKNOWN_ERRORS: "Errore ezezagunak"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Grav konfigurazio fitxategiaren tamainaren muga gainditu da"
+ UNSUPPORTED_FILE_TYPE: "Fitxategi mota bateraezina"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Akatsa igotako fitxategia mugitzean"
+ FILE_UPLOADED_SUCCESSFULLY: "Fitxategia ondo igo da"
+ FILE_DELETED: "Fitxategia ezabatuta"
+ FILE_COULD_NOT_BE_DELETED: "Fitxategia ezin da ezabatu"
+ FILE_NOT_FOUND: "Fitxategia ez da aurkitu"
+ NO_FILE_FOUND: "Fitxategirik ez da aurkitu"
+ GRAV_UPDATE_FAILED: "Grav eguneraketak huts egin du"
+ EVERYTHING_UPDATED: "Dena eguneratuta"
+ UPDATES_FAILED: "Eguneraketek huts egin dute"
+ LAST_BACKUP: "Azkenengo babes kopia"
+ FULL_NAME: "Izen osoa"
+ USERNAME: "Erabiltzaile izena"
+ EMAIL: "Helbide Elektronikoa"
+ USERNAME_EMAIL: "Erabiltzaile izena edo helbide elektronikoa"
+ PASSWORD: "Pasahitza"
+ PASSWORD_CONFIRM: "Pasahitza egiaztatu"
+ TITLE: "Izenburua"
+ LANGUAGE: "Hizkuntza"
+ ACCOUNT: "Kontua"
+ EMAIL_VALIDATION_MESSAGE: "Benetako helbide elektroniko bat izan behar da"
+ PASSWORD_VALIDATION_MESSAGE: "Pasahitzak zenbaki bat eta letra maiuskula eta minuskula bat eduki eduki behar du gutxienez, eta 8 edo karaktere gehiago"
+ LANGUAGE_HELP: "Gogoko hizkuntza ezarri"
+ MEDIA: "Media"
+ DEFAULTS: "Lehenetsitakoak"
+ SITE_TITLE: "Gunearen Izenburua"
+ SITE_TITLE_PLACEHOLDER: "Gune osoko izenburua"
diff --git a/user/plugins/admin/languages/fa.yaml b/user/plugins/admin/languages/fa.yaml
new file mode 100644
index 0000000..0a5554e
--- /dev/null
+++ b/user/plugins/admin/languages/fa.yaml
@@ -0,0 +1,731 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "این یک نسخه تکمیل نشده است! استفاده از آن در محصولتان شمارا با خطر مواجه میکند..."
+ ADMIN_REPORT_ISSUE: "یک مشکل پیدا شد؟ لطفاً آن را در گیت هاب گزارش کنید."
+ EMAIL_FOOTER: " استفاده شده از Grav\n - سیستم مدیریت محتوای فایل محور"
+ LOGIN_BTN: "ورود"
+ LOGIN_BTN_FORGOT: "فراموش کرده اید"
+ LOGIN_BTN_RESET: "تنظیم مجدد رمز عبور"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "ارسال دستورالعمل تنظیم مجدد"
+ LOGIN_BTN_CLEAR: "پاک کردن فرم"
+ LOGIN_BTN_CREATE_USER: "ایجاد کاربر"
+ LOGIN_LOGGED_IN: "شما با موفقیت به سیستم وارد شده اید"
+ LOGIN_FAILED: "ورود به سیستم ناموفق بود"
+ LOGGED_OUT: "شما خارج شده اید"
+ RESET_NEW_PASSWORD: "لطفاً رمز عبور جدید را وارد کنید …"
+ RESET_LINK_EXPIRED: "لینک بازتنظیم منقضی شده است لطفا دوباره تلاش کنید"
+ RESET_PASSWORD_RESET: "کلمه عبور مجدد تنظیم شده است"
+ RESET_INVALID_LINK: "از لینک باز تنظیم نامعتبر استفاده شده است. لطفا دوباره تلاش کنید"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "فرامین مربوط به تنظیم مجدد کلمه عبور به ایمیل ( S%) شما ارسال شده است"
+ FORGOT_FAILED_TO_EMAIL: "ارسال فرامین به ایمیل ناموفق بود ، لطفا دوباره تلاش کنید"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "نمی توان رمز عبور را برای %S تنظیم مجدد کرد ، نشانی پست الکترونیکی تنظیم نشده است"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "کاربر با نام کاربری %s موجود نیست"
+ FORGOT_EMAIL_NOT_CONFIGURED: "قادر به تنظیم مجدد رمز عبور نیست. این سایت برای ارسال ایمیل پیکربندی نشده است"
+ FORGOT_EMAIL_SUBJECT: "%s درخواست تنظیم مجدد رمزعبور"
+ FORGOT_EMAIL_BODY: "تنظیم مجدد رمز عبور: \nعزیز %1$s,
\nیک درخواست ایجاد شده توسط %4$s برای تنظیم مجدد رمز عبور شما.
\n\n برای تنظیم مجدد رمز عبور بر روی این کلیک کنید \n \n
\nیا لینک زیر را کپی و در نوار آدرس مرورگر خود قرار دهید و اجرا کنید:
%2$s
\nبا سپاس و احترام, %3$s
"
+ MANAGE_PAGES: "مدیریت صفحات"
+ CONFIGURATION: "پیکربندی"
+ PAGES: "صفحات"
+ PLUGINS: "افزونهها"
+ PLUGIN: "افزونه"
+ THEMES: "پوسته ها"
+ LOGOUT: "خروج"
+ BACK: "بازگشت"
+ NEXT: "بعدی"
+ PREVIOUS: "قبلی"
+ ADD_PAGE: "اضافه کردن صفحه"
+ ADD_MODULAR: "اضافه کردن ماژول"
+ MOVE: "جابجایی"
+ DELETE: "حذف کردن"
+ UNSET: "تعیین نشده"
+ VIEW: "نمایش"
+ SAVE: "ذخيره"
+ NORMAL: "عادی"
+ EXPERT: "حرفه ای"
+ EXPAND_ALL: "بازکردن همه"
+ COLLAPSE_ALL: "بستن همگی"
+ ERROR: "خطا"
+ CLOSE: "بستن"
+ CANCEL: "لغو"
+ CONTINUE: "ادامه"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "نیاز به تائید دارد"
+ MODAL_CHANGED_DETECTED_TITLE: "تغییرات شناسایی شده"
+ MODAL_CHANGED_DETECTED_DESC: "شما تغییراتی دارید که ذخیره نشده اند، آیا مطمئن به ترک کردن ، بدون ذخیره کردن هستید؟?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "نیاز به تائید دارد"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "آیا مطمئن هستید می خواهید این فایل را حذف کنید؟ این عمل قابل بازگشت نیست."
+ ADD_FILTERS: "اضافه کردن فیلتر"
+ SEARCH_PAGES: "جستجو در صفحات"
+ VERSION: "نسخه"
+ WAS_MADE_WITH: "ساخته شده با"
+ BY: "توسط"
+ UPDATE_THEME: "به روز رسانی پوسته"
+ UPDATE_PLUGIN: "به روز رسانی افزونه"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "از این پوسته اکنون در دسترس است"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "از این افزونه اکنون در دسترس است"
+ AUTHOR: "نویسنده"
+ HOMEPAGE: "صفحه اصلی"
+ DEMO: "پیش نمایش"
+ BUG_TRACKER: "پیگیری اشکالات"
+ KEYWORDS: "کلیدواژه ها"
+ LICENSE: "مجوز"
+ DESCRIPTION: "توضیحات"
+ README: "من را بخوان"
+ REMOVE_THEME: "حذف پوسته"
+ INSTALL_THEME: "نصب پوسته"
+ THEME: "پوسته"
+ BACK_TO_THEMES: "بازگشت به پوسته ها"
+ BACK_TO_PLUGINS: "بازگشت به افزونه ها"
+ CHECK_FOR_UPDATES: "بررسی برای به روز رسانی"
+ ADD: "افزودن"
+ CLEAR_CACHE: "پاک کردن حافظه نهان"
+ CLEAR_CACHE_ALL_CACHE: "تمام حافظه نهان"
+ CLEAR_CACHE_ASSETS_ONLY: "تنها دارایی های"
+ CLEAR_CACHE_IMAGES_ONLY: "فقط تصاویر"
+ CLEAR_CACHE_CACHE_ONLY: "فقط حافظه نهان"
+ CLEAR_CACHE_TMP_ONLY: "تنها موقتی ها"
+ DASHBOARD: "پیشخوان"
+ UPDATES_AVAILABLE: "به رو رسانی در دسترس است"
+ DAYS: "روزها"
+ UPDATE: "به روز رسانی"
+ BACKUP: "پشتیبان"
+ BACKUPS: "نسخههای پشتیبان"
+ BACKUP_NOW: "پشتیبان گیریی"
+ BACKUPS_STATS: "آمار پشتیبان"
+ BACKUPS_HISTORY: "سابقه پشتیبان"
+ BACKUPS_PURGE_CONFIG: "پیکربندی پاکسازی نسخههای پشتیبان"
+ BACKUPS_PROFILES: "شناسههای پشتیبان"
+ BACKUPS_COUNT: "تعداد پشتیبانها"
+ BACKUPS_PROFILES_COUNT: "تعداد شناسهها"
+ BACKUPS_TOTAL_SIZE: "فضای استفاده شده"
+ BACKUPS_NEWEST: "جدیدترین نسخه پشتیبان"
+ BACKUPS_OLDEST: "قدیمیترین نسخه پشتیبان"
+ BACKUPS_PURGE: "پاکسازی"
+ BACKUPS_NOT_GENERATED: "هیچ نسخه پشتیبانی هنوز تولید نشده..."
+ BACKUPS_PURGE_NUMBER: "درحال استفاده %s از %s اسلات پشتیبان"
+ BACKUPS_PURGE_TIME: "%s روز از پشتیبانگیری گذشته است"
+ BACKUPS_PURGE_SPACE: "درحال استفاده %s از %s"
+ BACKUP_DELETED: "پشتیبان با موفقیت حذف شد"
+ BACKUP_NOT_FOUND: "نسخه پشتیبان یافت نشد"
+ BACKUP_DATE: "تاریخ نسخه پشتیبان"
+ STATISTICS: "آمارها"
+ TODAY: "امروز"
+ WEEK: "هفته"
+ MONTH: "ماه"
+ LATEST_PAGE_UPDATES: "آخرین به روز رسانی صفحه"
+ MAINTENANCE: "تعمیر و نگهداری"
+ UPDATED: "به روز رسانی شد"
+ MON: "دوشنبه"
+ TUE: "سه شنبه"
+ WED: "چهارشنبه"
+ THU: "پنج شنبه"
+ FRI: "جمعه"
+ SAT: "شنبه"
+ SUN: "یک شنبه"
+ COPY: "رونوشت"
+ EDIT: "ویرایش"
+ CREATE: "ایجاد"
+ GRAV_ADMIN: "مدیر Grav"
+ GRAV_OFFICIAL_PLUGIN: "پلاگین رسمی Grav"
+ GRAV_OFFICIAL_THEME: "تم رسمی Grav"
+ PLUGIN_SYMBOLICALLY_LINKED: "این افزونه به صورت نمادین متصل شده است. به روز رسانی ها قابل تشخیص نخواهند بود."
+ THEME_SYMBOLICALLY_LINKED: "این پوسته به صورت نمادین متصل شده است. به روز رسانی ها قابل تشخیص نخواهند بود"
+ REMOVE_PLUGIN: "حذف افزونه"
+ INSTALL_PLUGIN: "نصب افزونه"
+ AVAILABLE: "در دسترس"
+ INSTALLED: "نصب شده"
+ INSTALL: "نصب کردن"
+ ACTIVE_THEME: "پوسته ی فعال"
+ SWITCHING_TO: "تعویض به"
+ SWITCHING_TO_DESCRIPTION: "با تغییر به یک پوسته متفاوت ، هیچ ضمانتی برای پشتیبانی از تمام طرح بندی های صفحات وجود ندارد ،موقعیکه برای بارگذاری صفحات گفته شده تلاش میکنید امکان رویداد دادن خطاهایی وجود دارد."
+ SWITCHING_TO_CONFIRMATION: "آیا می خواهید ادامه دهید و پوسته را تعویض کنید ؟"
+ CREATE_NEW_USER: "ساخت کاربر جدید"
+ REMOVE_USER: "حذف کاربر"
+ ACCESS_DENIED: "امکان دسترسی وجود ندارد"
+ ACCOUNT_NOT_ADMIN: "حساب شما مجوزهای مدیر سیستم را ندارد"
+ PHP_INFO: "اطلاعات PHP"
+ INSTALLER: "نصب کننده"
+ AVAILABLE_THEMES: "پوسته های در دسترس"
+ AVAILABLE_PLUGINS: "افزونه های در دسترس"
+ INSTALLED_THEMES: "پوسته های نصب شده"
+ INSTALLED_PLUGINS: "افزونه های نصب شده"
+ BROWSE_ERROR_LOGS: "بازکردن گزارش خطاهای ثبت شده"
+ SITE: "وب سایت"
+ INFO: "اطلاعات"
+ SYSTEM: "سامانه"
+ USER: "کاربر"
+ ADD_ACCOUNT: "افزودن حساب کاربری"
+ SWITCH_LANGUAGE: "تعویض زبان"
+ SUCCESSFULLY_ENABLED_PLUGIN: "افزونه با موفقیت فعال شد"
+ SUCCESSFULLY_DISABLED_PLUGIN: "افزونه با موفقیت غیرفعال شد"
+ SUCCESSFULLY_CHANGED_THEME: "پوسته پیش فرض با موفقیت تغییر یافت"
+ INSTALLATION_FAILED: "عمل نصب ناموفق بود"
+ INSTALLATION_SUCCESSFUL: "نصب و راه اندازی موفقیت آمیز بود"
+ UNINSTALL_FAILED: "عمل حذف باموفقیت همراه نبود"
+ UNINSTALL_SUCCESSFUL: "عمل حذف با موفقیت همراه بود"
+ SUCCESSFULLY_SAVED: "با موفقیت ذخیره شد"
+ SUCCESSFULLY_COPIED: "با موفقیت نسخه برداری شد"
+ REORDERING_WAS_SUCCESSFUL: "دوباره مرتب سازی با موفقیت انجام شد"
+ SUCCESSFULLY_DELETED: "با موفقیت حذف شد"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "زبان با موفقیت تعویض شد"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "شما سطح دسترسی کافی برای انجام کارندارید"
+ CACHE_CLEARED: "حافظه نهان /کش پاک شد"
+ METHOD: "شیوه"
+ ERROR_CLEARING_CACHE: "خطا در پاکسازی حافظه نهان"
+ AN_ERROR_OCCURRED: "یک خطا روی داده است"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "نسخه پشتیبان برای دانلود آماده است"
+ DOWNLOAD_BACKUP: "دانلود نسخه پشتیبان"
+ PAGES_FILTERED: "صفحه های فیلتر شده"
+ NO_PAGE_FOUND: "هیچ صفحه ای فیلتر نشد"
+ INVALID_PARAMETERS: "پارامترهای نامعتبر"
+ NO_FILES_SENT: "پروند های ارسال نشد"
+ EXCEEDED_FILESIZE_LIMIT: "از محدوده اندازه فایل در پیکربندی PHP فراتر رفته است"
+ UNKNOWN_ERRORS: "خطاهای ناشناخته"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "از محدوده اندازه فایل در پیکربندی گراو فراتر رفته است"
+ UNSUPPORTED_FILE_TYPE: "نوع فایل پشتیبانی نشده"
+ FAILED_TO_MOVE_UPLOADED_FILE: "عدم موفقیت در انتقال فایل آپلود شده."
+ FILE_UPLOADED_SUCCESSFULLY: "فایل با موفقیت آپلود شد"
+ FILE_DELETED: "پروند حذف شد"
+ FILE_COULD_NOT_BE_DELETED: "پرونده نمی تواند حذف شود"
+ FILE_NOT_FOUND: "پرونده پیدا نشد"
+ NO_FILE_FOUND: "هیچ فایلی یافت نشد"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "به روزرسانی Grav با موفقیت انجام شد"
+ GRAV_UPDATE_FAILED: "به روز رسانی Grav ناموفق بود"
+ EVERYTHING_UPDATED: "همه چیز به روز شد"
+ UPDATES_FAILED: "بروز رسانیها موفق نبود"
+ AVATAR_BY: "آواتار توسط"
+ AVATAR_UPLOAD_OWN: "یا مال خود را بارگذاری کنید..."
+ LAST_BACKUP: "آخرین پشتیبان گیری"
+ FULL_NAME: "نام کامل"
+ USERNAME: "نام کاربری"
+ EMAIL: "ایمیل"
+ USERNAME_EMAIL: "نام کاربری یا ایمیل"
+ PASSWORD: "گذرواژه"
+ PASSWORD_CONFIRM: "تأیید گذرواژه"
+ TITLE: "عنوان"
+ LANGUAGE: "زبان"
+ ACCOUNT: "حساب کاربری"
+ EMAIL_VALIDATION_MESSAGE: "باید یک آدرس ایمیل معتبر باشد"
+ PASSWORD_VALIDATION_MESSAGE: "رمز عبور باید حداقل یک عدد و یک حرف بزرگ و یک حرف کوچک و حداقل 8 کاراکتر داشته باشد"
+ LANGUAGE_HELP: "تنظیم زبان مورد علاقه"
+ MEDIA: "رسانه"
+ DEFAULTS: "پیش فرض ها"
+ SITE_TITLE: "عنوان سایت"
+ SITE_TITLE_PLACEHOLDER: "عنوان گسترده سایت"
+ SITE_TITLE_HELP: "عنوان پیش فرض برای سایت خود، معمولا در قالبها استفاده می شود"
+ SITE_DEFAULT_LANG: "زبان پیشفرض"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "زبان پیشفرض جهت استفاده در تگ قالب"
+ SITE_DEFAULT_LANG_HELP: "زبان پیشفرض جهت استفاده در تگ قالب"
+ DEFAULT_AUTHOR: "نگارنده پیش فرض"
+ DEFAULT_AUTHOR_HELP: "نام پیشفرض نویسنده ٬ معمولاً در قالب یا محتوای برخی صفحات استفاده میشود"
+ DEFAULT_EMAIL: "ایمیل پیش فرض"
+ DEFAULT_EMAIL_HELP: "ایمیل پیشفرض جهت استفاده در قالب یا صفحات وبسایت"
+ TAXONOMY_TYPES: "انواع طبقه بندی"
+ TAXONOMY_TYPES_HELP: "در صورت تمایل به استفاده از انواع طبقه بندی در صفحات وبسایت ٬ تعیین کنید"
+ PAGE_SUMMARY: "صفحه خلاصه"
+ ENABLED: "فعال"
+ ENABLED_HELP: "فعال کردن صفحه خلاصه (صفحه خلاصه، همان محتوای صفحه را باز می گرداند)"
+ 'YES': "بله"
+ 'NO': "خیر"
+ SUMMARY_SIZE: "اندازه گزارش"
+ SUMMARY_SIZE_HELP: "میزان کاراکترهای مورد استفاده از یک صفحه برای استفاده بعنوان محتوای خلاصه"
+ FORMAT: "قالب بندی"
+ FORMAT_HELP: "کوتاه= از اولین رویداد جداکننده یا اندازه استفاده کنید.; بلند= خلاصه جداکننده درنظر گرفته نخواهد شد"
+ SHORT: "کوتاه"
+ LONG: "بلند"
+ DELIMITER: "جداکننده"
+ DELIMITER_HELP: "جداکننده خلاصه (پیشفرض '===')"
+ METADATA: "ابرداده"
+ METADATA_HELP: "به صورت پیش فرض ابر داده ها در هر صفحه نمایش داده خواهند شد مگر این در صفحه تغییریابند"
+ NAME: "نام"
+ CONTENT: "محتوا"
+ SIZE: "اندازه"
+ ACTION: "عملیات"
+ REDIRECTS_AND_ROUTES: "تغییر جهت ها & مسیرها"
+ CUSTOM_REDIRECTS: "تغییر جهت های سفارشی"
+ CUSTOM_REDIRECTS_HELP: "تغییر مسیر و تغییر جهت به صفحات دیگر. جایگذاری متن معتبر سازی درست است"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/شما/لقب"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/ شما/تغییر مسیر"
+ CUSTOM_ROUTES: "مسیرهای سفارشی"
+ CUSTOM_ROUTES_HELP: "مسیر به نام مستعار صفحات دیگر. جایگذاری متن معتبر سازی استاندارد صحیح است"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/شما/نام مستعار"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/ شما/مسیر"
+ FILE_STREAMS: "جریانهای پرونده"
+ DEFAULT: "پیشفرض"
+ PAGE_MEDIA: "صفحه رسانه"
+ OPTIONS: "موارد"
+ PUBLISHED: "منتشرشده"
+ PUBLISHED_HELP: "پیشفرض ، یک صفحه انتشار می یابد ، مگر اینکه شما صراحتا گزینه انتشار را false یا زمان انتشاردر آینده یا تاریخ عدم انتشار را در تاریخ قبل از الان ست کرده باشید"
+ DATE: "تاریخ"
+ DATE_HELP: "متغیر تاریخ به شما این اجازه را میده که مشخصا یک تاریخ را برای این صفحه تنظیم کنید."
+ PUBLISHED_DATE: "تاریخ انتشار"
+ PUBLISHED_DATE_HELP: "میتواند یک زمان خودکار برای انجام تراکنش انتشار فراهم کنید."
+ UNPUBLISHED_DATE: "تاریخ از حالت انتشار خارج شده"
+ UNPUBLISHED_DATE_HELP: "می توانید یک زمان را برای عدم انتشار خودکار تعیین کنید."
+ ROBOTS: "روبوتها"
+ TAXONOMIES: "رده بندی ها"
+ TAXONOMY: "رده بندی"
+ ADVANCED: "پیشرفته"
+ SETTINGS: "تنظيمات"
+ FOLDER_NUMERIC_PREFIX: "پیشوند عددی پوشه"
+ FOLDER_NUMERIC_PREFIX_HELP: "پیشوند عددی که مرتب سازی دستی را فراهم و مفهوم قابلیت نمایش داشتن را میرساند"
+ FOLDER_NAME: "نام پوشه"
+ FOLDER_NAME_HELP: "نام پوشه ایی که برای این صفحه در سیستم مدیریت فایل ذخیره میشود"
+ PARENT: "والد"
+ DEFAULT_OPTION_ROOT: "- ریشه -"
+ DEFAULT_OPTION_SELECT: "- انتخاب -"
+ DISPLAY_TEMPLATE: "نمایش قالب"
+ DISPLAY_TEMPLATE_HELP: "نوع صفحه که دلالت دارد بر اینکه کدام قالب Twig صفحه را رندر میکند"
+ BODY_CLASSES: "بدنه ی کلاسها"
+ ORDERING: "مرتب سازی"
+ PAGE_ORDER: "ترتیب صفحه"
+ OVERRIDES: "ملغی کردن"
+ MENU: "فهرست"
+ MENU_HELP: "متن مورد استفاده در فهرست. اگر تعیین نشود از عنوان استفاده خواهد شد."
+ SLUG: "نام یکتا"
+ SLUG_HELP: "به کمک متغییر نام یکتا می توانید بخشی از URL که مربوط به صفحه می باشد را تعیین کنید"
+ SLUG_VALIDATE_MESSAGE: "در نام یکتا فقط می توانید از کاراکتر های عدد ، حروف کوچک و خط تیره استفاده کنید"
+ PROCESS: "روند"
+ PROCESS_HELP: "تعیین کنید صفحات چگونه پردازش شوند. می توانید علاوه به تنظیم کلی، این تنظیمات را برای هر صفحه بصورت جداگانه نیز انجام دهید"
+ DEFAULT_CHILD_TYPE: "نوع پیشفرض فرزند (Child)"
+ USE_GLOBAL: "استفاده جهانی"
+ ROUTABLE: "قابل مسیردهی"
+ ROUTABLE_HELP: "اگر این صفحه توسط یک URL قابل دسترسی باشد"
+ CACHING: "کش کردن"
+ VISIBLE: "قابل مشاهده"
+ VISIBLE_HELP: "تعیین می کند که یک صفحه در فهرست نمایش داده شود یا خیر."
+ DISABLED: "غیرفعال"
+ ITEMS: "موارد"
+ ORDER_BY: "مرتب کردن با"
+ ORDER: "ترتیب"
+ FOLDER: "پوشه"
+ ASCENDING: "صعودی"
+ DESCENDING: "نزولی"
+ ADD_MODULAR_CONTENT: "اضافه کردن محتوای مدولار"
+ PAGE_TITLE: "عنوان صفحه"
+ PAGE_TITLE_HELP: "عنوان صفحه"
+ PAGE: "صفحه"
+ MODULAR_TEMPLATE: "قالب مدولار"
+ FRONTMATTER: "سربرگ"
+ FILENAME: "نام پرونده"
+ PARENT_PAGE: "صفحه مرجع"
+ HOME_PAGE: "صفحه اصلی"
+ HOME_PAGE_HELP: "صفحه ای که گراو از آن بعنوان صفحه خانگی استفاده می کند"
+ DEFAULT_THEME: "پوسته پیش فرض"
+ DEFAULT_THEME_HELP: "پوسته پیشفرض گراو را تعیین کنید (Antimatter پوسته پیشفرض است)"
+ TIMEZONE: "منظقه زمانی"
+ TIMEZONE_HELP: "نادیده گرفتن منطقه زمانی پیش فرض سرور"
+ SHORT_DATE_FORMAT: "فرمت کوتاه نمایش تاریخ"
+ SHORT_DATE_FORMAT_HELP: "تنظیم فرمت کوتاه نمایش تاریخ که میتواند برای پوسته ها مورد استفاده قرار گیرد"
+ LONG_DATE_FORMAT: "فرمت طولانی نمایش تاریخ"
+ LONG_DATE_FORMAT_HELP: "تنظیم فرمت طولانی نمایش تاریخ که میتواند برای پوسته ها مورد استفاده قرار گیرد"
+ DEFAULT_ORDERING: "ترتیب بندی پیشفرض"
+ DEFAULT_ORDERING_HELP: "با استفاده از این امکان صفحات در یک لیست ارائه خواهد شد در غیر این صورت لغو میشود"
+ DEFAULT_ORDERING_DEFAULT: "پیش فرض - بر اساس نام پوشه"
+ DEFAULT_ORDERING_FOLDER: "پوشه - بر اساس نام پوشه با پیشوند کمتر"
+ DEFAULT_ORDERING_TITLE: "عنوان - براساس عنوان زمینه در سرصفحه"
+ DEFAULT_ORDERING_DATE: "تاریخ - بر اساس تاریخ زمینه در سر صفحه"
+ DEFAULT_ORDER_DIRECTION: "جهت سفارش پیش فرض"
+ DEFAULT_ORDER_DIRECTION_HELP: "جهت صفحات در لیست"
+ DEFAULT_PAGE_COUNT: "تعداد دفعات مشاهده صفحه پیش فرض"
+ DEFAULT_PAGE_COUNT_HELP: "مقدار پیشفرض بیشترین تعداد صفحات دریک لیست"
+ DATE_BASED_PUBLISHING: "انتشار برا اساس تاریخ"
+ DATE_BASED_PUBLISHING_HELP: "به طور خودکار انتشاریا عدم انشتار پست ها به تاریخ آنها وابسته باشد"
+ EVENTS: "رویدادها"
+ EVENTS_HELP: "فعال یا غیر فعال کردن رویدادهای مشخص. غیرفعال کردن اینها میتواند افزونه ها را جدا کند"
+ REDIRECT_DEFAULT_ROUTE: "مسیر پیشفرض برای تعیر جهت"
+ REDIRECT_DEFAULT_ROUTE_HELP: "به طور خودکار به مسیر پیش فرض صفحه تغییر جهت دهد"
+ LANGUAGES: "زبانها"
+ SUPPORTED: "پشتیبانی شده"
+ SUPPORTED_HELP: "حرف کاما برای جدا سازی کد زبانهای که دو حرفی هستند استفاده میشود. مانند (en,fr,de)"
+ TRANSLATIONS_ENABLED: "ترجمه کردن فعال شد"
+ TRANSLATIONS_ENABLED_HELP: "پشتیبانی ترجمه در Grav و افزونه ها و الحاقات"
+ TRANSLATIONS_FALLBACK: "ترجمه مجدد"
+ TRANSLATIONS_FALLBACK_HELP: "ترجمه مجدد از طریق زبانهای پشتیبانی شده اگر زبان فعال وجود نداشت"
+ ACTIVE_LANGUAGE_IN_SESSION: "فعال کردن زبان در Session"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "ذخیره کردن زبان فعال در جلسه"
+ HTTP_HEADERS: "هدر های HTTP"
+ EXPIRES: "منقضی شد"
+ EXPIRES_HELP: "تنظیم کردن تاریخ انقضا. مقدار به ثانیه است."
+ CACHE_CONTROL: "کنترل حافظه موقت HTTP"
+ CACHE_CONTROL_HELP: "تنظیم مقدار معتبر برای کنترل کش مانند `no-cache, no-store, must-revalidate`"
+ LAST_MODIFIED: "آخرین ویرایش"
+ LAST_MODIFIED_HELP: "اخرین هدر ویرایش شده ، که میتواند کش کردن در مرورگر و استفاده از پراکسی را بهینه کند را تنظیم کنید"
+ ETAG: "ETag"
+ ETAG_HELP: "Etag هدر را برای کمک به تعیین هویت کردن ، زمانی که یک صفحه ویرایش میشود تنظیم کنید"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "تنظیم کردن هدر Vary: accept encoding به کش کردن CDN و پراکسی کمک میکند"
+ MARKDOWN: "MarkDown"
+ MARKDOWN_EXTRA_HELP: "فعال کردن پشتیبانی پیشفرض برای MarkDown\n- https://michelf.ca/projects/php-markdown/extra"
+ AUTO_LINE_BREAKS: "به طور خودکار خط میشکند"
+ AUTO_LINE_BREAKS_HELP: "فعال سازی پشتیبانی زا شکستن خط خودکار در MarkDown"
+ AUTO_URL_LINKS: "ارتباط دادن خودکار آدرسهای URL"
+ AUTO_URL_LINKS_HELP: "فعال کردن تبدیل خودکار URL ها به لینک های HTML"
+ ESCAPE_MARKUP: "رها سازی نشانه گذاری"
+ ESCAPE_MARKUP_HELP: "رهاسازی نشانه گذاری تگها درون عناصر HTML"
+ CACHING_HELP: "انتخاب Y/N به صورت سراسری برای فعال/غیرفعال کردن عمل کشینگ در Grav"
+ CACHE_CHECK_METHOD: "روش بررسی کش"
+ CACHE_CHECK_METHOD_HELP: "انتخاب روشی که با آن Grav چک میکند که آیا فایل های صفحات اصلاح شده اند."
+ CACHE_DRIVER: "راه انداز کش"
+ CACHE_DRIVER_HELP: "انتخاب اینکه Grav از کدام کش درایور بهتر است استفاده کند. 'َAuto Detect' این گزینه بهترین گزینه را برای شما پیدا میکند"
+ CACHE_PREFIX: "پیشوند کش"
+ CACHE_PREFIX_HELP: "یک شناسه برای بخش Grav key. مادامی که نمیدانید چه کار میخواهید انجام دهید تغییر ندهید."
+ CACHE_PREFIX_PLACEHOLDER: "برگرفته شده از URL پایه (جایگزین شده با وارد کردن رشته متنی تصادفی)"
+ CACHE_PURGE_JOB: "کار پاکسازی برنامهریزی شده را اجرا کنید"
+ CACHE_PURGE: "پاکسازی حافظهپنهان قدیمی"
+ LIFETIME: "عمر مفید"
+ LIFETIME_HELP: "زمان عمر مفید کش را به ثانیه تعیین میکند. مقدار 0 برابر با بی نهایت است"
+ GZIP_COMPRESSION: "فشرده سازی به صورت Gzip"
+ GZIP_COMPRESSION_HELP: "فعال کردن فشرده سازی Gzip برای صفحه Grav به منظور افزایش کارایی."
+ TWIG_TEMPLATING: "قالب سازی Twig"
+ TWIG_CACHING: "کش کردن Twig"
+ TWIG_CACHING_HELP: "کنترل مکانیزم Twig کشینگ. برای بهترین کارایی این را فعال کنید."
+ TWIG_DEBUG: "اشکال زدایی Twig"
+ TWIG_DEBUG_HELP: "اجازه گزینه ی بارگذاری نکردن ، اکستنشن اشکال زدایی Twig را میدهد"
+ DETECT_CHANGES: "تشخیص تغییرات"
+ DETECT_CHANGES_HELP: "به صورت خودکار Twig کش خود را در صورتی کخ هرگونه تغییر در پوسته اش شناسایی کند باز گردآوری میکند"
+ AUTOESCAPE_VARIABLES: "متغیرهای Autoescape"
+ AUTOESCAPE_VARIABLES_HELP: "تمام متغییر ها Autoescapes شوند. این کار به احتمال زیاد سایت شما را خراب خواهد کرد"
+ ASSETS: "اموال"
+ CSS_PIPELINE: "CSS pipeline"
+ CSS_PIPELINE_HELP: "CSS pipeline یکی سازو متحد کردن چند منبع به یک فایل CSS است"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "مشمولات در CSS pipeline"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "آدرس خارجی گاهی فایل مرتبط به مراجع دارد و نباید pipeline شود"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "اولین CSS pipeline ارائه شده"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "ارائه CSS pipeline قبل از هر منبع CSS دیگر که شامل نمیشود "
+ CSS_MINIFY: "فشرده سازی CSS"
+ CSS_MINIFY_HELP: "کوچک کردن CSS در pipelining"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "فشرده سازی اجباری CSS برای ویندوز"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "فشرده سازی اجباری برای پلاتفرم های ویندوزی. با توجه به ThreadStackSize بصورت پیشفرض غیر فعال است"
+ CSS_REWRITE: "بازنویس CSS"
+ CSS_REWRITE_HELP: "بازنویسی هر آدرس مرتبط CSS در حین pipelining"
+ JAVASCRIPT_PIPELINE: "pipelining جاوا اسکریپت"
+ JAVASCRIPT_PIPELINE_HELP: "JS pipeline یکی ساز و متحد کننده چند منبع به یک فایل JS است"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "مشمولات در JS pipeline"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "آدرس خارجی گاهی فایل مرتبط به مراجع دارد و نباید pipeline شود"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "در ابتدا ارائه JS pipeline"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "ارائه JS pipeline قبل از هر منبع JS دیگر که آن را شامل نمیشود "
+ JAVASCRIPT_MINIFY: "فشرده سازی جاوا اسکریپت"
+ JAVASCRIPT_MINIFY_HELP: "کوچک کردن JS در حین pipelining"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "فعال کردن برچسب زمانی در دارایی"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "فعالسازی برچسب زمانی دارایی"
+ COLLECTIONS: "مجموعه"
+ ERROR_HANDLER: "کنترلگر خطا"
+ DISPLAY_ERRORS: "نمایش خطاها"
+ DISPLAY_ERRORS_HELP: "نمایش استایل صفحه ردیابی کامل خطا"
+ LOG_ERRORS: "گزارش خطاها"
+ LOG_ERRORS_HELP: "گزارش خطاها در پوشه گزارشها"
+ LOG_HANDLER: "سیستم کنترل کننده رخدادها"
+ LOG_HANDLER_HELP: "محل خروجی رخدادهها"
+ SYSLOG_FACILITY: "امکانات syslog"
+ SYSLOG_FACILITY_HELP: "امکانات Syslog برای خروجی"
+ DEBUGGER: "اشکال زدا"
+ DEBUGGER_HELP: "فعال کردن اشکال زدای Grav و تنظیمات زیر"
+ DEBUG_TWIG: "اشکال زدایی Twig"
+ DEBUG_TWIG_HELP: "فعال کردن اشکال زدایی از پوسته های Twig"
+ SHUTDOWN_CLOSE_CONNECTION: "خاموش کردن ارتباط نزدیک"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "قبل از فراخوانی onShutdown() اتصال را ببندید. غیرفعال برای حالت خطا زادیی"
+ DEFAULT_IMAGE_QUALITY: "کیفیت پیش فرض تصویر"
+ DEFAULT_IMAGE_QUALITY_HELP: "کیفیت تصویر پیش فرض برای استفاده در هنگام باز نمونه گيری یا ذخیره تصاویر (85%)"
+ CACHE_ALL: "ذخیره تمام تصاویر در حافظه کش"
+ CACHE_ALL_HELP: "اجرای تمام تصاویر توسط سیستم کش Grav حتی اگر هیچ رسانه ای دستکاری نشده باشد"
+ IMAGES_DEBUG: "علامت اشکال زدایی تصویر"
+ IMAGES_DEBUG_HELP: "نمایش یک پوشش بر روی تصاویر هنگام کار با رتینا، که عمق پیکسل تصاویر را نشان دهد برای مثال"
+ UPLOAD_LIMIT: "محدودیت آپلود فایل"
+ UPLOAD_LIMIT_HELP: "تنظیم حداکثر مقدار آپلود به بایت (0 نامحدود است)"
+ ENABLE_MEDIA_TIMESTAMP: "فعالسازی برچسب زمانی روی رسانه"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "افزودن برچسب زمانی بر اساس آخرین تاریخ اصلاح برای هر یک از آیتم های رسانه"
+ SESSION: "جلسه"
+ SESSION_ENABLED_HELP: "فعال کردن پشتیبانی از جلسه در GRAV"
+ TIMEOUT: "اتمام مهلت"
+ TIMEOUT_HELP: "تنظیم مهلت جلسه به ثانیه"
+ SESSION_NAME_HELP: "یک شناسه به نام کوکی جلسه استفاده میشود"
+ SESSION_UNIQUENESS: "رشتههای منحصر به فرد"
+ ABSOLUTE_URLS: "URL های مطلق"
+ ABSOLUTE_URLS_HELP: "Url مطلق یا نسبی برای 'base_url'"
+ PARAMETER_SEPARATOR: "پارامتر جدا کننده"
+ PARAMETER_SEPARATOR_HELP: "جدا کننده برای پارامتر های تایید شده، که می توان آن را برای آپاچی بر روی ویندوز تغییر داد"
+ TASK_COMPLETED: "کار تمام شد"
+ EVERYTHING_UP_TO_DATE: "همه چیز بهروز است"
+ UPDATES_ARE_AVAILABLE: "به روز رسانی ها در دسترس هستند"
+ IS_AVAILABLE_FOR_UPDATE: "برای به روز رسانی در دسترس است"
+ IS_NOW_AVAILABLE: "در حال حاضر در دسترس است"
+ CURRENT: "فعلی"
+ UPDATE_GRAV_NOW: "همین حالا Grav را بروزرسانی کنید"
+ GRAV_SYMBOLICALLY_LINKED: "Grav به طور نمادین متصل شده است. ارتقا در دسترس نخواهد بود"
+ UPDATING_PLEASE_WAIT: "بروزرسانی.... لطفا شکیبا باشید. در حال دانلود کردن"
+ OF_THIS: "از این"
+ OF_YOUR: "از شما"
+ HAVE_AN_UPDATE_AVAILABLE: "یک بروز رسانی دردسترس دارد"
+ SAVE_AS: "ذخیره بعنوان"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "ایا اطمینان دارید که میخواهید این صفحه به همراه تمام زیر صفحاتش را حذف کنید؟ اگر صفحه در زبانهای دیگر ترجمه شده باشد ،ترجمه های آن ها حفظ نخواهند شد و باید به صورت جداگانه حذف شوند. درغیر اینصورت پوشه صفحه به همراه همه زیر صفحه های آن را نیز حذف خواهد شد. این عملیات نمیتواند ناتمام بماند."
+ AND: "و"
+ UPDATE_AVAILABLE: "بروزرسانی در دستر است"
+ METADATA_KEY: "کلید( کلمه کلیدی)"
+ METADATA_VALUE: "مقدار (مثله 'وبلاگ', 'Grav')"
+ USERNAME_HELP: "نام کاربری باید بین 3 و 16 حرف باشد، که میتواند شامل حروف کوچک ، اعداد ، خط زیر، خط تیره باشد\nاستفاده از حروف بزرگ ،فاصله ، و حروف خاص مجاز نمی باشد"
+ FULLY_UPDATED: "به طور کامل به روز رسانی شد"
+ SAVE_LOCATION: "ذخیره مکان"
+ PAGE_FILE: "قالب صفحه"
+ PAGE_FILE_HELP: "نام فایل صفحه پوسته ،و نمایش پوسته پیش فرض برای این صفحه"
+ NO_USER_ACCOUNTS: "حساب های کاربری پیدا نشد ، لطفا ابتدا یکی بسازید..."
+ REDIRECT_TRAILING_SLASH: "اسلش «\\» تغییر مسیر"
+ REDIRECT_TRAILING_SLASH_HELP: "بجای استفاده از اسلش «/» در آدرس از تغییر مسیر ۳۰۱ استفاده شود."
+ DEFAULT_DATE_FORMAT: "فرمت تاریخ صفحه"
+ DEFAULT_DATE_FORMAT_HELP: "نوع نمایش تاریخ صفحه توسط Grav. به صورت پیش فرض Grav قصد دارد نوع نمایش تاریخ را پیشنهاد کند ، هرچند شما میتوانید یک نوع نمایش تاریخ با استفاده از سینتکس تاریخ PHP مشخص کنید (مانند: Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "حدس خودکار"
+ IGNORE_FILES: "نادیده گرفتن فایل ها"
+ IGNORE_FILES_HELP: "فایل های خاصی که هنگام پردازش صفحات باید از آنها چشم پوشی کرد"
+ IGNORE_FOLDERS: "نایده گرفتن پوشه"
+ IGNORE_FOLDERS_HELP: "پوشه های خاصی که هنگام پردازش صفحات باید از آنها چشم پوشی کرد"
+ HIDE_EMPTY_FOLDERS: "پنهان کردن پوشههای خالی"
+ HTTP_ACCEPT_LANGUAGE: "تنظیم زبان از مرورگر"
+ HTTP_ACCEPT_LANGUAGE_HELP: "شما می توانید تنظیم زبان را مبتنی بر 'http_accept_language' برچسب سربرگ در مرورگر انتخاب کنید"
+ OVERRIDE_LOCALE: "نادیده گرفتن محلی"
+ OVERRIDE_LOCALE_HELP: "نادیده گرفتن تنظیمات محلی در PHP بر اساس زبان فعلی"
+ REDIRECT: "تغییر مسیر صفحه"
+ REDIRECT_HELP: "یک مسیر صفحه و یا یک آدرس خارجی برای تغییر مسیر این صفحه انتخاب کنید.به عنوان مثال `/some/route`و یا 'http://somesite.com'"
+ PLUGIN_STATUS: "وضعیت افزونه"
+ INCLUDE_DEFAULT_LANG: "شامل زبان پیش فرض"
+ INCLUDE_DEFAULT_LANG_HELP: "این گزینه نام زبان پیشفرض را به ابتدای تمام آدرس های صفحات آن زبان اصافه می کند. مثال: `/en/blog/my-post`"
+ ALLOW_URL_TAXONOMY_FILTERS: "فیلترهای طبقه بندی URL"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "مجموعه ها بر اساس صفحه به شما اجازه فیلتر میدهد `/طیقه بندی : ارزش`."
+ REDIRECT_DEFAULT_CODE: "کد تغییر مسیر پیش فرض"
+ REDIRECT_DEFAULT_CODE_HELP: "از کد وضعیت HTTP برای تغییر مسیر استفاده کنید"
+ IGNORE_HIDDEN: "نادیده گرفتن پنهان"
+ IGNORE_HIDDEN_HELP: "نپذیرفتن تمام فایل ها و پوشه ها که با نقطه شروع می شوند"
+ WRAPPED_SITE: "سایت پیچیده"
+ WRAPPED_SITE_HELP: "برای پوسته/پلاگین هایی که Grav که توسط سیستم عامل دیگری پیچیده می شود"
+ FALLBACK_TYPES: "انواع پلان های جایگزین محاز"
+ FALLBACK_TYPES_HELP: "انواع فایل های مجاز که از طریق صفحه مسیر قابل دسترسی است. به صورت پیش فذض برای هر نوع رسانه ای پشتیبانی میشود "
+ INLINE_TYPES: "انواع پلان های جایگزین درون خطی"
+ INLINE_TYPES_HELP: "لیست انواع فایل هایی که باید به جای دانلود درون خطی نمایش داده شود"
+ APPEND_URL_EXT: "اضافه پسوند URL"
+ APPEND_URL_EXT_HELP: "فرمت های سفارشی را به آدرس این صفحه اضافه کنید. توجه داشته باشید این به این معنیست که Grav در جستجوی ' خواهد بود . . اtwig` template"
+ PAGE_MODES: "حالت صفحه"
+ PAGE_TYPES: "نوع صفحه"
+ ACCESS_LEVELS: "سطوح دسترسی"
+ GROUPS: "گروه ها"
+ GROUPS_HELP: "فهرست گروه هایی که کاربر در آن عضو است"
+ ADMIN_ACCESS: "دسترسی مدیریت"
+ SITE_ACCESS: "دسترسی سایت"
+ INVALID_SECURITY_TOKEN: "نشانه امنیت نامعتبر"
+ ACTIVATE: "فعالسازی"
+ TWIG_UMASK_FIX: "تنظیم umask "
+ TWIG_UMASK_FIX_HELP: "به صورت پیش فرض Twig کش فایل ها را روی 0755 ایجاد میکند . روی 0775 تنظیم نمایید"
+ CACHE_PERMS: "مجوزهای کش"
+ CACHE_PERMS_HELP: "دسترسی های پیشفرض پوشه کش. معمولاً ۰۷۵۵ یا ۰۷۷۵ می باشد"
+ REMOVE_SUCCESSFUL: "حذف با موفقیت انجام شد"
+ REMOVE_FAILED: "حذف انجام نشد"
+ HIDE_HOME_IN_URLS: " مخفی کردن مسیر خانه در URL ها"
+ HIDE_HOME_IN_URLS_HELP: "اطمینان حاصل خواهد شد که مسیر پیشفرض صفحات زیرین صفحه خانه به مسیر عمومی آن ارجاع داده نشود"
+ TWIG_FIRST: "ابتدا فرآیند Twig "
+ TWIG_FIRST_HELP: "اگر پردازش صفحات توسط Twig را فعال کرده اید، اکنون می توانید تعیین کنید این پروسه قبل یا بعد از پردازش markdown انجام شود"
+ SESSION_SECURE: "امن"
+ SESSION_SECURE_HELP: "اگر درست باشد ، نشان میدهد که ارتباط این کوکی ها پیش از انتقال باید رمزگذاری شده باشند . اخطار : این گزینه را تنها در سایت هایی که منحصرا در HTTPS اجرا میشوند فعال کنید."
+ SESSION_HTTPONLY: "فقط HTTP"
+ SESSION_HTTPONLY_HELP: "اگر درست باشد،نشان میدهد که کوکی ها باید بیشتر از HTTP استفاده شود و اصلاح جاوا اسکریپت مجاز نمیباشد."
+ REVERSE_PROXY: "پروکسی معکوس"
+ REVERSE_PROXY_HELP: "در صورتی که شما در پشت یک پروکسی معکوس هستید و با آدرس های حاوی پورت های غلط مشکل دارید این گزینه را فعال کنید"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "سربرگ نامعتبر ، ذخیره نشد"
+ ADD_FOLDER: "افزودن پوشه"
+ PROXY_URL: "URL پروکسی"
+ PROXY_URL_HELP: "پروکسی هاست یا آی پی و پورت را وارد نمایید"
+ NOTHING_TO_SAVE: "هیچ چیزس برای ذخیره وجود ندارد"
+ FILE_ERROR_ADD: "هنگام تلاش برای اضافه کردن فایل خطایی رخ داده است"
+ FILE_ERROR_UPLOAD: "خطایی در حین تلاش برای آپلود فایل به وجود آمده است"
+ FILE_UNSUPPORTED: "نوع فایل پشتیبانی نشده"
+ ADD_ITEM: "افزودن مورد"
+ FILE_TOO_LARGE: "پرونده بیش از حد برای آپلود بزرگ می باشد، با توجه به تنظیمات پی اچ پی حداکثر مقدار مجاز %s است. افزایش مقدار توسط تنظیمات پی اچ پی 'post_max_size'"
+ INSTALLING: "درحال نصب"
+ LOADING: "درحال بارگذاری.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "وابستگی های زیر در ابتدا لازم است تکمیل شوند :"
+ ERROR_INSTALLING_PACKAGES: "خطا در حین نصب بسته"
+ INSTALLING_DEPENDENCIES: "در حال نصب متعلقات..."
+ INSTALLING_PACKAGES: "در حال نصب بسته.."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "بسته با موفقیت نصب شد."
+ READY_TO_INSTALL_PACKAGES: "آماده برای نصب بسته"
+ PACKAGES_NOT_INSTALLED: "بسته نصب نشد"
+ PACKAGES_NEED_UPDATE: "بسته در حال حاضر نصب شده اما نسخه خیلی قدیمی است"
+ PACKAGES_SUGGESTED_UPDATE: "بسته از قبل نصب شده و نسخه خوبی است ، اما برای بروز بودن شما آپدیت خواهد شد"
+ REMOVE_THE: "حذف %s"
+ CONFIRM_REMOVAL: "آیا شما مطمئن هستید که میخواهید این ٪ s را حذف کنید؟"
+ REMOVED_SUCCESSFULLY: "%s با موفقیت حذف شد"
+ ERROR_REMOVING_THE: "خطا در حذف %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "%s نیازمند وابستگی های زیر است برای نصب بسته دیگر لازم نیست. اگر شما از آنها استفاده نمی کنید، می توانید آنها را به طور مستقیم از اینجا حذف کنید."
+ READY_TO_UPDATE_PACKAGES: "آماده برای به روز رسانی بستهpackage(s)"
+ ERROR_UPDATING_PACKAGES: "خطا در حین به روز رسانی بسته"
+ UPDATING_PACKAGES: "در حال بروزرسانی بسته.."
+ PACKAGES_SUCCESSFULLY_UPDATED: "بسته با موفقیت به روز رسانی شد."
+ UPDATING: "به روز رسانی"
+ GPM_RELEASES: "انتشار GPM"
+ GPM_RELEASES_HELP: "برای نصب بتا یا نسخه های تست \"تست کردن\" را انتخاب کنید"
+ GPM_METHOD: "روش واکشیدن از راه دور"
+ GPM_METHOD_HELP: "هنگام تنظیم روی حالت خودکار، Grav تعیین خواهد کرد اگر fopen موجود است از آن استفاده میکند ، در غیر این صورتcURL را مورد استفاده قرار میدهد. و مجبور به استفاده از تنظیمات دیگری میشود."
+ GPM_VERIFY_PEER: "بررسی همکار از راه دور (SSL)"
+ GPM_VERIFY_PEER_HELP: "به نظر میرسد برخی از ارائه دهندگان برای تایید گواهینامه getgrav.org SSL به دلیل کار نکردن GPM با شکست مواجه شده اند . اگر این اتفاق برای شما رخ داده است ، غیر فعال کردن این تنظیم ممکن است به شما کمک کند"
+ AUTO: "خودكار"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "پایدار"
+ TESTING: "تست کردن"
+ FRONTMATTER_PROCESS_TWIG: "پردازش سربرگ Twig"
+ FRONTMATTER_PROCESS_TWIG_HELP: "هنگامی که فعال باشد شما می توانید از متغیر های پیکربندی Twig در جلو صفحه استفاده نمایید"
+ FRONTMATTER_IGNORE_FIELDS: "نادیده گرفتن زمینه های مقدمه"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "زمینه دقیق سربرگ ممکن است حاوی Twig باشد اما قابل پردازش نیست. مانند ' فرم '"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "بسته %s با موفقیت نصب شد"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "چیدمان تنظیمات والد ، ترتیب بندی غیر فعال است"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "صفحه قابل رویت نیست ، سفارش غیر فعال میباشد"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "مرتب سازی از طریق مدیر پشتیبانی نمیشود زیرا بیش از 200 هم نوع وجود دارد"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "ترتیب بندی صفحه برای این صفحه غیرفعال شده است زیرا اعداد پیشوند پوشه فعال نشده است"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "توجه: شما نمیتوانید فایل حاوی رسانه ای را اضافه کنید تا زمانی که صفحه را ذخیره کنید .فقط در بالا روی \"ذخیره\"کلیک کنید"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "توجه : قبل از اینکه شما بتوانیدفایل ها را آپلود نمایید صفحه باید ذخیره شود"
+ DROP_FILES_HERE_TO_UPLOAD: "فایل های خو را اینجا رها کنید یا اینجا کلیک کنید "
+ INSERT: "وارد کردن"
+ UNDO: "انصراف و لغو"
+ REDO: "انجام دوباره"
+ HEADERS: "عناوین"
+ BOLD: "ضخیم"
+ ITALIC: "کج"
+ STRIKETHROUGH: "خط خورده"
+ SUMMARY_DELIMITER: "خلاصه جداساز"
+ LINK: "لینک"
+ IMAGE: "تصویر"
+ BLOCKQUOTE: "نقل قول"
+ UNORDERED_LIST: "لیست نا مرتب"
+ ORDERED_LIST: "لیست سفارش"
+ EDITOR: "ویرایشگر"
+ PREVIEW: "پیشنمایش"
+ FULLSCREEN: "تمام صفحه"
+ MODULAR: "ماژولار"
+ NON_ROUTABLE: "غیر قابل مسیر یابی"
+ NON_MODULAR: "بدون ماژولار"
+ NON_VISIBLE: "غیر قابل مشاهده"
+ NON_PUBLISHED: "منتشر نشده"
+ CHARACTERS: "كاركترها"
+ PUBLISHING: "انتشار"
+ NOTIFICATIONS: "اطلاعیهها"
+ MEDIA_TYPES: "نوع رسانه"
+ IMAGE_OPTIONS: "گزینه های تصویر"
+ MIME_TYPE: "نوع فایل"
+ THUMB: "نما"
+ TYPE: "نوع"
+ FILE_EXTENSION: "پسوند فایل"
+ LEGEND: "شرح صفحه"
+ MEMCACHE_SERVER: "سرور حافظه موقت"
+ MEMCACHE_SERVER_HELP: "آدرس سرور حافظه موقت"
+ MEMCACHE_PORT: "درگاه حافظه موقت"
+ MEMCACHE_PORT_HELP: "درگاه سرور حافظه موقت"
+ MEMCACHED_SERVER: "سرور حافظه موقت"
+ MEMCACHED_SERVER_HELP: "آدرس سرور حافظه موقت"
+ MEMCACHED_PORT: "درگاه حافظه موقت"
+ MEMCACHED_PORT_HELP: "درگاه سرور حافظه موقت"
+ REDIS_SERVER: "سرور Redis"
+ REDIS_SERVER_HELP: "آدرس سرور Redis"
+ REDIS_PORT: "درگاه Redis"
+ REDIS_PORT_HELP: "درگاه سرور Redis"
+ REDIS_PASSWORD: "ردیست رمز عبور/ راز"
+ ALL: "همه"
+ FROM: "از"
+ TO: "به"
+ RELEASE_DATE: "تاریخ انتشار"
+ SORT_BY: "مرتب سازی بر اساس"
+ RESOURCE_FILTER: "فیلتر..."
+ FORCE_SSL: "استفاده از نیروی SSL"
+ FORCE_SSL_HELP: "نیروی جهانی SSL, اگر فعال شود زمانی که سایت از طریق HTTP وصل میشود ، Grav یک تغییر مسیر به صفحه HTTPS میفرستد"
+ NEWS_FEED: "خوراک خبرها"
+ EXTERNAL_URL: "آدرس خارجی"
+ SESSION_PATH: "مسیر جلسه"
+ SESSION_PATH_HELP: "اگر می خواهید یک URL سفارشی پایه قرار دهید از این گزینه استفاده نمایید . (دامنه سایت / پوشه فرعی)"
+ CUSTOM_BASE_URL: "URL سفارشی پایه"
+ CUSTOM_BASE_URL_HELP: "اگر می خواهید دامنه سایت را مجددا بنویسید یا از پوشه فرعی متفاوتی نسبت به آنچه توسط Grav استفاده شده ،از این گزینه استفاده کنید . مثال: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: '"%s" را نمیتوان خارج از صفحات استفاده کرد'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'ناتوان در بارگذاری فایل %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'ناتوان در انتقال پرونده %s به "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'لغو آپلود'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'آیا مطمئن هستید که می خواهید ارسال این فایل را لغو کنید؟'
+ DROPZONE_DEFAULT_MESSAGE: 'فایل های خو را اینجا رها کنید یا اینجا کلیک کنید '
+ DROPZONE_FALLBACK_MESSAGE: 'مرورگر شما کشیدن و رها کردن فایل را پشتیبانی نمیکند'
+ DROPZONE_FALLBACK_TEXT: 'لطفا از فرم ذخیره زیر برای آپلود فایل های خود مانند روزهای دیگر استفاده نمایید.'
+ DROPZONE_FILE_TOO_BIG: 'فایل خیلی بزرگ ({{filesize}}MiB) است. حداکثر اندازه فایل: {{maxFilesize}}MiB میباشد.'
+ DROPZONE_INVALID_FILE_TYPE: "شما قادر به ارسال فایل های از این نوع نیستید."
+ DROPZONE_MAX_FILES_EXCEEDED: "شما نمیتوانید فایل بیشتری آپلود کنید."
+ DROPZONE_REMOVE_FILE: "حذف فایل"
+ DROPZONE_RESPONSE_ERROR: "پاسخ سرور با کد {{statusCode}}."
+ PREMIUM_PRODUCT: "نسخه ویژه"
+ ADMIN_CACHING: "فعالسازی کش مدیریت"
+ ADMIN_CACHING_HELP: "کش مدیریت میتواند به صورت مستقل از سایت جلویی کنترل شوذ"
+ DESTINATION_NOT_SPECIFIED: "مقصد مشخص نشده است"
+ UPLOAD_ERR_NO_TMP_DIR: "یک پوشه موقت گم شده است"
+ SESSION_SPLIT: "جلسه تقسیم"
+ SESSION_SPLIT_HELP: "تقسیم جلسات مستقل بین سایت و پلاگین های دیگر ( مانند مدیریت )"
+ ERROR_FULL_BACKTRACE: "ردیابی کامل خطا"
+ ERROR_SIMPLE: "خطای ساده"
+ ERROR_SYSTEM: "خطای سیستم"
+ IMAGES_AUTO_FIX_ORIENTATION: "تنظیم تعمیر خودکار"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "تنظیم خودکار جهت تصویر بر اساس داده های Exif"
+ REDIS_SOCKET: "سوکت redis"
+ REDIS_SOCKET_HELP: "سوکت Redis"
+ NOT_SET: "تنظیم نشده"
+ PERMISSIONS: "دسترسیها"
+ NEVER_CACHE_TWIG: " Twig هیچ گاه ذخیره نشود"
+ NEVER_CACHE_TWIG_HELP: "فقط محتویات کش و پردازش Twig برای هر صفحه در هر لحظه ذخیره میشود .تنظیمات Twig_first را نادیده می گیرد."
+ ALLOW_WEBSERVER_GZIP: "اجازه دادن به وب سرور Gzip"
+ ALLOW_WEBSERVER_GZIP_HELP: "به صورت پیش فرض غیرفعال است. در صورت فعال بودن پیکر بندی وب سرور Gzip/Deflate به صورت فشرده کار خواهد کرد اما اتصال http قبل از رویداد onShutDown() بسته نخواهد شد و سرعت بارگزاری صفحه را کندتر میکند."
+ OFFLINE_WARNING: "اتصال به GPM امکان پذیر نمیباشد"
+ CLEAR_IMAGES_BY_DEFAULT: "پاکسازی حافظه موقت تصویر به صورت پیشفرض"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "به طور پیشفرض تصاویر پردازش شده از حافظه پنهان پاکسازی میشوند، این امکان میتواند غیرفعال شود"
+ CLI_COMPATIBILITY: "سازگاری CLI"
+ CLI_COMPATIBILITY_HELP: "دقت کنید فقط از کش کننده های سبک استفاده شود (file، redis، memcache و...)"
+ REINSTALL_PLUGIN: "نصب مجدد افزونه"
+ REINSTALL_THEME: "نصب مجدد پوسته"
+ REINSTALL_THE: "نصب مجدد تم %s"
+ CONFIRM_REINSTALL: "آیا از نصب مجدد %s مطمئنید؟"
+ REINSTALLED_SUCCESSFULLY: "%s با موفقیت نصب شد"
+ ERROR_REINSTALLING_THE: "خطا در نصب %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "پکیج %s با موفقیت نصب شد"
+ REINSTALLATION_FAILED: "نصب مجدد ناموفق بود"
+ TOOLS: "ابزارها"
+ DIRECT_INSTALL: "نصب مستقیم"
+ NO_PACKAGE_NAME: "نام بسته مشخص نشده است"
+ PACKAGE_EXTRACTION_FAILED: "استخراج بسته انجام نشد"
+ NOT_VALID_GRAV_PACKAGE: "بسته معتبر Grav نیست"
+ NAME_COULD_NOT_BE_DETERMINED: "نمیتوان نام را تعیین کرد"
+ CANNOT_OVERWRITE_SYMLINKS: "قادر به بازنویسی سیملینک نیست"
+ ZIP_PACKAGE_NOT_FOUND: "بستهی ZIP موجود نیست"
+ GPM_OFFICIAL_ONLY: "فقط GPM رسمی"
+ GPM_OFFICIAL_ONLY_HELP: "فقط نصب مستقیم از انباره ی GPM رسمی مجاز است."
+ NO_CHILD_TYPE: "هیچ نوع فرزندی برای این مسیر وجود ندارد"
+ SORTABLE_PAGES: "صفحات قابل مرتب شدن:"
+ UNSORTABLE_PAGES: "صفحات مرتب نشده"
+ ADMIN_SPECIFIC_OVERRIDES: "ابطال های خاص مدیر"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "ترتیب نمایش زیرمجموعهها"
+ PWD_PLACEHOLDER: "به صورت رشتههای پیچیده حداقل ۸ حرف"
+ PWD_REGEX: "ریجکس گذرواژه"
+ PWD_REGEX_HELP: "به صورت پیش فرض: رمز عبور باید حداقل یک عدد و یک حرف بزرگ و یک حرف کوچک و حداقل 8 کاراکتر داشته باشد"
+ USERNAME_PLACEHOLDER: "فقط حروف کوچک، مانند: 'admin'"
+ USERNAME_REGEX: "رجیکس نامکاربری"
+ ENABLE_AUTO_METADATA: "اطلاعات متای خودکار از Exif"
+ 2FA_TITLE: "احراز هویت دو مرحلهای"
+ 2FA_LABEL: "دسترسی مدیر"
+ 2FA_ENABLED: "احراز هویت دومرحلهای فعال شد"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "رمز احراز هویت دو مرحلهای"
+ 2FA_REGENERATE: "ایجاد مجدد"
+ FORCE_LOWERCASE_URLS: "نویسهی آدرسها به صورت اجباری با حروف کوچیک باشند"
+ FORCE_LOWERCASE_URLS_HELP: "به صورت پیشفرض Grav تمام آدرسها و مسیریابیها را با حروف کوچک انجام میدهد، اگر این گزینه را غیرفعال کنید میتوانید از حروف بزرگ استفاده کنید"
+ VIEW_SITE_TIP: "مشاهده سایت"
+ TOOLS_DIRECT_INSTALL_TITLE: "نصب مستقیم بستههای Grav"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "نصب مستقیم از طریق بارگذاری فایل ZIP"
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "نصب بسته از طریق URLمرجع"
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "بارگذاری و نصب"
+ ROUTE_DEFAULT: "مسیریابی پیشفرض"
+ ROUTE_ALIASES: "نامهای مستعار مسیریابی"
+ OPEN_NEW_TAB: "باز کردن در پنجره جدید"
+ SCHEDULER: "زمانبندی"
+ SCHEDULER_INSTALL_INSTRUCTIONS: "دستورالعمل نصب"
+ SCHEDULER_INSTALLED_READY: "نصبشده و آماده"
+ SCHEDULER_CRON_NA: "Cron موجود نیست"
+ SCHEDULER_NOT_ENABLED: "فعال نشد"
+ SCHEDULER_SETUP: "راه اندازی برنامهریز"
+ SECURITY: "امنیت"
+ REPORTS: "گزارشها"
diff --git a/user/plugins/admin/languages/fi.yaml b/user/plugins/admin/languages/fi.yaml
new file mode 100644
index 0000000..39396a9
--- /dev/null
+++ b/user/plugins/admin/languages/fi.yaml
@@ -0,0 +1,237 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Tämä on beta-versio! Käyttö tuotannossa omalla vastuulla..."
+ ADMIN_REPORT_ISSUE: "Löysitkö virheen tai puutteen? Ilmoita siitä Github-palveluun."
+ EMAIL_FOOTER: "Tehty Gravilla - Moderni Flat File CMS"
+ LOGIN_BTN: "Kirjaudu sisään"
+ LOGIN_BTN_FORGOT: "Unohditko"
+ LOGIN_BTN_RESET: "Nollaa salasana"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Lähetä salasanan nollausohjeet"
+ LOGIN_BTN_CLEAR: "Tyhjennä lomake"
+ LOGIN_BTN_CREATE_USER: "Luo käyttäjä"
+ LOGIN_LOGGED_IN: "Sisäänkirjautuminen onnistui"
+ LOGIN_FAILED: "Sisäänkirjautuminen epäonnistui"
+ LOGGED_OUT: "Olet kirjautunut ulos"
+ RESET_NEW_PASSWORD: "Anna uusi salasana …"
+ RESET_LINK_EXPIRED: "Nollauslinkki on vanhentunut, yritä uudelleen"
+ RESET_PASSWORD_RESET: "Salasana on nollattu"
+ RESET_INVALID_LINK: "Virheellinen nollauslinkki käytetty, yritä uudelleen"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Ohjeet salasanasi palautukseen lähetetty sähköpostitse osoitteeseen %s"
+ FORGOT_FAILED_TO_EMAIL: "Ohjeiden lähetys epäonnistui, yritä uudelleen myöhemmin"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Ei voida nollata salasanaa tilille %s, sähköpostiosoitetta ei ole asetettu"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Käyttäjää nimellä %s ei löydy"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Salasanaa ei voida nollata. Sivustoa ei ole määritelty lähettämään sähköpostiviestejä"
+ FORGOT_EMAIL_SUBJECT: "%s Salasanan nollauspyyntö"
+ FORGOT_EMAIL_BODY: "Salasanan nollaaminen %1$s,
Sivustolla %4$s on tehty pyyntö salasanasi nollaamiseksi.
Klikkaa tätä nollataksesi salasanasi
Vaihtoehtoisesti, voit kopioida seuraavan osoitteen selaimesi osoitekenttään:
%2$s
Ystävällisin terveisin, %3$s
"
+ MANAGE_PAGES: "Hallinnoi sivuja"
+ CONFIGURATION: "Asetukset"
+ PAGES: "Sivut"
+ PLUGINS: "Lisäosat"
+ PLUGIN: "Lisäosa"
+ THEMES: "Teemat"
+ LOGOUT: "Kirjaudu ulos"
+ BACK: "Takaisin"
+ NEXT: "Seuraava"
+ PREVIOUS: "Edellinen"
+ ADD_PAGE: "Lisää sivu"
+ MOVE: "Siirrä"
+ DELETE: "Poista"
+ SAVE: "Tallenna"
+ NORMAL: "Normaali"
+ EXPERT: "Asiantuntija"
+ EXPAND_ALL: "Laajenna kaikki"
+ COLLAPSE_ALL: "Sulje kaikki"
+ ERROR: "Virhe"
+ CLOSE: "Sulje"
+ CANCEL: "Peruuta"
+ CONTINUE: "Jatka"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Vaatii vahvistuksen"
+ MODAL_CHANGED_DETECTED_TITLE: "Muutoksia havaittu"
+ MODAL_CHANGED_DETECTED_DESC: "Sinulla on tallentamattomia muutoksia. Oletko varma, että haluat poistua tältä sivulta tallentamatta?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Vahvistus tarvitaan"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Oletko varma, että haluat poistaa tämän tiedoston? Tätä toimintoa ei voi kumota."
+ ADD_FILTERS: "Lisää suodattimia"
+ SEARCH_PAGES: "Hae sivuilta"
+ VERSION: "Versio"
+ WAS_MADE_WITH: "Tehty"
+ BY: "Tekijältä"
+ UPDATE_THEME: "Päivitä teema"
+ UPDATE_PLUGIN: "Päivitä liitännäinen"
+ AUTHOR: "Tekijä"
+ HOMEPAGE: "Etusivu"
+ DEMO: "Demo"
+ BUG_TRACKER: "Virheseuranta"
+ KEYWORDS: "Avainsanat"
+ LICENSE: "Lisenssi"
+ DESCRIPTION: "Kuvaus"
+ README: "Readme"
+ REMOVE_THEME: "Poista teema"
+ INSTALL_THEME: "Asenna teema"
+ THEME: "Teema"
+ BACK_TO_THEMES: "Takaisin teemoihin"
+ BACK_TO_PLUGINS: "Takaisin lisäosiin"
+ CHECK_FOR_UPDATES: "Tarkista päivitykset"
+ ADD: "Lisää"
+ CLEAR_CACHE: "Tyhjennä välimuisti"
+ CLEAR_CACHE_ALL_CACHE: "Koko välimuisti"
+ CLEAR_CACHE_IMAGES_ONLY: "Vain kuvat"
+ CLEAR_CACHE_CACHE_ONLY: "Vain välimuisti"
+ DASHBOARD: "Ohjauspaneeli"
+ UPDATES_AVAILABLE: "Päivityksiä saatavilla"
+ DAYS: "Päivät"
+ UPDATE: "Päivitä"
+ BACKUP: "Varmuuskopioi"
+ BACKUPS: "Varmuuskopiot"
+ BACKUP_NOW: "Varmuuskopioi nyt"
+ BACKUPS_NEWEST: "Uusin varmuuskopio"
+ BACKUPS_OLDEST: "Vanhin varmuuskopio"
+ BACKUPS_NOT_GENERATED: "Varmuuskopioita ei ole vielä luotu..."
+ BACKUP_DELETED: "Varmuuskopio poistettu onnistuneesti"
+ STATISTICS: "Tilastot"
+ TODAY: "Tänään"
+ WEEK: "Viikko"
+ MONTH: "Kuukausi"
+ LATEST_PAGE_UPDATES: "Viimeiset sivujen päivitykset"
+ MAINTENANCE: "Huolto"
+ UPDATED: "Päivitetty"
+ MON: "Ma"
+ TUE: "Ti"
+ WED: "Ke"
+ THU: "To"
+ FRI: "Pe"
+ SAT: "La"
+ SUN: "Su"
+ COPY: "Kopioi"
+ EDIT: "Muokkaa"
+ CREATE: "Luo"
+ GRAV_ADMIN: "Grav admin"
+ GRAV_OFFICIAL_PLUGIN: "Grav: n virallinen lisäosa"
+ GRAV_OFFICIAL_THEME: "Grav: n virallinen teema"
+ PLUGIN_SYMBOLICALLY_LINKED: "Tämä lisäosa on symbolisesti yhteydessä. Päivityksiä ei tulla haivaitsemaan."
+ THEME_SYMBOLICALLY_LINKED: "Tämä teema on symbolisesti yhteydessä. Päivityksiä ei tulla haivaitsemaan"
+ REMOVE_PLUGIN: "Poista lisäosa"
+ INSTALL_PLUGIN: "Asenna lisäosa"
+ AVAILABLE: "Saatavilla"
+ INSTALLED: "Asennettu"
+ INSTALL: "Asenna"
+ ACTIVE_THEME: "Käytössä oleva teema"
+ SWITCHING_TO: "Vaihdetaan"
+ SWITCHING_TO_DESCRIPTION: "Vaihtamalla eri teemaan, emme takaa että kaikkien sivujen asettelu on tuettu, joka voi mahdollisesti aiheuttaa virheitä sivujen latautuessa."
+ SWITCHING_TO_CONFIRMATION: "Haluatko jatkaa ja vaihtaa teemaa"
+ CREATE_NEW_USER: "Luo uusi käyttäjä"
+ REMOVE_USER: "Poista käyttäjä"
+ ACCESS_DENIED: "Pääsy kielletty"
+ ACCOUNT_NOT_ADMIN: "käyttäjätunnuksellasi ei ole pääkäyttäjäoikeuksia"
+ PHP_INFO: "PHP tiedot"
+ INSTALLER: "Asentaja"
+ AVAILABLE_THEMES: "Saatavilla olevat teemat"
+ AVAILABLE_PLUGINS: "Saatavilla olevat lisäosat"
+ INSTALLED_THEMES: "Asennetut Teemat"
+ INSTALLED_PLUGINS: "Asennetut lisäosat"
+ BROWSE_ERROR_LOGS: "Selaa virhelokeja"
+ SITE: "Sivusto"
+ INFO: "Info"
+ SYSTEM: "Järjestelmä"
+ USER: "Käyttäjä"
+ ADD_ACCOUNT: "Lisää tili"
+ SWITCH_LANGUAGE: "Vaihda kieli"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Lisäosa otettiin käyttöön onnistuneesti"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Lisäosa poistettu käytöstä onnistuneesti"
+ SUCCESSFULLY_CHANGED_THEME: "Oletusteeman muutto onnistui"
+ INSTALLATION_FAILED: "Asennus epäonnistui"
+ INSTALLATION_SUCCESSFUL: "Asennus onnistui"
+ UNINSTALL_FAILED: "Poisto epäonnistui"
+ UNINSTALL_SUCCESSFUL: "Poisto onnistui"
+ SUCCESSFULLY_SAVED: "Tallennus onnistui"
+ SUCCESSFULLY_COPIED: "Kopiointi onnistui"
+ SUCCESSFULLY_DELETED: "Poisto onnistui"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Kielen vaihto onnistui"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Käyttöoikeutesi eivät riitä tehtävään"
+ CACHE_CLEARED: "Välimuisti tyhjennetty"
+ METHOD: "Menetelmä"
+ ERROR_CLEARING_CACHE: "Virhe välimuistin tyhjentämisessä"
+ AN_ERROR_OCCURRED: "On tapahtunut virhe"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Varmuuskopiosi on valmis ladattavaksi"
+ DOWNLOAD_BACKUP: "Lataa varmuuskopio"
+ PAGES_FILTERED: "Sivut suodatettu"
+ NO_PAGE_FOUND: "Sivua ei löydy"
+ INVALID_PARAMETERS: "Virheellisiä parametrejä"
+ NO_FILES_SENT: "Tiedostoja ei lähetetty"
+ UNKNOWN_ERRORS: "Tuntemattomia virheitä"
+ UNSUPPORTED_FILE_TYPE: "Tiedostotyyppiä ei tueta"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Ladatun tiedoston siirtäminen epäonnistui."
+ FILE_UPLOADED_SUCCESSFULLY: "Tiedoston lataaminen onnistui"
+ FILE_DELETED: "Tiedosto poistettu"
+ FILE_COULD_NOT_BE_DELETED: "Tiedostoa ei voitu poistaa"
+ FILE_NOT_FOUND: "Tiedostoa ei löytynyt"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav päivitettiin onnistuneesti versioon"
+ GRAV_UPDATE_FAILED: "Grav: n päivitys epäonnistui"
+ EVERYTHING_UPDATED: "Kaikki päivitetty"
+ UPDATES_FAILED: "Päivitykset epäonnistuivat"
+ AVATAR_BY: "Avatar"
+ LAST_BACKUP: "Viimeisin varmuuskopio"
+ FULL_NAME: "Koko nimi"
+ USERNAME: "Käyttäjätunnus"
+ EMAIL: "Sähköpostiosoite"
+ USERNAME_EMAIL: "Käyttäjänimi tai sähköposti"
+ PASSWORD: "Salasana"
+ PASSWORD_CONFIRM: "Toista salasana"
+ TITLE: "Otsikko"
+ LANGUAGE: "Kieli"
+ ACCOUNT: "Tili"
+ EMAIL_VALIDATION_MESSAGE: "Sähköpostin pitää olla validi"
+ PASSWORD_VALIDATION_MESSAGE: "Salasanan pitää sisältää vähintään yhden numeron ja yhden ison kirjaimen ja pienen kirjaimen sekä pituus pitää olla 8 tai enemmän merkkejä"
+ LANGUAGE_HELP: "Aseta suosikki kieli"
+ MEDIA: "Media"
+ DEFAULTS: "Oletukset"
+ SITE_TITLE: "Sivuston otsikko"
+ SITE_DEFAULT_LANG: "Oletuskieli"
+ DEFAULT_EMAIL: "Oletussähköposti"
+ ENABLED: "Käytössä"
+ 'YES': "Kyllä"
+ 'NO': "Ei"
+ FORMAT: "Muoto"
+ SHORT: "Lyhyt"
+ LONG: "Pitkä"
+ METADATA: "Metadata"
+ NAME: "Nimi"
+ CONTENT: "Sisältö"
+ OPTIONS: "Asetukset"
+ PUBLISHED: "Julkaistu"
+ DATE: "Päiväys"
+ SETTINGS: "Asetukset"
+ FOLDER_NAME: "Kansion Nimi"
+ MENU: "Valikkko"
+ ORDER: "Järjestys"
+ FILENAME: "Tiedostonimi"
+ HOME_PAGE: "Etusivu"
+ TIMEZONE: "Aikavyöhyke"
+ LANGUAGES: "Kielet"
+ AUTO_URL_LINKS: "Automaattiset URL-linkit"
+ SESSION: "Istunto"
+ TIMEOUT: "Aikakatkaisu"
+ IS_NOW_AVAILABLE: "on nyt saatavilla"
+ UPDATE_GRAV_NOW: "Päivitä Grav nyt"
+ SAVE_AS: "Tallenna nimellä"
+ UPDATE_AVAILABLE: "Päivitys saatavilla"
+ FULLY_UPDATED: "Täysin päivitetty"
+ HTTP_ACCEPT_LANGUAGE: "Aseta kieli selaimesta"
+ IGNORE_HIDDEN: "Ohita piilotetut"
+ GROUPS: "Ryhmät"
+ GROUPS_HELP: "Ryhmät, joihin käyttäjä kuuluu"
+ ADD_FOLDER: "Lisää kansio"
+ FILE_UNSUPPORTED: "Tiedostotyyppiä ei tueta"
+ LOADING: "Lataa.."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Paketti/-it asennettu onnistuneesti."
+ UPDATING: "Päivitetään"
+ CURL: "cURL"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Paketti %s asennettu onnistuneesti"
+ UNDO: "Kumoa"
+ IMAGE: "Kuva"
+ FULLSCREEN: "Kokoruutu"
+ ALL: "Kaikki"
+ DROPZONE_INVALID_FILE_TYPE: "Tämäntyyppisiä tiedostoja ei voi ladata."
+ DROPZONE_REMOVE_FILE: "Poista tiedosto"
+ ERROR_SYSTEM: "Järjestelmävirhe"
+ TOOLS: "Työkalut"
+ 2FA_TITLE: "Kaksivaiheinen todennus"
diff --git a/user/plugins/admin/languages/fr.yaml b/user/plugins/admin/languages/fr.yaml
new file mode 100644
index 0000000..4ed9166
--- /dev/null
+++ b/user/plugins/admin/languages/fr.yaml
@@ -0,0 +1,819 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Ceci est une version bêta ! Utilisez-là en production à vos risques et périls..."
+ ADMIN_REPORT_ISSUE: "Vous avez rencontré une anomalie ? Veuillez la signaler sur GitHub."
+ EMAIL_FOOTER: "Propulsé par Grav - Le CMS Moderne sans base de données"
+ LOGIN_BTN: "Connexion"
+ LOGIN_BTN_FORGOT: "Mot de passe oublié"
+ LOGIN_BTN_RESET: "Réinitialiser le mot de passe"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Envoyer les instructions de réinitialisation"
+ LOGIN_BTN_CLEAR: "Effacer le formulaire"
+ LOGIN_BTN_CREATE_USER: "Créer un utilisateur"
+ LOGIN_LOGGED_IN: "Vous vous êtes connecté avec succès"
+ LOGIN_FAILED: "Échec de la connexion"
+ LOGGED_OUT: "Vous avez été déconnecté"
+ RESET_NEW_PASSWORD: "Veuillez saisir un nouveau mot de passe…"
+ RESET_LINK_EXPIRED: "Le lien de réinitialisation a expiré, veuillez réessayer"
+ RESET_PASSWORD_RESET: "Le mot de passe a été réinitialisé"
+ RESET_INVALID_LINK: "Lien de réinitialisation invalide, veuillez réessayer"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Les instructions pour réinitialiser votre mot de passe ont été envoyées par e-mail à %s"
+ FORGOT_FAILED_TO_EMAIL: "Impossible d'envoyer les instructions par e-mail. Veuillez réessayez ultérieurement."
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Impossible de réinitialiser le mot de passe pour %s, aucune adresse e-mail n'est définie."
+ FORGOT_USERNAME_DOES_NOT_EXIST: "L'utilisateur avec le nom d'utilisateur %s n'existe pas"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Impossible de réinitialiser le mot de passe. Ce site n'est pas configuré pour envoyer des e-mails"
+ FORGOT_EMAIL_SUBJECT: "Requête de réinitialisation du mot de passe de %s"
+ FORGOT_EMAIL_BODY: "Réinitialisation du mot de passe %1$s,
Une requête a été faite sur %4$s pour réinitialiser votre mot de passe.
Cliquez ici pour réinitialiser votre mot de passe
Alternativement, copiez l'URL suivante dans la barre d'adresse de votre navigateur:
%2$s
Cordialement, %3$s
"
+ MANAGE_PAGES: "Gestion des pages"
+ CONFIGURATION: "Configuration"
+ PAGES: "Pages"
+ PLUGINS: "Plugins"
+ PLUGIN: "Plugin"
+ THEMES: "Thèmes"
+ LOGOUT: "Déconnexion"
+ BACK: "Retour"
+ NEXT: "Suivant"
+ PREVIOUS: "Précédent"
+ ADD_PAGE: "Ajouter une page"
+ ADD_MODULAR: "Ajouter un contenu modulaire"
+ MOVE: "Déplacer"
+ DELETE: "Supprimer"
+ UNSET: "Débranché"
+ VIEW: "Vue"
+ SAVE: "Enregistrer"
+ NORMAL: "Normal"
+ EXPERT: "Expert"
+ EXPAND_ALL: "Tout afficher"
+ COLLAPSE_ALL: "Tout réduire"
+ ERROR: "Erreur"
+ CLOSE: "Fermer"
+ CANCEL: "Annuler"
+ CONTINUE: "Continuer"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Confirmation requise"
+ MODAL_CHANGED_DETECTED_TITLE: "Modifications détectées"
+ MODAL_CHANGED_DETECTED_DESC: "Vous avez des modifications non enregistrées. Êtes-vous sûr de vouloir quitter sans enregistrer ?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Confirmation requise"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Êtes-vous sûr de vouloir supprimer ce fichier ? Cette action ne pourra pas être annulée."
+ ADD_FILTERS: "Ajouter des filtres"
+ SEARCH_PAGES: "Rechercher des pages"
+ VERSION: "Version "
+ WAS_MADE_WITH: "A été réalisé avec"
+ BY: "Par"
+ UPDATE_THEME: "Mettre à jour le thème"
+ UPDATE_PLUGIN: "Mettre à jour le plugin"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "de ce thème est maintenant disponible"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "de ce plugin est maintenant disponible"
+ AUTHOR: "Auteur "
+ HOMEPAGE: "Page d'accueil "
+ DEMO: "Démo "
+ BUG_TRACKER: "Gestion des anomalies "
+ KEYWORDS: "Mots clés "
+ LICENSE: "Licence "
+ DESCRIPTION: "Description "
+ README: "Lisez-moi "
+ REMOVE_THEME: "Supprimer le thème"
+ INSTALL_THEME: "Installer le thème"
+ THEME: "Thème "
+ BACK_TO_THEMES: "Retour aux thèmes"
+ BACK_TO_PLUGINS: "Retour aux plugins"
+ CHECK_FOR_UPDATES: "Vérifier les mises à jour"
+ ADD: "Ajouter"
+ CLEAR_CACHE: "Vider le cache"
+ CLEAR_CACHE_ALL_CACHE: "Tous les caches"
+ CLEAR_CACHE_ASSETS_ONLY: "Uniquement les assets"
+ CLEAR_CACHE_IMAGES_ONLY: "Uniquement les images"
+ CLEAR_CACHE_CACHE_ONLY: "Uniquement le cache"
+ CLEAR_CACHE_TMP_ONLY: "Tmp seulement"
+ DASHBOARD: "Tableau de bord"
+ UPDATES_AVAILABLE: "Mises à jour disponibles"
+ DAYS: "Jours"
+ UPDATE: "Mettre à jour"
+ BACKUP: "Sauvegarder"
+ BACKUPS: "Sauvegardes"
+ BACKUP_NOW: "Sauvegarder Maintenant"
+ BACKUPS_STATS: "Statistiques de Sauvegarde"
+ BACKUPS_HISTORY: "Historique des sauvegardes"
+ BACKUPS_PURGE_CONFIG: "épurer configuration de sauvegarde"
+ BACKUPS_PROFILES: "Profils des sauvegardes"
+ BACKUPS_COUNT: "Nombre de Sauvegardes"
+ BACKUPS_PROFILES_COUNT: "Nombre de Profils"
+ BACKUPS_TOTAL_SIZE: "Espace utilisé"
+ BACKUPS_NEWEST: "Dernière sauvegarde"
+ BACKUPS_OLDEST: "Sauvegarde plus ancienne"
+ BACKUPS_PURGE: "épurer ou purger"
+ BACKUPS_NOT_GENERATED: "Aucune sauvegarde générée pour l'instant..."
+ BACKUPS_PURGE_NUMBER: "Utilisation %s de %s emplacements de sauvegardes"
+ BACKUPS_PURGE_TIME: "%s jours de sauvegarde restants"
+ BACKUPS_PURGE_SPACE: "Utilisation de %s de %s"
+ BACKUP_DELETED: "Sauvegarde supprimée avec succès"
+ BACKUP_NOT_FOUND: "Sauvegarde introuvable"
+ BACKUP_DATE: "Date de sauvegarde"
+ STATISTICS: "Statistiques"
+ TODAY: "Aujourd'hui"
+ WEEK: "Semaine"
+ MONTH: "Mois"
+ LATEST_PAGE_UPDATES: "Pages dernièrement mises à jour"
+ MAINTENANCE: "Maintenance"
+ UPDATED: "Mis à jour"
+ MON: "lun"
+ TUE: "mar"
+ WED: "mer"
+ THU: "jeu"
+ FRI: "ven"
+ SAT: "sam"
+ SUN: "dim"
+ COPY: "Copier"
+ EDIT: "Modifier"
+ CREATE: "Créer"
+ GRAV_ADMIN: "Administration de Grav"
+ GRAV_OFFICIAL_PLUGIN: "Plugin officiel Grav"
+ GRAV_OFFICIAL_THEME: "Thème officiel Grav"
+ PLUGIN_SYMBOLICALLY_LINKED: "Ce plugin est lié de manière symbolique. Les mises à jour ne seront pas détectées."
+ THEME_SYMBOLICALLY_LINKED: "Ce thème est lié de manière symbolique. Les mises à jour ne seront pas détectées."
+ REMOVE_PLUGIN: "Supprimer le plugin"
+ INSTALL_PLUGIN: "Installer le plugin"
+ AVAILABLE: "Disponible"
+ INSTALLED: "Installé"
+ INSTALL: "Installer"
+ ACTIVE_THEME: "Thème actif"
+ SWITCHING_TO: "Basculer vers"
+ SWITCHING_TO_DESCRIPTION: "En basculant vers un thème différent, il n'y a aucune garantie que la mise en page soit prise en charge. Il y a un risque potentiel d'erreurs lors du chargement des dites pages."
+ SWITCHING_TO_CONFIRMATION: "Souhaitez-vous poursuivre et basculer vers le thème"
+ CREATE_NEW_USER: "Créer un nouvel utilisateur"
+ REMOVE_USER: "Supprimer l'utilisateur"
+ ACCESS_DENIED: "Accès refusé"
+ ACCOUNT_NOT_ADMIN: "votre compte ne possède pas les permissions d'administrateur"
+ PHP_INFO: "Informations PHP"
+ INSTALLER: "Installateur"
+ AVAILABLE_THEMES: "Thèmes disponibles"
+ AVAILABLE_PLUGINS: "Plugins disponibles"
+ INSTALLED_THEMES: "Thèmes installés"
+ INSTALLED_PLUGINS: "Plugins installés"
+ BROWSE_ERROR_LOGS: "Parcourir le journal des erreurs"
+ SITE: "Site"
+ INFO: "Informations"
+ SYSTEM: "Système"
+ USER: "Utilisateur"
+ ADD_ACCOUNT: "Ajouter un compte"
+ SWITCH_LANGUAGE: "Changer de langue"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Plugin activé avec succès"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Plugin désactivé avec succès"
+ SUCCESSFULLY_CHANGED_THEME: "Thème par défaut changé avec succès"
+ INSTALLATION_FAILED: "Échec de l'installation"
+ INSTALLATION_SUCCESSFUL: "Installation réussie"
+ UNINSTALL_FAILED: "La désinstallation a échoué"
+ UNINSTALL_SUCCESSFUL: "Désinstallation réussie"
+ SUCCESSFULLY_SAVED: "Enregistrement réussi"
+ SUCCESSFULLY_COPIED: "Copié avec succès"
+ REORDERING_WAS_SUCCESSFUL: "La réorganisation a été un succès"
+ SUCCESSFULLY_DELETED: "Supprimé avec succès"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Langue changée avec succès"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Vous n'avez pas les permissions nécessaires pour effectuer cette tâche"
+ CACHE_CLEARED: "Cache vidé"
+ METHOD: "Méthode "
+ ERROR_CLEARING_CACHE: "Erreur lors du vidage du cache"
+ AN_ERROR_OCCURRED: "Une erreur est survenue"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Votre sauvegarde est prête à être téléchargée"
+ DOWNLOAD_BACKUP: "Télécharger la sauvegarde"
+ PAGES_FILTERED: "Pages filtrées"
+ NO_PAGE_FOUND: "Aucune page trouvée"
+ INVALID_PARAMETERS: "Paramètres invalides"
+ NO_FILES_SENT: "Aucun fichier envoyé"
+ EXCEEDED_FILESIZE_LIMIT: "Dépassement de la limite de taille de fichier dans la configuration PHP"
+ EXCEEDED_POSTMAX_LIMIT: "Configuration PHP post_max_size dépassée"
+ UNKNOWN_ERRORS: "Erreurs inconnues"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Dépassement de la limite de taille de fichier dans la configuration de Grav"
+ UNSUPPORTED_FILE_TYPE: "Type de fichier non supporté"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Échec lors du déplacement du fichier envoyé."
+ FILE_UPLOADED_SUCCESSFULLY: "Fichier chargé avec succès"
+ FILE_DELETED: "Fichier supprimé"
+ FILE_COULD_NOT_BE_DELETED: "Le fichier n'a pas pu pas être supprimé"
+ FILE_NOT_FOUND: "Fichier non trouvé"
+ NO_FILE_FOUND: "Aucun fichier trouvé"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav a été mis à jour avec succès"
+ GRAV_UPDATE_FAILED: "La mise à jour de Grav a échoué"
+ EVERYTHING_UPDATED: "Tout est à jour"
+ UPDATES_FAILED: "Les mises à jour ont échoué"
+ AVATAR_BY: "Avatar par"
+ AVATAR_UPLOAD_OWN: "Ou chargez le votre..."
+ LAST_BACKUP: "Dernière sauvegarde"
+ FULL_NAME: "Nom complet"
+ USERNAME: "Nom d'utilisateur"
+ EMAIL: "E-mail"
+ USERNAME_EMAIL: "Nom d'utilisateur ou e-mail"
+ PASSWORD: "Mot de passe"
+ PASSWORD_CONFIRM: "Confirmer le mot de passe"
+ TITLE: "Titre"
+ LANGUAGE: "Langue"
+ ACCOUNT: "Compte"
+ EMAIL_VALIDATION_MESSAGE: "Doit être une adresse e-mail valide"
+ PASSWORD_VALIDATION_MESSAGE: "Le mot de passe doit contenir au moins un chiffre, une lettre minuscule, majuscule et au minimum 8 caractères."
+ LANGUAGE_HELP: "Définir la langue préférée"
+ MEDIA: "Média"
+ DEFAULTS: "Par défaut"
+ SITE_TITLE: "Titre du site"
+ SITE_TITLE_PLACEHOLDER: "Titre général du site"
+ SITE_TITLE_HELP: "Titre par défaut pour votre site, souvent utilisé dans les thèmes"
+ SITE_DEFAULT_LANG: "Langue par défaut"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Langue par défaut à utiliser dans la balise du thème"
+ SITE_DEFAULT_LANG_HELP: "Langue par défaut à utiliser dans la balise du thème"
+ DEFAULT_AUTHOR: "Auteur par défaut"
+ DEFAULT_AUTHOR_HELP: "Un nom d'auteur par défaut, souvent utilisé dans les thèmes ou le contenu des pages"
+ DEFAULT_EMAIL: "E-mail par défaut"
+ DEFAULT_EMAIL_HELP: "Un e-mail par défaut de référence pour les thèmes ou pages."
+ TAXONOMY_TYPES: "Types de taxonomie"
+ TAXONOMY_TYPES_HELP: "Les types de taxinomie doivent être définis ici si vous souhaitez les utiliser dans les pages."
+ PAGE_SUMMARY: "Résumé de page"
+ ENABLED: "Activé"
+ ENABLED_HELP: "Activer le résumé de page (le résumé retourne le même contenu que la page)"
+ 'YES': "Oui"
+ 'NO': "Non"
+ SUMMARY_SIZE: "Longueur du résumé"
+ SUMMARY_SIZE_HELP: "Le nombre de caractères de la page à utiliser comme contenu de résumé"
+ FORMAT: "Format"
+ FORMAT_HELP: "court = utilise la première occurrence du délimiteur ou la taille ; long = le délimiteur de résumé sera ignoré"
+ SHORT: "Court"
+ LONG: "Long"
+ DELIMITER: "Délimiteur"
+ DELIMITER_HELP: "Le délimiteur de résumé (par défaut '===')"
+ METADATA: "Métadonnées"
+ METADATA_HELP: "Valeurs par défaut des métadonnées qui seront affichées pour chaque page sauf si substituées par la page"
+ NAME: "Nom"
+ CONTENT: "Contenu"
+ SIZE: "Taille"
+ ACTION: "Action"
+ REDIRECTS_AND_ROUTES: "Redirections & Chemins"
+ CUSTOM_REDIRECTS: "Redirections personnalisées"
+ CUSTOM_REDIRECTS_HELP: "chemin pour rediriger vers d'autres pages. Le remplacement par expressions régulières standards est valide"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/votre/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/votre/redirection"
+ CUSTOM_ROUTES: "Chemins personnalisés"
+ CUSTOM_ROUTES_HELP: "routes pour rediriger vers d'autres pages. Le remplacement par expressions rationnelles est possible"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/votre/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/votre/route"
+ FILE_STREAMS: "Flux de fichiers"
+ DEFAULT: "Par défaut"
+ PAGE_MEDIA: "Médias de la page"
+ OPTIONS: "Options"
+ PUBLISHED: "Publié"
+ PUBLISHED_HELP: "Par défaut, une page est publiée sauf si le paramètre Publiée est défini a 'Non', si la date de publication est dans le futur ou celle de retrait dans le passé"
+ DATE: "Date"
+ DATE_HELP: "La variable de date vous permet de définir une date spécifique associée à cette page."
+ PUBLISHED_DATE: "Date de publication"
+ PUBLISHED_DATE_HELP: "Permet de définir une date pour déclencher automatiquement la publication."
+ UNPUBLISHED_DATE: "Date de dépublication"
+ UNPUBLISHED_DATE_HELP: "Permet de définir une date pour déclencher automatiquement la dépublication."
+ ROBOTS: "Robots"
+ TAXONOMIES: "Taxonomies"
+ TAXONOMY: "Taxinomie"
+ ADVANCED: "Avancé"
+ SETTINGS: "Paramètres"
+ FOLDER_NUMERIC_PREFIX: "Préfixe numérique de dossier"
+ FOLDER_NUMERIC_PREFIX_HELP: "Les préfixes numériques qui permettent le classement manuel pour une meilleure visibilité"
+ FOLDER_NAME: "Nom de dossier"
+ FOLDER_NAME_HELP: "Le nom de dossier qui sera stocké dans le système de fichier pour cette page."
+ PARENT: "Parent"
+ DEFAULT_OPTION_ROOT: "- Racine -"
+ DEFAULT_OPTION_SELECT: "-Sélectionner-"
+ DISPLAY_TEMPLATE: "Template d'affichage"
+ DISPLAY_TEMPLATE_HELP: "Modèle d'affichage qui sera utilisé par twig pour effectuer le rendu de la page"
+ BODY_CLASSES: "Classes du Body"
+ ORDERING: "Ordre"
+ PAGE_ORDER: "Ordre des pages"
+ OVERRIDES: "Substitutions"
+ MENU: "Menu"
+ MENU_HELP: "La chaîne de caractères à utiliser dans le menu. Si non défini, le Titre sera utilisé."
+ SLUG: "Slug"
+ SLUG_HELP: "La variable slug vous permet de définir une portion spécifique de l'URL d'une page"
+ SLUG_VALIDATE_MESSAGE: "Un slug doit contenir uniquement des caractères alphanumériques minuscules et des traits d'union"
+ PROCESS: "Traitement"
+ PROCESS_HELP: "Contrôle la façon dont les pages sont traitées. Peut être défini par page plutôt que globalement"
+ DEFAULT_CHILD_TYPE: "Type enfant par défaut"
+ USE_GLOBAL: "Utilisation globale"
+ ROUTABLE: "Accessible"
+ ROUTABLE_HELP: "Si cette page est accessible par une URL"
+ CACHING: "Mise en cache"
+ VISIBLE: "Visible"
+ VISIBLE_HELP: "Détermine si une page est visible dans la navigation."
+ DISABLED: "Désactivé"
+ ITEMS: "Éléments"
+ ORDER_BY: "Trier par"
+ ORDER: "Ordre"
+ FOLDER: "Dossier"
+ ASCENDING: "Croissant"
+ DESCENDING: "Décroissant"
+ ADD_MODULAR_CONTENT: "Ajouter un contenu modulaire"
+ PAGE_TITLE: "Titre de page"
+ PAGE_TITLE_HELP: "Le titre de la page"
+ PAGE: "Page"
+ MODULAR_TEMPLATE: "Template modulaire"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Nom de fichier"
+ PARENT_PAGE: "Page parente"
+ HOME_PAGE: "Page d'accueil"
+ HOME_PAGE_HELP: "La page que Grav utilisera comme page de destination par défaut"
+ DEFAULT_THEME: "Thème par défaut"
+ DEFAULT_THEME_HELP: "Définit le thème que Grav utilisera par défaut (par défaut Antimatter)"
+ TIMEZONE: "Fuseau horaire"
+ TIMEZONE_HELP: "Substitution au fuseau horaire par défaut du serveur"
+ SHORT_DATE_FORMAT: "Format d'affichage court de la date"
+ SHORT_DATE_FORMAT_HELP: "Définit le format de date courte qui peut être utilisé dans les thèmes"
+ LONG_DATE_FORMAT: "Format d'affichage long de la date"
+ LONG_DATE_FORMAT_HELP: "Définit le format de la date longue qui peut être utilisé dans les thèmes"
+ DEFAULT_ORDERING: "Tri par défaut"
+ DEFAULT_ORDERING_HELP: "Les pages d'une liste seront affichées en utilisant ce tri à moins qu'il est été substitué."
+ DEFAULT_ORDERING_DEFAULT: "Défaut - basé sur le nom de dossier"
+ DEFAULT_ORDERING_FOLDER: "Dossier - basé sur le nom de dossier sans le préfixe"
+ DEFAULT_ORDERING_TITLE: "Titre - basé sur le champ titre de l'en-tête"
+ DEFAULT_ORDERING_DATE: "Date - basé sur le champ date de l'en-tête"
+ DEFAULT_ORDER_DIRECTION: "Sens du tri par défaut"
+ DEFAULT_ORDER_DIRECTION_HELP: "Le sens d'affichage des pages dans une liste (classement)"
+ DEFAULT_PAGE_COUNT: "Nombre de pages par défaut"
+ DEFAULT_PAGE_COUNT_HELP: "Nombre maximum de pages dans une liste par défaut"
+ DATE_BASED_PUBLISHING: "Publication basée sur la date"
+ DATE_BASED_PUBLISHING_HELP: "(Dé)publie automatiquement les pages en fonction de leur date"
+ EVENTS: "Évènements"
+ EVENTS_HELP: "Active ou désactive des évènements spécifiques. Les désactiver peut altérer le comportement des plugins"
+ REDIRECT_DEFAULT_ROUTE: "Route par défaut de redirection"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Redirige automatiquement vers une route de page par défaut"
+ LANGUAGES: "Langues"
+ SUPPORTED: "Prises en charge"
+ SUPPORTED_HELP: "Liste de code langue formé de 2 lettres et séparé par une virgule (par exemple 'en,fr,de')"
+ TRANSLATIONS_ENABLED: "Traductions activées"
+ TRANSLATIONS_ENABLED_HELP: "Prise en charge des traductions dans Grav, plugins et extensions"
+ TRANSLATIONS_FALLBACK: "Traductions de repli"
+ TRANSLATIONS_FALLBACK_HELP: "Traductions de repli qui seront prises en charge si la langue active n'existe pas"
+ ACTIVE_LANGUAGE_IN_SESSION: "Activer la langue dans les sessions"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Mémorise la langue active dans la session"
+ HTTP_HEADERS: "En-têtes HTTP"
+ EXPIRES: "Expire"
+ EXPIRES_HELP: "Définit l'expiration de l'en-tête. La valeur est en secondes."
+ CACHE_CONTROL: "Contrôle du cache HTTP"
+ CACHE_CONTROL_HELP: "Définissez une valeur de cache-control valide telle que `no-cache, no-store, must-revalidate`"
+ LAST_MODIFIED: "Last modified"
+ LAST_MODIFIED_HELP: "Définit l'en-tête 'Last modified' pouvant améliorer l'optimisation du cache des proxies et navigateurs"
+ ETAG: "ETag"
+ ETAG_HELP: "Définit l'en-tête 'ETag' afin d'aider à identifier quand une page a été modifiée."
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Définit l'en-tête 'Vary: Accept Encoding' afin d'aider la mise en cache avec les proxy et CDN."
+ MARKDOWN: "Démarquage"
+ MARKDOWN_EXTRA: "Markdown Extra"
+ MARKDOWN_EXTRA_HELP: "Active par défaut la prise en charge de Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ MARKDOWN_EXTRA_ESCAPE_FENCES: "Échapper aux éléments HTML dans les clôtures supplémentaires de démarquage"
+ MARKDOWN_EXTRA_ESCAPE_FENCES_HELP: "Échappe les éléments HTML dans les clôtures supplémentaires de démarque"
+ AUTO_LINE_BREAKS: "Sauts de ligne automatiques"
+ AUTO_LINE_BREAKS_HELP: "Active la prise en charge des sauts de ligne automatiques dans markdown"
+ AUTO_URL_LINKS: "Liens URL automatiques"
+ AUTO_URL_LINKS_HELP: "Active la conversion automatique des URL en hyperliens HTML"
+ ESCAPE_MARKUP: "Échappement des balises HTML"
+ ESCAPE_MARKUP_HELP: "Échappe les balises en entités HTML"
+ CACHING_HELP: "Interrupteur global ON/OFF pour activer/désactiver la mise en cache Grav"
+ CACHE_CHECK_METHOD: "Méthode de contrôle du cache"
+ CACHE_CHECK_METHOD_HELP: "Sélectionnez la méthode que Grav utilisera pour contrôler si les fichiers de page ont été modifiés."
+ CACHE_DRIVER: "Gestionnaire de cache"
+ CACHE_DRIVER_HELP: "Sélectionner le gestionnaire de cache que Grav devrait utiliser. 'Auto Detect' permet de trouver le meilleur pour vous"
+ CACHE_PREFIX: "Préfixe du cache"
+ CACHE_PREFIX_HELP: "Un identificateur d'une partie de la clé Grav. Ne rien changer à moins que vous sachiez ce que vous faites."
+ CACHE_PREFIX_PLACEHOLDER: "Dérivé de l'URL de base (entrez une chaîne aléatoire pour remplacer)"
+ CACHE_PURGE_JOB: "Exécuter la tâche programmée de purge"
+ CACHE_PURGE_JOB_HELP: "Avec le planificateur, vous pouvez périodiquement effacer les anciens dossiers de cache et de fichiers de Doctrine"
+ CACHE_CLEAR_JOB: "Exécution d'une tâche de suppression planifiée"
+ CACHE_CLEAR_JOB_HELP: "Vous pouvez effacer le cache Grav avec le planificateur"
+ CACHE_JOB_TYPE: "Type de tâche de cache"
+ CACHE_JOB_TYPE_HELP: "Supprimer le cache des dossiers en utilisant la suppression 'standard', ou en utilisant 'tous' les dossiers"
+ CACHE_PURGE: "Purger toutes les caches"
+ LIFETIME: "Durée de vie"
+ LIFETIME_HELP: "Définit la durée de vie du cache en secondes. 0 = infinie"
+ GZIP_COMPRESSION: "Compression Gzip"
+ GZIP_COMPRESSION_HELP: "Active la compression GZip des pages Grav pour améliorer les performances."
+ TWIG_TEMPLATING: "Templating Twig"
+ TWIG_CACHING: "Mise en cache Twig"
+ TWIG_CACHING_HELP: "Contrôle le mécanisme de mise en cache de Twig. Laissez-le activé pour une meilleur performance."
+ TWIG_DEBUG: "Débogage Twig"
+ TWIG_DEBUG_HELP: "Autorise ou non l'option permettant de charger l'extension Twig Debugger"
+ DETECT_CHANGES: "Détecter les modifications"
+ DETECT_CHANGES_HELP: "Twig recompilera automatiquement le cache Twig si il détecte des modifications dans les modèles Twig"
+ AUTOESCAPE_VARIABLES: "Auto-échappement des variables"
+ AUTOESCAPE_VARIABLES_HELP: "Auto-échappe toutes les variables. Cela va probablement impacter votre site."
+ ASSETS: "Assets"
+ CSS_PIPELINE: "Concaténation CSS"
+ CSS_PIPELINE_HELP: "La concaténation CSS permet l'unification de multiples ressources CSS en un seul fichier"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Inclure les CSS externes dans la concaténation"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Il arrive parfois que les URL externes intègrent des références de fichiers relatives et ne devraient pas être soumises à la concaténation."
+ CSS_PIPELINE_BEFORE_EXCLUDES: "Traitement des CSS concaténés en premier"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Traiter les CSS concaténés avant les autres références CSS non intégrées."
+ CSS_MINIFY: "Minification CSS"
+ CSS_MINIFY_HELP: "Minification du CSS durant la procédure de concaténation"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "Substitution minification CSS Windows"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Minification pour plateformes Windows. Non par défaut en raison de ThreadStackSize"
+ CSS_REWRITE: "Réécriture CSS"
+ CSS_REWRITE_HELP: "Réécriture des URL relatives de CSS durant la procédure de concaténation"
+ JAVASCRIPT_PIPELINE: "Concaténation JavaScript"
+ JAVASCRIPT_PIPELINE_HELP: "La concaténation JS permet l'unification de multiples ressources JS en un seul fichier"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Inclure les JS externes dans la concaténation"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Il arrive parfois que les URL externes intègrent des références de fichiers relatives et ne devraient pas être soumises à la concaténation."
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Traitement des JS concaténés en premier"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Traiter les JS concaténés avant les autres références JS non intégrées."
+ JAVASCRIPT_MINIFY: "Minification JavaScript"
+ JAVASCRIPT_MINIFY_HELP: "Minification du JS durant la procédure de concaténation"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Active l'horodatage des assets"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Active l'horodatage des assets"
+ COLLECTIONS: "Collections"
+ ERROR_HANDLER: "Gestionnaire des erreurs"
+ DISPLAY_ERRORS: "Afficher les erreurs"
+ DISPLAY_ERRORS_HELP: "Affiche une page d'erreurs détaillée"
+ LOG_ERRORS: "Journal des erreurs"
+ LOG_ERRORS_HELP: "Journal des erreurs pour le dossier /logs"
+ LOG_HANDLER: "Gestionnaire de journal"
+ LOG_HANDLER_HELP: "Où sortir les journaux"
+ SYSLOG_FACILITY: "Installation Syslog"
+ SYSLOG_FACILITY_HELP: "Installation Syslog pour sortie"
+ DEBUGGER: "Débogueur"
+ DEBUGGER_HELP: "Active le débogueur Grav et les paramètres associés"
+ DEBUG_TWIG: "Débogage Twig"
+ DEBUG_TWIG_HELP: "Active le débogage des templates Twig"
+ SHUTDOWN_CLOSE_CONNECTION: "Shutdown ferme la connexion"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Ferme la connexion avant d'appeler onShutdown(). Sur 'false' pour débogage."
+ DEFAULT_IMAGE_QUALITY: "Qualité d'image par défaut"
+ DEFAULT_IMAGE_QUALITY_HELP: "Qualité d'image par défaut à utiliser lors du rééchantillonnage ou de la mise en cache des images (85%)."
+ CACHE_ALL: "Mise en cache de toutes les images"
+ CACHE_ALL_HELP: "Gérer toutes les images via le système de cache de Grav même si il n'y pas de manipulations de média"
+ IMAGES_DEBUG: "Filigrane de débogage d'image"
+ IMAGES_DEBUG_HELP: "Affiche une incrustation sur les images indiquant la profondeur de pixel de l'image lorsque l'on travaille avec retina par exemple."
+ IMAGES_SEOFRIENDLY: "Noms d'images optimisés SEO"
+ IMAGES_SEOFRIENDLY_HELP: "Lorsque activé, le nom de l'image est affiché en premier, puis un condensé plus petit pour refléter les opérations traitées"
+ UPLOAD_LIMIT: "Taille limite d'envoi pour un fichier"
+ UPLOAD_LIMIT_HELP: "Définir la taille maximum de chargement en octets (0 pour illimitée)"
+ ENABLE_MEDIA_TIMESTAMP: "Active l'horodatage sur les médias"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Ajoute un horodatage basé sur la dernière date de modification à chaque objet média"
+ SESSION: "Session"
+ SESSION_ENABLED_HELP: "Activer le support de session au sein de Grav"
+ TIMEOUT: "Durée de session"
+ TIMEOUT_HELP: "Définit la durée d'activité de la session en secondes"
+ SESSION_NAME_HELP: "Identifiant utilisé pour composer le nom du cookie de session"
+ SESSION_UNIQUENESS: "Chaîne unique"
+ SESSION_UNIQUENESS_HELP: "hash MD5 du chemin racine de Grav, ex `GRAV_ROOT' (par défaut) ou un basé sur la chaîne aléatoire `security.salt'."
+ ABSOLUTE_URLS: "URL absolues"
+ ABSOLUTE_URLS_HELP: "URL absolues ou relatives pour 'base_url'"
+ PARAMETER_SEPARATOR: "Séparateur de paramètres"
+ PARAMETER_SEPARATOR_HELP: "Séparateur pour les paramètres passés qui peuvent être changés pour Apache sur Windows"
+ TASK_COMPLETED: "Tâches complétées"
+ EVERYTHING_UP_TO_DATE: "Tout est à jour"
+ UPDATES_ARE_AVAILABLE: "des mises à jour sont disponibles"
+ IS_AVAILABLE_FOR_UPDATE: "est disponible pour mise à jour"
+ IS_NOW_AVAILABLE: "est maintenant disponible"
+ CURRENT: "Actuel"
+ UPDATE_GRAV_NOW: "Mettre à jour Grav maintenant"
+ GRAV_SYMBOLICALLY_LINKED: "Grav est symboliquement lié. Les mises à jour ne seront pas disponibles"
+ UPDATING_PLEASE_WAIT: "Mise à jour... veuillez patienter, téléchargement"
+ OF_THIS: "de ce"
+ OF_YOUR: "de votre"
+ HAVE_AN_UPDATE_AVAILABLE: "a une mise à jour disponible"
+ SAVE_AS: "Enregistrer sous"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Êtes-vous sûr de vouloir supprimer cette page et tous ses enfants ? Si la page est traduite dans d'autres langues, ces traductions seront conservées et devront être supprimées séparément. Sinon, le dossier de la page sera supprimé avec ses sous-pages. Cette action ne pourra pas être annulée."
+ AND: "et"
+ UPDATE_AVAILABLE: "Mise à jour disponible"
+ METADATA_KEY: "Clé (ex : 'mots clés')"
+ METADATA_VALUE: "Valeur (ex : 'Blog, Grav')"
+ USERNAME_HELP: "Le nom d'utilisateur doit être composé de 3 à 16 caractères, pouvant inclure des lettres minuscules, nombres, caractère de soulignement et traits d'union. Les lettres majuscules, espaces et caractères spéciaux ne sont pas autorisés."
+ FULLY_UPDATED: "À jour"
+ SAVE_LOCATION: "Enregistrer vers"
+ PAGE_FILE: "Template de page"
+ PAGE_FILE_HELP: "Le nom du fichier template de la page, et par défaut, le template d'affichage pour cette page"
+ NO_USER_ACCOUNTS: "Aucun compte utilisateur trouvé. Veuillez en créer un..."
+ NO_USER_EXISTS: "Aucun utilisateur local n'existe pour ce compte, impossible de sauvegarder..."
+ REDIRECT_TRAILING_SLASH: "Slash final de redirection"
+ REDIRECT_TRAILING_SLASH_HELP: "Effectue une redirection de type 301 au lieu de traiter de façon transparente le slash final d'URI."
+ DEFAULT_DATE_FORMAT: "Format de date des pages"
+ DEFAULT_DATE_FORMAT_HELP: "Format de la date des pages utilisé par Grav. Par défaut, Grav essaie de deviner votre format de date, toutefois vous pouvez spécifier un format en utilisant la syntaxe des dates de PHP (ex : d-m-Y H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Sélectionner automatiquement"
+ IGNORE_FILES: "Ignorer les fichiers"
+ IGNORE_FILES_HELP: "Fichiers spécifiques à ignorer lors du traitement des pages"
+ IGNORE_FOLDERS: "Ignorer les dossiers"
+ IGNORE_FOLDERS_HELP: "Dossiers spécifiques à ignorer lors du traitement des pages"
+ HIDE_EMPTY_FOLDERS: "Cacher les répertoires vides"
+ HIDE_EMPTY_FOLDERS_HELP: "Si le répertoire n'a pas de fichier .md, doit-il être caché à la navigation tout en étant non-routable"
+ HTTP_ACCEPT_LANGUAGE: "Définir la langue à partir du navigateur"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Vous pouvez tenter de définir la langue en se basant sur la balise d'en-tête http_accept_language du navigateur."
+ OVERRIDE_LOCALE: "Remplacer le réglage de langue PHP"
+ OVERRIDE_LOCALE_HELP: "Substituer le réglage de langue dans PHP par la langue actuelle"
+ REDIRECT: "Redirection de page"
+ REDIRECT_HELP: "Saisir une route ou une URL externe vers laquelle cette page sera redirigée. ex : '/une/route/' ou 'http://site-externe.com'"
+ PLUGIN_STATUS: "Statut du plugin"
+ INCLUDE_DEFAULT_LANG: "Inclure la langue par défaut"
+ INCLUDE_DEFAULT_LANG_HELP: "Toutes les URL de langue seront préfixées avec la langue par défaut. ex : `/fr/blog/mon-post`"
+ PAGES_FALLBACK_ONLY: "Pages de secours uniquement"
+ PAGES_FALLBACK_ONLY_HELP: "Seul 'repli' pour trouver le contenu de la page à travers les langues supportées, le comportement par défaut est d'afficher n'importe quelle langue trouvée si la langue active manque."
+ ALLOW_URL_TAXONOMY_FILTERS: "Filtres de taxonomie d'URL"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Les collections basées sur des pages vous permettent de filtrer via `/taxonomy:valeur`."
+ REDIRECT_DEFAULT_CODE: "Code de redirection par défaut"
+ REDIRECT_DEFAULT_CODE_HELP: "Le code du statut HTTP à utiliser pour les redirections"
+ IGNORE_HIDDEN: "Ignorer les fichiers cachés"
+ IGNORE_HIDDEN_HELP: "Ignorer tous les fichiers et dossiers qui commencent par un point"
+ WRAPPED_SITE: "Site encapsulé"
+ WRAPPED_SITE_HELP: "Pour que les thèmes/plugins sachent si Grav est intégré par une autre plateforme"
+ FALLBACK_TYPES: "Types de fallback autorisés"
+ FALLBACK_TYPES_HELP: "Types de fichiers autorisés qui peuvent être trouvés via le chemin des pages. Par défaut, tous les types de fichier sont supportés."
+ INLINE_TYPES: "Types de fichiers en ligne"
+ INLINE_TYPES_HELP: "Une liste de types de fichier qui peuvent être affichés en ligne plutôt que d'être téléchargés."
+ APPEND_URL_EXT: "Ajouter l'extension à l'URL"
+ APPEND_URL_EXT_HELP: "Ajoutera une extension personnalisée à l'URL de la page. Remarque : cela implique que Grav va rechercher le template d'affichage `..twig`."
+ PAGE_MODES: "Modes de page"
+ PAGE_TYPES: "Types de page"
+ PAGE_TYPES_HELP: "Détermine les types de pages pris en charge par Grav et l'ordre détermine à quel type de page revenir si la requête est ambiguë"
+ ACCESS_LEVELS: "Niveaux d'accès"
+ GROUPS: "Groupes"
+ GROUPS_HELP: "Liste des groupes dont l'utilisateur fait partie"
+ ADMIN_ACCESS: "Accès à l'administration"
+ SITE_ACCESS: "Accès au site"
+ INVALID_SECURITY_TOKEN: "Jeton de sécurité invalide"
+ ACTIVATE: "Activer"
+ TWIG_UMASK_FIX: "Correctif Umask"
+ TWIG_UMASK_FIX_HELP: "Par défaut, Twig va créer des fichiers en 0755, le correctif va les basculer en 0775."
+ CACHE_PERMS: "Permissions des caches"
+ CACHE_PERMS_HELP: "Permissions pour les dossiers de cache par défaut. Habituellement en 0755 ou 0775 en fonction des réglages."
+ REMOVE_SUCCESSFUL: "Suppression réussie"
+ REMOVE_FAILED: "Échec de la suppression"
+ HIDE_HOME_IN_URLS: "Masquer le chemin de l'accueil dans les URL"
+ HIDE_HOME_IN_URLS_HELP: "Assurera que les chemins par défaut pour toutes les pages dépendantes de la page d'accueil ne feront pas référence au chemin normalement utilisé pour la page d'accueil"
+ TWIG_FIRST: "Traiter Twig en premier"
+ TWIG_FIRST_HELP: "Si vous avez activé le traitement Twig de la page, vous pouvez définir le traitement de Twig avant ou après celui de markdown."
+ SESSION_SECURE: "Sécuriser"
+ SESSION_SECURE_HELP: "Si défini sur Oui, indique que la communication pour ce cookie doit se faire via une transmission cryptée. AVERTISSEMENT : à activer uniquement sur les sites qui fonctionnent exclusivement en HTTPS."
+ SESSION_HTTPONLY: "HTTP uniquement"
+ SESSION_HTTPONLY_HELP: "Si défini sur Oui, indique que les cookies doivent être utilisés uniquement pour les sites en HTTP et que la modification JavaScript n'est pas autorisée."
+ REVERSE_PROXY: "Proxy inversé"
+ REVERSE_PROXY_HELP: "Activer si vous êtes derrière un proxy inversé et que vous rencontrez des problèmes avec les URL contenant des ports incorrects."
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Frontmatter non valide, enregistrement impossible"
+ ADD_FOLDER: "Ajouter un dossier"
+ PROXY_URL: "URL du proxy"
+ PROXY_URL_HELP: "Indiquez le proxy HÔTE ou l'IP et le PORT"
+ NOTHING_TO_SAVE: "Rien à enregistrer"
+ FILE_ERROR_ADD: "Une erreur s'est produite lors de la tentative d'ajout du fichier"
+ FILE_ERROR_UPLOAD: "Une erreur s'est produite lors de la tentative de chargement du fichier"
+ FILE_UNSUPPORTED: "Type de fichier non pris en charge"
+ ADD_ITEM: "Ajouter un élément"
+ FILE_TOO_LARGE: "Le fichier est trop volumineux pour être téléchargé, le maximum autorisé est de %s selon vos paramètres PHP. Veuillez augmenter votre paramètre PHP `post_max_size`."
+ INSTALLING: "Installation en cours"
+ LOADING: "Chargement..."
+ DEPENDENCIES_NOT_MET_MESSAGE: "Les dépendances suivantes sont requises pour pouvoir continuer :"
+ ERROR_INSTALLING_PACKAGES: "Erreur lors de l'installation de(s) paquet(s)"
+ INSTALLING_DEPENDENCIES: "Installation des dépendances..."
+ INSTALLING_PACKAGES: "Installation de paquet(s)..."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Paquet(s) installé(s) avec succès."
+ READY_TO_INSTALL_PACKAGES: "Prêt pour l'installation de paquet(s)"
+ PACKAGES_NOT_INSTALLED: "Paquets non installés"
+ PACKAGES_NEED_UPDATE: "Paquets déjà installés, mais obsolètes"
+ PACKAGES_SUGGESTED_UPDATE: "Paquets installés dont la version est valide, mais qui seront mis à jour vers la dernière version"
+ REMOVE_THE: "Supprimer le %s"
+ CONFIRM_REMOVAL: "Êtes-vous sûr de vouloir supprimer ce %s ?"
+ REMOVED_SUCCESSFULLY: "%s a été supprimé avec succès"
+ ERROR_REMOVING_THE: "Erreur lors de la suppression de %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "%s possédait les dépendances suivantes qui ne sont pas requises par d'autres paquets installés. Si vous ne les utilisez pas, vous pouvez les supprimer directement ici."
+ READY_TO_UPDATE_PACKAGES: "Prêt à mettre à jour le(s) paquet(s)"
+ ERROR_UPDATING_PACKAGES: "Erreur lors de la mise à jour de(s) paquet(s)"
+ UPDATING_PACKAGES: "Mise à jour de(s) paquet(s) en cours..."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Paquet(s) mis à jour avec succès."
+ UPDATING: "Mise à jour"
+ GPM_RELEASES: "Versions GPM"
+ GPM_RELEASES_HELP: "Choisir 'En test' pour installer les versions bêta ou de test"
+ GPM_METHOD: "Méthode de récupération à distance"
+ GPM_METHOD_HELP: "Lorsque réglé sur Auto, Grav va déterminer si fopen est disponible et l'utiliser sinon, il utilisera cURL. Pour forcer l’utilisation de l’un ou l’autre, il suffit de changer le réglage."
+ GPM_VERIFY_PEER: "Vérification à distance Peer (SSL)"
+ GPM_VERIFY_PEER_HELP: "Certains fournisseurs semblent échouer lors de la vérification du certificat SSL getgrav.org empèchant GPM de fonctionner. Si c’est le cas pour vous, désactiver ce paramètre peut aider."
+ AUTO: "Auto"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stable"
+ TESTING: "En test"
+ FRONTMATTER_PROCESS_TWIG: "Traitement du Twig dans Frontmatter"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Si activé, vous pouvez utiliser des variables de configuration Twig dans le frontmatter de la page."
+ FRONTMATTER_IGNORE_FIELDS: "Ignorer les champs frontmatter"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Certains champs frontmatter peuvent contenir du Twig qui ne devraient pas être traités, les formulaires par exemple."
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Paquet %s installé avec succès"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "L'ordre est défini par le parent, le tri est désactivé"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "La page n'est pas visible, le tri est désactivé"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "La gestion de l'ordre des pages n'est pas disponible dans l'interface d'administration au-delà de 200 éléments"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Le classement des pages est désactivé pour cette page car l'option Préfixe Numérique du dossier n'est pas activé"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "Remarque : Vous ne pouvez pas ajouter de fichiers multimédias avant d'avoir enregistré la page. Cliquez sur `Enregistrer` en haut."
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "Remarque : La Page doit être enregistrée avant de pouvoir y télécharger des fichiers."
+ DROP_FILES_HERE_TO_UPLOAD: "Déposez vos fichiers ici ou cliquez dans cette zone "
+ INSERT: "Insérer"
+ UNDO: "Annuler"
+ REDO: "Rétablir"
+ HEADERS: "En-têtes"
+ BOLD: "Gras"
+ ITALIC: "Italique"
+ STRIKETHROUGH: "Barré"
+ SUMMARY_DELIMITER: "Délimiteur de résumé"
+ LINK: "Lien"
+ IMAGE: "Image"
+ BLOCKQUOTE: "Citation"
+ UNORDERED_LIST: "Liste non ordonnée"
+ ORDERED_LIST: "Liste ordonnée"
+ EDITOR: "Éditeur"
+ PREVIEW: "Aperçu"
+ FULLSCREEN: "Plein écran"
+ MODULAR: "Modulaire"
+ MODULAR_SETUP: "Configuration modulaire"
+ NON_ROUTABLE: "Non routable"
+ NON_MODULAR: "Non modulaire"
+ NON_VISIBLE: "Non visible"
+ NON_PUBLISHED: "Non publié"
+ CHARACTERS: "caractères"
+ PUBLISHING: "Publication"
+ NOTIFICATIONS: "Notifications"
+ MEDIA_TYPES: "Types de média"
+ IMAGE_OPTIONS: "Options d'image"
+ MIME_TYPE: "Type Mime"
+ THUMB: "Miniature"
+ TYPE: "Type"
+ FILE_EXTENSION: "Extension de fichier"
+ LEGEND: "Légende de la page"
+ MEMCACHE_SERVER: "Serveur Memcache"
+ MEMCACHE_SERVER_HELP: "L’adresse du serveur Memcache"
+ MEMCACHE_PORT: "Port Memcache"
+ MEMCACHE_PORT_HELP: "Le port du serveur Memcache"
+ MEMCACHED_SERVER: "Serveur Memcached"
+ MEMCACHED_SERVER_HELP: "L’adresse du serveur Memcached"
+ MEMCACHED_PORT: "Port Memcached"
+ MEMCACHED_PORT_HELP: "Le port du serveur Memcached"
+ REDIS_SERVER: "Serveur Redis"
+ REDIS_SERVER_HELP: "L'adresse du serveur Redis"
+ REDIS_PORT: "Port Redis"
+ REDIS_PORT_HELP: "Le port du serveur Redis"
+ REDIS_PASSWORD: "mot de passe/secret Redis"
+ ALL: "Tous"
+ FROM: "de"
+ TO: "à"
+ RELEASE_DATE: "Date de sortie"
+ SORT_BY: "Trier par"
+ RESOURCE_FILTER: "Filtre..."
+ FORCE_SSL: "Forcer SSL"
+ FORCE_SSL_HELP: "Forcer SSL globalement. Si activé, lorsque le site est accessible via HTTP, Grav envoie une redirection vers la page HTTPS."
+ NEWS_FEED: "Fil d'actualité"
+ EXTERNAL_URL: "URL externe"
+ SESSION_PATH: "Le chemin d’accès de la session"
+ SESSION_PATH_HELP: "Utiliser uniquement si vous choisissez une URL de base personnalisée (réécriture du domaine de site / sous-dossier)"
+ CUSTOM_BASE_URL: "URL de base personnalisée"
+ CUSTOM_BASE_URL_HELP: "À utiliser si vous souhaitez réécrire le domaine du site ou utiliser un sous-dossier différent de celui utilisé par Grav. Par exemple : http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'Impossible d’utiliser "%s" en dehors des pages.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Impossible de charger le fichier %s : %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'Impossible de déplacer le fichier %s vers "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'Annuler le chargement'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Êtes-vous sûr de vouloir annuler ce chargement ?'
+ DROPZONE_DEFAULT_MESSAGE: 'Déposez vos fichiers ici ou cliquez dans cette zone '
+ DROPZONE_FALLBACK_MESSAGE: 'Votre navigateur ne prend pas en charge le téléversement de fichier par glissé-déposé.'
+ DROPZONE_FALLBACK_TEXT: 'Veuillez utiliser le formulaire classique ci-dessous pour téléverser vos fichiers comme dans le passé.'
+ DROPZONE_FILE_TOO_BIG: 'Le fichier est trop lourd ({{filesize}} Mo). La taille maximale est de {{maxFilesize}} Mo.'
+ DROPZONE_INVALID_FILE_TYPE: "Vous ne pouvez pas télécharger des fichiers de ce type."
+ DROPZONE_MAX_FILES_EXCEEDED: "Vous ne pouvez plus téléverser de fichiers."
+ DROPZONE_REMOVE_FILE: "Supprimer le fichier"
+ DROPZONE_RESPONSE_ERROR: "Le serveur a répondu avec le code {{statusCode}}."
+ PREMIUM_PRODUCT: "Prémium"
+ ADMIN_CACHING: "Activer le cache d’administration"
+ ADMIN_CACHING_HELP: "La mise en cache dans l’administration peut être contrôlée indépendamment de celui du front-end."
+ DESTINATION_NOT_SPECIFIED: "Destination non spécifée"
+ UPLOAD_ERR_NO_TMP_DIR: "Il manque un dossier temporaire"
+ SESSION_SPLIT: "Session Split"
+ SESSION_SPLIT_HELP: "Division de sessions indépendantes entre le site et d’autres plugins (par exemple celui d'administration)"
+ ERROR_FULL_BACKTRACE: "Erreur complète Backtrace"
+ ERROR_SIMPLE: "Erreur simple"
+ ERROR_SYSTEM: "Erreur système"
+ IMAGES_AUTO_FIX_ORIENTATION: "Ajuster automatiquement l’orientation"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Ajuster automatiquement l’orientation de l’image en fonction des données Exif"
+ REDIS_SOCKET: "Connecteur Redis"
+ REDIS_SOCKET_HELP: "Le connecteur Redis"
+ NOT_SET: "Non défini"
+ PERMISSIONS: "Permissions"
+ NEVER_CACHE_TWIG: "Ne jamais mettre en cache Twig"
+ NEVER_CACHE_TWIG_HELP: "Systématiquement pour les pages : ne mettre en cache que le contenu et lancer Twig. Ignore le paramètre twig_first."
+ ALLOW_WEBSERVER_GZIP: "Autorisé Gzip WebServer"
+ ALLOW_WEBSERVER_GZIP_HELP: "Option désactivée par défaut. Si activée, la compression Gzip/Deflate configurée pour le serveur Web fonctionnera, mais la connexion http ne sera pas fermée avant l'événement onShutDown(), avec pour résultat un chargement de page plus lent"
+ OFFLINE_WARNING: "Impossible d’établir la connexion au Gestionnaire de Paquets Grav (GPM)"
+ CLEAR_IMAGES_BY_DEFAULT: "Vider le cache image par défaut"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "Par défaut, les images traitées sont effacées pour chaque nettoyage du cache, ceci peut être désactivé"
+ CLI_COMPATIBILITY: "Compatibilité CLI"
+ CLI_COMPATIBILITY_HELP: "Vérifie que seuls les pilotes non-volatiles du cache sont utilisés (fichier, redis, memcache, etc.)"
+ REINSTALL_PLUGIN: "Réinstaller le Plugin"
+ REINSTALL_THEME: "Réinstaller le thème"
+ REINSTALL_THE: "Réinstaller le %s"
+ CONFIRM_REINSTALL: "Êtes-vous certain de vouloir réinstaller ce %s?"
+ REINSTALLED_SUCCESSFULLY: "%s réinstallé avec succès"
+ ERROR_REINSTALLING_THE: "Erreur lors de la réinstallation de %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Le package %s a été réinstallé avec succès"
+ REINSTALLATION_FAILED: "Échec de la réinstallation"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "La version installée actuellement n'est pas la plus récente. En cliquant sur continuer, vous supprimerez la version actuelle et installerez la dernière version disponible"
+ TOOLS: "Outils"
+ DIRECT_INSTALL: "Installation directe"
+ NO_PACKAGE_NAME: "Nom du package non spécifié"
+ PACKAGE_EXTRACTION_FAILED: "Echec de l'extraction du package"
+ NOT_VALID_GRAV_PACKAGE: "Package Grav invalide"
+ NAME_COULD_NOT_BE_DETERMINED: "Le nom n’a pas pu être déterminé"
+ CANNOT_OVERWRITE_SYMLINKS: "Impossible d'écraser les symlinks"
+ ZIP_PACKAGE_NOT_FOUND: "Archive ZIP du package introuvable"
+ GPM_OFFICIAL_ONLY: "Uniquement GPM officiel"
+ GPM_OFFICIAL_ONLY_HELP: "N'autoriser l'installation directe que depuis le dépôt GPM officiel."
+ NO_CHILD_TYPE: "Aucun type d’enfant pour cette rawroute"
+ SORTABLE_PAGES: "Pages triables:"
+ UNSORTABLE_PAGES: "Pages non triables"
+ ADMIN_SPECIFIC_OVERRIDES: "Exceptions relatives à l'admin"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Ordre d'affichage des enfants"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "L'ordre dans lequel les enfants de cette page doivent être affichés dans la vue 'Pages' du plugin Admin"
+ PWD_PLACEHOLDER: "chaîne complexe longue d'au moins 8 caractères"
+ PWD_REGEX: "Règles d'exclusion du mdp"
+ PWD_REGEX_HELP: "Par défaut : le mot de passe doit contenir au moins un nombre et une majuscule/minuscule, et au moins 8 caractères"
+ USERNAME_PLACEHOLDER: "minuscules seulement, ex. 'admin'"
+ USERNAME_REGEX: "Règles d'exclusion du pseudo"
+ USERNAME_REGEX_HELP: "Par défaut : uniquement minuscules, chiffres, tirets et underscores. Longueur : 3 - 16"
+ CONTENT_PADDING: "Bordure du contenu"
+ CONTENT_PADDING_HELP: "Activer/désactiver la bordure autour de la zone de contenu pour laisser plus d'espace"
+ ENABLE_AUTO_METADATA: "Metadonnées automatiques par Exif"
+ ENABLE_AUTO_METADATA_HELP: "Générer automatiquement des métadonnées pour les images avec les informations exif"
+ 2FA_TITLE: "Authentification à deux facteurs"
+ 2FA_INSTRUCTIONS: "##### Authentification à deux facteurs\nL'**authentification à deux facteurs** est activée sur ce compte. Veuillez utiliser l'application **A2F** pour entrer le **code à 6 chiffres** actuel afin de terminer le processus de connexion."
+ 2FA_REGEN_HINT: "Si vous regénérez la clef d'authentification, vous devrez mettre à jour la configuration de votre application d'authentification en deux étapes"
+ 2FA_LABEL: "Accès administrateur"
+ 2FA_FAILED: "Code d'authentification 2-Factor invalide, veuillez réessayer..."
+ 2FA_ENABLED: "A2F activée"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "Clef d'authentification pour l'application A2F"
+ 2FA_SECRET_HELP: "Scannez ce QR Code dans votre [application d'authentification à deux étapes](https://learn.getgrav.org/admin-panel/2fa#apps). Aussi, il serait une bonne idée de sauvegarder la clef de configuration en lieu sûr, dans le cas où votre application serait réinitialisée. Consultez la [documentation de Grav](https://learn.getgrav.org/admin-panel/2fa) pour plus d'informations "
+ 2FA_REGENERATE: "Régénérer"
+ FORCE_LOWERCASE_URLS: "Forcer les URL en minuscule"
+ FORCE_LOWERCASE_URLS_HELP: "Par défaut, Grav s'assurera que tous les slugs et les URLs seront en minuscules. En désactivant cette option, des slugs et URLs en majuscules pourront être utilisées"
+ INTL_ENABLED: "Intégration du module Intl"
+ INTL_ENABLED_HELP: "Utiliser le module PHP Intl et collate pour trier les collections enregistrées en UTF-8"
+ VIEW_SITE_TIP: "Voir le site"
+ TOOLS_DIRECT_INSTALL_TITLE: "Installation directe de paquets Grav"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "Installer un paquet en téléversant une archive ZIP"
+ TOOLS_DIRECT_INSTALL_UPLOAD_DESC: "Vous pouvez facilement installer un plugin ou un thème valide, ou même une mise à jour de Grav lui-même sous la forme d'un paquet compressé en ZIP, via cette méthode. Ce paquet n'a pas à être enregistré via GPM, ce qui vous permet de facilement revenir à une version antérieure d'un paquet, ou installer une version de tests."
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "Installer un paquet depuis une adresse distante"
+ TOOLS_DIRECT_INSTALL_URL_DESC: "Alternativement, vous pouvez également référencer une URL complète vers l'archive ZIP du paquet, afin que Grav l'installe en téléchargeant le fichier depuis cette URL."
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Télécharger et installer"
+ ROUTE_OVERRIDES: "Surcharge de la route"
+ ROUTE_DEFAULT: "Route par défaut"
+ ROUTE_CANONICAL: "Route canonique"
+ ROUTE_ALIASES: "Alias de la route"
+ OPEN_NEW_TAB: "Ouvrir dans un nouvel onglet"
+ PAGEMEDIA_RESIZER: "> Les paramètres suivants s'appliquent au images téléchargées par le biais des pages média. Redimensionner Largeur / Hauteur redimensionnera automatiquement et proportionnellement une image si elle dépasse les limites définies dans les entrées. Les valeurs minimales et maximales de résolution définissent les plages de taille des images téléchargées. Définissez les champs à 0 pour éviter toute manipulation."
+ SESSION_INITIALIZE: "Initialiser la Session"
+ SESSION_INITIALIZE_HELP: "Fait démarrer une session à Grav. Cette fonction est nécessaire pour faire fonctionner toute interaction de l'utilisateur, comme la connexion, les formulaires, etc. Le plugin Admin n'est pas affecté par ce paramètre."
+ STRICT_YAML_COMPAT: "Compatibilité YAML"
+ STRICT_YAML_COMPAT_HELP: "Retourne à l'analyseur Symfony 2.4 YAML si l'analyseur Natif ou 3.4 échoue"
+ STRICT_TWIG_COMPAT: "Compatibilité Twig"
+ STRICT_TWIG_COMPAT_HELP: "Permet le réglage obsolète de l'échappement automatique Twig. Lorsqu'il est désactivé, le filtre |brut est nécessaire pour sortir du HTML car Twig va automatiquement sortir de l'échappement."
+ SCHEDULER: "Planificateur"
+ SCHEDULER_INSTALL_INSTRUCTIONS: "Instructions d'installation"
+ SCHEDULER_INSTALLED_READY: "Installé et prêt"
+ SCHEDULER_CRON_NA: "Cron Non Disponible"
+ SCHEDULER_NOT_ENABLED: "Non Actif"
+ SCHEDULER_SETUP: "Configuration du planificateur"
+ SCHEDULER_INSTRUCTIONS: "Le Planificateur Grav vous permet de créer et de planifier des travaux personnalisés. Il fournit également une méthode permettant aux plugins Grav d'intégrer de manière programmatique et dynamique des tâches à exécuter périodiquement."
+ SCHEDULER_POST_INSTRUCTIONS: "Pour activer la fonctionnalité du planificateur, vous devez ajouter le Planificateur Grav au fichier crontab de votre système. Lancez la commande ci-dessus depuis le terminal pour l'ajouter automatiquement. Une fois sauvegardée, actualisez cette page pour voir l'état Prêt."
+ SCHEDULER_JOBS: "Tâches du planificateur personnalisées"
+ SCHEDULER_STATUS: "Status du planificateur"
+ SCHEDULER_RUNAT: "Exécuter à"
+ SCHEDULER_RUNAT_HELP: "Syntaxe formatée 'at'"
+ SCHEDULER_OUTPUT: "Fichier de sortie"
+ SCHEDULER_OUTPUT_HELP: "Le chemin/nom du fichier de sortie (à partir de la racine de l'installation Grav)"
+ SCHEDULER_OUTPUT_TYPE: "Type de sortie"
+ SCHEDULER_OUTPUT_TYPE_HELP: "Ajouter chaque exécution au fichier, ou écraser le fichier à chaque exécution"
+ SCHEDULER_EMAIL: "Email"
+ SCHEDULER_EMAIL_HELP: "Destinataire du fichier de sortie. Nécessite que le fichier de sortie soit défini"
+ SECURITY: "Sécurité"
+ XSS_SECURITY: "Sécurité XSS pour le Contenu"
+ XSS_WHITELIST_PERMISSIONS: "Permissions pour Liste blanche"
+ XSS_WHITELIST_PERMISSIONS_HELP: "Les Utilisateurs avec ces permissions ignoreront les règles XSS lors de l'enregistrement de contenu"
+ XSS_ON_EVENTS: "Filtrer les événements"
+ XSS_INVALID_PROTOCOLS: "Filtrer les protocoles invalides"
+ XSS_INVALID_PROTOCOLS_LIST: "Liste de protocoles invalides"
+ XSS_MOZ_BINDINGS: "Filtrer les liaisons Moz"
+ XSS_HTML_INLINE_STYLES: "Filtrer les styles HTML en ligne"
+ XSS_DANGEROUS_TAGS: "Filtrer les balises HTML dangereuses"
+ XSS_DANGEROUS_TAGS_LIST: "Liste des balises HTML dangereuses"
+ XSS_ONSAVE_ISSUE: "Sauvegarde échouée: problème XSS détecté..."
+ XSS_ISSUE: "NOTICE: Grav a trouvé des problèmes potentiels d'XSS dans %s "
+ UPLOADS_SECURITY: "Téléchargements de sécurité"
+ UPLOADS_DANGEROUS_EXTENSIONS: "Extensions dangereuses"
+ UPLOADS_DANGEROUS_EXTENSIONS_HELP: "Empêcher ces extensions d'être téléchargées peu importe les types MIME acceptés"
+ REPORTS: "Raports"
+ LOGS: "Journaux"
+ LOG_VIEWER_FILES: "Visionneuse de fichiers journaux"
+ LOG_VIEWER_FILES_HELP: "Fichiers dans /logs/ qui seront disponibles pour visualiser dans Outils - Journaux ex. 'grav' = /logs/grav.log"
+ BACKUPS_STORAGE_PURGE_TRIGGER: "Déclenchement de purge des sauvegardes"
+ BACKUPS_MAX_COUNT: "Nombre maximum de sauvegardes"
+ BACKUPS_MAX_COUNT_HELP: "0 pour illimité"
+ BACKUPS_MAX_SPACE: "Espace de sauvegarde maximum"
+ BACKUPS_MAX_RETENTION_TIME: "Durée de conservation maximum"
+ BACKUPS_MAX_RETENTION_TIME_APPEND: "en jours"
+ BACKUPS_PROFILE_NAME: "Nom de la sauvegarde"
+ BACKUPS_PROFILE_ROOT_FOLDER: "Dossier racine"
+ BACKUPS_PROFILE_ROOT_FOLDER_HELP: "Cela peut être un chemin absolu ou un flux"
+ BACKUPS_PROFILE_EXCLUDE_PATHS: "Exclure les chemins"
+ BACKUPS_PROFILE_EXCLUDE_PATHS_HELP: "Chemins absolus à exclure, un par ligne"
+ BACKUPS_PROFILE_EXCLUDE_FILES: "Exclure les fichiers"
+ BACKUPS_PROFILE_EXCLUDE_FILES_HELP: "Fichiers ou dossiers spécifiques à exclure, un par ligne"
+ BACKUPS_PROFILE_SCHEDULE: "Activer la planification"
+ BACKUPS_PROFILE_SCHEDULE_AT: "Exécuter la planification"
+ COMMAND: "Commande"
+ EXTRA_ARGUMENTS: "Arguments supplémentaires"
diff --git a/user/plugins/admin/languages/he.yaml b/user/plugins/admin/languages/he.yaml
new file mode 100644
index 0000000..42a8c1d
--- /dev/null
+++ b/user/plugins/admin/languages/he.yaml
@@ -0,0 +1,418 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "זוהי מהדורת ביתא! השימוש על אחריותכם בלבד..."
+ ADMIN_REPORT_ISSUE: "GitHub נמצאה בעיה? נא לדווח עליה דרך"
+ EMAIL_FOOTER: "אתר זה מופעל על ידי גראב -מערכת ניהול בקובץ שטוח מודרני"
+ LOGIN_BTN: "התחברות"
+ LOGIN_BTN_FORGOT: "שכחת"
+ LOGIN_BTN_RESET: "איפוס סיסמה"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "שליחת הוראות לאיפוס"
+ LOGIN_BTN_CLEAR: "נקוי טופס"
+ LOGIN_BTN_CREATE_USER: "יצירת משתמש חדש"
+ LOGIN_LOGGED_IN: "הצלחת להתחבר"
+ LOGIN_FAILED: "התחברות נכשלה"
+ LOGGED_OUT: "התנתקת מהמערכת"
+ RESET_NEW_PASSWORD: "יש לחבר סיסמה חדשה …"
+ RESET_LINK_EXPIRED: "פג תוקף קישור האיפוס, יש לנסות שוב"
+ RESET_PASSWORD_RESET: "הסיסמא אופסה בהצלחה"
+ RESET_INVALID_LINK: "קישור לאיפוס לא חוקי, יש לנסות שוב"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "ההוראות לאיפוס הסיסמא נשלחו למייל שלך"
+ FORGOT_FAILED_TO_EMAIL: "נכשלה שליחת ההוראות לאיפוס, נא לנסות מאוחר יותר"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "אין אפשרות לאפס את הסיסמה עבור %s, כתובת המייל אינה רשומה"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "שם משתמש %s אינו קיים"
+ FORGOT_EMAIL_NOT_CONFIGURED: "אין אפשרות לאפס את הסיסמה. אתר זה אינו מוגדר לשלוח מיילים"
+ FORGOT_EMAIL_SUBJECT: "%s - בקשה לאיפוס סיסמה"
+ FORGOT_EMAIL_BODY: "איפוס סיסמא %1$s יקר,
הבקשה נשלחה ב %4$s בכדי לאפס את הסיסמא שלך
להקליק כאן בכדי לאפס סיסמא
או להעתיק את הקישור הבא לשורת הכתובות בדפדפן
%2$s
גלישה נעימה %3$s
"
+ MANAGE_PAGES: "ניהול עמודים"
+ CONFIGURATION: "הגדרות"
+ PAGES: "עמודים"
+ PLUGINS: "תוספים"
+ PLUGIN: "תוספים"
+ THEMES: "ערכות עיצוב"
+ LOGOUT: "התנתקות"
+ BACK: "בחזרה"
+ NEXT: "הבא"
+ PREVIOUS: "הקודם"
+ ADD_PAGE: "הוספת דף"
+ ADD_MODULAR: "הוסף מודולרי"
+ MOVE: "הזזה"
+ DELETE: "מחיקה"
+ VIEW: "תצוגה"
+ SAVE: "שמירה"
+ NORMAL: "מצב רגיל"
+ EXPERT: "מומחה"
+ EXPAND_ALL: "הרחבה להכל"
+ COLLAPSE_ALL: "כיווץ להכל"
+ ERROR: "שגיאה"
+ CLOSE: "סגירה"
+ CANCEL: "ביטול"
+ CONTINUE: "המשך"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "נדרש אישור"
+ MODAL_CHANGED_DETECTED_TITLE: "שינויים שאותרו"
+ MODAL_CHANGED_DETECTED_DESC: "ישנם שינויים שלא נשמרו. האם לצאת מדף זה ללא שמירה?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "נדרש אישור"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "האם למחוק את הקובץ? לא ניתן לבטל פעולה זו."
+ ADD_FILTERS: "הוספת מסנן"
+ SEARCH_PAGES: "חיפוש עמודים"
+ VERSION: "גירסא"
+ WAS_MADE_WITH: "נבנה עם"
+ BY: "על ידי"
+ UPDATE_THEME: "עדכון ערכת עיצוב"
+ UPDATE_PLUGIN: "עדכון תוסף"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "לערכת עיצוב זו זמין כעת"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "של תוסף זה זמין כעת"
+ AUTHOR: "המחבר"
+ HOMEPAGE: "דף הבית"
+ DEMO: "הדגמה"
+ BUG_TRACKER: "גשש באגים"
+ KEYWORDS: "מילות מפתח"
+ LICENSE: "רישיון"
+ DESCRIPTION: "תאור"
+ README: "מסמך מידע"
+ REMOVE_THEME: "הסרת ערכת עיצוב"
+ INSTALL_THEME: "התקנת ערכת עיצוב"
+ THEME: "ערכת עיצוב"
+ BACK_TO_THEMES: "חזרה אל ערכות העיצוב"
+ BACK_TO_PLUGINS: "בחזרה לתוספים"
+ CHECK_FOR_UPDATES: "בדיקת עדכונים"
+ ADD: "הוספה"
+ CLEAR_CACHE: "נקוי מטמון"
+ CLEAR_CACHE_ALL_CACHE: "כל המטמון"
+ CLEAR_CACHE_ASSETS_ONLY: "קבצי נכס בלבד"
+ CLEAR_CACHE_IMAGES_ONLY: "קבצי תמונה בלבד"
+ CLEAR_CACHE_CACHE_ONLY: "מטמון בלבד"
+ CLEAR_CACHE_TMP_ONLY: "קבצים זמניים בלבד"
+ DASHBOARD: "לוח בקרה"
+ UPDATES_AVAILABLE: "ישנם עדכונים זמינים"
+ DAYS: "ימים"
+ UPDATE: "עדכון"
+ BACKUP: "גיבוי"
+ STATISTICS: "סטטיסטיקות"
+ TODAY: "היום"
+ WEEK: "שבוע"
+ MONTH: "חודש"
+ LATEST_PAGE_UPDATES: "העדכונים האחרונים שנעשו בדף"
+ MAINTENANCE: "תחזוקה"
+ UPDATED: "עודכן"
+ MON: "ב׳"
+ TUE: "ג'"
+ WED: "ד'"
+ THU: "ה'"
+ FRI: "ו"
+ SAT: "שבת"
+ SUN: "א'"
+ COPY: "העתק"
+ EDIT: "עריכה"
+ CREATE: "צור"
+ GRAV_ADMIN: "מנהל גראב"
+ GRAV_OFFICIAL_PLUGIN: "תוסף רשמי של גראב"
+ GRAV_OFFICIAL_THEME: "ערכת עיצוב רשמית של גראב"
+ PLUGIN_SYMBOLICALLY_LINKED: "תוסף זה מקושר באופן סמלי. העדכונים לא יזוהו."
+ THEME_SYMBOLICALLY_LINKED: "ערכת העיצוב הזו מקושרת באופן סמלי. עדכונים לא יזוהו"
+ REMOVE_PLUGIN: "הסרת תוסף"
+ INSTALL_PLUGIN: "התקנת תוסף"
+ AVAILABLE: "זמין"
+ INSTALLED: "הותקן"
+ INSTALL: "התקנה"
+ ACTIVE_THEME: "ערכת עיצוב פעילה"
+ SWITCHING_TO: "מעבר ל"
+ SWITCHING_TO_DESCRIPTION: "על ידי החלפת ערכת עיצוב לאחרת, יתכן כי לא כל דפי הפריסה נתמכים. לכן יכולות להגרם שגיאות בעת הניסיון לטעון את הדפים שאינם נתמכים."
+ SWITCHING_TO_CONFIRMATION: "להמשיך ולשנות את ערכת העיצוב"
+ CREATE_NEW_USER: "יצירת משתמש חדש"
+ REMOVE_USER: "הסרת משתמש"
+ ACCESS_DENIED: "הגישה נדחתה"
+ ACCOUNT_NOT_ADMIN: "לחשבון שלך אין הרשאות מנהל"
+ PHP_INFO: "מידע PHP"
+ INSTALLER: "מתקין"
+ AVAILABLE_THEMES: "ערכות עיצוב זמינות"
+ AVAILABLE_PLUGINS: "תוספים זמינים"
+ INSTALLED_THEMES: "ערכות עיצוב מותקנות"
+ INSTALLED_PLUGINS: "תוספים מותקנים"
+ BROWSE_ERROR_LOGS: "עיון ביומני השגיאות"
+ SITE: "אתר"
+ INFO: "מידע"
+ SYSTEM: "מערכת"
+ USER: "משתמש"
+ ADD_ACCOUNT: "הוספת חשבון"
+ SWITCH_LANGUAGE: "החלפת שפה"
+ SUCCESSFULLY_ENABLED_PLUGIN: "התוסף הופעל בהצלחה"
+ SUCCESSFULLY_DISABLED_PLUGIN: "התוסף כובה בהצלחה"
+ SUCCESSFULLY_CHANGED_THEME: "ערכת העיצוב שונתה בהצלחה"
+ INSTALLATION_FAILED: "ההתקנה נכשלה"
+ INSTALLATION_SUCCESSFUL: "ההתקנה מוצלחת"
+ UNINSTALL_FAILED: "ההסרה נכשלה"
+ UNINSTALL_SUCCESSFUL: "הסרת התקנה מוצלחת"
+ SUCCESSFULLY_SAVED: "נשמר בהצלחה"
+ SUCCESSFULLY_COPIED: "ההעתקה התבצעה בהצלחה"
+ REORDERING_WAS_SUCCESSFUL: "הסדר השתנה בהצלחה"
+ SUCCESSFULLY_DELETED: "המחיקה התבצעה בהצלחה"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "החלפת השפה בוצעה"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "אין לך את ההרשאה הרצויה בכדי לבצע שינוי"
+ CACHE_CLEARED: "זכרון המטמון נקי"
+ METHOD: "שיטה"
+ ERROR_CLEARING_CACHE: "שגיאה בעת ניקוי מטמון"
+ AN_ERROR_OCCURRED: "אירעה שגיאה"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "גיבוי מוכן להורדה"
+ DOWNLOAD_BACKUP: "הורדת גיבוי"
+ PAGES_FILTERED: "עמודים מסוננים"
+ NO_PAGE_FOUND: "העמוד לא נמצא"
+ INVALID_PARAMETERS: "הפרמטרים אינם תקינים"
+ NO_FILES_SENT: "לא נשלחו קבצים"
+ EXCEEDED_FILESIZE_LIMIT: "חריגה ממגבלת גודל הקובץ בהגדרות ה PHP"
+ UNKNOWN_ERRORS: "שגיאות לא ידועות"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "חריגה ממגבלת גודל הקובץ בהגדרות גראב"
+ UNSUPPORTED_FILE_TYPE: "סוג הקובץ אינו נתמך"
+ FAILED_TO_MOVE_UPLOADED_FILE: "נכשלה העברת הקובץ שהועלה"
+ FILE_UPLOADED_SUCCESSFULLY: "הקובץ נטען בהצלחה"
+ FILE_DELETED: "הקובץ נמחק"
+ FILE_COULD_NOT_BE_DELETED: "אין אפשרות למחוק את הקובץ"
+ FILE_NOT_FOUND: "קובץ לא נמצא"
+ NO_FILE_FOUND: "לא נמצא קובץ תואם"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "גראב עודכה בהצלחה לגרסא"
+ GRAV_UPDATE_FAILED: "עדכון גראב נכשל"
+ EVERYTHING_UPDATED: "הכל עודכן"
+ UPDATES_FAILED: "העדכון נכשל"
+ AVATAR_BY: "אבאטר ע\"י"
+ AVATAR_UPLOAD_OWN: "או להעלות משלך..."
+ LAST_BACKUP: "הגיבוי האחרון"
+ FULL_NAME: "שם מלא"
+ USERNAME: "שם משתמש"
+ EMAIL: "מייל"
+ USERNAME_EMAIL: "שם משתשמש או מייל"
+ PASSWORD: "סיסמא"
+ PASSWORD_CONFIRM: "אישור סיסמא"
+ TITLE: "כותרת"
+ LANGUAGE: "שפה"
+ ACCOUNT: "חשבון"
+ EMAIL_VALIDATION_MESSAGE: "כתובת מייל לא חוקית"
+ PASSWORD_VALIDATION_MESSAGE: "הסיסמא צריכה להכיל לפחות מספר, אות גדולה, אות קטנה ו 8 תווים או יותר"
+ LANGUAGE_HELP: "הגדרת שפה מועדפת"
+ MEDIA: "מדיה"
+ DEFAULTS: "ברירות מחדל"
+ SITE_TITLE: "כותרת האתר"
+ SITE_TITLE_PLACEHOLDER: "כותרת האתר, רחבה"
+ SITE_TITLE_HELP: "כותרת ראשית לאתר ניתן לרוב להגדיר בערכת הנושא"
+ SITE_DEFAULT_LANG: "שפת ברירת מחדל"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "שפת בירירת מחדל עבור ערכת העיצוב "
+ SITE_DEFAULT_LANG_HELP: "שפת בירירת מחדל עבור ערכת העיצוב "
+ DEFAULT_AUTHOR: "מחבר ראשי"
+ DEFAULT_AUTHOR_HELP: "שם המחבר הראשי, לרוב משמש לערכות עיצוב או לתוכן הדפים"
+ DEFAULT_EMAIL: "תיבת מייל ראשית"
+ DEFAULT_EMAIL_HELP: "תיבת מייל ראשית להגדרות ערכות עיצוב או דפים"
+ TAXONOMY_TYPES: "סוגי טקסונומיה"
+ TAXONOMY_TYPES_HELP: "סוגי טקסונומיה יש להגדיר כאן אם ברצונך להשתמש בהם בעמודים"
+ PAGE_SUMMARY: "דף סיכום"
+ ENABLED: "מופעל"
+ ENABLED_HELP: "הפעלת דף סיכום (הסיכום משקף את תוכן הדף)"
+ 'YES': "כן"
+ 'NO': "לא"
+ SUMMARY_SIZE: "גודל סיכום"
+ SUMMARY_SIZE_HELP: "כמות התווים של דף שישמש לתוכן סיכום"
+ FORMAT: "סוג"
+ FORMAT_HELP: "קצר = use the first occurrence of delimiter or size; ארוך = summary delimiter will be ignored"
+ SHORT: "קצר"
+ LONG: "ארוך"
+ DELIMITER: "תוחם"
+ DELIMITER_HELP: "תוחם ראשי לסיכום (ברירת המחדל ' = = = ')"
+ METADATA: "תת נתונים"
+ METADATA_HELP: "ערכי ברירת מחדל תת-נתונים שיוצגו בכל עמוד אלא אם כן נעקפו על ידי עמוד"
+ NAME: "שם"
+ CONTENT: "תוכן"
+ REDIRECTS_AND_ROUTES: "נתיבים & ניתובים מחדש"
+ CUSTOM_REDIRECTS: "הפניות מותאמות"
+ CUSTOM_REDIRECTS_HELP: "מסלולים לניתוב לעמודים אחרים. החלפת regex אפשרית"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/הכינוי/שלך"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/הניתוב/שלך"
+ CUSTOM_ROUTES: "ניתובים מותאמים"
+ CUSTOM_ROUTES_HELP: "מסלולים לשם אחר לעמודים אחרים. החלפת regex אפשרית"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/הכינוי/שלך"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/נתיב/שלך"
+ FILE_STREAMS: "תזרימי הקבצים"
+ DEFAULT: "ברירת מחדל"
+ PAGE_MEDIA: "המדיה בדף"
+ OPTIONS: "אפשרויות"
+ PUBLISHED: "פורסם"
+ PUBLISHED_HELP: "כברירת מחדל, הדף מתפרסם אלא אם קבעת במפורש published: false או דרך הערכים publish_date בעתיד או unpublish_date בעבר\n\n "
+ DATE: "תאריך"
+ DATE_HELP: "ערכי התאריך מאפשרים להגדיר תאריך ספציפי המשוייך לעמוד זה"
+ PUBLISHED_DATE: "תאריך פרסום"
+ PUBLISHED_DATE_HELP: "יכול לספק תאריך להפעלת פרסום אוטומטי."
+ UNPUBLISHED_DATE: "תאריך הסרת פרסום"
+ UNPUBLISHED_DATE_HELP: "יכול לספק תאריך להפעלת הסרת פרסום אוטומטי."
+ ROBOTS: "רובוטים"
+ TAXONOMIES: "מיונים"
+ TAXONOMY: "מיונים"
+ ADVANCED: "הגדרות מתקדמות"
+ SETTINGS: "הגדרות"
+ FOLDER_NUMERIC_PREFIX: "קידומות מספריות לתקיות"
+ FOLDER_NUMERIC_PREFIX_HELP: "קידומת מספרית שמאפשרת הזמנה ידנית ומאפשרת ראות"
+ FOLDER_NAME: "שם תיקיה"
+ FOLDER_NAME_HELP: "שם התיקיה שיאוחסן במערכת עבור דף זה"
+ PARENT: "הורה"
+ DEFAULT_OPTION_ROOT: "-שורש-"
+ DEFAULT_OPTION_SELECT: "-בחירה-"
+ DISPLAY_TEMPLATE: "הצגת תבנית"
+ DISPLAY_TEMPLATE_HELP: "סוג דף אשר מתרגם לתוך איזה ענף תבנית מעבד את העמוד"
+ BODY_CLASSES: "Body Classes"
+ ORDERING: "סדר"
+ PAGE_ORDER: "סדר הדף"
+ OVERRIDES: "מעקפים"
+ MENU: "תפריט"
+ MENU_HELP: "String לשימוש בתפריט. אם לא מוגדר, הכותרת תהיה בשימוש."
+ SLUG: "סלאג"
+ SLUG_HELP: "המשתנה סלאג מאפשרת לך להגדיר באופן ספציפי את שם הדף בכתובת ה-URL"
+ SLUG_VALIDATE_MESSAGE: "סלאג חייב להכיל רק אותיות קטנות תווים אלפאנומריים ומקפים"
+ PROCESS: "תהליך"
+ PROCESS_HELP: "שולט בתהליך עיבוד העמודים. ניתן להגדיר לכל דף ולא רק כהגדרה גלובאלית"
+ DEFAULT_CHILD_TYPE: "סוג \"ילד\" המוגדר כברירת מחדל"
+ USE_GLOBAL: "החלה גלובאלית"
+ ROUTABLE: "ניתן לניתוב"
+ ROUTABLE_HELP: "קובע אם דף זה נגיש באמצעות כתובת URL"
+ CACHING: "מטמון"
+ VISIBLE: "גלוי"
+ VISIBLE_HELP: "קובע אם הדף גלוי בתפריט הניווט."
+ DISABLED: "מנוטרל"
+ ITEMS: "פריטים"
+ ORDER_BY: "סדר לפי"
+ ORDER: "סדר"
+ FOLDER: "תיקיה"
+ ASCENDING: "בסדר עולה"
+ DESCENDING: "בסדר יורד"
+ ADD_MODULAR_CONTENT: "להוסיף תוכן מודולרי"
+ PAGE_TITLE: "כותרת העמוד"
+ PAGE_TITLE_HELP: "כותרת העמוד"
+ PAGE: "עמוד"
+ MODULAR_TEMPLATE: "תבנית מודולרית"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "שם הקובץ"
+ PARENT_PAGE: "דף אב"
+ HOME_PAGE: "עמוד הבית"
+ HOME_PAGE_HELP: "העמוד שגראב תשתמש כדף הנחיתה הראשי"
+ DEFAULT_THEME: "ערכת עיצוב ראשית"
+ DEFAULT_THEME_HELP: "ערכת העיצוב המוגדרת כברירת מחדל עבור גראב (ברירת המחדל היא Antimatter)"
+ TIMEZONE: "אזור זמן"
+ TIMEZONE_HELP: "עקיפת אזור הזמן המוגדר על השרת"
+ SHORT_DATE_FORMAT: "בחירת תבנית תאריך"
+ SHORT_DATE_FORMAT_HELP: "הגדר את תבניות התאריך הקצרות כך לשימוש בערכת העיצוב"
+ LONG_DATE_FORMAT: "בחירת תבנית תאריך ארוכה"
+ LONG_DATE_FORMAT_HELP: "הגדר את תבניות התאריך הארוכות כך לשימוש בערכת העיצוב"
+ DEFAULT_ORDERING: "מיון ברירת מחדל"
+ DEFAULT_ORDERING_HELP: "הדפים ברשימה יעובדו על פי הסדר הבא, במידה ולא נעקפו"
+ DEFAULT_ORDERING_DEFAULT: "ברירת המחדל - בהתבסס על שם התקיה"
+ DEFAULT_ORDERING_FOLDER: "תיקייה - בהתבסס על שם תיקייה ללא הקידומת"
+ DEFAULT_ORDERING_TITLE: "כותרת - בהתבסס על שדה הכותרת בכותרת עמוד"
+ DEFAULT_ORDERING_DATE: "תאריך - בהתבסס על שדה התאריך בכותרת עמוד"
+ DEFAULT_ORDER_DIRECTION: "כיוון המיון ברירת המחדל"
+ DEFAULT_ORDER_DIRECTION_HELP: "הכיוון של הדפים ברשימה"
+ DEFAULT_PAGE_COUNT: "מספר דפים ברירת מחדל"
+ DEFAULT_PAGE_COUNT_HELP: "מקסימום מספר דפים ברשימה ברירת מחדל"
+ DATE_BASED_PUBLISHING: "פרסום מבוסס תאריך"
+ DATE_BASED_PUBLISHING_HELP: "פרסם\\הורד מפירסום אוטומוטית בהתבסס על תאריך"
+ EVENTS: "אירועים"
+ EVENTS_HELP: "להפעיל או לבטל אירועים ספציפיים. ביטול שלהם יכול לשבור תוספים"
+ REDIRECT_DEFAULT_ROUTE: "ניתוב מחדש ברירת המחדל"
+ REDIRECT_DEFAULT_ROUTE_HELP: "לנתב מחדש באופן אוטומטי אל נתיב ברירת מחדל של דף"
+ LANGUAGES: "שפות"
+ SUPPORTED: "נתמך"
+ SUPPORTED_HELP: "רשימה מופרדת באמצעות פסיקים של קודים בני שני אותיות של שפה (לדוגמה en, fr, וכו')"
+ TRANSLATIONS_ENABLED: "תרגומים מאופשרים"
+ TRANSLATIONS_ENABLED_HELP: "תמיכה בתרגומים בגראב, תוספים והרחבות"
+ TRANSLATIONS_FALLBACK: "חלופה לתרגום"
+ TRANSLATIONS_FALLBACK_HELP: "חפש חלופות בתרגומים נתמכים אם לא קיים תרגום בשפה הפעילה"
+ ACTIVE_LANGUAGE_IN_SESSION: "שמור שפה פעילה בחיבור"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "שמור את השפה הפעילה בחיבור"
+ HTTP_HEADERS: "כותרות HTTP"
+ EXPIRES: "פג תוקף"
+ EXPIRES_HELP: "קובע את הכותרת 'expires'. ערך בשניות."
+ CACHE_CONTROL: "HTTP שליטה במטמון"
+ CACHE_CONTROL_HELP: "קבע לערך cache-control חוקי כגון `no-cache, no-store, must-revalidate`"
+ LAST_MODIFIED: "עודכן לאחרונה"
+ LAST_MODIFIED_HELP: "מגדיר את הכותרת שינוי-אחרון שיכול לעזור לייעל מטמונים של פרוקסי ודפדפן"
+ ETAG: "ETag"
+ ETAG_HELP: "מגדיר את כותרת etag כדי לזהות מתי דף שונה"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "קובע את הכותרת `Vary: Accept Encoding` כדי לעזור עם מטמון של פרוקסי ו CDN"
+ MARKDOWN_EXTRA_HELP: "אפשר תמיכה בברירת מחדל ל Markdown Extra ראה https://michelf.ca/projects/php-markdown/extra/"
+ AUTO_LINE_BREAKS: "מעברי שורה אוטומטים"
+ AUTO_LINE_BREAKS_HELP: "הפעל תמיכה עבור מעברי שורה אוטומטים ב Markdown"
+ AUTO_URL_LINKS: "קישורי URL אוטומטים"
+ AUTO_URL_LINKS_HELP: "לאפשר המרה אוטומטית של כתובות URL לקישורי HTML"
+ ESCAPE_MARKUP: "Escape markup"
+ ESCAPE_MARKUP_HELP: "Escape markup tags into HTML entities"
+ CACHING_HELP: "מתג הפעלה/כיבוי גלובלי כדי להפעיל/להשבית מטמון בגראב"
+ CACHE_CHECK_METHOD: "שיטת בדיקת מטמון"
+ CACHE_CHECK_METHOD_HELP: "בחר את השיטה שגראב משתמש כדי לבדוק אם קבצי דף שונו."
+ CACHE_DRIVER: "מנהל התקן המטמון"
+ CACHE_DRIVER_HELP: "בחר באיזה מנהל-התקן מטמון גראב צריך להשתמש. 'זיהוי אוטומטי' מנסה למצוא את הטוב ביותר בשבילך"
+ CACHE_PREFIX: "קידומת מטמון"
+ CACHE_PREFIX_HELP: "מזהה עבור חלק ממפתח ה-Grav. נא לא לשנות אלא אם כן אתם יודעים מה אתם עושים."
+ CACHE_PREFIX_PLACEHOLDER: "נגזר מכתובת האתר הבסיסית (ניתן לעקוף על ידי הזנת מחרוזת אקראית)"
+ LIFETIME: "לכל החיים"
+ LIFETIME_HELP: "מגדיר את משך הזמן של המטמון בשניות. 0 = אינסופי"
+ GZIP_COMPRESSION: "דחיסת Gzip"
+ ASSETS: "נכסים"
+ CSS_PIPELINE: "צינור CSS"
+ COLLECTIONS: "אוספים"
+ ERROR_HANDLER: "סדרן שגיאות"
+ DISPLAY_ERRORS: "הצג שגיאות"
+ LOG_ERRORS: "יומן רישום שגיאות"
+ SAVE_AS: "שמירה בשם"
+ UPDATE_AVAILABLE: "עדכון זמין"
+ PAGE_MODES: "מצבי דפים"
+ PAGE_TYPES: "סוגי דפים"
+ ACCESS_LEVELS: "רמת נגישות"
+ GROUPS: "קבוצות"
+ GROUPS_HELP: "רשימת הקבוצות שהמשתמש הוא חלק מהן"
+ ADMIN_ACCESS: "גישת מנהל"
+ SITE_ACCESS: "גישה לאתר"
+ INVALID_SECURITY_TOKEN: "קוד אבטחה לא תקין"
+ ACTIVATE: "להפעיל"
+ REMOVE_FAILED: "הסרה נכשלה"
+ ADD_ITEM: "הוספת פריט"
+ INSTALLING: "מתקין"
+ LOADING: "טוען.."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "החבילות הותקנו בהצלחה."
+ REMOVED_SUCCESSFULLY: "%s הוסר בהצלחה"
+ PACKAGES_SUCCESSFULLY_UPDATED: "החבילות עודכנו בהצלחה."
+ UPDATING: "מעדכן .."
+ AUTO: "אוטומטית"
+ STABLE: "יציב"
+ TESTING: "בודק..."
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "חבילת %s הותקנו בהצלחה"
+ DROP_FILES_HERE_TO_UPLOAD: "להקליק כאן ניתן לגרור את הקבצים לכאן או"
+ LINK: "קישור"
+ IMAGE: "תמונה"
+ BLOCKQUOTE: "ציטוט"
+ UNORDERED_LIST: "רשימה שאינה מסודרת"
+ ORDERED_LIST: "רשימה מסודרת"
+ EDITOR: "עורך"
+ PREVIEW: "תצוגה"
+ FULLSCREEN: "מסך מלא"
+ MODULAR: "מודולרי"
+ NON_ROUTABLE: "שאינו ניתן לניתוב"
+ NON_MODULAR: "לא מודולרי"
+ NON_VISIBLE: "אינו נראה"
+ NON_PUBLISHED: "לא פורסם"
+ CHARACTERS: "תווים"
+ PUBLISHING: "פורסם"
+ NOTIFICATIONS: "התראות"
+ MEDIA_TYPES: "סוגי מדיה"
+ IMAGE_OPTIONS: "אפשרויות תמונה"
+ TYPE: "סוג"
+ FILE_EXTENSION: "סוג הקובץ"
+ ALL: "הכל"
+ FROM: "מאת"
+ TO: "אל"
+ RELEASE_DATE: "תאריך הפצה"
+ SORT_BY: "מיון לפי"
+ RESOURCE_FILTER: "מסנן..."
+ FORCE_SSL: "נעילת SSL"
+ FORCE_SSL_HELP: "נעילת SSL כללית, אם מופעלת כאשר האתר יקרא דרך HTTP, גראב תשלח ניתוב מחדש לדף HTTPS"
+ NEWS_FEED: "הזנת חדשות"
+ EXTERNAL_URL: "כתובת URL חיצונית"
+ DROPZONE_CANCEL_UPLOAD: 'לבטל את ההעלאה'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'באמת לבטל העלאה זו?'
+ DROPZONE_DEFAULT_MESSAGE: 'להקליק כאן ניתן לגרור את הקבצים לכאן או'
+ DROPZONE_FALLBACK_MESSAGE: 'הדפדפן שלך אינו תומך ההעלאה של קבצים בגרירה.'
+ PERMISSIONS: "הרשאות"
+ CLI_COMPATIBILITY: "תאימות CLI"
diff --git a/user/plugins/admin/languages/hr.yaml b/user/plugins/admin/languages/hr.yaml
new file mode 100644
index 0000000..fd3151d
--- /dev/null
+++ b/user/plugins/admin/languages/hr.yaml
@@ -0,0 +1,609 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Ovo je Beta izdanje! Koristite u produkciji na vlastiti rizik..."
+ ADMIN_REPORT_ISSUE: "Pronašli ste grešku? Molimo Vas da prijavite na GitHub."
+ EMAIL_FOOTER: "Pokreće Grav - Moderni Flat File CMS"
+ LOGIN_BTN: "Prijava"
+ LOGIN_BTN_FORGOT: "Zaboravih"
+ LOGIN_BTN_RESET: "Resetiraj Lozinku"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Pošalji Upute za Resetiranje Lozinke"
+ LOGIN_BTN_CLEAR: "Očisti obrazac"
+ LOGIN_BTN_CREATE_USER: "Stvori Korisnika"
+ LOGIN_LOGGED_IN: "Uspješno si se prijavio"
+ LOGIN_FAILED: "Prijava nije uspjela"
+ LOGGED_OUT: "Odjavio si se"
+ RESET_NEW_PASSWORD: "Molimo vas da unesete novu lozinku …"
+ RESET_LINK_EXPIRED: "Link za resetiranje je istekao, pokušaj ga stvoriti opet"
+ RESET_PASSWORD_RESET: "Lozinka je resetirana"
+ RESET_INVALID_LINK: "Korišten je neispravan link za resetiranje, pokušaj opet"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Upute za resetiranje lozinke su ti poslane emailom na %s"
+ FORGOT_FAILED_TO_EMAIL: "Slanje email uputa nije uspjelo, pokušaj opet"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Ne mogu resetirati lozinku, jer %s, email adresa nije unesena"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Korisnik sa korisničkim imenom %s ne postoji"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Ne mogu resetirati lozinku. Ovaj sajt nije konfiguriran da šalje emailove"
+ FORGOT_EMAIL_SUBJECT: "%s Zahtjev za Resetiranje Lozinke"
+ FORGOT_EMAIL_BODY: "Resetiranje Lozinke Dragi %1$s,
Podnesen je zahtjev %4$s za resetiranjem tvoje lozinke.
Klikni ovdje kako bi lozinka bila resetirana
Alternativno, kopiraj sljedeći link u svoj web preglednik:
%2$s
Lijep pozdrav, %3$s
"
+ MANAGE_PAGES: "Upravljanje stranicama"
+ CONFIGURATION: "Konfiguracija"
+ PAGES: "Stranice"
+ PLUGINS: "Dodaci"
+ PLUGIN: "Dodatak (Plugin)"
+ THEMES: "Teme"
+ LOGOUT: "Odjava"
+ BACK: "Natrag"
+ NEXT: "Sljedeći"
+ PREVIOUS: "Prethodni"
+ ADD_PAGE: "Dodaj Stranicu"
+ ADD_MODULAR: "Dodaj Modular"
+ MOVE: "Makni"
+ DELETE: "Obriši"
+ VIEW: "Prikaz"
+ SAVE: "Sačuvaj"
+ NORMAL: "Normalna"
+ EXPERT: "Stručna"
+ EXPAND_ALL: "Proširi sve"
+ COLLAPSE_ALL: "Skupi sve"
+ ERROR: "Greška"
+ CLOSE: "Zatvori"
+ CANCEL: "Otkaži"
+ CONTINUE: "Nastavi"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Potrebna je potvrda"
+ MODAL_CHANGED_DETECTED_TITLE: "Detektirane Izmjene"
+ MODAL_CHANGED_DETECTED_DESC: "Imaš nesačuvane izmjene. Jesi li siguran da želiš otići bez da ih sačuvaš?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Potvrda tražena"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Jesi li siguran da želiš obrisati ovaj fajl? Ova akcija ne može biti poništena."
+ ADD_FILTERS: "Dodaj Filtere"
+ SEARCH_PAGES: "Pretraži Stranice"
+ VERSION: "Verzija"
+ WAS_MADE_WITH: "je stvorena sa"
+ BY: "Od"
+ UPDATE_THEME: "Obnovi Temu"
+ UPDATE_PLUGIN: "Obnovi Dodatak"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "ove teme je sada dostupna"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "ovog dodatka je sada dostupna"
+ AUTHOR: "Autor"
+ HOMEPAGE: "Stranica"
+ DEMO: "Demo"
+ BUG_TRACKER: "Bug Tracker"
+ KEYWORDS: "Ključne riječi"
+ LICENSE: "Licenca"
+ DESCRIPTION: "Opis"
+ README: "Pročitaj me"
+ REMOVE_THEME: "Makni Temu"
+ INSTALL_THEME: "Instaliraj Temu"
+ THEME: "Tema"
+ BACK_TO_THEMES: "Povratak na Teme"
+ BACK_TO_PLUGINS: "Natrag na Dodatke"
+ CHECK_FOR_UPDATES: "Provjeri postoje li obnove"
+ ADD: "Dodaj"
+ CLEAR_CACHE: "Očisti Cache"
+ CLEAR_CACHE_ALL_CACHE: "Sav Cache"
+ CLEAR_CACHE_ASSETS_ONLY: "Samo Assets"
+ CLEAR_CACHE_IMAGES_ONLY: "Samo Slike"
+ CLEAR_CACHE_CACHE_ONLY: "Samo Cache"
+ CLEAR_CACHE_TMP_ONLY: "Samo Tmp"
+ DASHBOARD: "Upravljačka ploča"
+ UPDATES_AVAILABLE: "Dostupna su ažuriranja"
+ DAYS: "Dana"
+ UPDATE: "Ažuriraj"
+ BACKUP: "Backup"
+ BACKUPS: "Sigurnosne kopije"
+ BACKUP_NOW: "Stvori sigurnosnu kopiju"
+ BACKUPS_PROFILES_COUNT: "Broj profila"
+ BACKUPS_TOTAL_SIZE: "Iskorišten prostor"
+ BACKUPS_NEWEST: "Najnovija sigurnosna kopija"
+ BACKUPS_OLDEST: "Najstarija sigurnosna kopija"
+ BACKUP_DELETED: "Sigurnosna kopija uspješno je obrisana"
+ BACKUP_NOT_FOUND: "Sigurnosna kopija nije nađena"
+ BACKUP_DATE: "Datum sigurnosne kopije"
+ STATISTICS: "Statistike"
+ TODAY: "Danas"
+ WEEK: "Tjedan"
+ MONTH: "Mjesec"
+ LATEST_PAGE_UPDATES: "Zadnje Obnovljene Stranice"
+ MAINTENANCE: "Održavanje"
+ UPDATED: "Obnovljeno"
+ MON: "Pon"
+ TUE: "Uto"
+ WED: "Sri"
+ THU: "Čet"
+ FRI: "Pet"
+ SAT: "Sub"
+ SUN: "Ned"
+ COPY: "Kopiraj"
+ EDIT: "Editiraj"
+ CREATE: "Kreiraj"
+ GRAV_ADMIN: "Grav administracija"
+ GRAV_OFFICIAL_PLUGIN: "Službeni Grav Dodatak"
+ GRAV_OFFICIAL_THEME: "Grav Službena Tema"
+ PLUGIN_SYMBOLICALLY_LINKED: "Ovaj dodatak je linkan simbolično. Obnove neće biti detektirane."
+ THEME_SYMBOLICALLY_LINKED: "Ova tema je linkana simbolično. Obnove neće biti detektirane."
+ REMOVE_PLUGIN: "Ukloni Dodatak"
+ INSTALL_PLUGIN: "Instaliraj Dodatak"
+ AVAILABLE: "Dostupno"
+ INSTALLED: "Instalirano"
+ INSTALL: "Instaliraj"
+ ACTIVE_THEME: "Aktivna Tema"
+ SWITCHING_TO: "Mijenjam na"
+ SWITCHING_TO_DESCRIPTION: "Prebacivanjem na drugu temu, ne postoji garancija da će sve stranice biti podržane, moguće su potencijalne greške pri učitavanju stranica."
+ SWITCHING_TO_CONFIRMATION: "Želiš li nastaviti i primijeniti temu"
+ CREATE_NEW_USER: "Stvori Novog Korisnika"
+ REMOVE_USER: "Makni Korisnika"
+ ACCESS_DENIED: "Pristup odbijen"
+ ACCOUNT_NOT_ADMIN: "tvoj račun nema ovlasti administratora"
+ PHP_INFO: "PHP info"
+ INSTALLER: "Instalacijski program"
+ AVAILABLE_THEMES: "Dostupne Teme"
+ AVAILABLE_PLUGINS: "Dostupni Dodaci"
+ INSTALLED_THEMES: "Instalirane Teme"
+ INSTALLED_PLUGINS: "Instalirani Dodaci"
+ BROWSE_ERROR_LOGS: "Pregledaj Dnevnike Pogrešaka"
+ SITE: "Web stranica"
+ INFO: "Informacije"
+ SYSTEM: "Sustav"
+ USER: "Korisnik"
+ ADD_ACCOUNT: "Dodaj Račun"
+ SWITCH_LANGUAGE: "Promijeni Jezik"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Uspješno omogućen dodatak"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Uspješno onemogućen dodatak"
+ SUCCESSFULLY_CHANGED_THEME: "Zadana tema je uspješno promijenjena"
+ INSTALLATION_FAILED: "Instalacija nije uspjela"
+ INSTALLATION_SUCCESSFUL: "Instalacija je uspjela"
+ UNINSTALL_FAILED: "Deinstalacija nije uspjela"
+ UNINSTALL_SUCCESSFUL: "Deinstalacija je uspjela"
+ SUCCESSFULLY_SAVED: "Uspješno sačuvano"
+ SUCCESSFULLY_COPIED: "Uspješno kopirano"
+ REORDERING_WAS_SUCCESSFUL: "Reordering je uspješan"
+ SUCCESSFULLY_DELETED: "Uspješno obrisano"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Jezik je uspješno promijenjen."
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Nemaš dovoljno ovlasti za izvršavanje tog zadatka."
+ CACHE_CLEARED: "Cache očišćen"
+ METHOD: "Metoda"
+ ERROR_CLEARING_CACHE: "Greška u čišćenju cachea"
+ AN_ERROR_OCCURRED: "Dogodila se greška"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Sigurnosna kopija je spremna za preuzimanje"
+ DOWNLOAD_BACKUP: "Preuzmi sigurnosnu kopiju"
+ PAGES_FILTERED: "Filtrirane Stranice"
+ NO_PAGE_FOUND: "Stranica nije nađena"
+ INVALID_PARAMETERS: "Nevaljani Parameteri"
+ NO_FILES_SENT: "Fajlovi nisu poslani"
+ EXCEEDED_FILESIZE_LIMIT: "Premašeno je PHP-om konfigurirano ograničenje veličine datoteke"
+ UNKNOWN_ERRORS: "Nepoznate greške"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Premašeno je PHP-om konfigurirano ograničenje veličine datoteke"
+ UNSUPPORTED_FILE_TYPE: "Nepodržan tip fajla"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Pogreška u premještaju uplodane datoteke."
+ FILE_UPLOADED_SUCCESSFULLY: "Fajl je uspješno uploadan"
+ FILE_DELETED: "Fajl obrisan"
+ FILE_COULD_NOT_BE_DELETED: "Fajl nije mogao biti obrisan"
+ FILE_NOT_FOUND: "Fajl nije nađen"
+ NO_FILE_FOUND: "Nije nađen fajl"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav je uspješno obnovljen"
+ GRAV_UPDATE_FAILED: "Obnova Grava nije uspjela"
+ EVERYTHING_UPDATED: "Sve je obnovljeno"
+ UPDATES_FAILED: "Obnove nisu uspjele"
+ AVATAR_BY: "Avatar sa"
+ AVATAR_UPLOAD_OWN: "Ili pošaljite svoj vlastiti..."
+ LAST_BACKUP: "Posljednja sigurnosna kopija"
+ FULL_NAME: "Puno ime"
+ USERNAME: "Korisničko ime"
+ EMAIL: "e-pošta"
+ USERNAME_EMAIL: "Korisničko ime ili E-mail"
+ PASSWORD: "Lozinka"
+ PASSWORD_CONFIRM: "Potvrdi Lozinku"
+ TITLE: "Naslov"
+ LANGUAGE: "Jezik"
+ ACCOUNT: "Račun"
+ EMAIL_VALIDATION_MESSAGE: "Email adresa mora biti valjana."
+ PASSWORD_VALIDATION_MESSAGE: "Lozinka mora sadržavati bar jedan broj i jedno veliko i malo slovo, i barem 8 ili više znakova."
+ LANGUAGE_HELP: "Postavi omiljeni jezik"
+ MEDIA: "Medijske datoteke"
+ DEFAULTS: "Osnovno"
+ SITE_TITLE: "Ime Stranice"
+ SITE_TITLE_PLACEHOLDER: "Pretpostavljen naslov stranice"
+ SITE_TITLE_HELP: "Zadano ime tvoje stranice, često korišteno u temama"
+ SITE_DEFAULT_LANG: "Zadani jezik"
+ DEFAULT_AUTHOR: "Zadani Autor"
+ DEFAULT_AUTHOR_HELP: "Ime zadanog autora, često korišteno u temama ili sadržaju stranice"
+ DEFAULT_EMAIL: "Email"
+ DEFAULT_EMAIL_HELP: "Zadani email to reference u temama ili stranicama"
+ TAXONOMY_TYPES: "Tipovi Taxonomije"
+ TAXONOMY_TYPES_HELP: "Tipovi Taxonomije moraju biti definirani ovdje, ako ih želiš koristiti u stranicama"
+ PAGE_SUMMARY: "Sažetak Stranice"
+ ENABLED: "Omogućen"
+ ENABLED_HELP: "Omogući sažetak stranice (the summary returns the same as the page content)"
+ 'YES': "Da"
+ 'NO': "Ne"
+ SUMMARY_SIZE: "Veličina Sažetka"
+ SUMMARY_SIZE_HELP: "Broj znakova sa stranice koji će se koristiti u sažetku sadržaja"
+ FORMAT: "Format"
+ FORMAT_HELP: "short = use the first occurrence of delimiter ili size; long = summary delimiter će biti ignoriran"
+ SHORT: "Kratko"
+ LONG: "Dugo"
+ DELIMITER: "Graničnik"
+ DELIMITER_HELP: "Delimiter sažetka (default '===')"
+ METADATA: "Metapodaci"
+ METADATA_HELP: "Zadane metadata vrijednosti koje će biti prikazane na svakoj stranici, osim ako je overridden by the page"
+ NAME: "Ime"
+ CONTENT: "Sadržaj"
+ SIZE: "Veličina"
+ ACTION: "Radnja"
+ REDIRECTS_AND_ROUTES: "Preusmjeravanja i rute"
+ CUSTOM_REDIRECTS: "Prilagođena preusmjeravanja"
+ CUSTOM_REDIRECTS_HELP: "rute za preusmjeravanje na druge stranice. Standardne Regex zamjene vrijede"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/ vaš/nadimak"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/ vaša/preusmjeravanja"
+ CUSTOM_ROUTES: "Vlastite rute"
+ CUSTOM_ROUTES_HELP: "rute za preusmjeravanje na druge stranice. Standardne Regex zamjene vrijede"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/ vaš/nadimak"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/ vaše/rute"
+ FILE_STREAMS: "File Streams"
+ DEFAULT: "Zadano"
+ PAGE_MEDIA: "Stranica medija"
+ OPTIONS: "Opcije"
+ PUBLISHED: "Objavljeno"
+ PUBLISHED_HELP: "Stranica je defaultno objavljena, osim ako izričito postaviš objavljeno: false ili via a publish_date being in the future, or unpublish_date in the past"
+ DATE: "Datum"
+ DATE_HELP: "Varijabla datuma omogućuje ti da specifično postaviš datum asociran sa ovom stranicom."
+ PUBLISHED_DATE: "Datum Objave"
+ PUBLISHED_DATE_HELP: "Can provide datum to automatically trigger publication."
+ UNPUBLISHED_DATE: "Datum povlačenja objave"
+ ROBOTS: "Roboti"
+ TAXONOMIES: "Taxonomije"
+ TAXONOMY: "Taxonomija"
+ ADVANCED: "Napredno"
+ SETTINGS: "Postavke"
+ FOLDER_NUMERIC_PREFIX: "Numerički Prefix Foldera"
+ FOLDER_NUMERIC_PREFIX_HELP: "Numerički prefix koji pruža manualni poredak i implicira vidljivost"
+ FOLDER_NAME: "Ime Foldera"
+ FOLDER_NAME_HELP: "Ime foldera koji će biti spremljen u filesystem za ovu stranicu"
+ PARENT: "Nadređena kategorija"
+ DEFAULT_OPTION_ROOT: "- Root -"
+ DEFAULT_OPTION_SELECT: "- Odaberi -"
+ DISPLAY_TEMPLATE: "Predložak prikaza"
+ DISPLAY_TEMPLATE_HELP: "Tip stranice koja određuje koji Twig predložak generira stranicu"
+ BODY_CLASSES: "Body Klase"
+ ORDERING: "Poredak"
+ PAGE_ORDER: "Redoslijed stranica"
+ OVERRIDES: "Zaobilaženja"
+ MENU: "Meni"
+ MENU_HELP: "String koji će biti korišten u meniu. Ako nije postavljeno, biti će korišten Title."
+ SLUG: "Slug"
+ SLUG_HELP: "Slug varijabla ti omogućuje da specifično set the page's portion of the URL"
+ SLUG_VALIDATE_MESSAGE: "Slug mora sadržavati samo mala slova, brojeve i crtice"
+ PROCESS: "Proces"
+ PROCESS_HELP: "Kontroliraj kako su stranice procesirane. Can be set per-page rather than globally"
+ DEFAULT_CHILD_TYPE: "Predpostavljena pod stranica"
+ USE_GLOBAL: "Koristi Globalno"
+ ROUTABLE: "Rutabilno"
+ ROUTABLE_HELP: "Ako je ova stranica reachable by a URL"
+ CACHING: "Predmemoriranje [Cache]"
+ VISIBLE: "Vidljivost"
+ VISIBLE_HELP: "Određuje da li je stranica vidljiva u navigaciji."
+ DISABLED: "Onemogućen"
+ ITEMS: "Stavke"
+ ORDER_BY: "Poredaj po"
+ ORDER: "Poredak"
+ FOLDER: "Folder"
+ ASCENDING: "Uzlazni"
+ DESCENDING: "Silazni"
+ ADD_MODULAR_CONTENT: "Dodaj Modularni Sadržaj"
+ PAGE_TITLE: "Ime Stranice"
+ PAGE_TITLE_HELP: "Naslov stranice"
+ PAGE: "Stranica"
+ MODULAR_TEMPLATE: "Modularni predložak"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Ime fajla"
+ PARENT_PAGE: "Glavna Stranica"
+ HOME_PAGE: "Naslovnica"
+ HOME_PAGE_HELP: "Stranica koju će Grav koristiti kao zadanu landing stranicu"
+ DEFAULT_THEME: "Zadana tema"
+ DEFAULT_THEME_HELP: "Postavi temu koju će Grav koristiti kao zadanu (zadana je Antimatter)"
+ TIMEZONE: "Vremenska zona"
+ TIMEZONE_HELP: "Zaobiđi pretpostavljenu vremensku zonu poslužitelja"
+ SHORT_DATE_FORMAT: "Kratak prikaz formata datuma"
+ SHORT_DATE_FORMAT_HELP: "Postavi kratki format datuma koji može biti korišten u temama"
+ LONG_DATE_FORMAT: "Dug prikaz formata datuma"
+ LONG_DATE_FORMAT_HELP: "Postavi dugi format datuma koji može biti korišten u temama"
+ DEFAULT_ORDERING: "Defaultni poredak"
+ DEFAULT_ORDERING_HELP: "Stranice na listi će render koristeći ovaj poredak osim ako je overridden"
+ DEFAULT_ORDERING_DEFAULT: "Defaultni - baziran na imenu fajla"
+ DEFAULT_ORDERING_FOLDER: "Folder - baziran na imenu foldera bez prefixa"
+ DEFAULT_ORDERING_TITLE: "Imenu - baziran na polju imena u headeru"
+ DEFAULT_ORDERING_DATE: "Datum - baziran na polju datuma u headeru"
+ DEFAULT_ORDER_DIRECTION: "Zadani smjer redosljeda"
+ DEFAULT_ORDER_DIRECTION_HELP: "Poredak stranica na listi"
+ DEFAULT_PAGE_COUNT: "Defaultni broj stranica"
+ DEFAULT_PAGE_COUNT_HELP: "Defaultni maximum broja stranica na listi"
+ DATE_BASED_PUBLISHING: "Objavljivanje bazirano na datumu"
+ DATE_BASED_PUBLISHING_HELP: "Automatski objavi ili povuci objave postova baziranih prema njihovom datumu"
+ EVENTS: "Događaji"
+ EVENTS_HELP: "Omogući ili onemogući specifične događaje. Onemogućavanje ovih može potrgati dodatke"
+ REDIRECT_DEFAULT_ROUTE: "Preusmjeravanje pretpostavljene rute"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Automatski redirect to a page's default route"
+ LANGUAGES: "Jezici"
+ SUPPORTED: "Omogućeno"
+ SUPPORTED_HELP: "Zarezom odijeljena lista dvoslovnih jezičnih kodova (na primjer 'en,fr,hr')"
+ TRANSLATIONS_ENABLED: "Prijevodi su omogućeni"
+ TRANSLATIONS_ENABLED_HELP: "Omogući prijevode u Gravu, dodatke i extenzije"
+ TRANSLATIONS_FALLBACK: "Osnovni prijevod"
+ TRANSLATIONS_FALLBACK_HELP: "Fallback kroz podržane prijevode, ako aktivni jezik ne postoji"
+ ACTIVE_LANGUAGE_IN_SESSION: "Aktivni jezik u prijavi"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Sačuvaj aktivni jezik u prijavi"
+ HTTP_HEADERS: "HTTP Headeri"
+ EXPIRES: "Istječe"
+ EXPIRES_HELP: "Sets the expires header. Vrijednost je u sekundama."
+ LAST_MODIFIED: "Zadnji put izmijenjeno"
+ LAST_MODIFIED_HELP: "Sets the last modified header koji može pomoći optimizirati proxy i caching web preglednika"
+ ETAG: "ETag"
+ ETAG_HELP: "Postavljanje e-oznake u zaglavlje da bi se otkrile promjene na stranici"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Sets the `Vary: Accept Encoding` header to help sa proxy i CDN caching"
+ MARKDOWN: "Markdown"
+ MARKDOWN_EXTRA_HELP: "Omogući defaultnu potporu za Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ AUTO_LINE_BREAKS: "Automatsko lomljenje linija"
+ AUTO_LINE_BREAKS_HELP: "Omogući podršku za automatsko lomljenje linija u markdownu"
+ AUTO_URL_LINKS: "Auto URL linkovi"
+ AUTO_URL_LINKS_HELP: "Omogući automatsku konverziju URLova u HTML hyperlinkove"
+ ESCAPE_MARKUP: "Escape markup"
+ ESCAPE_MARKUP_HELP: "Escape markup tags u HTML entities"
+ CACHING_HELP: "Globalni ON/OFF prekidač za uključivanje/isključivanje Grav cache-a"
+ CACHE_CHECK_METHOD: "Cache check metoda"
+ CACHE_CHECK_METHOD_HELP: "Odaberi metodu koju će Grav koristiti da provjeri jesu li fajlovi stranice bili modificirani."
+ CACHE_DRIVER: "Cache driver"
+ CACHE_DRIVER_HELP: "Odaberi koji cache driver će Grav koristiti. 'Auto Detektiraj' attempts to find the best for you"
+ CACHE_PREFIX: "Cache prefiks"
+ CACHE_PREFIX_HELP: "An identifier for part of the Grav key. Ne mijenjaj osim ako znaš što radiš."
+ CACHE_PREFIX_PLACEHOLDER: "Derivat osnovnog URL-a (premošćivo sa ubacivanjem nasumičnog retka)"
+ LIFETIME: "Trajanje"
+ LIFETIME_HELP: "Određuje cache lifetime u sekundama. 0 = beskonačno"
+ GZIP_COMPRESSION: "Gzip kompresija"
+ GZIP_COMPRESSION_HELP: "Omogući GZip kompresiju Grav stranice radi poboljšanja performansi."
+ TWIG_TEMPLATING: "Twig predložak"
+ TWIG_CACHING: "Twig caching"
+ TWIG_CACHING_HELP: "Kontroliraj Twig caching mehanizam. Ostavi ovo omogućeno za najbolju performansu."
+ TWIG_DEBUG: "Twig debug"
+ TWIG_DEBUG_HELP: "Dopušta opciju ne učitavanja Twig Debugger extenzije"
+ DETECT_CHANGES: "Detektiraj izmjene"
+ DETECT_CHANGES_HELP: "Twig će automatski recompilirati Twig cache ako detektira ikakve promjene u Twig templateima"
+ AUTOESCAPE_VARIABLES: "Autoescape varijable."
+ AUTOESCAPE_VARIABLES_HELP: "Autoescapes sve varijable. Ovo će najvjerojatnije potrgati tvoj sajt"
+ ASSETS: "Assets"
+ CSS_PIPELINE: "CSS pipeline"
+ CSS_PIPELINE_HELP: "CSS pipeline je ujedinjenje više CSS resursa u jedan fajl"
+ CSS_MINIFY: "CSS minificiraj"
+ CSS_MINIFY_HELP: "Minificiraj CSS tijekom pipelining"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "CSS minifikacija Windows premošćenje"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Minify Override for Windows platforms. False by default due to ThreadStackSize"
+ CSS_REWRITE: "CSS rewrite"
+ CSS_REWRITE_HELP: "Prepiši sve relativne CSS URL-ove tokom spajanja"
+ JAVASCRIPT_PIPELINE: "JavaScript pipeline"
+ JAVASCRIPT_PIPELINE_HELP: "JS pipeline je ujedinjenje više JS resursa u jedan fajl"
+ JAVASCRIPT_MINIFY: "Umanji JavaScript"
+ JAVASCRIPT_MINIFY_HELP: "Minificiraj JS tijekom spajanja"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Omogući timestamps on assets"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Omogući asset timestamps"
+ COLLECTIONS: "Kolekcije"
+ ERROR_HANDLER: "Rukovatelj greškama"
+ DISPLAY_ERRORS: "Prikaži greške"
+ DISPLAY_ERRORS_HELP: "Display full backtrace-style error page"
+ LOG_ERRORS: "Dnevnik grešaka"
+ LOG_ERRORS_HELP: "Log greške to /logs folder"
+ DEBUGGER: "Debuger"
+ DEBUGGER_HELP: "Omogući Grav debugger i following settings"
+ DEBUG_TWIG: "Debugiraj Twig"
+ DEBUG_TWIG_HELP: "Omogući debugging of Twig templatea"
+ SHUTDOWN_CLOSE_CONNECTION: "Ugasi prekinute veze"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Prekidanje veze prije zvanja onShutdown(). false u slučaju debuging-a"
+ DEFAULT_IMAGE_QUALITY: "Defaultna kvaliteta slika"
+ DEFAULT_IMAGE_QUALITY_HELP: "Predpostavljena kvaliteta slika korištena tokom resamplinga ili cachinga (85%)"
+ CACHE_ALL: "Cache sve slike"
+ CACHE_ALL_HELP: "Provuci sve slike kroz Grav-ov cache sistem čak iako nemaju nikakve medijske promjene"
+ IMAGES_DEBUG: "Image debug watermark"
+ IMAGES_DEBUG_HELP: "Prikaži sloj preko slike koji prikazuje dubinu piksela slike, npr. u slučaju rada sa retina zasonima"
+ UPLOAD_LIMIT: "Limit veličine uploadane datoteke"
+ UPLOAD_LIMIT_HELP: "Postavka maksimalne veličine uploada (0 je neograničeno)"
+ ENABLE_MEDIA_TIMESTAMP: "Omogući timestamps on media"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Dodaje vrijeme promijene na medijske datoteke"
+ SESSION: "Sesija"
+ SESSION_ENABLED_HELP: "Omogući session podršku unutar Grava"
+ TIMEOUT: "Timeout"
+ TIMEOUT_HELP: "Određuje trajanje prijave u sekundama"
+ SESSION_NAME_HELP: "An identifier korišten da bi se formiralo ime session kolačića"
+ ABSOLUTE_URLS: "Apsolutni URLovi"
+ ABSOLUTE_URLS_HELP: "Apsolutni ili relativni URLovi za `base_url`"
+ PARAMETER_SEPARATOR: "Separator Parametara"
+ PARAMETER_SEPARATOR_HELP: "Separator za passed parametre koji može biti promijenjen za Apache na Windowsima"
+ TASK_COMPLETED: "Zadatak izvršen"
+ EVERYTHING_UP_TO_DATE: "Sve je najnovije"
+ UPDATES_ARE_AVAILABLE: "obnove su dostupne"
+ IS_AVAILABLE_FOR_UPDATE: "je dostupna za obnavljanje"
+ IS_NOW_AVAILABLE: "je sada dostupna"
+ CURRENT: "Trenutna"
+ UPDATE_GRAV_NOW: "Obnovi Grav Sada"
+ GRAV_SYMBOLICALLY_LINKED: "Grav je simbolično linkan. Upgrade neće biti dostupan."
+ UPDATING_PLEASE_WAIT: "Obnavljanje... pričekaj malo, preuzimam"
+ OF_THIS: "ovog"
+ OF_YOUR: "tvog"
+ HAVE_AN_UPDATE_AVAILABLE: "ima dostupnu obnovu"
+ SAVE_AS: "Sačuvaj kao"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Jesi li siguran da želiš obrisati ovu stranicu i sve podstranice? Ako je stranica prevedena na druge jezike, ti prijevodi će biti ostavljeni i moraju biti obrisani posebno. U suprotnom folder stranice će biti obrisan zajedno sa svojim podstranicama. Ova akcija ne može biti poništena."
+ AND: "i"
+ UPDATE_AVAILABLE: "Obnova je dostupna"
+ METADATA_KEY: "Key (e.g. 'Ključne riječi')"
+ METADATA_VALUE: "Vrijednost (npr. 'Blog, Grav')"
+ USERNAME_HELP: "Korisničko ime bi trebalo biti između 3 i 16 znakova, uključujući mala slova, brojeve, underscores (_), i crtice. Velika slova, razmaci i posebni znakovi nisu dopušteni"
+ FULLY_UPDATED: "Potpuno Obnovljen"
+ SAVE_LOCATION: "Lokacija sačuvanog"
+ PAGE_FILE: "Template Stranice"
+ NO_USER_ACCOUNTS: "Nije nađen nijedan korisnički račun, stvori ga prvo..."
+ REDIRECT_TRAILING_SLASH: "Preusmjeri /"
+ REDIRECT_TRAILING_SLASH_HELP: "301 preusmjeravanje rađe nego transparentno rokovanje sa / URL-ovima."
+ DEFAULT_DATE_FORMAT: "Format datuma stranice"
+ DEFAULT_DATE_FORMAT_HELP: "Oblik datuma kojeg koristi Grav. Prema zadanim postavkama, Grav pokušava pogoditi format datuma, ali možete i specificirati format koristeći PHP sintaksu (npr.: Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Pogodi automatski"
+ IGNORE_FILES: "Ignoriraj fajlove"
+ IGNORE_FILES_HELP: "Specifični fajlovi to ignore pri procesiranju stranica"
+ IGNORE_FOLDERS: "Ignoriraj foldere"
+ IGNORE_FOLDERS_HELP: "Specifični folderi koje treba ignorirati pri procesiranju stranica"
+ HTTP_ACCEPT_LANGUAGE: "Postavi jezik iz web preglednika"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Možete odabrati da se jezik postavi bazirano na `http_accept_language` header tag-u u internet pregledniku"
+ OVERRIDE_LOCALE: "Zaobilaženje regionalnih postavki"
+ OVERRIDE_LOCALE_HELP: "Zaobiđi PHP regionalne postavke, bazirano po trenutno odabranom jeziku"
+ REDIRECT: "Preusmjeravanje stranica"
+ REDIRECT_HELP: "Unesite rute ili eksterni URL za preusmjeravanje stranice npr. `/neka/ruta` ili `http://nekastranica.com`"
+ PLUGIN_STATUS: "Status Dodatka"
+ INCLUDE_DEFAULT_LANG: "Uključi defaultni jezik"
+ INCLUDE_DEFAULT_LANG_HELP: "Ovo će prepend sve URLs in the default language with the default language. e.g. `/en/blog/my-post`"
+ ALLOW_URL_TAXONOMY_FILTERS: "URL filteri razvrstavanja"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Page-based kolekcije dopuštaju ti da filtriraš preko `/taxonomy:value`."
+ REDIRECT_DEFAULT_CODE: "Predpostavljeni kod za preusmjeravanje"
+ REDIRECT_DEFAULT_CODE_HELP: "HTTP status kod koji se koristi kod preusmjeravanja"
+ IGNORE_HIDDEN: "Ignoriraj skriveno"
+ IGNORE_HIDDEN_HELP: "Ignoriraj sve fajlove i foldere koji počinju sa DOT"
+ WRAPPED_SITE: "Omotana stranica"
+ WRAPPED_SITE_HELP: "Za teme/dodatke da znaju ako je Grav omotan drugom platformom"
+ FALLBACK_TYPES: "Dopušteni fallback tipovi"
+ FALLBACK_TYPES_HELP: "Dopušteni tipovi fajlova koji mogu biti nađeni ako se pristupa preko Page route"
+ INLINE_TYPES: "Inline fallback tipovi"
+ INLINE_TYPES_HELP: "Lista tipova datoteka koje trebaju biti prikazane, a ne preuzete"
+ APPEND_URL_EXT: "Dodaj URL ekstenzije"
+ APPEND_URL_EXT_HELP: "Dodat će prilagođenu ekstenziju URL_u stranice. Ovo znači da u tom slučaju će Grav tražiti predložak u formatu `..twig`"
+ PAGE_MODES: "Modovi Stranica"
+ PAGE_TYPES: "Vrste stranica"
+ ACCESS_LEVELS: "Razine pristupa"
+ GROUPS: "Grupe"
+ GROUPS_HELP: "Popis grupa kojima korisnik pripada"
+ ADMIN_ACCESS: "Administratorski pristup"
+ SITE_ACCESS: "Pristup stranici"
+ INVALID_SECURITY_TOKEN: "Nevažeći sigurnosni token"
+ ACTIVATE: "Aktiviraj"
+ TWIG_UMASK_FIX: "Umask Fix"
+ TWIG_UMASK_FIX_HELP: "Po defaultu Twig stvara cache fajlove sa 0755 dozvolama, fix mijenja to na 0775"
+ CACHE_PERMS: "Cache dozvole"
+ CACHE_PERMS_HELP: "Predpostavljene dozvole datoteke za cache. Inače je 0755 ili 0775 ovisno o postavkama"
+ REMOVE_SUCCESSFUL: "Uspješno uklonjeno"
+ REMOVE_FAILED: "Uklanjanje nije uspjelo"
+ HIDE_HOME_IN_URLS: "Sakrij home rute u URL-ovima"
+ HIDE_HOME_IN_URLS_HELP: "Osigurava da pretpostavljene rute za stranice koje su pod home-om ne upućuju na regularnu rutu home-a"
+ TWIG_FIRST: "Procesiraj prvo Twig"
+ TWIG_FIRST_HELP: "U slučaju da postavite Twig procesiranje stranica, možete postaviti da se Twig procesira prije ili poslije markdown-a"
+ SESSION_SECURE: "Sigurna"
+ SESSION_SECURE_HELP: "Ako je uključeno, označava da komunikacija za ovaj kolačić mora biti preko enkriptirane veze. UPOZORENJE: Uključite ovo samo za stranice koje isključivo idu preko HTTPS-a"
+ SESSION_HTTPONLY: "Samo HTTP"
+ SESSION_HTTPONLY_HELP: "Ako je uključeno, označava da kolačići trebaju biti korišteni samo preko HTTP-a i JavaScript modifikacije nisu dozvoljene"
+ REVERSE_PROXY: "Reverse Proxy"
+ REVERSE_PROXY_HELP: "Uključite ovo ako ste iza Reverse Proxy-a i imate poteškoće sa adresama sa krivim rutama"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Krivi frontmatter. Nemoguće spremiti"
+ ADD_FOLDER: "Dodaj direktorij"
+ PROXY_URL: "Proxy URL"
+ PROXY_URL_HELP: "Unesi proxy HOST ili IP i PORT"
+ NOTHING_TO_SAVE: "Nema ništa za sačuvati"
+ FILE_ERROR_ADD: "Pojavila se pogreška tijekom pokušaja dodavanja datoteke"
+ FILE_ERROR_UPLOAD: "Pojavila se pogreška tijekom pokušaja uploada datoteke"
+ FILE_UNSUPPORTED: "Nepodržan tip fajla"
+ ADD_ITEM: "Dodaj stavku"
+ FILE_TOO_LARGE: "Datoteka je prevelika za prenošenje, maksimalna dopuštena je %s prema tvojim PHP postavkama. Povećaj svoju 'post_max_size' PHP postavku"
+ INSTALLING: "Instalacija"
+ LOADING: "Učitavanje.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "Sljedeće zavisnosti trebaju prvo biti ispunjene:"
+ ERROR_INSTALLING_PACKAGES: "Greška prilikom instalacije paketa"
+ INSTALLING_DEPENDENCIES: "Instalacija ovisnosti..."
+ INSTALLING_PACKAGES: "Instaliranje paketa.."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Paket(i) uspješno instalirani."
+ READY_TO_INSTALL_PACKAGES: "Spreman za instalaciju paketa"
+ PACKAGES_NOT_INSTALLED: "Paketi nisu instalirani"
+ PACKAGES_NEED_UPDATE: "Paketi su već instalirani, ali su prestari"
+ PACKAGES_SUGGESTED_UPDATE: "Paketi su već instalirani, i verzija je u redu, ali će biti ažurirani, da bi sve bilo najnovije"
+ REMOVE_THE: "Makni %s"
+ CONFIRM_REMOVAL: "Da li ste sigurni da želite obrisati ovaj %s?"
+ REMOVED_SUCCESSFULLY: "%s je uspješno maknut"
+ ERROR_REMOVING_THE: "Pogreška prilikom uklanjanja %s"
+ READY_TO_UPDATE_PACKAGES: "Spreman za ažuriranje paketa"
+ ERROR_UPDATING_PACKAGES: "Greška prilikom ažuriranja paketa"
+ UPDATING_PACKAGES: "Ažuriranje paketa.."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Paketi su uspješno ažurirani."
+ UPDATING: "Ažuriranje"
+ GPM_RELEASES: "GPM izdanja"
+ GPM_VERIFY_PEER_HELP: "Neki davatelji usluga se ne uspjevaju provjeriti getgrav.org SSL certifikat, uzrokujući da GPM ne radi. Ako je ovo slučaj kod vas, isključivanje ove postavke bi moglo pomoći"
+ AUTO: "Auto"
+ STABLE: "Stabilna"
+ TESTING: "Testiranje"
+ FRONTMATTER_IGNORE_FIELDS: "Zanemari frontmatter polja"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Paket %s je uspješno instaliran"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Stranica nije vidljiva, ordering je onemogućen"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "Napomena: Stranica mora biti spremljena prije no što izvršite prijenos datoteka na nju."
+ DROP_FILES_HERE_TO_UPLOAD: "Ispustite datoteke ovdje ili kliknite ovdje "
+ INSERT: "Umetni"
+ UNDO: "Poništi"
+ REDO: "Vrati poništeno"
+ HEADERS: "Zaglavlja"
+ BOLD: "Podebljaj"
+ ITALIC: "Ukosi"
+ STRIKETHROUGH: "Precrtaj"
+ LINK: "Link"
+ IMAGE: "Slika"
+ BLOCKQUOTE: "Blok citat"
+ UNORDERED_LIST: "Neuređena lista"
+ ORDERED_LIST: "Uređena lista"
+ EDITOR: "Uređivač"
+ PREVIEW: "Pregled"
+ FULLSCREEN: "Puni ekran"
+ MODULAR: "Modularni"
+ NON_MODULAR: "Ne-Modularni"
+ NON_VISIBLE: "Ne-Vidljivo"
+ NON_PUBLISHED: "Neobjavljeno"
+ CHARACTERS: "znakovi"
+ PUBLISHING: "Objavljivanje"
+ NOTIFICATIONS: "Obavijesti"
+ MEDIA_TYPES: "Vrste medija"
+ IMAGE_OPTIONS: "Mogućnosti slike"
+ THUMB: "Sličica"
+ TYPE: "Vrsta"
+ FILE_EXTENSION: "Nastavak datoteke"
+ ALL: "Sve"
+ FROM: "od"
+ TO: "na"
+ RELEASE_DATE: "Datum objave"
+ SORT_BY: "Sortiraj prema"
+ RESOURCE_FILTER: "Filtar..."
+ EXTERNAL_URL: "Vanjski link"
+ SESSION_PATH: "Putanja sesije"
+ FILEUPLOAD_PREVENT_SELF: 'Ne možete koristiti "%s" izvan stranica.'
+ DROPZONE_CANCEL_UPLOAD: 'Odustani od prijenosa'
+ DROPZONE_DEFAULT_MESSAGE: 'Ispustite datoteke ovdje ili kliknite ovdje '
+ DROPZONE_FALLBACK_MESSAGE: 'Tvoj web preglednik ne podržava upload datoteka na povuci i ispusti način.'
+ DROPZONE_INVALID_FILE_TYPE: "Ne možeš uploadati datoteke ovog tipa."
+ DROPZONE_MAX_FILES_EXCEEDED: "Ne možeš uploadati više datoteka."
+ DROPZONE_REMOVE_FILE: "Ukloni datoteku"
+ PREMIUM_PRODUCT: "Premium"
+ ERROR_SIMPLE: "Jednostavna Greška"
+ ERROR_SYSTEM: "Pogreška sustava"
+ NOT_SET: "Nije postavljeno"
+ PERMISSIONS: "Dozvole"
+ REINSTALL_PLUGIN: "Ponovno instalirajte dodatak"
+ REINSTALL_THEME: "Ponovo instalirajte temu"
+ REINSTALL_THE: "Ponovo instalirajte %s"
+ CONFIRM_REINSTALL: "Da li ste sigurni da želite reinstalirati %s?"
+ REINSTALLED_SUCCESSFULLY: "%s je uspješno reinstaliran"
+ ERROR_REINSTALLING_THE: "Pogreška prilikom reinstaliranja %s"
+ REINSTALLATION_FAILED: "Ponovna instalacija nije uspjela"
+ TOOLS: "Alati"
+ DIRECT_INSTALL: "Izravno instaliranje"
+ NO_PACKAGE_NAME: "Ime paketa nije navedeno"
+ NOT_VALID_GRAV_PACKAGE: "Grav paket nije valjan"
+ NAME_COULD_NOT_BE_DETERMINED: "Ime nije moguće odrediti"
+ ZIP_PACKAGE_NOT_FOUND: "ZIP pakiranje nije nađeno"
+ GPM_OFFICIAL_ONLY: "Samo službeni GPM"
+ GPM_OFFICIAL_ONLY_HELP: "Dopustiti izravne instalacije samo iz službenih GPM repozitorija."
+ PWD_PLACEHOLDER: "kompleks niz dug barem 8 znakova"
+ USERNAME_PLACEHOLDER: "samo mala slova, npr. 'admin'"
+ 2FA_TITLE: "Dvo faktorska autentifikacija"
+ 2FA_LABEL: "Admin pristup"
+ 2FA_ENABLED: "2FA omogućena"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_REGENERATE: "Ponovno stvori"
+ VIEW_SITE_TIP: "Prikaz stranice"
+ TOOLS_DIRECT_INSTALL_TITLE: "Direktna instalacija Grav paketa"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "Instaliraj paket kroz izravni ZIP upload"
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "Instaliraj Paket kroz link"
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Uploadaj i instaliraj"
diff --git a/user/plugins/admin/languages/hu.yaml b/user/plugins/admin/languages/hu.yaml
new file mode 100644
index 0000000..0659cd5
--- /dev/null
+++ b/user/plugins/admin/languages/hu.yaml
@@ -0,0 +1,756 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Ez egy béta kiadás! Éles környezetben csak saját felelősségre használd..."
+ ADMIN_REPORT_ISSUE: "Találtál egy hibát? Kérlek jelezd a GitHub-on."
+ EMAIL_FOOTER: "Powered by Grav - Egy Modern, File-alapú CMS"
+ LOGIN_BTN: "Bejelentkezés"
+ LOGIN_BTN_FORGOT: "Elfelejtettem"
+ LOGIN_BTN_RESET: "Új jelszó"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Jelszócsere információk küldése"
+ LOGIN_BTN_CLEAR: "Űrlap törlése"
+ LOGIN_BTN_CREATE_USER: "Felhasználó létrehozása"
+ LOGIN_LOGGED_IN: "Sikeresen bejelentkeztél"
+ LOGIN_FAILED: "Nem sikerült bejelentkezni"
+ LOGGED_OUT: "Kijelentkeztél"
+ RESET_NEW_PASSWORD: "Kérjük adjon meg egy új jelszót …"
+ RESET_LINK_EXPIRED: "A jelszócsere hivatkozás elavult, kérj újat"
+ RESET_PASSWORD_RESET: "A jelszócsere megtörtént"
+ RESET_INVALID_LINK: "Érvénytelen jelszócsere hivatkozás, kérjük próbáld újra"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "A jelszócserével kapcsolatos instrukciók erre a címre lettek elküldve: %s"
+ FORGOT_FAILED_TO_EMAIL: "Nem sikerült elküldeni emailen az instrukciókat, kérjük próbáld meg később"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "%s jelszavát nem lehet lercserélni, nem tartozik hozzá cím"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "%s nevű felhasználó nem létezik"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Nem lehet jelszót cserélni. Ez a weboldal nincs email-küldéshez konfigurálva"
+ FORGOT_EMAIL_SUBJECT: "%s jelszócsere kérés"
+ FORGOT_EMAIL_BODY: "Jelszócsere Kedves %1$s,
Az alábbi időpontban valaki a jelszavad cseréjét kezdeményezte: %4$s .
Kattints ide jelszavad cseréjéhez
Esetleg másold a következő URL-t a böngésződ címsávjába:
%2$s
Üdvözlettel, %3$s
"
+ MANAGE_PAGES: "Oldalak Kezelése"
+ CONFIGURATION: "Beállítások"
+ PAGES: "Oldalak"
+ PLUGINS: "Pluginek"
+ PLUGIN: "Plugin"
+ THEMES: "Témák"
+ LOGOUT: "Kijelentkezés"
+ BACK: "Vissza"
+ NEXT: "Következő"
+ PREVIOUS: "Előző"
+ ADD_PAGE: "Oldal Hozzáadása"
+ ADD_MODULAR: "Aloldal Hozzáadása"
+ MOVE: "Áthelyezés"
+ DELETE: "Törlés"
+ UNSET: "Beállítás törlése"
+ VIEW: "Nézet"
+ SAVE: "Mentés"
+ NORMAL: "Normál"
+ EXPERT: "Haladó"
+ EXPAND_ALL: "Mindet Kinyit"
+ COLLAPSE_ALL: "Mindet Bezár"
+ ERROR: "Hiba"
+ CLOSE: "Bezárás"
+ CANCEL: "Mégse"
+ CONTINUE: "Folytatás"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Jóváhagyás Szükséges"
+ MODAL_CHANGED_DETECTED_TITLE: "Változások Észlelve"
+ MODAL_CHANGED_DETECTED_DESC: "Nincs minden változtatás elmentve. Biztosan elhagyod az oldalt mentés nélkül?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Jóváhagyás Szükséges"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Biztosan törölni szeretnéd ezt a fájlt? Ha törlöd, nem lehet visszaállítani."
+ ADD_FILTERS: "Szűrők Hozzáadása"
+ SEARCH_PAGES: "Oldalak Keresése"
+ VERSION: "Verzió"
+ WAS_MADE_WITH: "felhasználásával"
+ BY: "Készítette"
+ UPDATE_THEME: "Téma Frissítése"
+ UPDATE_PLUGIN: "Plugin Frissítése"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "ebből a témából elérhető"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "ebből a pluginból elérhető"
+ AUTHOR: "Szerző"
+ HOMEPAGE: "Honlap"
+ DEMO: "Demó"
+ BUG_TRACKER: "Hibabejelentés"
+ KEYWORDS: "Kulcsszavak"
+ LICENSE: "Licenc"
+ DESCRIPTION: "Leírás"
+ README: "Olvass El"
+ REMOVE_THEME: "Téma Eltávolítása"
+ INSTALL_THEME: "Téma Telepítése"
+ THEME: "Téma"
+ BACK_TO_THEMES: "Vissza a Témákhoz"
+ BACK_TO_PLUGINS: "Vissza a Pluginekhez"
+ CHECK_FOR_UPDATES: "Frissítések Keresése"
+ ADD: "Hozzáadás"
+ CLEAR_CACHE: "Gyorsítótár Törlése"
+ CLEAR_CACHE_ALL_CACHE: "Minden Gyorsítótár"
+ CLEAR_CACHE_ASSETS_ONLY: "Csak Egyéb Dolgok"
+ CLEAR_CACHE_IMAGES_ONLY: "Csak Képek"
+ CLEAR_CACHE_CACHE_ONLY: "Csak Gyorsítótár"
+ CLEAR_CACHE_TMP_ONLY: "Csak Tmp"
+ DASHBOARD: "Áttekintés"
+ UPDATES_AVAILABLE: "Új Frissítések Elérhetőek"
+ DAYS: "Napja"
+ UPDATE: "Frissítés"
+ BACKUP: "Biztonsági Mentés"
+ BACKUPS: "Biztonsági mentések"
+ BACKUP_NOW: "Biztonsági mentés készítése"
+ BACKUPS_STATS: "Biztonsági mentés statisztika"
+ BACKUPS_HISTORY: "Biztonsági mentés történet"
+ BACKUPS_PURGE_CONFIG: "Biztonsági mentés tisztításának beállítása"
+ BACKUPS_PROFILES: "Mentési profilok"
+ BACKUPS_COUNT: "Mentések száma"
+ BACKUPS_PROFILES_COUNT: "Profilok száma"
+ BACKUPS_TOTAL_SIZE: "Használt tárhely"
+ BACKUPS_NEWEST: "Legfrissebb mentés"
+ BACKUPS_OLDEST: "Legrégebbi mentés"
+ BACKUPS_PURGE: "Tisztítás"
+ BACKUPS_NOT_GENERATED: "Még nem készültek biztonsági mentések..."
+ BACKUPS_PURGE_TIME: "%s nap biztonsági mentés maradt"
+ BACKUP_DELETED: "Mentés sikeresen törölve"
+ BACKUP_NOT_FOUND: "A biztonsági mentés nem található"
+ BACKUP_DATE: "Mentés dátuma"
+ STATISTICS: "Statisztika"
+ TODAY: "Mai"
+ WEEK: "Heti"
+ MONTH: "Havi"
+ LATEST_PAGE_UPDATES: "Utoljára Módosított Lapok"
+ MAINTENANCE: "Karbantartás"
+ UPDATED: "Frissítve"
+ MON: "Hé"
+ TUE: "Ke"
+ WED: "Sze"
+ THU: "Csü"
+ FRI: "Pé"
+ SAT: "Szo"
+ SUN: "Vas"
+ COPY: "Másolás"
+ EDIT: "Szerkesztés"
+ CREATE: "Létrehozás"
+ GRAV_ADMIN: "Grav admin"
+ GRAV_OFFICIAL_PLUGIN: "Grav Hivatalos Plugin"
+ GRAV_OFFICIAL_THEME: "Grav Hivatalos Téma"
+ PLUGIN_SYMBOLICALLY_LINKED: "Ez a plugin szimbolikusan linkelt. A frissítések nem lesznek észlelve"
+ THEME_SYMBOLICALLY_LINKED: "Ez a téma szimbolikusan linkelt. A frissítések nem lesznek észlelve"
+ REMOVE_PLUGIN: "Plugin Eltávolítása"
+ INSTALL_PLUGIN: "Plugin Telepítése"
+ AVAILABLE: "Elérhető"
+ INSTALLED: "Telepítve"
+ INSTALL: "Telepítés"
+ ACTIVE_THEME: "Aktív Téma"
+ SWITCHING_TO: "Átváltás"
+ SWITCHING_TO_DESCRIPTION: "Miután egy másik témára váltasz, nem garantálható, hogy minden oldalelrendezés működni fog - hibák léphetnek fel bizonyos oldalak betöltésekor."
+ SWITCHING_TO_CONFIRMATION: "Folytatod és átváltasz erre a témára"
+ CREATE_NEW_USER: "Új Felhasználó"
+ REMOVE_USER: "Felhasználó Törlése"
+ ACCESS_DENIED: "Hozzáférés Megtagadva"
+ ACCOUNT_NOT_ADMIN: "a felhasználód nem rendelkezik adminisztrátor jogokkal"
+ PHP_INFO: "PHP Infó"
+ INSTALLER: "Telepítő"
+ AVAILABLE_THEMES: "Elérhető Témák"
+ AVAILABLE_PLUGINS: "Elérhető Pluginek"
+ INSTALLED_THEMES: "Telepített Témák"
+ INSTALLED_PLUGINS: "Telepített Pluginek"
+ BROWSE_ERROR_LOGS: "Hibanapló Olvasása"
+ SITE: "Oldal"
+ INFO: "Infó"
+ SYSTEM: "Rendszer"
+ USER: "Felhasználó"
+ ADD_ACCOUNT: "Fiók Létrehozása"
+ SWITCH_LANGUAGE: "Nyelv Váltása"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Sikeresen plugin aktiváció"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Sikeresen letiltott plugin\""
+ SUCCESSFULLY_CHANGED_THEME: "Alapértelmezett téma megváltoztatva"
+ INSTALLATION_FAILED: "Sikertelen telepítés"
+ INSTALLATION_SUCCESSFUL: "Sikeres telepítés"
+ UNINSTALL_FAILED: "Sikertelen eltávolítás"
+ UNINSTALL_SUCCESSFUL: "Sikeres eltávolítás"
+ SUCCESSFULLY_SAVED: "Sikeresen mentve"
+ SUCCESSFULLY_COPIED: "Sikeresen másolva"
+ REORDERING_WAS_SUCCESSFUL: "Az átrendezés sikerült"
+ SUCCESSFULLY_DELETED: "Sikeres törlés"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Sikeres váltás az új nyelvre"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Nincs jogosultságod erre a műveletre"
+ CACHE_CLEARED: "Gyorsítótár törölve"
+ METHOD: "Metódus"
+ ERROR_CLEARING_CACHE: "Hiba a gyorsítótár törlésekor"
+ AN_ERROR_OCCURRED: "Egy hiba lépett fel"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "A biztonsági mentés letölthető"
+ DOWNLOAD_BACKUP: "Biztonsági mentés letöltése"
+ PAGES_FILTERED: "Kiszűrt oldalak"
+ NO_PAGE_FOUND: "Nincs ilyen oldal"
+ INVALID_PARAMETERS: "Érvénytelen Paraméterek"
+ NO_FILES_SENT: "Nincs küldött fájl"
+ EXCEEDED_FILESIZE_LIMIT: "Meghaladta a PHP konfigurációs fájlméret korlátot"
+ UNKNOWN_ERRORS: "Ismeretlen hiba"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Meghaladta a Grav konfigurációs fájlméret korlátot"
+ UNSUPPORTED_FILE_TYPE: "Nem támogatott fájlformátum"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Nem sikerült áthelyezni a feltöltött fájlt."
+ FILE_UPLOADED_SUCCESSFULLY: "A fájl sikeresen feltöltve"
+ FILE_DELETED: "Fájl törölve"
+ FILE_COULD_NOT_BE_DELETED: "A fájl nem törölhető"
+ FILE_NOT_FOUND: "A fájl nem található"
+ NO_FILE_FOUND: "Nem található fájl"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "A Grav sikeresen frissült a következő verzióra:"
+ GRAV_UPDATE_FAILED: "A Grav frissítés nem sikerült"
+ EVERYTHING_UPDATED: "Minden frissítve lett"
+ UPDATES_FAILED: "Sikertelen frissítés"
+ AVATAR_BY: "A profilkép forrása"
+ AVATAR_UPLOAD_OWN: "Vagy töltsd fel a sajátod..."
+ LAST_BACKUP: "Utolsó Mentés"
+ FULL_NAME: "Teljes név"
+ USERNAME: "Felhasználónév"
+ EMAIL: "Emailcím"
+ USERNAME_EMAIL: "Felhasználónév vagy e-mail"
+ PASSWORD: "Jelszó"
+ PASSWORD_CONFIRM: "Jelszó megerősítése"
+ TITLE: "Cím"
+ LANGUAGE: "Nyelv"
+ ACCOUNT: "Felhasználói fiók"
+ EMAIL_VALIDATION_MESSAGE: "Érvénytelen email-cím"
+ PASSWORD_VALIDATION_MESSAGE: "A jelszónak legalább egy számot, egy kis- és egy nagybetűt kell tartalmaznia, és legalább 8 karakter hosszú kell, hogy legyen"
+ LANGUAGE_HELP: "Kedvenc nyelv beállítása"
+ MEDIA: "Média"
+ DEFAULTS: "Alapértelmezett Értékek"
+ SITE_TITLE: "Weboldal Címe"
+ SITE_TITLE_PLACEHOLDER: "Weboldal Általános Címe"
+ SITE_TITLE_HELP: "A weboldal alapértelmezett címe, a témákban többször szerepelhet"
+ SITE_DEFAULT_LANG: "Alapértelmezett nyelv"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Alapértelmezett nyelv a téma tag-jében"
+ SITE_DEFAULT_LANG_HELP: "Alapértelmezett nyelv a téma tag-jében"
+ DEFAULT_AUTHOR: "Alapértelmezett Szerző"
+ DEFAULT_AUTHOR_HELP: "Egy alapértelmezett szerzői név, témákban valamint a lapokban többször előfordul"
+ DEFAULT_EMAIL: "Alapértelmezett email"
+ DEFAULT_EMAIL_HELP: "Egy alapértelmezett email, melyre a témák és lapok is hivatkozhatnak"
+ TAXONOMY_TYPES: "Taxonómia Típusok"
+ TAXONOMY_TYPES_HELP: "Az oldalakon használatos taxonómia-típusokat itt kell definiálni"
+ PAGE_SUMMARY: "Oldal Összefoglaló"
+ ENABLED: "Be"
+ ENABLED_HELP: "Oldal összegfoglaló bekapcsolása (az összefoglaló ugyanazt tartalmazza, mint az oldal)"
+ 'YES': "Igen"
+ 'NO': "Nem"
+ SUMMARY_SIZE: "Összefoglaló Mérete"
+ SUMMARY_SIZE_HELP: "A tartalom összefoglalóban szereplő karakterek száma"
+ FORMAT: "Formátum"
+ FORMAT_HELP: "rövid = az első elválasztóig, vagy a meghatározott karakterszámig; hosszú = az elválasztó figyelmen kívül lesz hagyva"
+ SHORT: "Rövid"
+ LONG: "Hosszú"
+ DELIMITER: "Elválasztó"
+ DELIMITER_HELP: "Az összefoglaló elválasztója (alapértelmezetten '===')"
+ METADATA: "Meta-adat"
+ METADATA_HELP: "Alapértelmezett meta-adatok, melyek minden oldalon megjelennek, hacsak nem kerülnek felülírásra az oldalnál"
+ NAME: "Név"
+ CONTENT: "Tartalom"
+ SIZE: "Méret"
+ ACTION: "Művelet"
+ REDIRECTS_AND_ROUTES: "Átirányítások és Útvonalak"
+ CUSTOM_REDIRECTS: "Saját Átirányítások"
+ CUSTOM_REDIRECTS_HELP: "útvonalak, melyek más oldalakra visznek. Sztenderd Regex kifejezések használhatóak"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/sajat/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/sajat/atiranyitas"
+ CUSTOM_ROUTES: "Saját Útvonalak"
+ CUSTOM_ROUTES_HELP: "útvonal aliasok más lapokra. Sztenderd Regex kifejezések használhatók"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/sajat/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/sajat/utvonal"
+ FILE_STREAMS: "Fájl Folyamok"
+ DEFAULT: "Alapértelmezett"
+ PAGE_MEDIA: "A Lap Médiái"
+ OPTIONS: "Lehetőségek"
+ PUBLISHED: "Közzétéve"
+ PUBLISHED_HELP: "Alapértelmezés szerint egy lap közzé van téve, ha csak a közzététel nem hamis, vagy a publish_date a jövőbe mutat, vagy az unpublish_date a múltban van"
+ DATE: "Dátum"
+ DATE_HELP: "A megadott dátum lesz az oldal létrehozásának dátuma."
+ PUBLISHED_DATE: "Közzététel Dátuma"
+ PUBLISHED_DATE_HELP: "Megadható tetszőleges dátum, amikor az oldal közzétételre kerül."
+ UNPUBLISHED_DATE: "Közlés Visszavonása"
+ UNPUBLISHED_DATE_HELP: "Megadható egy dátum, amikor az oldal közzététele visszavonásra kerül."
+ ROBOTS: "Robotok"
+ TAXONOMIES: "Taxonómiák"
+ TAXONOMY: "Taxonómia"
+ ADVANCED: "Haladó Beállítások"
+ SETTINGS: "Beállítások"
+ FOLDER_NUMERIC_PREFIX: "Mappa Numerikus Előtag"
+ FOLDER_NUMERIC_PREFIX_HELP: "Numerikus előtag, mellyel felülírható a lapok sorrendje és meghatározza láthatóságukat"
+ FOLDER_NAME: "Mappa Neve"
+ FOLDER_NAME_HELP: "A lap ebben a mappában lesz fizikailag tárolva"
+ PARENT: "Szülő"
+ DEFAULT_OPTION_ROOT: "- Gyökér -"
+ DEFAULT_OPTION_SELECT: "- Válassz -"
+ DISPLAY_TEMPLATE: "Megjelenítő Sablonja"
+ DISPLAY_TEMPLATE_HELP: "A laptípus, mely azt mondja meg, hogy melyik twig-sablon állítja elő a lapot"
+ BODY_CLASSES: "Tartalom Osztályok"
+ ORDERING: "Sorrend"
+ PAGE_ORDER: "Lapsorrend"
+ OVERRIDES: "Felülírások"
+ MENU: "Menü"
+ MENU_HELP: "Ez a felirat jelenik meg a menüben. Ha nincs beállítva, a cím fog megjelenni."
+ SLUG: "Slug"
+ SLUG_HELP: "A slug meghatározza a lapra jellemző részt az URL-ben"
+ SLUG_VALIDATE_MESSAGE: "Egy slug csak kis alfanumerikus karaktereket és kötőjelet tartalmazhat"
+ PROCESS: "Feldolgozás"
+ PROCESS_HELP: "Szabályozza a sablonok feldolgozását. Állítható laponként és globálisan is"
+ DEFAULT_CHILD_TYPE: "Alapértelmezett Aloldal Típus"
+ USE_GLOBAL: "Alapértelmezés Szerint"
+ ROUTABLE: "Elérhető"
+ ROUTABLE_HELP: "Meghatározza, hogy az oldal elérhető-e URL alapján"
+ CACHING: "Gyorsítótárazás"
+ VISIBLE: "Látható"
+ VISIBLE_HELP: "Meghatározza, hogy az oldal megjelenik-e a navigációban"
+ DISABLED: "Ki"
+ ITEMS: "Elemek"
+ ORDER_BY: "Rendezés"
+ ORDER: "Sorrend"
+ FOLDER: "Mappa"
+ ASCENDING: "Növekvő"
+ DESCENDING: "Csökkenő"
+ ADD_MODULAR_CONTENT: "Moduláris Tartalom Hozzáadása"
+ PAGE_TITLE: "Oldal Címe"
+ PAGE_TITLE_HELP: "Az oldal címe"
+ PAGE: "Oldal"
+ MODULAR_TEMPLATE: "Moduláris Sablon"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Fájlnév"
+ PARENT_PAGE: "Szülő Oldal"
+ HOME_PAGE: "Kezdőoldal"
+ HOME_PAGE_HELP: "A lap, amit a Grav alapértelmezett kiindulólapnak fog használni"
+ DEFAULT_THEME: "Alapértelmezett téma"
+ DEFAULT_THEME_HELP: "Az alapértelmezett téma, amelyet a Grav használ (ez az Antimatter)"
+ TIMEZONE: "Időzóna"
+ TIMEZONE_HELP: "Felülíja a szerver alapértelmezett időzónáját"
+ SHORT_DATE_FORMAT: "Rövid dátumformátum"
+ SHORT_DATE_FORMAT_HELP: "Beállítja a rövid dátumformátumot, amit a témák használnak"
+ LONG_DATE_FORMAT: "Hosszú dátumformátum"
+ LONG_DATE_FORMAT_HELP: "A hosszú dátumformátum, amit a témák használnak"
+ DEFAULT_ORDERING: "Alapértelmezett rendezés"
+ DEFAULT_ORDERING_HELP: "A lapok ezt a sorrendet használva jelennek meg, amíg ez a lista nem kerül felülírásra"
+ DEFAULT_ORDERING_DEFAULT: "Alapértelmezett - a mappa neve alapján"
+ DEFAULT_ORDERING_FOLDER: "Mappa - előtagmentes mappanév alapján"
+ DEFAULT_ORDERING_TITLE: "Cím - a fejlécben szereplő cím alapján"
+ DEFAULT_ORDERING_DATE: "Dátum - a fejlécben szereplő dátum alapján"
+ DEFAULT_ORDER_DIRECTION: "Rendezés alapértelmezett iránya"
+ DEFAULT_ORDER_DIRECTION_HELP: "Egy listában szereplő lapok rendezési sorrendje"
+ DEFAULT_PAGE_COUNT: "Oldalak alapértelmezett száma"
+ DEFAULT_PAGE_COUNT_HELP: "Lapok alapértelmezett maximális száma"
+ DATE_BASED_PUBLISHING: "Dátum-alapú közzététel"
+ DATE_BASED_PUBLISHING_HELP: "Automatikusan publikál/visszavon bejegyzéseket dátum alapján"
+ EVENTS: "Események"
+ EVENTS_HELP: "Meghatározott események engedélyezése, vagy tiltása. Tiltás esetén bizonyos pluginek nem működhetnek"
+ REDIRECT_DEFAULT_ROUTE: "Átírányítás útvonalra"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Automatikusan az alapértelmezett útvonalra irányít át"
+ LANGUAGES: "Nyelvek"
+ SUPPORTED: "Támogatott"
+ SUPPORTED_HELP: "Kétbetűs nyelvkódok vesszővel elválasztott listája (pl.: 'en,fr,de')"
+ TRANSLATIONS_ENABLED: "Fordítások engedélyezve"
+ TRANSLATIONS_ENABLED_HELP: "Fordítások engedélyezése a Grav-ban, a pluginekben és kiegészítőkben"
+ TRANSLATIONS_FALLBACK: "Alapértelmezett Fordítás"
+ TRANSLATIONS_FALLBACK_HELP: "Ha az aktív nyelven nem elérhető a fordítás, ezt használjuk"
+ ACTIVE_LANGUAGE_IN_SESSION: "Aktív nyelv a sessionben"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Az aktuális nyelv tárolása sessionben"
+ HTTP_HEADERS: "HTTP Fejlécek"
+ EXPIRES: "Lejárat"
+ EXPIRES_HELP: "A lejárati idő a fejlécben. Másodpercben értendő."
+ CACHE_CONTROL: "HTTP-Cache-Vezérlő"
+ CACHE_CONTROL_HELP: "Állíts be egy érvényes cache-vezérlő értéket, mint pl. `no-cahce, no-store, must-revalide`"
+ LAST_MODIFIED: "Utoljára módosítva"
+ LAST_MODIFIED_HELP: "Beállítja az utoljára módosítva fejlécet, mely segít optimalizálni a proxy és a böngésző gyorstárazását"
+ ETAG: "ETag"
+ ETAG_HELP: "Beállítja az etag fejlécet, mely megmutatja, módosult-e a tartalom"
+ VARY_ACCEPT_ENCODING: "\"Vary accept encoding\" HTTP fejléc"
+ VARY_ACCEPT_ENCODING_HELP: "Beállítja a `Vary: Accept Encoding` fejlécet, mely elősegíti a proxy és CDN gyorsítótárazást"
+ MARKDOWN: "Markdown"
+ MARKDOWN_EXTRA: "Markdown extra"
+ MARKDOWN_EXTRA_HELP: "Engedélyezi a Markdown Extra támogatást - https://michelf.ca/projects/php-markdown/extra/"
+ AUTO_LINE_BREAKS: "Automatikus sortörés"
+ AUTO_LINE_BREAKS_HELP: "Engedélyezi az automatikus sortörést a markdown szövegekben"
+ AUTO_URL_LINKS: "Automatikus URL linkek"
+ AUTO_URL_LINKS_HELP: "Engedélyezi az URL-ek automatikus HTML-linkekké alakítását"
+ ESCAPE_MARKUP: "Markup tagek átalakítása"
+ ESCAPE_MARKUP_HELP: "A markup tagek HTML-elemekké alakítása"
+ CACHING_HELP: "A Grav gyorsítótárának globális KI/BE kapcsolója"
+ CACHE_CHECK_METHOD: "Gyorsítótár ellenőrzés módja"
+ CACHE_CHECK_METHOD_HELP: "Meghatárzza, hogy a Grav milyen módon vizsgálja meg, hogy egy oldal módosult-e?"
+ CACHE_DRIVER: "Gyorsítótár meghajtó"
+ CACHE_DRIVER_HELP: "Válaszd ki melyik meghajtót használja a Grav. 'Auto Detect' esetén a legjobbnak tűnő megoldást használjuk"
+ CACHE_PREFIX: "Gyorsítótár előtag"
+ CACHE_PREFIX_HELP: "Egy azonosító, mely a Grav kulcsának része lesz. Ha nem vagy biztos a dolgodban, ne változtasd meg."
+ CACHE_PREFIX_PLACEHOLDER: "A bázis URL-ből származtatva (felülírhatod egy tetszőleges szöveggel)"
+ CACHE_PURGE_JOB: "Ütemezett tisztítás indítása"
+ CACHE_PURGE_JOB_HELP: "Az ütemező segítségével időről időre törölheti a régi Doctrine fájlok gyorsítótár mappáit ezzel a feladattal"
+ CACHE_PURGE: "Régi gyorsítótárak törlése"
+ LIFETIME: "Élettartam"
+ LIFETIME_HELP: "A gyorsítótár élettartama. 0 = végtelen"
+ GZIP_COMPRESSION: "Gzip-tömörítés"
+ GZIP_COMPRESSION_HELP: "Engedélyezi a GZip-tömörítést a Grav oldalakon, a gyorsabb elérés érdekében."
+ TWIG_TEMPLATING: "Twig Sablonok"
+ TWIG_CACHING: "Twig gyorsítótár"
+ TWIG_CACHING_HELP: "Szabályozza a Twig gyorsítótárazását. Hagyd bekapcsolva a jobb teljesítmény érdekében."
+ TWIG_DEBUG: "Twig hibakeresés"
+ TWIG_DEBUG_HELP: "Lehetőséget biztosít, hogy ne töltődjön be a Twig Debugger kiegészítő"
+ DETECT_CHANGES: "Változások észlelése"
+ DETECT_CHANGES_HELP: "A Twig automatikusan újragenerálja a gyorsítótárat, ha változást észlel a sablonokban"
+ AUTOESCAPE_VARIABLES: "Változók átalakítása"
+ AUTOESCAPE_VARIABLES_HELP: "Minden változót automatikusan átalakít. Ez legtöbbször kinyírja az oldaladat"
+ ASSETS: "Webes állományok"
+ CSS_PIPELINE: "CSS pipeline"
+ CSS_PIPELINE_HELP: "A CSS pipeline segítségével az összes CSS állomány egy fájlba kerül kiírásra"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Külső hivatkozások hozzávétele a CSS pipeline-ba"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "A külső URL-eknek lehetnek relatív hivatkozásai, ezért nem kéne belevenni a pipeline-ba"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "CSS pipeline feldolgozása előbb"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "A CSS pipeline feldolgozása a többi nem beillesztett CSS hivatkozás előtt"
+ CSS_MINIFY: "CSS zsugorítás"
+ CSS_MINIFY_HELP: "Összezsugorítja a CSS állományokat a pipelining során"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "CSS minify felülbírálása Windows alatt"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "A minify művelet felülbírálása Windows platformon. A ThreadStackSize értéke miatt Nem alapértelmezetten"
+ CSS_REWRITE: "CSS átírás"
+ CSS_REWRITE_HELP: "A pipelining alatt minden relatív URL-t átír a CSS definíciókban"
+ JAVASCRIPT_PIPELINE: "JavaScript pipeline"
+ JAVASCRIPT_PIPELINE_HELP: "A JS pipeline segítségével az összes JS állomány egy fájlba kerül kiírásra"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Külső elemek beillesztése a JS pipeline-ba"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "A külső URL-eknek lehetnek relatív hivatkozásai, ezért nem kéne belevenni a pipeline-ba"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "JS pipeline feldolgozása előbb"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "A JS pipelinefeldolgozása a többi nem beillesztett JS hivatkozás előtt"
+ JAVASCRIPT_MINIFY: "JavaScript zsugorítás"
+ JAVASCRIPT_MINIFY_HELP: "Összezsugorítja a JS állományokat pipelining során"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Időbélyegek az állományokon"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Engedélyezi az időbélyegek használatát a Webes állományokon"
+ COLLECTIONS: "Kollekciók"
+ ERROR_HANDLER: "Hibakezelő"
+ DISPLAY_ERRORS: "Hibák megjelenítése"
+ DISPLAY_ERRORS_HELP: "Teljes, visszakövethető hibaoldal megjelenítése"
+ LOG_ERRORS: "Hibák naplózása"
+ LOG_ERRORS_HELP: "Hibák naplózása a /logs mappában"
+ LOG_HANDLER: "Napló-kezelő"
+ LOG_HANDLER_HELP: "Hova mentsük a naplófájlokat"
+ SYSLOG_FACILITY: "Syslog szolgáltatás"
+ SYSLOG_FACILITY_HELP: "Syslog szolgáltatás a kimenethez"
+ DEBUGGER: "Hibakereső"
+ DEBUGGER_HELP: "Engedélyezi a Grav hibakeresőt és az alábbiakat"
+ DEBUG_TWIG: "Twig Hibakeresés"
+ DEBUG_TWIG_HELP: "Engedélyezi a Twig sablonok hibakeresését"
+ SHUTDOWN_CLOSE_CONNECTION: "A leállítás bezárja a kapcsolatokat"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Lezárja a kapcsolatot az onShutdown() hívása előtt. Legyen Nem hibakeresésnél"
+ DEFAULT_IMAGE_QUALITY: "Alapértelmezett képminőség"
+ DEFAULT_IMAGE_QUALITY_HELP: "Az alapértelmezett képminőség átméretezés, vagy gyorsítótárazás során (85%)"
+ CACHE_ALL: "Az összes kép gyorsítótárazása"
+ CACHE_ALL_HELP: "Az összes képen végigfut a Grav gyorsítótárazó, még azokon is, amelyek nincsenek manipulálva"
+ IMAGES_DEBUG: "Vízjel hibakereséshez"
+ IMAGES_DEBUG_HELP: "Megjelenít egy vízjelet a kép felett annak pixelsűrűségével, amely hasznos lehet retinakijelzőkön, például"
+ UPLOAD_LIMIT: "File-feltölés limit"
+ UPLOAD_LIMIT_HELP: "Beállítja a maximális feltöltési limitet (0 esetén korlátlan)"
+ ENABLE_MEDIA_TIMESTAMP: "Időbélyeg a médiaállományokon"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Hozzáfűzi minden miédiafájl nevéhez az utolsó módosítás időbélyegét"
+ SESSION: "Munkamenet"
+ SESSION_ENABLED_HELP: "A Grav session-kezelés bekapcsolása"
+ TIMEOUT: "Időtúllépés"
+ TIMEOUT_HELP: "A session időtúllépése, mp-ben"
+ SESSION_NAME_HELP: "Egy azonosító, a session süti generálásához"
+ SESSION_UNIQUENESS: "Egyedi string"
+ ABSOLUTE_URLS: "Abszolút URL-ek"
+ ABSOLUTE_URLS_HELP: "Abszolút vagy relatív URL-ek a bázis URL-ben"
+ PARAMETER_SEPARATOR: "Paraméter-elválasztó"
+ PARAMETER_SEPARATOR_HELP: "Az átadott paraméterek elválasztója, mely az Apache Windowsos változatánál változtatható meg"
+ TASK_COMPLETED: "Feladat kész"
+ EVERYTHING_UP_TO_DATE: "Minden naprakész"
+ UPDATES_ARE_AVAILABLE: "frissítések elérhető"
+ IS_AVAILABLE_FOR_UPDATE: "frissítés érhető el"
+ IS_NOW_AVAILABLE: "elérhető"
+ CURRENT: "Jelenlegi"
+ UPDATE_GRAV_NOW: "Grav Frissítése Most"
+ GRAV_SYMBOLICALLY_LINKED: "A Grav szimbolikusan linkelt. A frissítés nem lehetséges"
+ UPDATING_PLEASE_WAIT: "Frissítés... kis türelmet, letöltés"
+ OF_THIS: "változatú"
+ OF_YOUR: "a te"
+ HAVE_AN_UPDATE_AVAILABLE: "van frissebb változat"
+ SAVE_AS: "Mentés, mint"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Biztosan törlöd ezt az oldalt és aloldalait? Ha tartozik hozzá fordítás, azokat még egyesével törölni kell. Máskülönben a mappa oldalakkal együtt törölve lesz. Ez a művelet nem visszavonható."
+ AND: "és"
+ UPDATE_AVAILABLE: "Frissítés elérhető"
+ METADATA_KEY: "Kulcs (pl. 'Keywords')"
+ METADATA_VALUE: "Érték (pl. 'Blog, Grav')"
+ USERNAME_HELP: "A felhasználónév 3 és 16 karakter hosszú legyen, tartalmazhat kisbetűket, számokat, aláhúzást és kötőjelet. Nagybetűk, szóköz és speciális karakterek nem használhatóak."
+ FULLY_UPDATED: "Teljesen Naprakész"
+ SAVE_LOCATION: "Mentés helye"
+ PAGE_FILE: "Oldal sablon"
+ PAGE_FILE_HELP: "Oldal sablon fájlneve és alapértelmezetten a megjelenítési sablon az oldalhoz"
+ NO_USER_ACCOUNTS: "Nincs egyetlen felhasználó sem, először hozz létre egyet..."
+ NO_USER_EXISTS: "Nincs helyi felhasználó ehhez a fiókhoz, nem tudjuk menteni..."
+ REDIRECT_TRAILING_SLASH: "Perjellel végőződ címek"
+ REDIRECT_TRAILING_SLASH_HELP: "Végrehajt egy 301-es átirányítást a perjellel végződő URI-ken, ahelyett hogy transzparensen lekezelné azokat."
+ DEFAULT_DATE_FORMAT: "Oldal dátum formátuma"
+ DEFAULT_DATE_FORMAT_HELP: "A Grav által használt dátumformátum. Alapértelmezetten a Grav megpróbálja ezt kitalálni, ám megadható a PHP's dátumszintaxisát használva (pl.: Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Próbálja meg kitalálni"
+ IGNORE_FILES: "Mellőzött fájlok listája"
+ IGNORE_FILES_HELP: "Ezek a fájlok figyelmen kívül lesznek hagyva a lapok feldolgozásakor"
+ IGNORE_FOLDERS: "Mellőzött mappák"
+ IGNORE_FOLDERS_HELP: "Ezek a mappák figyelmen kívül lesznek hagyva a lapok feldolgozásakor"
+ HIDE_EMPTY_FOLDERS: "Üres mappák elrejtése"
+ HTTP_ACCEPT_LANGUAGE: "Nyelv beállítása böngészőből"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Választhatod, hogy a böngésző `http_accept_language` fejléc tagjének alapján kerüljön a nyelv kiválasztásra"
+ OVERRIDE_LOCALE: "Helyszín felülbírálása"
+ OVERRIDE_LOCALE_HELP: "A PHP területi beállításainak felülbírálása a nyelv kiválasztásához"
+ REDIRECT: "Oldal átirányítása"
+ REDIRECT_HELP: "Adj meg egy útvonalat, vagy külső URL-t, ahova ez az oldal mutasson, pl. `/egy/utvonal` vagy `http://egyoldal.hu`"
+ PLUGIN_STATUS: "Plugin állapota"
+ INCLUDE_DEFAULT_LANG: "Nyelv beillesztése"
+ INCLUDE_DEFAULT_LANG_HELP: "Minden URL elé beilleszti az alapértelmezett nyelvet. pl. `/en/blog/my-post`"
+ ALLOW_URL_TAXONOMY_FILTERS: "URL Taxonómia Szűrők"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Taxonómia alapú szűrés URL-ben: `/taxonomy:ertek`."
+ REDIRECT_DEFAULT_CODE: "Alapértelmezett átirányítási kód"
+ REDIRECT_DEFAULT_CODE_HELP: "HTTP státuszkód átirányításokhoz"
+ IGNORE_HIDDEN: "Rejtett fájlok mellőzése"
+ IGNORE_HIDDEN_HELP: "Minden állomány és mappa mellőzése, amely ponttal kezdődik"
+ WRAPPED_SITE: "Beágyazott weblap"
+ WRAPPED_SITE_HELP: "A témák/pluginek számára, hogy tudják a Grav be lett ágyazva más platformba"
+ FALLBACK_TYPES: "Engedélyezett alaptípusok"
+ FALLBACK_TYPES_HELP: "Engedélyezett fájlípusok, melyek elérhetőek oldal útvonalakból. Alapértelmezés szerint bármilyen támogatott médiatípus."
+ INLINE_TYPES: "Beágyazott alaptípusok"
+ INLINE_TYPES_HELP: "Azon fájltípusok listája, amelyek letöltés helyett megjelenítésre kerülnek"
+ APPEND_URL_EXT: "Kiterjesztés hozzáfűzése az URL-hez"
+ APPEND_URL_EXT_HELP: "Egyedi kiterjesztés hozzáadása az oldal URL-jéhez. Fontos, hogy így a Grav `..twig` formátumban várja a sablonokat"
+ PAGE_MODES: "Oldal Módok"
+ PAGE_TYPES: "Oldaltípusok"
+ ACCESS_LEVELS: "Hozzáférési Szintek"
+ GROUPS: "Csoportok"
+ GROUPS_HELP: "Csoportok listája, melyeknek tagja a felhasználó"
+ ADMIN_ACCESS: "Adminisztrátor Jogosultságok"
+ SITE_ACCESS: "Weboldalszintű Jogosultságok"
+ INVALID_SECURITY_TOKEN: "Érvénytelen Biztonsági Token"
+ ACTIVATE: "Aktiválás"
+ TWIG_UMASK_FIX: "Umask javítás"
+ TWIG_UMASK_FIX_HELP: "Alapértelmezés szerint a Twig a gyorsítótárazott fájlokat 0755 jogokkal hozza létre, ez javítja ezt 0775-re"
+ CACHE_PERMS: "Gyorsítótár-engedélyek"
+ CACHE_PERMS_HELP: "Alapértelmezés szerint a gyorsítótár mappába jogosultságok. Általában 0755 vagy 0775, beállításaitól függően"
+ REMOVE_SUCCESSFUL: "Sikeresek eltávolítása"
+ REMOVE_FAILED: "Sikertelenek eltávolítása"
+ HIDE_HOME_IN_URLS: "Kezdőoldal útvonal elrejtése az URL-ekben"
+ HIDE_HOME_IN_URLS_HELP: "Biztosítja, hogy az alapértelmezett útvonal a kezdőoldal alatt ne hivatkozzon a kezdőoldal szokásos útvonalára"
+ TWIG_FIRST: "Twig feldolgozása először"
+ TWIG_FIRST_HELP: "Ha engedélyezte a Twig oldalfeldolgozást, itt beállíthatja, hogy a Twig-et a markdown előtt vagy után dolgozza fel"
+ SESSION_SECURE: "Biztonságos"
+ SESSION_SECURE_HELP: "Ha ez be van jelölve, az ehhez a cookie-hoz tartozó kommunikáció kódolt átviteli csatornán mehet csak. FIGYELEM: Csak olyan oldal esetén engedélyezd, ami kizárólag HTTPS-en forgalmaz"
+ SESSION_HTTPONLY: "Csak HTTP"
+ SESSION_HTTPONLY_HELP: "Ha ez bejelölt, a cookie-kat csak HTTP-n keresztül lehet használni és JavaScript módosítás nem engedélyezett"
+ REVERSE_PROXY: "Fordított proxy"
+ REVERSE_PROXY_HELP: "Engedélyezd ezt, ha egy fordított proxy mögött vagy és probléma van a helytelen portokat tartalmazó URL-címek betöltésével"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Érvénytelen leírás, nem lehet menteni"
+ ADD_FOLDER: "Mappa hozzáadása"
+ PROXY_URL: "Proxy URL"
+ PROXY_URL_HELP: "Add meg a proxy gépnevét vagy IP címét és a portot"
+ NOTHING_TO_SAVE: "Nincs mit menteni"
+ FILE_ERROR_ADD: "Hiba történt a fájl hozzáadásakor"
+ FILE_ERROR_UPLOAD: "Hiba történt a fájl feltöltésekor"
+ FILE_UNSUPPORTED: "Nem támogatott fájltípus"
+ ADD_ITEM: "Elem hozzáadása"
+ FILE_TOO_LARGE: "A fájl túl nagy ahhoz, hogy feltölthessük. A maximálisan engedélyezett méret: %s a PHP beállítások szerint. Növelje meg a `post_max_size` PHP beállítást"
+ INSTALLING: "Telepítés"
+ LOADING: "Betöltés…"
+ DEPENDENCIES_NOT_MET_MESSAGE: "Elsőnek a következő függőségek kell teljesítenünk::"
+ ERROR_INSTALLING_PACKAGES: "Hiba történt a csomag(ok) telepítése közben"
+ INSTALLING_DEPENDENCIES: "Függőségek telepítése..."
+ INSTALLING_PACKAGES: "Csomag(ok) telepítése..."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Csomag(ok) sikeresen telepítve."
+ READY_TO_INSTALL_PACKAGES: "A csomag(ok) telepítésre készen áll(nak)"
+ PACKAGES_NOT_INSTALLED: "Nem telepített csomagok"
+ PACKAGES_NEED_UPDATE: "Már telepített, de régi csomagok"
+ PACKAGES_SUGGESTED_UPDATE: "Már telepített, nem elavult csomagok, melyekhez újabb frissítések állnak rendelkezésre"
+ REMOVE_THE: "%s eltávolítása"
+ CONFIRM_REMOVAL: "Biztosan törli ezt a(z) %s?"
+ REMOVED_SUCCESSFULLY: "%s sikeresen eltávolítva"
+ ERROR_REMOVING_THE: "Hiba történt %s eltávolítása közben"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "A következő függőségeket csak a(z) %s követeli meg, így amennyiben nem használjuk őket, eltávolíthatjuk őket közvetlenül innen."
+ READY_TO_UPDATE_PACKAGES: "A csomag(ok) frissítésre készen áll(nak)"
+ ERROR_UPDATING_PACKAGES: "Hiba történt a csomag(ok) frissítése közben"
+ UPDATING_PACKAGES: "Csomag(ok) frissítésére..."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Csomag(ok) sikeresen frissítve."
+ UPDATING: "Frissítés"
+ GPM_RELEASES: "GPM kiadások"
+ GPM_RELEASES_HELP: "Válassza a 'Teszt'-et a béta vagy tesztelési verziók telepítéséhez"
+ GPM_METHOD: "A külső beszerzés módja (GPM)"
+ GPM_METHOD_HELP: "Auto-ra állítva a Grav fopen-t használ, amennyiben az elérhető, ellenkező esetben tartalékként a cURL-t hívja meg. A többi opcióval kikényszeríthetjük egyikük állandó használatát."
+ GPM_VERIFY_PEER: "GPM peer távoli ellenőrzése (SSL)"
+ GPM_VERIFY_PEER_HELP: "Egyes csomagszolgáltatók a getgrav.org SSL tanúsítványának ellenőrzésekor megbuknak, és emiatt a GPM nem fog működni. Ilyen esetekben segít, ha kikapcsoljuk ezt a beállítást"
+ AUTO: "Auto"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stabil"
+ TESTING: "Teszt"
+ FRONTMATTER_PROCESS_TWIG: "Twig leírás feldolgozása"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Amikor engedélyezve van, az oldal leíró részében Twig változókat lehet használni"
+ FRONTMATTER_IGNORE_FIELDS: "Leíró mezők figyelmen kívül hagyása"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Bizonyos leíró mezők tartalmazhatnak Twig-eket, de nem űrlapként kell feldolgozni őket"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "%s csomag sikeresen telepítve."
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "A szülő elem rendezése érvényes. Itt a rendezés nem elérhető"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Az oldal nem látható, a rendezés nem elérhető"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "Az admin felületen keresztüli rendezés nem támogatott, mert több, mint 200 elem van"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Az oldalrendezés ezen az oldalon ki van kapcsolva, mert a Mappa Numerikus Előtag is ki van kapcsolva"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "Megyjegyzés: Amíg az oldal nincs elmentve, nem lehet médiafájlokat hozzáadni. Kattintson felül a 'Mentés'-re"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "Megjegyezés: Az oldalt menteni kell fájl feltöltése előtt."
+ DROP_FILES_HERE_TO_UPLOAD: "Ejtse erre a területre a fájlokat"
+ INSERT: "Beillesztés"
+ UNDO: "Visszavonás"
+ REDO: "Mégis"
+ HEADERS: "Fejléc"
+ BOLD: "Félkövér"
+ ITALIC: "Dőlt"
+ STRIKETHROUGH: "Áthúzott"
+ SUMMARY_DELIMITER: "Összefoglaló határoló"
+ LINK: "Link"
+ IMAGE: "Kép"
+ BLOCKQUOTE: "Idézet"
+ UNORDERED_LIST: "Rendezetlen lista"
+ ORDERED_LIST: "Rendezett lista"
+ EDITOR: "Szerkesztő"
+ PREVIEW: "Előnézet"
+ FULLSCREEN: "Teljes képernyő"
+ MODULAR: "Moduláris"
+ NON_ROUTABLE: "Nem route-olható"
+ NON_MODULAR: "Nem-moduláris"
+ NON_VISIBLE: "Nem látható"
+ NON_PUBLISHED: "Közzé nem tett"
+ CHARACTERS: "karakter"
+ PUBLISHING: "Közzététel"
+ NOTIFICATIONS: "Értesítések"
+ MEDIA_TYPES: "Médiatípusok"
+ IMAGE_OPTIONS: "Képbeállítások"
+ MIME_TYPE: "MIME-típus"
+ THUMB: "Bélyegkép"
+ TYPE: "Típus"
+ FILE_EXTENSION: "Fájlkiterjesztés"
+ LEGEND: "Oldal előzmények"
+ MEMCACHE_SERVER: "Memcache kiszolgáló"
+ MEMCACHE_SERVER_HELP: "Memcache kiszolgáló címe"
+ MEMCACHE_PORT: "Memcache port"
+ MEMCACHE_PORT_HELP: "A Memcache kiszolgáló portja"
+ MEMCACHED_SERVER: "Memcached kiszolgáló"
+ MEMCACHED_SERVER_HELP: "Memcached kiszolgáló címe"
+ MEMCACHED_PORT: "Memcached port"
+ MEMCACHED_PORT_HELP: "A Memcached kiszolgáló portja"
+ REDIS_SERVER: "Redis kiszolgáló"
+ REDIS_SERVER_HELP: "A Redis kiszolgáló címe"
+ REDIS_PORT: "Redis port"
+ REDIS_PORT_HELP: "A Redis kiszolgáló portja"
+ REDIS_PASSWORD: "Redis jelszó/titkos kulcs"
+ ALL: "Mind"
+ FROM: "ettől"
+ TO: "eddig"
+ RELEASE_DATE: "Megjelenési dátum"
+ SORT_BY: "Rendezés"
+ RESOURCE_FILTER: "Szűrés..."
+ FORCE_SSL: "SSL kényszerítése"
+ FORCE_SSL_HELP: "SSL kapcsolat globális kikényszerítése. Ha be van kapcsolva, a Grav a HTTP lekéréseket automatikusan HTTPS-re irányítja át"
+ NEWS_FEED: "Hírfolyam"
+ EXTERNAL_URL: "Külső URL-cím"
+ SESSION_PATH: "A munkamenet elérési útja"
+ SESSION_PATH_HELP: "Csak akkor használatos, ha egyedi bázis URL lett beállítva (az oldal domain-je / az almappa át lett írva)"
+ CUSTOM_BASE_URL: "Egyedi bázis URL"
+ CUSTOM_BASE_URL_HELP: "Akkor használatos, ha az oldal domain-je felülírandó, vagy a Grav sajátjától eltérő almappával dolgozunk.\nPélda: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'Nem lehet használni a "%s"-t az oldalakon kívül.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'A fájlt %s nem lehet feltölteni: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'A fájl nem lehet áthelyezni innen %s ide "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'Feltöltés megszakítása'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Biztosan meg akarod szakítani a fájlfeltöltést?'
+ DROPZONE_DEFAULT_MESSAGE: 'Húzd be ide a feltöltendő fájlt, vagy kattints erre a területre '
+ DROPZONE_FALLBACK_MESSAGE: 'A böngésződ nem támogatja drag''n''drop fájlfeltöltést.'
+ DROPZONE_FALLBACK_TEXT: 'Kérjük használd a tartalék űrlapot a fájlok feltöltéséhez (csakúgy, mint a régi szép időkben).'
+ DROPZONE_FILE_TOO_BIG: 'A fájl mérete túl nagy ({{filesize}}MiB). Maximális fájlméret: {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "Nem lehet feltölteni ilyen típusú fájlt."
+ DROPZONE_MAX_FILES_EXCEEDED: "Nem lehet feltölteni több fájlt."
+ DROPZONE_REMOVE_FILE: "Fájl eltávolítása"
+ DROPZONE_RESPONSE_ERROR: "A szerver {{statusCode}} kóddal válaszolt."
+ PREMIUM_PRODUCT: "Prémium"
+ ADMIN_CACHING: "Admin gyorsítótár engedélyezése"
+ ADMIN_CACHING_HELP: "Az admin felület gyorsítótárazása a weboldal frontend részétől függetlenül vezérelhető."
+ DESTINATION_NOT_SPECIFIED: "A cél meghatározatlan"
+ UPLOAD_ERR_NO_TMP_DIR: "Hiányzik az átmeneti mappa"
+ SESSION_SPLIT: "Munkamenet Felosztás"
+ SESSION_SPLIT_HELP: "Független megosztott munkamenetek a webhely és más bővítmények között (mint pl. admin)"
+ ERROR_FULL_BACKTRACE: "Teljes visszakövetés a hibaüzenetben"
+ ERROR_SIMPLE: "Egyszerű hiba"
+ ERROR_SYSTEM: "Rendszerhiba"
+ IMAGES_AUTO_FIX_ORIENTATION: "Képorientáció automatikus javítása"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Exif adatok alapján a kép automatikus helyreforgatása"
+ REDIS_SOCKET: "Redis socket"
+ REDIS_SOCKET_HELP: "A Redis socket"
+ NOT_SET: "Nincs beállítva"
+ PERMISSIONS: "Engedélyek"
+ NEVER_CACHE_TWIG: "Sose gyorsítótárazza a Twig-eket"
+ NEVER_CACHE_TWIG_HELP: "Csak a tartalmat gyorsítótárazza, a Twig minden alkalommal kiértékelődik.\nFigyelmen kívül hagyja a twig_first beállítást."
+ ALLOW_WEBSERVER_GZIP: "Webszerver Gzip engedélyezése"
+ ALLOW_WEBSERVER_GZIP_HELP: "Kikapcsolva alapértelmezettként. Ha engedélyezve van, a WebServer-konfigurált Gzip/Deflate tömörítés működni fog, de http kapcsolaton keresztül nem lehet bezárni mielőtt az onShutDown() esemény lassú oldalbetöltést okoz"
+ OFFLINE_WARNING: "A GPM kapcsolatot nem lehet létrehozni"
+ CLEAR_IMAGES_BY_DEFAULT: "Alapértelmezés szerint törölje a kép gyorsítótárát"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "Alapértelmezés szerint a feldolgozott képek törlődnek az összes gyorsítótár törlésekor, de letiltható"
+ CLI_COMPATIBILITY: "CLI Kompatibilitás"
+ CLI_COMPATIBILITY_HELP: "Győződj meg róla, hogy csak a nem felejtő gyorsítótár-vezérlők vannak használatban (fájl, redis, memcache stb.)"
+ REINSTALL_PLUGIN: "Bővítmény Újratlepítése"
+ REINSTALL_THEME: "Téma Újratelepítése"
+ REINSTALL_THE: "%s Újratelepítése"
+ CONFIRM_REINSTALL: "Biztos vagy benne, hogy újra akarod telepíteni a(z) %s-t?"
+ REINSTALLED_SUCCESSFULLY: "%s újratelepítése sikeresen megtörtént"
+ ERROR_REINSTALLING_THE: "Hiba történt a(z) %s újratelepítése közben"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "%s csomag újratelepítése sikeresen megtörtént"
+ REINSTALLATION_FAILED: "Sikertelen újratelepítés"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "A telepített verzió nem a legfrissebb kiadás. A Tovább gombra kattintva, eltávolíthatod a jelenlegi verziót és telepítheted a legfrissebb elérhető kiadást"
+ TOOLS: "Eszközök"
+ DIRECT_INSTALL: "Közvetlen telepítése"
+ NO_PACKAGE_NAME: "A csomag neve nincs megadva"
+ PACKAGE_EXTRACTION_FAILED: "A csomag kitömörítése sikertelen"
+ NOT_VALID_GRAV_PACKAGE: "Nem érvényes Grav csomag"
+ NAME_COULD_NOT_BE_DETERMINED: "A név nem határozható meg"
+ CANNOT_OVERWRITE_SYMLINKS: "Nem lehet felülírni a symlinkeket"
+ ZIP_PACKAGE_NOT_FOUND: "Nem található ZIP archívum"
+ GPM_OFFICIAL_ONLY: "Csak hivatalos GPM"
+ GPM_OFFICIAL_ONLY_HELP: "Csak a hivatalos GPM repository-ból engedélyezett a közvetlen telepítés"
+ NO_CHILD_TYPE: "Nem tartozik alárendelt típus ehhez a rawroute-hoz"
+ SORTABLE_PAGES: "Csoportosítható oldalak:"
+ UNSORTABLE_PAGES: "Nem csoportosítható oldalak"
+ ADMIN_SPECIFIC_OVERRIDES: "Admin specifikus felülírások"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Alárendelt Megjelenítési Sorrend"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "A sorrend amiként ez az oldal aloldalai listázva legyenek az admin plugin 'Oldalak' menüpont alatt"
+ PWD_PLACEHOLDER: "komplex karakterlánc legalább 8 karakter hosszú"
+ PWD_REGEX: "Jelszó Regex"
+ PWD_REGEX_HELP: "A jelszónak legalább egy számot, egy nagybetűs és kis betűket, valamint legalább 8 vagy több karaktert kell tartalmaznia"
+ USERNAME_PLACEHOLDER: "csak kisbetűs karakterek, pl. 'admin'"
+ USERNAME_REGEX: "Felhasználónév Regex"
+ USERNAME_REGEX_HELP: "Alapértelmezés szerint: csak kisbetűk, számjegyek, kötőjelek és aláhúzásjelek használhatók. 3 - 16 karakter"
+ CONTENT_PADDING: "Tartalom padding"
+ CONTENT_PADDING_HELP: "Engedélyezd / Tiltsd le a tartalom padding-jét a tartalomterületen, hogy több helyet biztosíts"
+ ENABLE_AUTO_METADATA: "Automatikus metaadat az Exif-ből"
+ ENABLE_AUTO_METADATA_HELP: "Automatikusan létrehozhat metaadatfájlokat az exif információkkal rendelkező képekhez"
+ 2FA_TITLE: "Kétlépcsős Hitelesítés"
+ 2FA_INSTRUCTIONS: "##### Kétlépcsős Hitelesítés\nBeállítottad a **Kétlépcsős Hitelesítést** ehhez a fiókhoz. Kérlek használd a **Kétlépcsős Hitelesítés** applikációt, hogy beírd a **6 jegyű kódot** a bejelentkezési folyamat befejezéséhez. "
+ 2FA_LABEL: "Adminisztrátor Jogosultságok"
+ 2FA_FAILED: "Érvénytelen Kétlépcsős Hitelesítési kód, kérlek próbáld meg újra..."
+ 2FA_ENABLED: "Kétlépcsős Hitelesítés engedélyezve"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "Kétlépcsős Hitelesítési Titok"
+ 2FA_REGENERATE: "Újragenerálás"
+ VIEW_SITE_TIP: "Oldal megtekintése"
+ TOOLS_DIRECT_INSTALL_TITLE: "Grav csomagok közvetlen telepítése"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "Csomag telepítése a Direct ZIP feltöltés használatával"
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Feltöltés és telepítés"
+ ROUTE_OVERRIDES: "Útvonal felülírása"
+ ROUTE_DEFAULT: "Alapértelmezett útvonal"
+ OPEN_NEW_TAB: "Megnyitás új lapon"
+ SESSION_INITIALIZE: "Munkamenet inicializálása"
+ STRICT_YAML_COMPAT: "YAML kompatibilitás"
+ STRICT_TWIG_COMPAT: "Twig kompatibilitás"
+ SCHEDULER: "Ütemező"
+ SCHEDULER_INSTALL_INSTRUCTIONS: "Telepítési útmutató"
+ SCHEDULER_INSTALLED_READY: "Telepítve és készen áll"
+ SCHEDULER_CRON_NA: "Nem elérhető"
+ SCHEDULER_NOT_ENABLED: "Inaktiválva"
+ SCHEDULER_SETUP: "Ütemező beállítása"
+ SCHEDULER_JOBS: "Egyéni Ütemezett feladatok"
+ SCHEDULER_STATUS: "Ütemező állapota"
+ SECURITY: "Biztonság"
+ XSS_SECURITY: "XSS biztonság a tartalomhoz"
+ XSS_WHITELIST_PERMISSIONS: "Whitelist engedélyek"
+ XSS_WHITELIST_PERMISSIONS_HELP: "Az ilyen jogosultságokkal rendelkező felhasználók a tartalom mentésekor átugorják az XSS szabályokat"
+ XSS_ON_EVENTS: "Események szűrése"
+ XSS_INVALID_PROTOCOLS: "Érvénytelen protokollok szűrése"
+ XSS_INVALID_PROTOCOLS_LIST: "Érvénytelen protokollok listája"
+ XSS_HTML_INLINE_STYLES: "Inline HTML stílusok szűrése"
+ XSS_DANGEROUS_TAGS: "Szűrje a veszélyes HTML címkéket"
+ XSS_DANGEROUS_TAGS_LIST: "Veszélyes HTML-címkék listája"
+ XSS_ONSAVE_ISSUE: "Mentés sikertelen: XSS problémát észleltünk..."
+ XSS_ISSUE: "ÉRTESÍTÉS A Grav potenciális XSS hibákat talált a(z) %s "
+ UPLOADS_SECURITY: "Biztonsági feltöltések"
+ UPLOADS_DANGEROUS_EXTENSIONS: "Veszélyes kiterjesztések"
+ UPLOADS_DANGEROUS_EXTENSIONS_HELP: "Blokkolja ezeket a kiterjesztéseket a feltöltött MIME típusoktól függetlenül"
+ REPORTS: "Jelentések"
diff --git a/user/plugins/admin/languages/id.yaml b/user/plugins/admin/languages/id.yaml
new file mode 100644
index 0000000..fd5517e
--- /dev/null
+++ b/user/plugins/admin/languages/id.yaml
@@ -0,0 +1,696 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Ini merupakan rilisan Beta! Gunakan ini di produksi dengan resiko anda sendiri..."
+ ADMIN_REPORT_ISSUE: "Menemukan masalah? Harap laporkan di Github."
+ EMAIL_FOOTER: "Ditenagai oleh Grav - The Modern Flat File CMS"
+ LOGIN_BTN: "Login"
+ LOGIN_BTN_FORGOT: "Lupa"
+ LOGIN_BTN_RESET: "Reset Password"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Kirim Petunjuk Pembuatan Password"
+ LOGIN_BTN_CLEAR: "Bersihkan Form"
+ LOGIN_BTN_CREATE_USER: "Tambah Pengguna"
+ LOGIN_LOGGED_IN: "Anda telah berhasil login"
+ LOGIN_FAILED: "Login gagal"
+ LOGGED_OUT: "Anda telah logout"
+ RESET_NEW_PASSWORD: "Silakan masukkan password baru …"
+ RESET_LINK_EXPIRED: "Tautan setel ulang sudah tidak berlaku, silakan coba lagi"
+ RESET_PASSWORD_RESET: "Kata sandi telah diatur ulang"
+ RESET_INVALID_LINK: "Anda menggunakan tautan pembuatan password yang tidak valid, silakan coba lagi"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Petunjuk untuk menyetel ulang password telah dikirim ke alamat email Anda"
+ FORGOT_FAILED_TO_EMAIL: "Gagal mengirim email instruksi, silakan coba lagi nanti"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Tidak bisa membuat ulang password untuk %s, alamat email belum diisi"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Pengguna dengan username %s tidak ada"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Tidak dapat membuat ulang password. Situs ini belum dikonfigurasi untuk mengirim email"
+ FORGOT_EMAIL_SUBJECT: "Permintaan Reset Password %s"
+ FORGOT_EMAIL_BODY: "Reset Password Halo %1$s,
Permintaan dibuat pada %4$s untuk membuat ulang password.
< br / > Klik untuk menyetel ulang password < br / > < br / >
Atau, salin URL berikut ke baris alamat browser Anda:
%2$s
< br / > Salam, < br / > < br / >%3$s
"
+ MANAGE_PAGES: "Kelola Halaman"
+ CONFIGURATION: "Konfigurasi"
+ PAGES: "Halaman"
+ PLUGINS: "Plugin"
+ PLUGIN: "Plugin"
+ THEMES: "Tema"
+ LOGOUT: "Keluar"
+ BACK: "Kembali"
+ NEXT: "Berikutnya"
+ PREVIOUS: "Sebelumnya"
+ ADD_PAGE: "Tambah Halaman"
+ ADD_MODULAR: "Tambahkan Modular"
+ MOVE: "Pindahkan"
+ DELETE: "Hapus"
+ VIEW: "Lihat"
+ SAVE: "Simpan"
+ NORMAL: "Normal"
+ EXPERT: "Ahli"
+ EXPAND_ALL: "Bentangkan semua"
+ COLLAPSE_ALL: "Tutup Semua"
+ ERROR: "Eror"
+ CLOSE: "Tutup"
+ CANCEL: "Batalkan"
+ CONTINUE: "Lanjukan"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Konfirmasi"
+ MODAL_CHANGED_DETECTED_TITLE: "Terdeteksi Perubahan"
+ MODAL_CHANGED_DETECTED_DESC: "Anda memiliki perubahan yang belum disimpan. Apakah Anda yakin Anda ingin meninggalkan tanpa menyimpan?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Konfirmasi"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Apakah Anda yakin Anda ingin menghapus file ini? Tindakan ini tidak dapat dikembalikan."
+ ADD_FILTERS: "Tambah Filter"
+ SEARCH_PAGES: "Cari Halaman"
+ VERSION: "Versi"
+ WAS_MADE_WITH: "Dibuat dengan"
+ BY: "Oleh"
+ UPDATE_THEME: "Perbarui Tema"
+ UPDATE_PLUGIN: "Update Plugin"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "tema ini sudah tersedia"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "plugin ini sudah tersedia"
+ AUTHOR: "Penulis"
+ HOMEPAGE: "Homepage"
+ DEMO: "Demo"
+ BUG_TRACKER: "Bug Tracker"
+ KEYWORDS: "Keyword"
+ LICENSE: "Lisensi"
+ DESCRIPTION: "Deskripsi"
+ README: "Readme"
+ REMOVE_THEME: "Hapus tema"
+ INSTALL_THEME: "Instal tema"
+ THEME: "Tema"
+ BACK_TO_THEMES: "Kembali ke tema"
+ BACK_TO_PLUGINS: "Kembali ke plugin"
+ CHECK_FOR_UPDATES: "Periksa Update"
+ ADD: "Tambah"
+ CLEAR_CACHE: "Hapus Cache"
+ CLEAR_CACHE_ALL_CACHE: "Semua Cache"
+ CLEAR_CACHE_ASSETS_ONLY: "Hanya Assests"
+ CLEAR_CACHE_IMAGES_ONLY: "Hanya Images"
+ CLEAR_CACHE_CACHE_ONLY: "Hanya Cache"
+ CLEAR_CACHE_TMP_ONLY: "Hanya Tmp"
+ DASHBOARD: "Dashboard"
+ UPDATES_AVAILABLE: "Tersedia Update"
+ DAYS: "Hari"
+ UPDATE: "Update"
+ BACKUP: "Backup"
+ STATISTICS: "Statistik"
+ TODAY: "Hari ini"
+ WEEK: "Pekan"
+ MONTH: "Bulan"
+ LATEST_PAGE_UPDATES: "Update Halaman Terbaru"
+ MAINTENANCE: "Pemeliharaan"
+ UPDATED: "Diperbarui"
+ MON: "Sen"
+ TUE: "Sel"
+ WED: "Rab"
+ THU: "Kam"
+ FRI: "Jum"
+ SAT: "Sab"
+ SUN: "Min"
+ COPY: "Salin"
+ EDIT: "Edit"
+ CREATE: "Buat"
+ GRAV_ADMIN: "Grav Admin"
+ GRAV_OFFICIAL_PLUGIN: "Plugin Resmi Grav"
+ GRAV_OFFICIAL_THEME: "Tema Resmi Grav"
+ PLUGIN_SYMBOLICALLY_LINKED: "Plugin ini bertaut simbolik. Update tidak akan terdeteksi."
+ THEME_SYMBOLICALLY_LINKED: "Tema ini berkait secara simbolis. Perbaruan tidak akan terdeteksi"
+ REMOVE_PLUGIN: "Hapus Plugin"
+ INSTALL_PLUGIN: "Instal plugin"
+ AVAILABLE: "Tersedia"
+ INSTALLED: "Terpasang"
+ INSTALL: "Instal"
+ ACTIVE_THEME: "Tema aktif"
+ SWITCHING_TO: "Beralih ke"
+ SWITCHING_TO_DESCRIPTION: "Dengan beralih ke tema yang berbeda, tidak ada jaminan bahwa semua tata letak halaman akan didukung, yang berpotensi menyebabkan kesalahan ketika mencoba untuk memuat halaman."
+ SWITCHING_TO_CONFIRMATION: "Apakah Anda ingin melanjutkan dan beralih ke tema ini"
+ CREATE_NEW_USER: "Buat Pengguna Baru"
+ REMOVE_USER: "Hapus Pengguna"
+ ACCESS_DENIED: "Akses ditolak"
+ ACCOUNT_NOT_ADMIN: "akun anda tidak memiliki akses administrator"
+ PHP_INFO: "PHP Info"
+ INSTALLER: "Installer"
+ AVAILABLE_THEMES: "Tema yang tersedia"
+ AVAILABLE_PLUGINS: "Plugin yang tersedia"
+ INSTALLED_THEMES: "Tema Terpasang"
+ INSTALLED_PLUGINS: "Plugin Terpasang"
+ BROWSE_ERROR_LOGS: "Lihat Pesan Kesalahan"
+ SITE: "Situs"
+ INFO: "Info"
+ SYSTEM: "Sistem"
+ USER: "Pengguna"
+ ADD_ACCOUNT: "Tambah Akun"
+ SWITCH_LANGUAGE: "Ganti Bahasa"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Plugin berhasil diaktifkan"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Plugin berhasil dimatikan"
+ SUCCESSFULLY_CHANGED_THEME: "Tema default berhasil diubah"
+ INSTALLATION_FAILED: "Instalasi gagal"
+ INSTALLATION_SUCCESSFUL: "Instalasi berhasil"
+ UNINSTALL_FAILED: "Uninstall gagal"
+ UNINSTALL_SUCCESSFUL: "Uninstall berhasil"
+ SUCCESSFULLY_SAVED: "Berhasil disimpan"
+ SUCCESSFULLY_COPIED: "Berhasil disalin"
+ REORDERING_WAS_SUCCESSFUL: "Penyusunan ulang berhasil"
+ SUCCESSFULLY_DELETED: "Berhasil dihapus"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Berhasil beralih bahasa"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Anda tidak memiliki izin yang cukup untuk tugas"
+ CACHE_CLEARED: "Cache dibersihkan"
+ METHOD: "Metode"
+ ERROR_CLEARING_CACHE: "Ada kesalahan membersihkan cache"
+ AN_ERROR_OCCURRED: "Terjadi kesalahan"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Backup anda siap diunduh"
+ DOWNLOAD_BACKUP: "Download Backup"
+ PAGES_FILTERED: "Halaman yang disaring"
+ NO_PAGE_FOUND: "Tidak ada halaman yang ditemukan"
+ INVALID_PARAMETERS: "Parameter tidak valid"
+ NO_FILES_SENT: "Tidak ada file yang dikirimkan"
+ EXCEEDED_FILESIZE_LIMIT: "Melebihi batas ukuran file konfigurasi PHP"
+ UNKNOWN_ERRORS: "Kesalahan tak dikenal"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Melebihi batas ukuran file konfigurasi Grav"
+ UNSUPPORTED_FILE_TYPE: "Jenis file tidak didukung"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Gagal memindahkan file yang diunggah"
+ FILE_UPLOADED_SUCCESSFULLY: "File berhasil diunggah"
+ FILE_DELETED: "File dihapus"
+ FILE_COULD_NOT_BE_DELETED: "File tidak dapat dihapus"
+ FILE_NOT_FOUND: "File tidak ditemukan"
+ NO_FILE_FOUND: "Tidak ada file yang ditemukan"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav berhasil diperbarui ke"
+ GRAV_UPDATE_FAILED: "Update Grav gagal"
+ EVERYTHING_UPDATED: "Semuanya sudah diperbarui"
+ UPDATES_FAILED: "Update gagal"
+ AVATAR_BY: "Avatar oleh"
+ AVATAR_UPLOAD_OWN: "Atau upload sendiri..."
+ LAST_BACKUP: "Backup Terakhir"
+ FULL_NAME: "Nama lengkap"
+ USERNAME: "Username"
+ EMAIL: "Email"
+ USERNAME_EMAIL: "Username atau Email"
+ PASSWORD: "Password"
+ PASSWORD_CONFIRM: "Ulangi Password"
+ TITLE: "Judul"
+ LANGUAGE: "Bahasa"
+ ACCOUNT: "Akun"
+ EMAIL_VALIDATION_MESSAGE: "Harus alamat email yang valid"
+ PASSWORD_VALIDATION_MESSAGE: "Password harus mengandung setidaknya satu angka, satu huruf besar dan huruf kecil, dan minimal 8 karakter"
+ LANGUAGE_HELP: "Jadikan bahasa favorit"
+ MEDIA: "Media"
+ DEFAULTS: "Default"
+ SITE_TITLE: "Judul Situs"
+ SITE_TITLE_PLACEHOLDER: "Judul situs keseluruhan"
+ SITE_TITLE_HELP: "Judul standar situs Anda, biasanya digunakan dalam tema"
+ SITE_DEFAULT_LANG: "Bahasa default"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Bahasa default yang digunakan dalam tag tema"
+ SITE_DEFAULT_LANG_HELP: "Bahasa default yang digunakan dalam tag tema"
+ DEFAULT_AUTHOR: "Penulis default"
+ DEFAULT_AUTHOR_HELP: "Default nama penulis, biasanya digunakan dalam tema atau konten halaman"
+ DEFAULT_EMAIL: "Email default"
+ DEFAULT_EMAIL_HELP: "Standar email sebagai referensi dalam tema atau halaman"
+ TAXONOMY_TYPES: "Jenis taksonomi"
+ TAXONOMY_TYPES_HELP: "Taksonomi jenis harus didefinisikan di sini jika Anda ingin menggunakannya dalam halaman"
+ PAGE_SUMMARY: "Ringkasan Halaman"
+ ENABLED: "Aktif"
+ ENABLED_HELP: "Aktifkan ringkasan halaman (ringkasan sama dengan isi halaman)"
+ 'YES': "Ya"
+ 'NO': "Tidak"
+ SUMMARY_SIZE: "Ukuran Ringkasan"
+ SUMMARY_SIZE_HELP: "Jumlah karakter dari halaman yang digunakan sebagai konten ringkasan"
+ FORMAT: "Format"
+ FORMAT_HELP: "pendek = menggunakan pemisah atau ukuran; panjang = pemisah ringkasan akan diabaikan"
+ SHORT: "Pendek"
+ LONG: "Panjang"
+ DELIMITER: "Pemisah"
+ DELIMITER_HELP: "Pemisah ringkasan (default ' ===')"
+ METADATA: "Metadata"
+ METADATA_HELP: "Nilai default metadata yang akan ditampilkan pada setiap halaman kecuali ditimpa oleh Halaman"
+ NAME: "Nama"
+ CONTENT: "Konten"
+ REDIRECTS_AND_ROUTES: "Pengalihan & Rute"
+ CUSTOM_REDIRECTS: "Pengalihan kustom"
+ CUSTOM_REDIRECTS_HELP: "rute pengalihan ke halaman lain. Penggantian Regex Standar bisa berlaku"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/alias/anda"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/pengalihan/anda"
+ CUSTOM_ROUTES: "Rute kustom"
+ CUSTOM_ROUTES_HELP: "rute sebagai alias ke halaman lain. Penggantian Regex Standar bisa berlaku"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/alias/anda"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/rute/anda"
+ FILE_STREAMS: "Stream file"
+ DEFAULT: "Default"
+ PAGE_MEDIA: "Media Halaman"
+ OPTIONS: "Opsi"
+ PUBLISHED: "Diterbitkan"
+ PUBLISHED_HELP: "Secara default, halaman akan diterbitkan kecuali anda secara eksplisit menetapkan published: false atau lewat publish_date yang di masa depan, atau unpublish_date di masa lalu"
+ DATE: "Tanggal"
+ DATE_HELP: "Variabel tanggal memungkinkan Anda untuk secara khusus menetapkan tanggal untuk Halaman ini."
+ PUBLISHED_DATE: "Tanggal publikasi"
+ PUBLISHED_DATE_HELP: "Menyediakan fasilitas untuk mempublikasi pada tanggal tertentu."
+ UNPUBLISHED_DATE: "Tanggal Penghentian Publikasi"
+ UNPUBLISHED_DATE_HELP: "Menyediakan fasilitas untuk menghentikan publikasi pada tanggal tertentu."
+ ROBOTS: "Robots"
+ TAXONOMIES: "Taksonomi"
+ TAXONOMY: "Taksonomi"
+ ADVANCED: "Tingkat lanjut"
+ SETTINGS: "Pengaturan"
+ FOLDER_NUMERIC_PREFIX: "Awalan Numerik Folder"
+ FOLDER_NUMERIC_PREFIX_HELP: "Awalan numerik untuk urutan secara manual dan menunjukkan visibilitas"
+ FOLDER_NAME: "Nama Folder"
+ FOLDER_NAME_HELP: "Nama folder yang akan disimpan dalam filesystem untuk halaman ini"
+ PARENT: "Induk"
+ DEFAULT_OPTION_ROOT: "- Root -"
+ DEFAULT_OPTION_SELECT: "- Pilih -"
+ DISPLAY_TEMPLATE: "Tampilan Template"
+ DISPLAY_TEMPLATE_HELP: "Jenis halaman yang diterjemahkan dimana template twig merender Halaman"
+ BODY_CLASSES: "Kelas Body"
+ ORDERING: "Urutan"
+ PAGE_ORDER: "Urutan halaman"
+ OVERRIDES: "Timpa"
+ MENU: "Menu"
+ MENU_HELP: "String yang akan digunakan dalam menu. Jika tidak diisi, akan menggunakan judul."
+ SLUG: "Slug"
+ SLUG_HELP: "Variabel slug memungkinkan Anda untuk secara khusus mengatur bagian URL halaman "
+ SLUG_VALIDATE_MESSAGE: "Slug hanya boleh berisi huruf kecil huruf dan tanda minus"
+ PROCESS: "Proses"
+ PROCESS_HELP: "Mengontrol bagaimana halaman diproses. Dapat diatur per halaman juga global"
+ DEFAULT_CHILD_TYPE: "Jenis turunan default"
+ USE_GLOBAL: "Gunakan Global"
+ ROUTABLE: "Routable"
+ ROUTABLE_HELP: "Jika Halaman ini dapat dicapai dengan URL"
+ CACHING: "Caching"
+ VISIBLE: "Terlihat"
+ VISIBLE_HELP: "Menentukan apakah halaman muncul dalam navigasi."
+ DISABLED: "Nonaktif"
+ ITEMS: "Item"
+ ORDER_BY: "Urutkan Berdasarkan"
+ ORDER: "Urutan"
+ FOLDER: "Folder"
+ ASCENDING: "Naik"
+ DESCENDING: "Turun"
+ ADD_MODULAR_CONTENT: "Tambah Konten Modular"
+ PAGE_TITLE: "Judul Halaman"
+ PAGE_TITLE_HELP: "Judul halaman ini"
+ PAGE: "Halaman"
+ MODULAR_TEMPLATE: "Modular Template"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Nama File"
+ PARENT_PAGE: "Halaman induk"
+ HOME_PAGE: "Halaman Depan"
+ HOME_PAGE_HELP: "Halaman yang akan digunakan Grav sebagai halaman muka default"
+ DEFAULT_THEME: "Tema default"
+ DEFAULT_THEME_HELP: "Mengatur tema default yang akan digunakan (defaultnya adalah Antimatter)"
+ TIMEZONE: "Zona Waktu"
+ TIMEZONE_HELP: "Gantikan zona waktu server default"
+ SHORT_DATE_FORMAT: "Fomat tampilan tanggal pendek"
+ SHORT_DATE_FORMAT_HELP: "Menentukan format tanggal pendek yang digunakan tema"
+ LONG_DATE_FORMAT: "Format tampilan format tanggal panjang"
+ LONG_DATE_FORMAT_HELP: "Menentukan format tanggal panjang yang digunakan tema"
+ DEFAULT_ORDERING: "Pengurutan default"
+ DEFAULT_ORDERING_HELP: "Halaman dalam daftar akan dibuat dengan urutan ini kecuali dibuat berbeda"
+ DEFAULT_ORDERING_DEFAULT: "Default - menurut nama folder"
+ DEFAULT_ORDERING_FOLDER: "Folder - menurut nama folder tanpa awalan"
+ DEFAULT_ORDERING_TITLE: "Judul - berdasar pada bagian judul dalam header"
+ DEFAULT_ORDERING_DATE: "Tanggal - berdasarkan bagian tanggal dalam header"
+ DEFAULT_ORDER_DIRECTION: "Arah urutan default"
+ DEFAULT_ORDER_DIRECTION_HELP: "Arah halaman dalam daftar"
+ DEFAULT_PAGE_COUNT: "Jumlah halaman default"
+ DEFAULT_PAGE_COUNT_HELP: "Jumlah halaman maksimal secara default dalam daftar"
+ DATE_BASED_PUBLISHING: "Publikasi berbasis tanggal"
+ DATE_BASED_PUBLISHING_HELP: "Otomatis (tidak)terbitkan posting menurut tanggalnya"
+ EVENTS: "Events"
+ EVENTS_HELP: "Aktifkan atau Non-aktifkan event tertentu. Menonaktifkan ini bisa merusak plugin"
+ REDIRECT_DEFAULT_ROUTE: "Rute default pengalihan"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Otomatis alihkan ke rute default halaman "
+ LANGUAGES: "Bahasa"
+ SUPPORTED: "Didukung"
+ SUPPORTED_HELP: "Daftar terpisahkan koma dari kode bahasa 2 huruf (misalnya 'id,en,fr')"
+ TRANSLATIONS_ENABLED: "Terjemahan diaktifkan"
+ TRANSLATIONS_ENABLED_HELP: "Mendukung terjemahan dalam Grav, plugin, dan ekstensi"
+ TRANSLATIONS_FALLBACK: "Terjemahan alternatif"
+ TRANSLATIONS_FALLBACK_HELP: "Buka terjemahan yang ada jika bahasa yang aktif tidak mmeiliki terjemahan"
+ ACTIVE_LANGUAGE_IN_SESSION: "Bahasa yang aktif dalam sesi"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Simpan bahasa aktif dalam sesi"
+ HTTP_HEADERS: "Header HTTP"
+ EXPIRES: "Berakhir"
+ EXPIRES_HELP: "Menetapkan header expire. Nilai adalah dalam detik."
+ CACHE_CONTROL: "HTTP Cache-Control"
+ CACHE_CONTROL_HELP: "Mengatur nilai cache-control seperti 'no-cache, no-store, must-revalidate'"
+ LAST_MODIFIED: "Modifikasi terakhir"
+ LAST_MODIFIED_HELP: "Menetapan header Las Modified yang bisa membantu mengoptimalkan caching proxy dan browser"
+ ETAG: "ETag"
+ ETAG_HELP: "Menetapkan header ETag untuk membantu identifikasi saat halaman diperbarui"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Mengatur header ' Vary: Accept Encoding' untuk membantu proxy dan CDN caching"
+ MARKDOWN_EXTRA_HELP: "Mengaktifkan dukungan Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ AUTO_LINE_BREAKS: "Jeda baris auto"
+ AUTO_LINE_BREAKS_HELP: "Aktifkan dukungan untuk baris otomatis istirahat dalam penurunan harga"
+ AUTO_URL_LINKS: "Link URL otomatis"
+ AUTO_URL_LINKS_HELP: "Aktifkan konversi otomatis URL ke dalam HTML hyperlink"
+ ESCAPE_MARKUP: "Melarikan diri markup"
+ ESCAPE_MARKUP_HELP: "Melarikan diri tag markup ke dalam entitas HTML"
+ CACHING_HELP: "Global ON / OFF untuk mengaktifkan / menonaktifkan Grav caching"
+ CACHE_CHECK_METHOD: "Metode pemeriksaan Cache"
+ CACHE_CHECK_METHOD_HELP: "Select the method that Grav uses to check if page files have been modified."
+ CACHE_DRIVER: "Driver Cache"
+ CACHE_DRIVER_HELP: "Pilih yang tembolok pengemudi Grav harus menggunakan. 'Auto Detect' mencoba untuk menemukan yang terbaik untuk Anda"
+ CACHE_PREFIX: "Awalan Cache"
+ CACHE_PREFIX_HELP: "Pengidentifikasi untuk bagian dari Grav kunci. Jangan mengubah kecuali Anda tahu apa yang Anda lakukan."
+ CACHE_PREFIX_PLACEHOLDER: "Berasal dari URL dasar (override dengan memasukkan string acak)"
+ LIFETIME: "Seumur hidup"
+ LIFETIME_HELP: "Menetapkan seumur hidup cache pada detik. 0 = tak terbatas"
+ GZIP_COMPRESSION: "Kompresi Gzip"
+ GZIP_COMPRESSION_HELP: "Aktifkan Gzip kompresi Grav halaman untuk peningkatan kinerja."
+ TWIG_TEMPLATING: "Ranting Templating"
+ TWIG_CACHING: "Ranting caching"
+ TWIG_CACHING_HELP: "Mengontrol mekanisme caching Ranting. Meninggalkan ini diaktifkan untuk kinerja terbaik."
+ TWIG_DEBUG: "Ranting men-debug"
+ TWIG_DEBUG_HELP: "Memungkinkan pilihan untuk tidak memuat ekstensi Ranting Debugger"
+ DETECT_CHANGES: "Mendeteksi perubahan"
+ DETECT_CHANGES_HELP: "Ranting otomatis akan mengkompilasi ulang cache ranting jika mendeteksi perubahan di ranting template"
+ AUTOESCAPE_VARIABLES: "Variabel autoescape"
+ AUTOESCAPE_VARIABLES_HELP: "Autoescapes semua variabel. Hal ini akan merusak situs Anda kemungkinan besar"
+ ASSETS: "Aset"
+ CSS_PIPELINE: "CSS pipeline"
+ CSS_PIPELINE_HELP: "Pipa CSS adalah penyatuan beberapa sumber CSS ke dalam satu file"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Sertakan eksternal dalam pipa CSS"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "URL eksternal kadang-kadang memiliki referensi relatif berkas dan tidak harus pipelined"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "Pipa CSS membuat pertama"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Membuat pipa CSS sebelum referensi CSS lain yang tidak termasuk"
+ CSS_MINIFY: "CSS mengecilkan"
+ CSS_MINIFY_HELP: "Mengecilkan CSS selama pipelining"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "CSS mengecilkan menimpa Windows"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Mengecilkan Override untuk platform Windows. Palsu secara default karena ThreadStackSize"
+ CSS_REWRITE: "Menulis ulang CSS"
+ CSS_REWRITE_HELP: "Menulis ulang URL-URL relatif CSS selama pipelining"
+ JAVASCRIPT_PIPELINE: "Pipa JavaScript"
+ JAVASCRIPT_PIPELINE_HELP: "Pipa JS adalah penyatuan beberapa sumber JS ke dalam satu file"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Sertakan eksternal dalam pipa JS"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "URL eksternal kadang-kadang memiliki referensi relatif berkas dan tidak harus pipelined"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Pipa JS membuat pertama"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Membuat pipa JS sebelum referensi JS lainnya yang tidak termasuk"
+ JAVASCRIPT_MINIFY: "JavaScript mengecilkan"
+ JAVASCRIPT_MINIFY_HELP: "Mengecilkan JS selama pipelining"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Aktifkan cap waktu pada aset"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Aktifkan cap waktu aset"
+ COLLECTIONS: "Koleksi"
+ ERROR_HANDLER: "Perdagangan error"
+ DISPLAY_ERRORS: "Menampilkan kesalahan"
+ DISPLAY_ERRORS_HELP: "Menampilkan halaman kesalahan bergaya backtrace penuh"
+ LOG_ERRORS: "Log kesalahan"
+ LOG_ERRORS_HELP: "Log kesalahan ke folder / log"
+ DEBUGGER: "Debugger"
+ DEBUGGER_HELP: "Aktifkan Grav debugger dan mengikuti pengaturan"
+ DEBUG_TWIG: "Debug Ranting"
+ DEBUG_TWIG_HELP: "Aktifkan debugging dari Ranting template"
+ SHUTDOWN_CLOSE_CONNECTION: "Hubungan dekat Shutdown"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Menutup koneksi sebelum memanggil onShutdown (). palsu untuk debugging"
+ DEFAULT_IMAGE_QUALITY: "Kualitas gambar default"
+ DEFAULT_IMAGE_QUALITY_HELP: "Kualitas gambar standar yang digunakan ketika resampling atau caching gambar (85%)"
+ CACHE_ALL: "Cache semua gambar"
+ CACHE_ALL_HELP: "Menjalankan semua gambar melalui kelabu 's cache sistem bahkan jika mereka memiliki manipulasi media tidak"
+ IMAGES_DEBUG: "Gambar men-debug watermark"
+ IMAGES_DEBUG_HELP: "Tampilkan overlay di atas gambar yang menunjukkan kedalaman pixel gambar ketika bekerja dengan retina misalnya"
+ UPLOAD_LIMIT: "File upload batas"
+ UPLOAD_LIMIT_HELP: "Mengatur ukuran upload maksimum dalam bytes (0 adalah tidak terbatas)"
+ ENABLE_MEDIA_TIMESTAMP: "Aktifkan cap waktu pada media"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Menambahkan catatan waktu berdasarkan tanggal terakhir dimodifikasi untuk setiap item media yang"
+ SESSION: "Sesi"
+ SESSION_ENABLED_HELP: "Aktifkan dukungan sesi dalam Grav"
+ TIMEOUT: "Waktu habis"
+ TIMEOUT_HELP: "Menetapkan batas waktu sesi di detik"
+ SESSION_NAME_HELP: "Pengenal yang digunakan untuk membentuk nama cookie sesi"
+ ABSOLUTE_URLS: "URL mutlak"
+ ABSOLUTE_URLS_HELP: "URL absolut atau relatif untuk `base_url`"
+ PARAMETER_SEPARATOR: "Pemisah parameter"
+ PARAMETER_SEPARATOR_HELP: "Pemisah untuk parameter yang dikirimkan yang dapat diubah untuk Apache pada Windows"
+ TASK_COMPLETED: "Tugas selesai"
+ EVERYTHING_UP_TO_DATE: "Semuanya telah up-to-date"
+ UPDATES_ARE_AVAILABLE: "Update tersedia"
+ IS_AVAILABLE_FOR_UPDATE: "tersedia untuk update"
+ IS_NOW_AVAILABLE: "kini tersedia"
+ CURRENT: "Arus"
+ UPDATE_GRAV_NOW: "Update Grav Now"
+ GRAV_SYMBOLICALLY_LINKED: "Grav adalah simbolis terkait. Meng-upgrade tidak akan tersedia"
+ UPDATING_PLEASE_WAIT: "Memperbarui... harap tunggu, men-download"
+ OF_THIS: "ini"
+ OF_YOUR: "anda"
+ HAVE_AN_UPDATE_AVAILABLE: "memiliki update yang tersedia"
+ SAVE_AS: "Simpan sebagai"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Apakah Anda yakin ingin menghapus halaman ini dan semua anak-anaknya? Jika halaman diterjemahkan dalam bahasa lain, yang terjemahan akan disimpan dan harus dihapus secara terpisah. Jika tidak folder halaman akan dihapus bersama dengan sublaman nya. Tindakan ini tidak bisa dibatalkan."
+ AND: "dan"
+ UPDATE_AVAILABLE: "Update tersedia"
+ METADATA_KEY: "Key (misalnya 'Kata Kunci')"
+ METADATA_VALUE: "Nilai (e.g. 'Blog, Grav')"
+ USERNAME_HELP: "Nama pengguna harus antara 3 dan 16 karakter, termasuk huruf kecil, angka, garis bawah, dan tanda hubung. Huruf besar, spasi, dan karakter khusus tidak diperbolehkan"
+ FULLY_UPDATED: "Diperbarui sepenuhnya"
+ SAVE_LOCATION: "Simpan lokasi"
+ PAGE_FILE: "Template Halaman"
+ PAGE_FILE_HELP: "Template halaman nama file, dan secara default template tampilan halaman ini"
+ NO_USER_ACCOUNTS: "Tidak ada akun pengguna yang ditemukan, buat satu terlebih dahulu..."
+ REDIRECT_TRAILING_SLASH: "Pengalihan membuntuti garis miring"
+ REDIRECT_TRAILING_SLASH_HELP: "Lakukan pengalihan 301 dan bukannya menangani URI membuntuti garis miring dengan transparan."
+ DEFAULT_DATE_FORMAT: "Format tanggal Halaman"
+ DEFAULT_DATE_FORMAT_HELP: "Format tanggal halaman digunakan oleh Grav. Secara default, upaya Grav untuk menebak tanggal format, namun Anda dapat sebutkan format yang menggunakan PHP tanggal sintaks (misalnya: H:i Y-m-d)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Tebak secara otomatis"
+ IGNORE_FILES: "Mengabaikan berkas"
+ IGNORE_FILES_HELP: "File tertentu untuk mengabaikan saat memproses halaman"
+ IGNORE_FOLDERS: "Abaikan folder"
+ IGNORE_FOLDERS_HELP: "Folder tertentu untuk mengabaikan saat memproses halaman"
+ HTTP_ACCEPT_LANGUAGE: "Mengatur bahasa dari browser"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Anda dapat memilih untuk mencoba untuk mengatur bahasa berdasarkan `tag header http_accept_language` di browser"
+ OVERRIDE_LOCALE: "Mengesampingkan lokal"
+ OVERRIDE_LOCALE_HELP: "Menimpa pengaturan lokal di PHP didasarkan pada bahasa saat ini"
+ REDIRECT: "Halaman redirect"
+ REDIRECT_HELP: "Masukkan rute halaman atau URL eksternal untuk halaman ini untuk mengarahkan ke. misalnya `/ beberapa / route` atau` http: // somesite.com`"
+ PLUGIN_STATUS: "Plugin status"
+ INCLUDE_DEFAULT_LANG: "Sertakan bahasa default"
+ INCLUDE_DEFAULT_LANG_HELP: "Ini akan menambah semua URL dalam bahasa default dengan bahasa default. misalnya '/ en/blog/my-post'"
+ ALLOW_URL_TAXONOMY_FILTERS: "Penyaring taksonomi URL"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Koleksi berdasarkan halaman memungkinkan Anda untuk menyaring melalui `/ taksonomi: nilai`."
+ REDIRECT_DEFAULT_CODE: "Kode redirect standar"
+ REDIRECT_DEFAULT_CODE_HELP: "Kode status HTTP digunakan untuk pengalihan"
+ IGNORE_HIDDEN: "Abaikan disembunyikan"
+ IGNORE_HIDDEN_HELP: "Mengabaikan semua file dan folder yang dimulai dengan DOT sebuah"
+ WRAPPED_SITE: "Situs dibungkus"
+ WRAPPED_SITE_HELP: "Untuk tema / plugin untuk mengetahui apakah Grav dibungkus oleh platform lain"
+ FALLBACK_TYPES: "Diizinkan jenis fallback"
+ FALLBACK_TYPES_HELP: "Allowed file types that can be found if accessed via Page route. Defaults to any supported media type."
+ INLINE_TYPES: "Jenis mundur inline"
+ INLINE_TYPES_HELP: "Daftar jenis file yang harus ditampilkan inline bukan diunduh"
+ APPEND_URL_EXT: "Menambahkan ekstensi URL"
+ APPEND_URL_EXT_HELP: "Akan menambahkan ekstensi kustom untuk URL Page. Catatan, ini berarti Grav akan mencari ` . .twig` Template"
+ PAGE_MODES: "Metode halaman"
+ PAGE_TYPES: "Jenis halaman"
+ ACCESS_LEVELS: "Tingkat akses"
+ GROUPS: "Grup"
+ GROUPS_HELP: "Daftar kelompok pengguna adalah bagian dari"
+ ADMIN_ACCESS: "Akses Admin"
+ SITE_ACCESS: "Akses situs"
+ INVALID_SECURITY_TOKEN: "Token keamanan tidak valid"
+ ACTIVATE: "Mengaktifkan"
+ TWIG_UMASK_FIX: "Umask Fix"
+ TWIG_UMASK_FIX_HELP: "Secara default Ranting menciptakan file cache sebagai 0755, memperbaiki switch ini untuk 0775"
+ CACHE_PERMS: "Izin Cache"
+ CACHE_PERMS_HELP: "Default perms folder cache. Biasanya 0755 atau 0775 tergantung pada setup"
+ REMOVE_SUCCESSFUL: "Hapus Sukses"
+ REMOVE_FAILED: "Hapus Gagal"
+ HIDE_HOME_IN_URLS: "Rute rumah bersembunyi di URL"
+ HIDE_HOME_IN_URLS_HELP: "Akan memastikan rute default untuk setiap halaman di bawah rumah tidak referensi rute reguler rumah ini"
+ TWIG_FIRST: "Proses Ranting Pertama"
+ TWIG_FIRST_HELP: "Jika Anda mengaktifkan Ranting halaman pengolahan, maka Anda dapat mengkonfigurasi Ranting untuk memproses sebelum atau setelah penurunan harga"
+ SESSION_SECURE: "Aman"
+ SESSION_SECURE_HELP: "If true, indicate that the communication for this cookie must be over an encrypted transmission. WARNING: Enable this only on sites that run exclusively on HTTPS"
+ SESSION_HTTPONLY: "Hanya HTTP"
+ SESSION_HTTPONLY_HELP: "Jika benar, menunjukkan bahwa cookie harus digunakan hanya melalui HTTP, dan modifikasi JavaScript tidak diperbolehkan"
+ REVERSE_PROXY: "Membalikkan Proxy"
+ REVERSE_PROXY_HELP: "Aktifkan ini jika Anda berada di belakang membalikkan proxy dan Anda mengalami masalah dengan URLs yang berisi bagian yang salah"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Materi depan tidak valid, tidak bisa disimpan"
+ ADD_FOLDER: "Tambahkan Folder"
+ PROXY_URL: "Proxy URL"
+ PROXY_URL_HELP: "Masuk proxy HOST atau IP dan PORT"
+ NOTHING_TO_SAVE: "Tidak dapat untuk Menyimpan"
+ FILE_ERROR_ADD: "Terjadi kesalahan saat mencoba menambahkan berkas"
+ FILE_ERROR_UPLOAD: "Terjadi kesalahan saat mencoba mengunggah berkas"
+ FILE_UNSUPPORTED: "Jenis file tidak didukung"
+ ADD_ITEM: "Tambahkan item"
+ FILE_TOO_LARGE: "File yang terlalu besar untuk diunggah, maksimal yang diijinkan adalah %s sesuai dengan untuk pengaturan PHP anda. Tingkatkan pengaturan anda `post_max_size` pengaturan PHP"
+ INSTALLING: "Memasang"
+ LOADING: "Memuat.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "Ketergantungan berikut harus dipenuhi terlebih dahulu:"
+ ERROR_INSTALLING_PACKAGES: "Kesalahan saat memuat paket(s)"
+ INSTALLING_DEPENDENCIES: "Ketergantungan Memasang..."
+ INSTALLING_PACKAGES: "Memasang paket(s).."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Paket(s) berhasil dipasang."
+ READY_TO_INSTALL_PACKAGES: "Siap untuk memasang paket(s)"
+ PACKAGES_NOT_INSTALLED: "Paket tidak terpasang"
+ PACKAGES_NEED_UPDATE: "Paket sudah siap dipasang, tapi terlalu usang"
+ PACKAGES_SUGGESTED_UPDATE: "Paket sudah dipasang, dan versinya ok, namun akan diperbarui agar Anda tetap up to date"
+ REMOVE_THE: "Menghapus %s"
+ CONFIRM_REMOVAL: "Anda yakin ingin menghapus ini %s?"
+ REMOVED_SUCCESSFULLY: "%s berhasil dihapus"
+ ERROR_REMOVING_THE: "Kesalahan saat menghapus %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "%s membutuhkan dependensi berikut, yang tidak diperlukan oleh paket terinstal lainnya. Jika Anda tidak menggunakannya, Anda dapat menghapusnya langsung dari sini."
+ READY_TO_UPDATE_PACKAGES: "Siap untuk memperbarui paket(s)"
+ ERROR_UPDATING_PACKAGES: "Kesalahan saat memperbarui paket(s)"
+ UPDATING_PACKAGES: "Memperbarui paket(s).."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Paket(s) sukses diperbarui."
+ UPDATING: "Memperbarui"
+ GPM_RELEASES: "Rilis GPM"
+ GPM_RELEASES_HELP: "Pilih 'Testing' untuk memasang versi beta atau pengujian"
+ GPM_METHOD: "Metode Pengambilan Jarak Jauh"
+ GPM_METHOD_HELP: "Saat diatur ke Otomatis, Grav akan menentukan apakah fopen tersedia dan menggunakannya, jika tidak jatuh kembali ke cURL. Untuk memaksa penggunaan satu atau pengaturan lainnya pengaturan."
+ GPM_VERIFY_PEER: "Verifikasi Pengintai Remote (SSL)"
+ GPM_VERIFY_PEER_HELP: "Beberapa penyedia layanan tampaknya gagal memverifikasi sertifikat SSL getgrav.org, sehingga GPM tidak dapat bekerja. Jika ini yang terjadi pada Anda, mematikan pengaturan ini mungkin bisa membantu"
+ AUTO: "Otomatis"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stabil"
+ TESTING: "Pengujian"
+ FRONTMATTER_PROCESS_TWIG: "Proses Ranting Materi Depan"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Bila diaktifkan Anda bisa menggunakan variabel konfigurasi Ranting di halaman depan"
+ FRONTMATTER_IGNORE_FIELDS: "Abaikan bidang materi depan"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Bidang materi depan tertentu mungkin berisi Ranting namun tidak boleh diproses, seperti 'formulir'"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Paket %s berhasil dipasang"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Induk pengaturan pesanan, pemesanan dinonaktifkan"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Halaman tidak terlihat, memesan Penyandang Cacat"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "Pemesanan via admin tidak didukung karena ada lebih dari 200 persamaan"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Pemesanan halaman dinonaktifkan untuk halaman ini karenaAwalan Numerik Folder tidak diaktifkan"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "CATATAN: Anda tidak dapat menambahkan berkas media sampai Anda menyimpan halaman. Cukup klik 'Simpan' di atas"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "CATATAN: Halaman harus disimpan sebelum Anda dapat mengunggah berkas ke dalamnya."
+ DROP_FILES_HERE_TO_UPLOAD: "Jatuhkan file Anda ke sini atau klik disini "
+ INSERT: "Memasukkan"
+ UNDO: "Batalkan"
+ REDO: "Mengulang"
+ HEADERS: "Judul"
+ BOLD: "Tebal"
+ ITALIC: "Miring"
+ STRIKETHROUGH: "Dicoret"
+ SUMMARY_DELIMITER: "Ringkasan Pembatas"
+ LINK: "Tautan"
+ IMAGE: "Gambar"
+ BLOCKQUOTE: "Tanda kutip blok"
+ UNORDERED_LIST: "Daftar tak berurut"
+ ORDERED_LIST: "Daftar pesanan"
+ EDITOR: "Penyunting"
+ PREVIEW: "Pratinjau"
+ FULLSCREEN: "Layar penuh"
+ MODULAR: "Modular"
+ NON_ROUTABLE: "Tidak Rutin"
+ NON_MODULAR: "Tidak Modular"
+ NON_VISIBLE: "Tidak Terlihat"
+ NON_PUBLISHED: "Tidak diterbitkan"
+ CHARACTERS: "karakter"
+ PUBLISHING: "Penerbitan"
+ NOTIFICATIONS: "Pemberitahuan"
+ MEDIA_TYPES: "Jenis Media"
+ IMAGE_OPTIONS: "Pilihan gambar"
+ MIME_TYPE: "Jenis Mime"
+ THUMB: "Gambar kecil"
+ TYPE: "Jenis"
+ FILE_EXTENSION: "Berkas Tambahan"
+ LEGEND: "Riwayat Halaman"
+ MEMCACHE_SERVER: "Memcache server"
+ MEMCACHE_SERVER_HELP: "Alamat server Memcache"
+ MEMCACHE_PORT: "Memcache port"
+ MEMCACHE_PORT_HELP: "Port server Memcache"
+ MEMCACHED_SERVER: "Memcache server"
+ MEMCACHED_SERVER_HELP: "Alamat server Memcache"
+ MEMCACHED_PORT: "Memcache port"
+ MEMCACHED_PORT_HELP: "Port server Memcache"
+ REDIS_SERVER: "Ulangi server"
+ REDIS_SERVER_HELP: "Ulangi alamat server"
+ REDIS_PORT: "Ulangi port"
+ REDIS_PORT_HELP: "Ulangi port server"
+ REDIS_PASSWORD: "Ulangi sandi/rahasia"
+ ALL: "Semua"
+ FROM: "dari"
+ TO: "untuk"
+ RELEASE_DATE: "Tanggal rilis"
+ SORT_BY: "Sortir Oleh"
+ RESOURCE_FILTER: "Menyaring..."
+ FORCE_SSL: "Memaksa SSL"
+ FORCE_SSL_HELP: "Secara global memaksa SSL, jika diaktifkan saat situs tersebut dicapai melalui HTTP, Grav mengirimkan redirect ke halaman HTTPS"
+ NEWS_FEED: "Umpan Berita"
+ EXTERNAL_URL: "URL Eksternal"
+ SESSION_PATH: "Jalan sesi"
+ SESSION_PATH_HELP: "Gunakan hanya jika Anda memilih URL dasar khusus (Anda menulis ulang domain situs / subfolder)"
+ CUSTOM_BASE_URL: "URL dasar khusus"
+ CUSTOM_BASE_URL_HELP: "Gunakan jika Anda ingin menulis ulang domain situs atau menggunakan subfolder yang berbeda dari yang digunakan oleh Grav. Contoh: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'Tidak dapat menggunakan "%s" di luar halaman.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Tidak dapat mengunggah berkas %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'Tidak dapat memindahkan berkas %s to "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'Batal Unggah'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Apa Anda yakin ingin membatalkan unggahan ini?'
+ DROPZONE_DEFAULT_MESSAGE: 'Jatuhkan berkas Anda ke sini atau klik disini '
+ DROPZONE_FALLBACK_MESSAGE: 'Browser Anda tidak mendukung menyeret dan menjatuhkan unggahan berkas.'
+ DROPZONE_FALLBACK_TEXT: 'Silakan gunakan formulir mundur di bawah ini untuk mengunggah berkas Anda seperti di masa lalu.'
+ DROPZONE_FILE_TOO_BIG: 'Berkas terlalu besar ({{filesize}}MiB). Maksimal ukuran berkas: {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "Anda tidak dapat mengunggah berkas jenis ini."
+ DROPZONE_MAX_FILES_EXCEEDED: "Anda tidak bisa mengunggah berkas lagi."
+ DROPZONE_REMOVE_FILE: "Hapus berkas"
+ DROPZONE_RESPONSE_ERROR: "Server menanggapi dengan {{statusCode}} kode."
+ PREMIUM_PRODUCT: "Premium"
+ ADMIN_CACHING: "Aktifkan Caching Admin"
+ ADMIN_CACHING_HELP: "Caching di admin bisa dikontrol secara mandiri dari situs paling depan"
+ DESTINATION_NOT_SPECIFIED: "Tujuan tidak ditentukan"
+ UPLOAD_ERR_NO_TMP_DIR: "Tidak ada folder sementara"
+ SESSION_SPLIT: "Membagi sesi"
+ SESSION_SPLIT_HELP: "Membagi sesi terpisah antara situs dan plugin lainnya (seperti admin)"
+ ERROR_FULL_BACKTRACE: "Kembalikan jejak kesalahan"
+ ERROR_SIMPLE: "Kesalahan sederhana"
+ ERROR_SYSTEM: "Kesalahan sistem"
+ IMAGES_AUTO_FIX_ORIENTATION: "Perbaiki orientasi secara otomatis"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Secara otomatis perbaiki orientasi gambar berdasarkan data Exif"
+ REDIS_SOCKET: "Ulangi stopkontak"
+ REDIS_SOCKET_HELP: "Mengulangi stopkontak"
+ NOT_SET: "Tidak diatur"
+ PERMISSIONS: "Izin"
+ NEVER_CACHE_TWIG: "Tidak Pernah Menyembunyikan Ranting"
+ NEVER_CACHE_TWIG_HELP: "Hanya isi cache dan proses Ranting setiap waktu untuk halaman. Mengabaikan pengaturan twig_first."
+ ALLOW_WEBSERVER_GZIP: "Izinkan WebServer Gzip"
+ ALLOW_WEBSERVER_GZIP_HELP: "Tidak aktif secara default. Bila diaktifkan, kompresi Gzip/Deflate yang WebServer akan bekerja, namun koneksi http tidak akan ditutup sebelum event onShutDown() menyebabkan pemuatan halaman lebih lambat"
+ OFFLINE_WARNING: "Sambungan ke GPM tidak dapat dibangun"
+ CLEAR_IMAGES_BY_DEFAULT: "Hapus cache gambar secara default"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "Secara default gambar yang diproses dihapus untuk semua pembersihan cache, ini dapat dinonaktifkan"
+ CLI_COMPATIBILITY: "Kesesuaian CLI"
+ CLI_COMPATIBILITY_HELP: "Pastikan hanya driver Cache non-volatile yang digunakan (file, redis, memcache, dll.)"
+ REINSTALL_PLUGIN: "Instal ulang Plugin"
+ REINSTALL_THEME: "Instal ulang Tema"
+ REINSTALL_THE: "Menginstal ulang %s"
+ CONFIRM_REINSTALL: "Apakah anda yakin ingin menginstal ulang ini %s?"
+ REINSTALLED_SUCCESSFULLY: "%s Sukses menginstal ulang"
+ ERROR_REINSTALLING_THE: "Kesalahan saat menginstal ulang %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Paket %s berhasil diinstal ulang"
+ REINSTALLATION_FAILED: "Gagal Menginstal ulang"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "Versi yang terinstal bukanlah rilis terbaru. Dengan mengklik Continue, Anda akan menghapus versi sekarang dan menginstal rilis terbaru yang tersedia"
+ TOOLS: "Peralatan"
+ DIRECT_INSTALL: "Langsung Menginstall"
+ NO_PACKAGE_NAME: "Nama paket tidak ditentukan"
+ PACKAGE_EXTRACTION_FAILED: "Ekstraksi paket gagal"
+ NOT_VALID_GRAV_PACKAGE: "Bukan paket Grav yang valid"
+ NAME_COULD_NOT_BE_DETERMINED: "Nama tidak bisa ditentukan"
+ CANNOT_OVERWRITE_SYMLINKS: "Tidak bisa menimpa symlink"
+ ZIP_PACKAGE_NOT_FOUND: "Paket ZIP tidak dapat ditemukan"
+ GPM_OFFICIAL_ONLY: "Khusus GPM"
+ GPM_OFFICIAL_ONLY_HELP: "Hanya mengizinkan pemasangan langsung dari repositori GPM resmi saja."
+ NO_CHILD_TYPE: "Tidak ada tipe anak untuk rawroute ini"
+ SORTABLE_PAGES: "Halaman Dapat di perbaiki:"
+ UNSORTABLE_PAGES: "Halaman yang Tidak Dapat Diurutkan"
+ ADMIN_SPECIFIC_OVERRIDES: "Penimpaan Khusus Admin"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Tampilkan Anak Pesan"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "Urutan bahwa anak-anak dari halaman ini harus ditampilkan di tampilan 'Halaman' dari plugin Admin"
+ PWD_PLACEHOLDER: "urutan kompleks minimal panjang 8 karakter"
+ PWD_REGEX: "Sandi Regular"
+ PWD_REGEX_HELP: "Secara default: Sandi harus berisi setidaknya satu nomor dan satu huruf besar dan huruf kecil, dan setidaknya 8 karakter atau lebih"
+ USERNAME_PLACEHOLDER: "huruf kecil saja, misalnya. 'admin'"
+ USERNAME_REGEX: "Nama Pengguna Regular"
+ USERNAME_REGEX_HELP: "Secara default: Hanya huruf kecil, angka, tanda hubung, dan garis bawah. 3 - 16 karakter"
+ CONTENT_PADDING: "Konten lapisan"
+ CONTENT_PADDING_HELP: "Aktifkan / Nonaktifkan lapisan konten di sekitar wilayah konten untuk memberi lebih banyak tempat"
+ ENABLE_AUTO_METADATA: "Metadata otomatis dari Exif"
+ ENABLE_AUTO_METADATA_HELP: "Secara otomatis membuat berkas metadata untuk gambar dengan informasi exif"
+ 2FA_TITLE: "2-Faktor Otentikasi"
+ 2FA_INSTRUCTIONS: "##### 2-Faktor Otentikasi\nAnda mempunyai **2FA** diaktifkan di akun ini. Mohon gunakan **2FA** aplikasi untuk memasukkan arus **6-digit code** untuk menyelesaikan proses masuk."
+ 2FA_LABEL: "Akses Admin"
+ 2FA_FAILED: "Tidak valid 2-Faktor Kode otentikasi, silahkan coba lagi..."
+ 2FA_ENABLED: "2FA Diaktifkan"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "Rahasia 2FA"
+ 2FA_REGENERATE: "Diperbarui"
+ FORCE_LOWERCASE_URLS: "Paksa huruf kecil URLs"
+ VIEW_SITE_TIP: "Lihat situs web"
+ TOOLS_DIRECT_INSTALL_TITLE: "Instal langsung Paket Grav"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "Instal Paket melalui Unggah Langsung berkas ZIP"
+ TOOLS_DIRECT_INSTALL_URL_DESC: "Atau, Anda dapat pula merujuk kepada sebuah URL lengkap ke paket ZIP dan menginstalnya melalui URL rujukan ini."
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Unduh dan instal"
diff --git a/user/plugins/admin/languages/it.yaml b/user/plugins/admin/languages/it.yaml
new file mode 100644
index 0000000..68b9cdd
--- /dev/null
+++ b/user/plugins/admin/languages/it.yaml
@@ -0,0 +1,724 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Questa è una versione beta! Usare in produzione a proprio rischio e pericolo…"
+ ADMIN_REPORT_ISSUE: "Trovato un problema? Per favore, apri un ticket su GitHub"
+ EMAIL_FOOTER: "Powered by Grav - Il CMS Flat File e Moderno"
+ LOGIN_BTN: "Accedi"
+ LOGIN_BTN_FORGOT: "Dimenticata"
+ LOGIN_BTN_RESET: "Ripristina la password"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Invia istruzioni di ripristino"
+ LOGIN_BTN_CLEAR: "Resetta il modulo"
+ LOGIN_BTN_CREATE_USER: "Crea Utente"
+ LOGIN_LOGGED_IN: "Login effettuato con successo"
+ LOGIN_FAILED: "Login fallito"
+ LOGGED_OUT: "Sei stato disconnesso"
+ RESET_NEW_PASSWORD: "Inserisci una nuova password …"
+ RESET_LINK_EXPIRED: "Il link di ripristino è scaduto, ritenta per favore"
+ RESET_PASSWORD_RESET: "La password è stata ripristinata"
+ RESET_INVALID_LINK: "Il link di ripristino non è valido, ritenta per favore"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Le istruzioni per ripristinare la password sono state inviate via email a %s"
+ FORGOT_FAILED_TO_EMAIL: "Invio della email fallito, per favore ritenta più tardi"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Impossibile ripristinare la password per %s, nessun indirizzo email impostato"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "L'utente con username %s non esiste"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Impossibile ripristinare la password. Il sito non è configurato per inviare email"
+ FORGOT_EMAIL_SUBJECT: "%s Richiesta di ripristino password"
+ FORGOT_EMAIL_BODY: "Reset password Caro %1$s,
Una richiesta di reset password è stata effettuata su %4$s .
Clicca qui per resettare la tua password
In alternativa, copia il seguente URL nella barra indirizzi del tuo browser:
%2$s
Cordiali saluti, %3$s
"
+ MANAGE_PAGES: "Gestione Pagine"
+ CONFIGURATION: "Impostazioni"
+ PAGES: "Pagine"
+ PLUGINS: "Plugins"
+ PLUGIN: "Plugin"
+ THEMES: "Temi"
+ LOGOUT: "Esci"
+ BACK: "Indietro"
+ NEXT: "Successivo"
+ PREVIOUS: "Precedente"
+ ADD_PAGE: "Aggiungi Pagina"
+ ADD_MODULAR: "Aggiungi Modulo"
+ MOVE: "Sposta"
+ DELETE: "Elimina"
+ UNSET: "Non impostato"
+ VIEW: "Vista"
+ SAVE: "Salva"
+ NORMAL: "Normale"
+ EXPERT: "Esperto"
+ EXPAND_ALL: "Espandi tutto"
+ COLLAPSE_ALL: "Comprimi tutto"
+ ERROR: "Errore"
+ CLOSE: "Chiudi"
+ CANCEL: "Cancella"
+ CONTINUE: "Continua"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Conferma richiesta"
+ MODAL_CHANGED_DETECTED_TITLE: "Modifiche rilevate"
+ MODAL_CHANGED_DETECTED_DESC: "Sono stati rilevati cambiamenti non salvati. Si è sicuri di voler lasciare la pagina senza prima salvare?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Conferma richiesta"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Si è sicuri di voler eliminare questo file? Questa azione non può essere annullata."
+ ADD_FILTERS: "Aggiungi filtri"
+ SEARCH_PAGES: "Cerca pagine"
+ VERSION: "Versione"
+ WAS_MADE_WITH: "Prodotto con"
+ BY: "Da"
+ UPDATE_THEME: "Aggiorna Tema"
+ UPDATE_PLUGIN: "Aggiorna Plugin"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "di questo tema è ora disponibile"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "di questo plugin è ora disponibile"
+ AUTHOR: "Autore"
+ HOMEPAGE: "Pagina iniziale"
+ DEMO: "Demo"
+ BUG_TRACKER: "Bug Tracker"
+ KEYWORDS: "Parole chiave"
+ LICENSE: "Licenza"
+ DESCRIPTION: "Descrizione"
+ README: "Leggimi"
+ REMOVE_THEME: "Rimuovi tema"
+ INSTALL_THEME: "Installa tema"
+ THEME: "Tema"
+ BACK_TO_THEMES: "Torna ai Temi"
+ BACK_TO_PLUGINS: "Torna ai Plugins"
+ CHECK_FOR_UPDATES: "Controlla aggiornamenti"
+ ADD: "Aggiungi"
+ CLEAR_CACHE: "Svuota Cache"
+ CLEAR_CACHE_ALL_CACHE: "Tutta la Cache"
+ CLEAR_CACHE_ASSETS_ONLY: "Solo Assets"
+ CLEAR_CACHE_IMAGES_ONLY: "Solo Immagini"
+ CLEAR_CACHE_CACHE_ONLY: "Solo Cache"
+ CLEAR_CACHE_TMP_ONLY: "Solo temporanei"
+ DASHBOARD: "Pannello di Controllo"
+ UPDATES_AVAILABLE: "Aggiornamenti disponibili"
+ DAYS: "Giorni"
+ UPDATE: "Aggiorna"
+ BACKUP: "Backup"
+ BACKUPS: "Backup"
+ BACKUP_NOW: "Esegui backup ora"
+ BACKUPS_STATS: "Backup delle statistiche"
+ BACKUPS_HISTORY: "Backup della cronologia"
+ BACKUPS_PROFILES: "Backup dei profili"
+ BACKUPS_COUNT: "Numero di backup"
+ BACKUPS_PROFILES_COUNT: "Numero di Profili"
+ BACKUPS_TOTAL_SIZE: "Spazio utilizzato"
+ BACKUPS_NEWEST: "Backup più recente"
+ BACKUPS_OLDEST: "Backup meno recente"
+ BACKUPS_PURGE: "Rimuovi"
+ BACKUPS_NOT_GENERATED: "Nessun backup è stato ancora generato..."
+ BACKUPS_PURGE_NUMBER: "Sto usando %s di %s backup"
+ STATISTICS: "Statistiche"
+ TODAY: "Oggi"
+ WEEK: "Settimana"
+ MONTH: "Mese"
+ LATEST_PAGE_UPDATES: "Ultimi Aggiornamenti della Pagina"
+ MAINTENANCE: "Manutenzione"
+ UPDATED: "Aggiornato"
+ MON: "Lun"
+ TUE: "Mar"
+ WED: "Mer"
+ THU: "Gio"
+ FRI: "Ven"
+ SAT: "Sab"
+ SUN: "Dom"
+ COPY: "Copia"
+ EDIT: "Modifica"
+ CREATE: "Crea"
+ GRAV_ADMIN: "Admin di Grav"
+ GRAV_OFFICIAL_PLUGIN: "Plugin Ufficiale Grav"
+ GRAV_OFFICIAL_THEME: "Tema Ufficiale Grav"
+ PLUGIN_SYMBOLICALLY_LINKED: "Questo plugin ha un link simbolico. Gli aggiornamenti verranno ignorati."
+ THEME_SYMBOLICALLY_LINKED: "Questo tema ha un link simbolico. Gli aggiornamenti verranno ignorati"
+ REMOVE_PLUGIN: "Rimuovi Plugin"
+ INSTALL_PLUGIN: "Installa Plugin"
+ AVAILABLE: "Disponibile"
+ INSTALLED: "Installato"
+ INSTALL: "Installa"
+ ACTIVE_THEME: "Tema attivo"
+ SWITCHING_TO: "Passando a"
+ SWITCHING_TO_DESCRIPTION: "Cambiando il tema, non è garantito che tutti i layout di pagina siano supportati, potenzialmente causando errori quando si tenta di caricare tali pagine."
+ SWITCHING_TO_CONFIRMATION: "Si desidera continuare e cambiare il tema"
+ CREATE_NEW_USER: "Crea nuovo utente"
+ REMOVE_USER: "Rimuovi utente"
+ ACCESS_DENIED: "Accesso negato"
+ ACCOUNT_NOT_ADMIN: "il tuo account non ha permessi di amministratore"
+ PHP_INFO: "PHP Info"
+ INSTALLER: "Installazione"
+ AVAILABLE_THEMES: "Temi disponibili"
+ AVAILABLE_PLUGINS: "Plugin disponibili"
+ INSTALLED_THEMES: "Temi installati"
+ INSTALLED_PLUGINS: "Plugins installati"
+ BROWSE_ERROR_LOGS: "Vedi log errori"
+ SITE: "Sito"
+ INFO: "Info"
+ SYSTEM: "Sistema"
+ USER: "Utente"
+ ADD_ACCOUNT: "Aggiungi utente"
+ SWITCH_LANGUAGE: "Cambia lingua"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Plugin abilitato con successo"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Plugin disabilitato con successo"
+ SUCCESSFULLY_CHANGED_THEME: "Tema cambiato con successo"
+ INSTALLATION_FAILED: "Intallazione fallita"
+ INSTALLATION_SUCCESSFUL: "Installazione avvenuta con successo"
+ UNINSTALL_FAILED: "Disinstallazione fallita"
+ UNINSTALL_SUCCESSFUL: "Disinstallazione riuscita"
+ SUCCESSFULLY_SAVED: "Salvato con successo"
+ SUCCESSFULLY_COPIED: "Copiato con successo"
+ REORDERING_WAS_SUCCESSFUL: "Riordinato con successo"
+ SUCCESSFULLY_DELETED: "Eliminato con successo"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Lingua cambiata con successo"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Permissi insufficenti per l'operazione"
+ CACHE_CLEARED: "Cache svuotata"
+ METHOD: "Metodo"
+ ERROR_CLEARING_CACHE: "Errore nella pulizia della cache"
+ AN_ERROR_OCCURRED: "Errore rilevato"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Il tuo backup è pronto per il download"
+ DOWNLOAD_BACKUP: "Scarica backup"
+ PAGES_FILTERED: "Pagine filtrate"
+ NO_PAGE_FOUND: "Nessuna pagina trovata"
+ INVALID_PARAMETERS: "Parametri invalidi"
+ NO_FILES_SENT: "Nessun file inviato"
+ EXCEEDED_FILESIZE_LIMIT: "È stato superato il limite massimo della dimensione del file di configurazione PHP"
+ UNKNOWN_ERRORS: "Errore sconosciuto"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "È stato superato il limite massimo della dimensione del file di configurazione di Grav"
+ UNSUPPORTED_FILE_TYPE: "Tipo di file non supportato"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Fallito lo spostamento del file caricato"
+ FILE_UPLOADED_SUCCESSFULLY: "File caricato con successo"
+ FILE_DELETED: "File eliminato"
+ FILE_COULD_NOT_BE_DELETED: "Il file non può essere eliminato"
+ FILE_NOT_FOUND: "File non trovato"
+ NO_FILE_FOUND: "Nessun file trovato"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav è stato correttamente aggiornato a"
+ GRAV_UPDATE_FAILED: "Aggiornamento di Grav fallito"
+ EVERYTHING_UPDATED: "Tutto aggiornato"
+ UPDATES_FAILED: "Aggiornamento fallito"
+ AVATAR_BY: "Avatar da"
+ AVATAR_UPLOAD_OWN: "O carica il tuo..."
+ LAST_BACKUP: "Ultimo Backup"
+ FULL_NAME: "Nome completo"
+ USERNAME: "Nome utente"
+ EMAIL: "Email"
+ USERNAME_EMAIL: "Nome utente o email"
+ PASSWORD: "Password"
+ PASSWORD_CONFIRM: "Conferma Password"
+ TITLE: "Titolo"
+ LANGUAGE: "Lingua"
+ ACCOUNT: "Profilo"
+ EMAIL_VALIDATION_MESSAGE: "Deve essere un indirizzo email valido"
+ PASSWORD_VALIDATION_MESSAGE: "La password deve contenere almeno un numero, una lettera maiuscola e una minuscola, ed essere lunga almeno 8 caratteri"
+ LANGUAGE_HELP: "Imposta la lingua preferita"
+ MEDIA: "Media"
+ DEFAULTS: "Impostazioni predefinite"
+ SITE_TITLE: "Titolo del sito"
+ SITE_TITLE_PLACEHOLDER: "Titolo di tutto il sito"
+ SITE_TITLE_HELP: "Il titolo di default per il tuo sito, usato soprattutto dai temi"
+ SITE_DEFAULT_LANG: "Lingua predefinita"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Lingua predefinita usata dal tag del tema"
+ SITE_DEFAULT_LANG_HELP: "Lingua predefinita usata dal tag del tema"
+ DEFAULT_AUTHOR: "Autore predefinito"
+ DEFAULT_AUTHOR_HELP: "Un nome autore di default, usato dai temi o dal contenuto delle pagine"
+ DEFAULT_EMAIL: "Email predefinita"
+ DEFAULT_EMAIL_HELP: "Una email predefinita, usata dal tema o dalle pagine"
+ TAXONOMY_TYPES: "Tassonomie"
+ TAXONOMY_TYPES_HELP: "Le tassonomie devono essere definite qui per essere utilizzate nelle pagine"
+ PAGE_SUMMARY: "Sommario pagina"
+ ENABLED: "Abilitato"
+ ENABLED_HELP: "Abilita il sommario pagina (il sommario viene preso dal contenuto della pagina)"
+ 'YES': "Sì"
+ 'NO': "No"
+ SUMMARY_SIZE: "Dimensione del sommario"
+ SUMMARY_SIZE_HELP: "Il numero di caratteri del contenuto della pagina da usare come sommario"
+ FORMAT: "Formato"
+ FORMAT_HELP: "corto = usa la prima occorrenza di un delimitatore; lungo = il delimitatore di sommario verrà ignorato"
+ SHORT: "Corto"
+ LONG: "Lungo"
+ DELIMITER: "Delimitatore"
+ DELIMITER_HELP: "Il delimitatore di sommario (predefinito '===')"
+ METADATA: "Metadati"
+ METADATA_HELP: "I valori metadata predefiniti che verranno mostrati in ogni pagina, a meno che sovrascritti dalla pagina (override)"
+ NAME: "Nome"
+ CONTENT: "Contenuto"
+ REDIRECTS_AND_ROUTES: "Redirect & Route"
+ CUSTOM_REDIRECTS: "Redirect custom"
+ CUSTOM_REDIRECTS_HELP: "Route impostate per ridirigere gli alias indicati verso altre pagine. Regex accettate."
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/il-tuo/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/il-tuo/redirect"
+ CUSTOM_ROUTES: "Route custom"
+ CUSTOM_ROUTES_HELP: "Route impostate per essere alias di altre pagine. Regex accettate."
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/il-tuo/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/la-tua/route"
+ FILE_STREAMS: "Stream File"
+ DEFAULT: "Predefinito"
+ PAGE_MEDIA: "Media della pagina"
+ OPTIONS: "Opzioni"
+ PUBLISHED: "Pubblicato"
+ PUBLISHED_HELP: "Di default una pagina è pubblicata a meno che non venga esplicitamente impostata published: false, o con l'opzione publish_date impostata per il futuro, o altrimenti con l'opzione unpublish_date impostata nel passato"
+ DATE: "Data"
+ DATE_HELP: "La variabile data permette di specificare una data associata a questa pagina."
+ PUBLISHED_DATE: "Data di pubblicazione"
+ PUBLISHED_DATE_HELP: "Fornisce una data che permette la pubblicazione automatica della pagina."
+ UNPUBLISHED_DATE: "Data di rimozione dalla pubblicazione"
+ UNPUBLISHED_DATE_HELP: "Può fornire una data per rimuovere automaticamente la pubblicazione della pagina."
+ ROBOTS: "Robot"
+ TAXONOMIES: "Tassonomie"
+ TAXONOMY: "Tassonomia"
+ ADVANCED: "Avanzato"
+ SETTINGS: "Settaggi"
+ FOLDER_NUMERIC_PREFIX: "Prefisso numerico della cartella"
+ FOLDER_NUMERIC_PREFIX_HELP: "Prefisso numerico che fornisce ordinamento manuale ed implica visibilità"
+ FOLDER_NAME: "Nome Cartella"
+ FOLDER_NAME_HELP: "Il nome della cartella che sarà memorizzato nel filesystem per questa pagina"
+ PARENT: "Superiore"
+ DEFAULT_OPTION_ROOT: "- Radice -"
+ DEFAULT_OPTION_SELECT: "- Seleziona -"
+ DISPLAY_TEMPLATE: "Visualizza Template"
+ DISPLAY_TEMPLATE_HELP: "Il tipo di pagina che si traduce nel template Twig che genera la pagina"
+ BODY_CLASSES: "Classi del tag body"
+ ORDERING: "Ordinamento"
+ PAGE_ORDER: "Ordine pagina"
+ OVERRIDES: "Sovrascrittura"
+ MENU: "Menu"
+ MENU_HELP: "La stringa da utilizzare in un menù. Se non impostata, verrà utilizzato il Title."
+ SLUG: "Slug"
+ SLUG_HELP: "La variabile slug permette di specificare la porzione di URL associata alla pagina"
+ SLUG_VALIDATE_MESSAGE: "Lo slug deve contenere solo caratteri alfanumerici minuscoli e trattini"
+ PROCESS: "Processa"
+ PROCESS_HELP: "Controlla come sono processate le pagine. Può essere impostato per pagina anzichè globale"
+ DEFAULT_CHILD_TYPE: "Child Type Predefinito"
+ USE_GLOBAL: "Usa globale"
+ ROUTABLE: "Routabile"
+ ROUTABLE_HELP: "Se questa pagina è raggiungibile tramite URL"
+ CACHING: "Cache"
+ VISIBLE: "Visibile"
+ VISIBLE_HELP: "Determina se una pagina è visible nella navigazione"
+ DISABLED: "Disabilitato"
+ ITEMS: "Elementi"
+ ORDER_BY: "Ordina per"
+ ORDER: "Ordine"
+ FOLDER: "Cartella"
+ ASCENDING: "Ascendente"
+ DESCENDING: "Discendente"
+ ADD_MODULAR_CONTENT: "Aggiungi contenuto modulare"
+ PAGE_TITLE: "Titolo pagina"
+ PAGE_TITLE_HELP: "Il titolo della pagina"
+ PAGE: "Pagina"
+ MODULAR_TEMPLATE: "Template modulare"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Nome del file"
+ PARENT_PAGE: "Pagina Superiore"
+ HOME_PAGE: "Pagina iniziale"
+ HOME_PAGE_HELP: "La pagina che Grav userà come pagina predefinita di base"
+ DEFAULT_THEME: "Tema predefinito"
+ DEFAULT_THEME_HELP: "Imposta il tema predefinito utilizzato da Grav (il predefinito è Antimatter)"
+ TIMEZONE: "Fuso orario"
+ TIMEZONE_HELP: "Sovrascrivi il fuso orario predefinito del server"
+ SHORT_DATE_FORMAT: "Formato di data corto"
+ SHORT_DATE_FORMAT_HELP: "Imposta il formato di data corto che può essere usato dai temi"
+ LONG_DATE_FORMAT: "Formato di data lungo"
+ LONG_DATE_FORMAT_HELP: "Imposta il formato di data lungo che può essere usato dai temi"
+ DEFAULT_ORDERING: "Ordine predefinito"
+ DEFAULT_ORDERING_HELP: "Le pagine in una lista saranno disposte in questo ordine a meno che sia sovrascritto"
+ DEFAULT_ORDERING_DEFAULT: "Predefinito - basato sul nome della cartella"
+ DEFAULT_ORDERING_FOLDER: "Cartella - basato su nome della cartella senza prefisso"
+ DEFAULT_ORDERING_TITLE: "Titolo - basato sul campo titolo nell'intestazione della pagina"
+ DEFAULT_ORDERING_DATE: "Data - basato sul campo 'date' nell'intestazione della pagina"
+ DEFAULT_ORDER_DIRECTION: "Direzione ordinamento predefinita"
+ DEFAULT_ORDER_DIRECTION_HELP: "La direzione dell'ordine delle pagine in una lista"
+ DEFAULT_PAGE_COUNT: "Numero di pagine predefinito"
+ DEFAULT_PAGE_COUNT_HELP: "Il numero massimo predefinito di pagine elencate in una lista"
+ DATE_BASED_PUBLISHING: "Pubblicazione basata sulla data"
+ DATE_BASED_PUBLISHING_HELP: "Rimuovi dalla pubblicazione automaticamente i post in base alla data"
+ EVENTS: "Eventi"
+ EVENTS_HELP: "Abilita o disabilita specifici eventi. Disabitando questo campo può causare problemi nei plugin"
+ REDIRECT_DEFAULT_ROUTE: "Redirect alla route predefinita"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Esegui il redirect automatico di una pagina verso la sua route predefinita"
+ LANGUAGES: "Linguaggi"
+ SUPPORTED: "Supportati"
+ SUPPORTED_HELP: "Lista separata da virgole dei codici di linguaggio a 2 caratteri supportati (ad esempio en,fr,de)"
+ TRANSLATIONS_ENABLED: "Abilita traduzioni"
+ TRANSLATIONS_ENABLED_HELP: "Supporta le traduzioni in Grav, plugin e temi"
+ TRANSLATIONS_FALLBACK: "Fallback delle traduzioni"
+ TRANSLATIONS_FALLBACK_HELP: "Esegui il fallback delle traduzioni se la lingua attiva non ha una traduzione disponibile"
+ ACTIVE_LANGUAGE_IN_SESSION: "Linguaggio attivo nella sessione"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Memorizza il linguaggio attivo nella sessione"
+ HTTP_HEADERS: "Intestazioni HTTP"
+ EXPIRES: "Scade"
+ EXPIRES_HELP: "Imposta l'header expires. Il valore è espresso in secondi"
+ CACHE_CONTROL: "Controllo cache HTTP"
+ CACHE_CONTROL_HELP: "Imposta ad un valore valido di cache control come \"no-cache, non salvare, deve essere rivalidato\""
+ LAST_MODIFIED: "Ultima modifica"
+ LAST_MODIFIED_HELP: "Imposta l'header di ultima modifica che può aiutare ad ottimizzare proxy e cache del browser"
+ ETAG: "ETag"
+ ETAG_HELP: "Imposta l'header ETag per aiutare ad identificare quanto una pagina è stata modificata"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Imposta l'header `Vary: Accept Encoding` per aiutare con proxy e cache del CDN"
+ MARKDOWN_EXTRA: "Markdown extra"
+ MARKDOWN_EXTRA_HELP: "Abilita il supporto predefinito per Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ MARKDOWN_EXTRA_ESCAPE_FENCES: "Abilita l'escape delle entità HTML al di fuori del markdown"
+ MARKDOWN_EXTRA_ESCAPE_FENCES_HELP: "Abilita l'escape delle entità HTML al di fuori del markdown"
+ AUTO_LINE_BREAKS: "Interruzione di riga automatica"
+ AUTO_LINE_BREAKS_HELP: "Abilita il supporto per l'interruzione di rigua automatica in markdown"
+ AUTO_URL_LINKS: "Link URL automatici"
+ AUTO_URL_LINKS_HELP: "Abilita la conversione automatica di URL in link HTML"
+ ESCAPE_MARKUP: "Escape del markup"
+ ESCAPE_MARKUP_HELP: "Abilita l'escape automatico di tags di markup nelle entità HTML"
+ CACHING_HELP: "Switch globale per abilitare o disabilitare la cache di Grav"
+ CACHE_CHECK_METHOD: "Metodo controllo cache"
+ CACHE_CHECK_METHOD_HELP: "Seleziona il metodo usato da Grav per controllare che i file di una pagina siano stati modificati"
+ CACHE_DRIVER: "Driver di cache"
+ CACHE_DRIVER_HELP: "Scegli quale driver di cache deve essere usato da Grav. 'Auto Detect' troverà il migliore automaticamente"
+ CACHE_PREFIX: "Prefisso cache"
+ CACHE_PREFIX_HELP: "Un identificatore per parte della chiave cache di Grav. Non modificare a meno di essere sicuri di quello che si sta facendo."
+ CACHE_PREFIX_PLACEHOLDER: "Derivato dall'URL di base (sovrascrivere inserendo una stringa random)"
+ LIFETIME: "Durata"
+ LIFETIME_HELP: "Imposta la durata della cache in secondi. 0 = infinito"
+ GZIP_COMPRESSION: "Compressione GZip"
+ GZIP_COMPRESSION_HELP: "Abilita la compressione GZip per incrementare la performance della pagina"
+ TWIG_TEMPLATING: "Template Twig"
+ TWIG_CACHING: "Cache di Twig"
+ TWIG_CACHING_HELP: "Controlla il sistema di caching di Twig. Abilitare per una migliore prestazione"
+ TWIG_DEBUG: "Debug Twig"
+ TWIG_DEBUG_HELP: "Abilita o meno l'estensione Debugger di Twig"
+ DETECT_CHANGES: "Rileva cambiamenti"
+ DETECT_CHANGES_HELP: "Ricompila automaticamente la cache di Twig se vengono rilevati cambiamenti nei template Twig"
+ AUTOESCAPE_VARIABLES: "Autoescape variabili"
+ AUTOESCAPE_VARIABLES_HELP: "Autoescape tutte le variabili. Questo potrebbe facilmente causare errori nel sito"
+ ASSETS: "Assets"
+ CSS_PIPELINE: "Pipeline CSS"
+ CSS_PIPELINE_HELP: "La pipeline CSS è l'unificazione di più file CSS in uno solo"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Includere file esterni nella pipeline del CSS"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Gli URL esterni a volte hanno riferimenti di file relativi e non dovrebbero essere sottoposti a pipelining"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "La pipeline CSS viene renderizzata prima"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Il rendering della pipeline CSS viene inserito prima di qualsiasi altro riferimento CSS non incluso"
+ CSS_MINIFY: "Minify CSS"
+ CSS_MINIFY_HELP: "Minify il CSS durante il pipelining"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "CSS minify su Windows"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Ability il minify su piattaforma Windows. Disabilitato di default a causa di ThreadStackSize"
+ CSS_REWRITE: "Riscrivi CSS"
+ CSS_REWRITE_HELP: "Riscrivi tutti gli URL relativi contenuti nei file CSS durante il pipelining"
+ JAVASCRIPT_PIPELINE: "Pipeline JavaScript"
+ JAVASCRIPT_PIPELINE_HELP: "La pipeline di JavaScript è l'unificazione di più file JS in uno solo"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Includere file esterni nella pipeline JS"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Gli URL esterni a volte hanno riferimenti di file relativi e non dovrebbero essere sottoposti a pipelining"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "La pipeline JS viene renderizzata per prima"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Il rendering della pipeline JS viene inserito prima di qualsiasi altro riferimento JS non incluso"
+ JAVASCRIPT_MINIFY: "Minify JavaScript"
+ JAVASCRIPT_MINIFY_HELP: "Attiva il minify di JavaScript durante il pipelining"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Abilita timestamp sugli assets"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Abilita timestamp sugli assets"
+ COLLECTIONS: "Collezioni"
+ ERROR_HANDLER: "Gestione degli errori"
+ DISPLAY_ERRORS: "Mostra errori"
+ DISPLAY_ERRORS_HELP: "Mostra l'output completo degli errori"
+ LOG_ERRORS: "Log errori"
+ LOG_ERRORS_HELP: "Logga gli errori nella cartella /logs"
+ DEBUGGER: "Debugger"
+ DEBUGGER_HELP: "Abilita il debugger di Grav ed i seguenti settaggi"
+ DEBUG_TWIG: "Debug Twig"
+ DEBUG_TWIG_HELP: "Abilita il debug dei template Twig"
+ SHUTDOWN_CLOSE_CONNECTION: "Chiudi la connessione in onShutdown"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Chiudi la connessione prima di chiamare onShutdown(). Disabilita in fase di debug."
+ DEFAULT_IMAGE_QUALITY: "Qualità delle immagini predefinita"
+ DEFAULT_IMAGE_QUALITY_HELP: "La qualità predefinita per le immagini, utilizzata durante resampling o caching delle immagini (85%)"
+ CACHE_ALL: "Attiva la cache su tutte le immagini"
+ CACHE_ALL_HELP: "Attiva la cache su tutte le immagini, anche quelle che non subiscono manipolazioni da parte di Grav"
+ IMAGES_DEBUG: "Debug immagini"
+ IMAGES_DEBUG_HELP: "Mostra un overlay sopra le immagini riportante ad esempio la densità di pixel"
+ UPLOAD_LIMIT: "Limite di upload dei file"
+ UPLOAD_LIMIT_HELP: "Imposta la dimensione di massimo upload dei file, in byte (0 = illimitato)"
+ ENABLE_MEDIA_TIMESTAMP: "Abilita timestamps sui media"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Aggiunge un identificativo basato sulla data di ultima modifica di ogni media file"
+ SESSION: "Sessione"
+ SESSION_ENABLED_HELP: "Abilita il supporto alle sessioni in Grav"
+ TIMEOUT: "Timeout"
+ TIMEOUT_HELP: "Imposta il timeout di sessione, espresso in secondi"
+ SESSION_NAME_HELP: "Indentificatore usato per formare il nome del cookie di sessione"
+ ABSOLUTE_URLS: "URL assoluti"
+ ABSOLUTE_URLS_HELP: "URL assoluti o relativi per `base_url`"
+ PARAMETER_SEPARATOR: "Separatore dei parametri"
+ PARAMETER_SEPARATOR_HELP: "Separatore dei parametri passati, che può essere cambiato per Apache su Windows"
+ TASK_COMPLETED: "Operazione completata"
+ EVERYTHING_UP_TO_DATE: "Tutto aggiornato"
+ UPDATES_ARE_AVAILABLE: "aggiornamenti disponibili"
+ IS_AVAILABLE_FOR_UPDATE: "è disponibile per aggiornamento"
+ IS_NOW_AVAILABLE: "è ora disponibile"
+ CURRENT: "Corrente"
+ UPDATE_GRAV_NOW: "Aggiorna Grav adesso"
+ GRAV_SYMBOLICALLY_LINKED: "Grav è linkato simbolicamente, non è possibile aggiornare da qui"
+ UPDATING_PLEASE_WAIT: "Aggiornando... attendi per favore, sto scaricando"
+ OF_THIS: "di questo"
+ OF_YOUR: "del tuo"
+ HAVE_AN_UPDATE_AVAILABLE: "ha un aggiornamento disponibile"
+ SAVE_AS: "Salva come"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Sei sicuro di voler cancellare questa pagina e tutte le pagine figlie? Se la pagina è stata tradotta in altre lingue, tali lingue verranno preservate e dovranno essere cancellate singolarmente altrimenti verrà cancellata la cartella della pagina e tutte le sottopagine. Questa azione non può essere annullata."
+ AND: "e"
+ UPDATE_AVAILABLE: "Aggiornamento disponibile"
+ METADATA_KEY: "Chiave (ad esempio ' parole chiave')"
+ METADATA_VALUE: "Valore (ad esempio ' Blog, Grav')"
+ USERNAME_HELP: "Il nome utente deve essere compreso tra 3 e 16 caratteri, composti da lettere minuscole, numeri, trattini o il carattere di sottolineatura. Lettere maiuscole, spazi e caratteri speciali non sono consentiti"
+ FULLY_UPDATED: "Tutto aggiornato"
+ SAVE_LOCATION: "Salvato in"
+ PAGE_FILE: "Modello di pagina"
+ PAGE_FILE_HELP: "Nome file del modello di pagina e, per impostazione predefinita, il modello di visualizzazione per questa pagina"
+ NO_USER_ACCOUNTS: "Nessun utente trovato, per cortesia creane uno prima..."
+ REDIRECT_TRAILING_SLASH: "Reindirizza trailing slash"
+ REDIRECT_TRAILING_SLASH_HELP: "Eseguire un redirect 301 piuttosto che utilizzare la gestione delle URI."
+ DEFAULT_DATE_FORMAT: "Formato data pagina"
+ DEFAULT_DATE_FORMAT_HELP: "Formato data della pagina utilizzata da Grav. Come impostazione predefinita Grav cerca di utilizzare il tuo formato data, oppure puoi utilizzare un formato personalizzato seguendo la sintassi di PHP (esempio: Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Imposta automaticamente"
+ IGNORE_FILES: "Ignora i files"
+ IGNORE_FILES_HELP: "Files da ignorare quando le pagine vengono processate"
+ IGNORE_FOLDERS: "Ignora le cartelle"
+ IGNORE_FOLDERS_HELP: "Cartelle ignorate quando le pagine vengono processate"
+ HTTP_ACCEPT_LANGUAGE: "Importa la lingua dal Browser"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Si può scegliere di tentare di impostare la lingua sulla base del tag utilizzato in `http_accept_language` nel browser"
+ OVERRIDE_LOCALE: "Sovrascrivi locale"
+ OVERRIDE_LOCALE_HELP: "Sovrascrivi l'impostazione locale in PHP basato sulla tua lingua corrente"
+ REDIRECT: "Pagina di reindirizzamento"
+ REDIRECT_HELP: "Inserisci la route alla pagina oppure un URL esterno per questa pagina. Esempio: `/some/route` or `http://somesite.com`"
+ PLUGIN_STATUS: "Stato plugin"
+ INCLUDE_DEFAULT_LANG: "Include la lingua predefinita"
+ INCLUDE_DEFAULT_LANG_HELP: "Include l'identificatore della lingua in tutti gli URL nella lingua predefinita e.g. `/en/blog/my-post`"
+ ALLOW_URL_TAXONOMY_FILTERS: "Abilita Filtri di Tassonomia via URL"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Abilita la possibilità di filtrare le pagine attraverso URL di tipo `/taxonomy:value`."
+ REDIRECT_DEFAULT_CODE: "Codice redirect predefinito"
+ REDIRECT_DEFAULT_CODE_HELP: "L'HTTP status code da utilizzare per i redirect"
+ IGNORE_HIDDEN: "Ignora file nascosti"
+ IGNORE_HIDDEN_HELP: "Ignora tutti i file e cartelle che iniziano con un PUNTO"
+ WRAPPED_SITE: "Sito wrappato"
+ WRAPPED_SITE_HELP: "Permette a temi e plugin di sapere se Grav è inserito (wrappato) in un altra piattaforma"
+ FALLBACK_TYPES: "Tipi di fallback file consentiti"
+ FALLBACK_TYPES_HELP: "Abilita i tipi di file a cui si può accedere tramite Page Route. Per default sono consentiti tutti i file media supportati."
+ INLINE_TYPES: "Tipi di file inline"
+ INLINE_TYPES_HELP: "Lista di tipi di file che devono essere visualizzati anzichè scaricati"
+ APPEND_URL_EXT: "Appendi l'estensione all'URL"
+ APPEND_URL_EXT_HELP: "Aggiunge una estensione customizzata all'URL della pagina. Nota, questo significa che Grav cercherà il template `..twig`"
+ PAGE_MODES: "Modalità della pagina"
+ PAGE_TYPES: "Tipi di pagina"
+ ACCESS_LEVELS: "Livelli di accesso"
+ GROUPS: "Gruppi"
+ GROUPS_HELP: "Lista dei gruppi a cui appartiene l'utente"
+ ADMIN_ACCESS: "Accesso Amministrazione"
+ SITE_ACCESS: "Accesso Sito"
+ INVALID_SECURITY_TOKEN: "Token di sicurezza non valido"
+ ACTIVATE: "Attiva"
+ TWIG_UMASK_FIX: "Umask Fix"
+ TWIG_UMASK_FIX_HELP: "Per impostazione predefinita Twig crea file memorizzati nella cache come 0755, questa opzione li imposta 0775"
+ CACHE_PERMS: "Permessi Cache"
+ CACHE_PERMS_HELP: "Impostazione predefinita dei permessi della cartella cache. Solitamente 0755 o 0775 a seconda della configurazione"
+ REMOVE_SUCCESSFUL: "Rimozione avvenuta con Sucesso"
+ REMOVE_FAILED: "Rimozione Fallita"
+ HIDE_HOME_IN_URLS: "Nascondi il route Home negli URL"
+ HIDE_HOME_IN_URLS_HELP: "Assicura che i routes predefiniti per ogni pagina sotto Home, non riferiscano allo stesso route Home"
+ TWIG_FIRST: "Processa prima Twig"
+ TWIG_FIRST_HELP: "Se Twig Page Processing è abilitato, puoi configurare Twig in modo da essere processato prima o dopo Markdown"
+ SESSION_SECURE: "Cookie Sicuri"
+ SESSION_SECURE_HELP: "Indica che la comunicazione di questo cookie deve avvenire attraverso una trasmissione cifrata. ATTENZIONE: Abilitare soltanto su siti che vengono serviti esclusivamente via HTTPS"
+ SESSION_HTTPONLY: "Solo HTTP"
+ SESSION_HTTPONLY_HELP: "Indica che i cookie debbano soltanto essere usati attraverso HTTP, e la modifica via JavaScript non è abilitata"
+ REVERSE_PROXY: "Proxy inverso"
+ REVERSE_PROXY_HELP: "Abilitare se si utilizza proxy inverso e si riscontrano problemi con gli URL contenenti porte non corrette"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Frontmatter non valido, impossibile salvare"
+ ADD_FOLDER: "Aggiungi cartella"
+ PROXY_URL: "URL del proxy"
+ PROXY_URL_HELP: "Inserire l'HOST o l'IP del proxy e la porta"
+ NOTHING_TO_SAVE: "Niente da salvare"
+ FILE_ERROR_ADD: "Si è verificato un errore durante il tentativo di aggiungere il file"
+ FILE_ERROR_UPLOAD: "Si è verificato un errore durante il tentativo di caricare il file"
+ FILE_UNSUPPORTED: "Tipo di file non supportato"
+ ADD_ITEM: "Aggiungi elemento"
+ FILE_TOO_LARGE: "Il file è troppo grande per essere caricato. Il massimo consentito è %s secondo le tue impostazioni PHP. Aumentare il valore PHP di 'post_max_size'"
+ INSTALLING: "Installazione"
+ LOADING: "Caricamento.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "Le seguenti dipendenze devono essere soddisfatte prima:"
+ ERROR_INSTALLING_PACKAGES: "Errore durante l'installazione del pacchetto/i"
+ INSTALLING_DEPENDENCIES: "Installo le dipendenze..."
+ INSTALLING_PACKAGES: "Installo i pacchetti..."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Pacchetto/i installati correttamente."
+ READY_TO_INSTALL_PACKAGES: "Pronto per installare il pacchetto/i"
+ PACKAGES_NOT_INSTALLED: "Pacchetti non installati"
+ PACKAGES_NEED_UPDATE: "Pacchetti già installati, ma in una versione troppo vecchia"
+ PACKAGES_SUGGESTED_UPDATE: "Pacchetti già installati, e versione ok, ma che verranno aggiornati per avere l'ultima versione disponibile"
+ REMOVE_THE: "Rimuovi il %s"
+ CONFIRM_REMOVAL: "Sicuro di voler cancellare questo %s?"
+ REMOVED_SUCCESSFULLY: "%s rimosso con successo"
+ ERROR_REMOVING_THE: "Errore durante la rimozione di %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "%s richiede le seguenti dipendenze, che non sono richieste da altri pacchetti installati. Se non li si utilizza, è possibile rimuoverli direttamente da qui."
+ READY_TO_UPDATE_PACKAGES: "Pronto ad aggiornare il pacchetto/i"
+ ERROR_UPDATING_PACKAGES: "Errore durante l'aggiornamento del pacchetto/i"
+ UPDATING_PACKAGES: "Aggiorno il pacchetto/i..."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Pacchetto/i aggiornati correttamente."
+ UPDATING: "Aggiorno"
+ GPM_RELEASES: "GPM Releases"
+ GPM_RELEASES_HELP: "Scegliere 'Test' per installare versioni beta o test"
+ GPM_METHOD: "Metodo di recupero remoto"
+ GPM_METHOD_HELP: "Se impostato su Auto, Grav determinerà se fopen è disponibile e lo utilizzerà. In caso contrario Grav adotterà il fall back su cURL. Modificare l'impostazione per forzare l'uso dell'uno o dell'altro."
+ GPM_VERIFY_PEER: "Verifica remota del Peer (SSL)"
+ GPM_VERIFY_PEER_HELP: "Alcuni provider sembrano fallire la verifica del certificato SSL getgrav.org, causando il malfunzionamento di GPM. In questo caso la disattivazione di questa impostazione potrebbe essere di aiuto"
+ AUTO: "Automatico"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stabile"
+ TESTING: "Test"
+ FRONTMATTER_PROCESS_TWIG: "Elabora il frontmatter Twig"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Quando attivate, è possibile utilizzare le variabili di configurazione Twig nel frontmatter della pagina"
+ FRONTMATTER_IGNORE_FIELDS: "Ignora i campi del frontmatter"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Alcuni campi del frontmatter potrebbero contenere Twig ma, come nei 'form' questi non dovrebbero essere elaborati"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Pacchetto %s installato con successo"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Ordinamento dettato dal padre, ordinamento disabilitato"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Pagina non visibile, ordinamento disabilitato"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "L'ordinamento tramite Admin non è supportato perché ci sono più di 200 elementi da ordinare"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "L'ordinamento delle pagine è disattivato per questa pagina perché il prefisso numerico delle cartelle non è abilitato"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "NOTA: non è possibile aggiungere file dei media fino a quando non si salva la pagina. Clicca 'Salva' in alto per salvare la pagina"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "NOTA: La pagina deve essere salvata prima di poter caricare dei file."
+ DROP_FILES_HERE_TO_UPLOAD: "Rilascia i file qui o fai click in questa area "
+ INSERT: "Inserisci"
+ UNDO: "Ripristina"
+ REDO: "Ripeti azione"
+ HEADERS: "Intestazioni"
+ BOLD: "Grassetto"
+ ITALIC: "Corsivo"
+ STRIKETHROUGH: "Barrato"
+ SUMMARY_DELIMITER: "Segno di interruzione del riassunto"
+ LINK: "Link"
+ IMAGE: "Immagine"
+ BLOCKQUOTE: "Virgolettato"
+ UNORDERED_LIST: "Elenco non ordinato"
+ ORDERED_LIST: "Elenco ordinato"
+ EDITOR: "Editor"
+ PREVIEW: "Anteprima"
+ FULLSCREEN: "Schermo intero"
+ MODULAR: "Modulare"
+ NON_ROUTABLE: "Non-routabile"
+ NON_MODULAR: "Non modulare"
+ NON_VISIBLE: "Non visibile"
+ NON_PUBLISHED: "Non pubblicato"
+ CHARACTERS: "caratteri"
+ PUBLISHING: "Pubblicazione"
+ NOTIFICATIONS: "Notifiche"
+ MEDIA_TYPES: "Tipi di media"
+ IMAGE_OPTIONS: "Opzioni immagine"
+ MIME_TYPE: "Tipo di MIME"
+ THUMB: "Miniatura immagine"
+ TYPE: "Tipo"
+ FILE_EXTENSION: "Estensioni dei file"
+ LEGEND: "Legenda di pagina"
+ MEMCACHE_SERVER: "Server Memcache"
+ MEMCACHE_SERVER_HELP: "L'indirizzo del server Memcache"
+ MEMCACHE_PORT: "Porta Memcache"
+ MEMCACHE_PORT_HELP: "La porta del server Memcache"
+ MEMCACHED_SERVER: "Server Memcached"
+ MEMCACHED_SERVER_HELP: "L'indirizzo del server nella Memcache"
+ MEMCACHED_PORT: "Porta Memcached"
+ MEMCACHED_PORT_HELP: "La porta del server nella Memcache"
+ REDIS_SERVER: "Server Redis"
+ REDIS_SERVER_HELP: "L'indirizzo del server Redis"
+ REDIS_PORT: "Porta Redis"
+ REDIS_PORT_HELP: "La porta del server Redis"
+ REDIS_PASSWORD: "Redis password/segreto"
+ ALL: "Tutti"
+ FROM: "da"
+ TO: "a"
+ RELEASE_DATE: "Data di rilascio"
+ SORT_BY: "Ordina per"
+ RESOURCE_FILTER: "Filtro..."
+ FORCE_SSL: "Forza SSL"
+ FORCE_SSL_HELP: "Forza SSL a livello globale. Se abilitato, quando il sito viene raggiunto attraverso HTTP, Grav invia un redirect verso la pagina HTTPS"
+ NEWS_FEED: "Feed di notizie"
+ EXTERNAL_URL: "URL esterno"
+ SESSION_PATH: "Il percorso di sessione"
+ SESSION_PATH_HELP: "Utilizzare solo se si sceglie un URL di base personalizzato (rewrite del dominio del sito / sottocartella)"
+ CUSTOM_BASE_URL: "URL di base personalizzato"
+ CUSTOM_BASE_URL_HELP: "Utilizzare se si desidera fare il rewrite del dominio del sito o utilizzare una sottocartella diversa rispetto a quella utilizzata da Grav. Esempio: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'Non è possibile utilizzare "%s" al di fuori delle pagine.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Impossibile caricare il file %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'Impossibile spostare il file %s in "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'Annulla il caricamento'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Si è sicuri di voler annullare questo caricamento?'
+ DROPZONE_DEFAULT_MESSAGE: 'Rilascia qui i tuoi file o fai click in questa area '
+ DROPZONE_FALLBACK_MESSAGE: 'Il tuo browser non supporta il drag and drop per il caricamento dei file.'
+ DROPZONE_FALLBACK_TEXT: 'Si prega di utilizzare il form di fallback qui sotto per caricare i file, come nei tempi antichi.'
+ DROPZONE_FILE_TOO_BIG: 'Il file è troppo grande ({{filesize}}MiB). Dimensione massima del file ammessa: {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "Non è possibile caricare i file di questo tipo."
+ DROPZONE_MAX_FILES_EXCEEDED: "Non è possibile caricare altri file."
+ DROPZONE_REMOVE_FILE: "Cancella il file"
+ DROPZONE_RESPONSE_ERROR: "Il server ha risposto con il codice {{statusCode}}."
+ PREMIUM_PRODUCT: "Premium"
+ ADMIN_CACHING: "Abilita la cache di Admin"
+ ADMIN_CACHING_HELP: "La caching dell'admin può essere controllata indipendentemente da quella del sito di front-end"
+ DESTINATION_NOT_SPECIFIED: "Destinazione non specificata"
+ UPLOAD_ERR_NO_TMP_DIR: "Cartella temporanea mancante"
+ SESSION_SPLIT: "Split di sessione"
+ SESSION_SPLIT_HELP: "Split di sessione indipendente tra il sito e gli altri plugin (per es. admin)"
+ ERROR_FULL_BACKTRACE: "Errore completo di Backtrace"
+ ERROR_SIMPLE: "Errore semplice"
+ ERROR_SYSTEM: "Errore di sistema"
+ IMAGES_AUTO_FIX_ORIENTATION: "Orienta automaticamente"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Correggere automaticamente l'orientamento dell'immagine sulla base dei dati Exif"
+ REDIS_SOCKET: "Socket Redis"
+ REDIS_SOCKET_HELP: "Il socket Redis"
+ NOT_SET: "Non impostato"
+ PERMISSIONS: "Permessi"
+ NEVER_CACHE_TWIG: "Non utilizzare mai la cache per Twig"
+ NEVER_CACHE_TWIG_HELP: "Mantieni solamente la cache dei contenuti e processa le pagine sempre con Twig. Ignora l'impostazione twig_first."
+ ALLOW_WEBSERVER_GZIP: "Consentire Gzip del WebServer"
+ ALLOW_WEBSERVER_GZIP_HELP: "Disattivata per impostazione predefinita. Quando attivata, la compressione configurata Gzip/Deflate WebServer, funzionerà ma la connessione http non verrà chiusa prima dell'evento onShutDown() rallentando il caricamento della pagina"
+ OFFLINE_WARNING: "Non è possibile stabilire la connessione con il GPM"
+ CLEAR_IMAGES_BY_DEFAULT: "Cancella la cache delle immagini per impostazione predefinita"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "Per impostazione predefinita vengono cancellate le immagini elaborate per tutte le cancellazioni della cache. Ciò può essere disabilitato"
+ CLI_COMPATIBILITY: "Compatibilità CLI"
+ CLI_COMPATIBILITY_HELP: "Assicura che siano utilizzati solo driver Cache non volatili (file, redis, memcache, ecc.)"
+ REINSTALL_PLUGIN: "Reinstalla il Plugin"
+ REINSTALL_THEME: "Reinstalla il tema"
+ REINSTALL_THE: "Reinstalla il %s"
+ CONFIRM_REINSTALL: "Sei sicuro di voler reinstallare questo %s?"
+ REINSTALLED_SUCCESSFULLY: "%s reinstallato correttamente"
+ ERROR_REINSTALLING_THE: "Errore durante la reinstallazione di %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Pacchetto %s reinstallato con successo"
+ REINSTALLATION_FAILED: "Reinstallazione non riuscita"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "La versione installata non è la versione più recente. Cliccando su continua, si potrà rimuovere la versione corrente e installare l'ultima release disponibile"
+ TOOLS: "Strumenti"
+ DIRECT_INSTALL: "Installazione diretta"
+ NO_PACKAGE_NAME: "Nome del pacchetto non specificato"
+ PACKAGE_EXTRACTION_FAILED: "Estrazione del pacchetto non riuscita"
+ NOT_VALID_GRAV_PACKAGE: "Non è un pacchetto valido di Grav"
+ NAME_COULD_NOT_BE_DETERMINED: "Non è possibile determinare il nome"
+ CANNOT_OVERWRITE_SYMLINKS: "Non è possibile sovrascrivere i symlink"
+ ZIP_PACKAGE_NOT_FOUND: "Non è stato trovato il pacchetto ZIP"
+ GPM_OFFICIAL_ONLY: "Solo GPM ufficiale"
+ GPM_OFFICIAL_ONLY_HELP: "Consenti solo installazioni dirette solo dal repository ufficiale di GPM."
+ NO_CHILD_TYPE: "Nessun tipo di figlio per questo rawroute"
+ SORTABLE_PAGES: "Pagine ordinabili:"
+ UNSORTABLE_PAGES: "Pagine non ordinabili"
+ ADMIN_SPECIFIC_OVERRIDES: "Override specifici per l'Admin"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Ordine di visualizzazione dei figli"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "Ordine con il quale devono essere visualizzati i figli di questa pagina, nella visualizzazione 'Pagine' del plugin Admin"
+ PWD_PLACEHOLDER: "stringa complessa lunga almeno 8 caratteri"
+ PWD_REGEX: "Password Regex"
+ PWD_REGEX_HELP: "Per impostazione predefinita: la password deve contenere almeno un numero, una lettera maiuscola, una minuscola e deve essere di almeno 8 o più caratteri"
+ USERNAME_PLACEHOLDER: "solo caratteri minuscoli, ad esempio 'admin'"
+ USERNAME_REGEX: "Nome utente Regex"
+ USERNAME_REGEX_HELP: "Per impostazione predefinita: solo caratteri minuscoli, cifre, trattini e underscore. 3 - 16 caratteri"
+ CONTENT_PADDING: "Padding del contenuto"
+ CONTENT_PADDING_HELP: "Attivare/disattivare il padding del contenuto intorno all'area del contenuto per aumentare lo spazio"
+ ENABLE_AUTO_METADATA: "Metadati automatici da Exif"
+ ENABLE_AUTO_METADATA_HELP: "Genera automaticamente i file di metadati per le immagini che contengono informazioni exif"
+ 2FA_TITLE: "Autenticazione in due passaggi"
+ 2FA_INSTRUCTIONS: "##### 2-Factor Authentication \nLa **2FA** è abilitata per questo account. Si prega di utilizzare la vostra app **2FA** per immettere il **6-digit code** per completare il processo di login."
+ 2FA_REGEN_HINT: "La rigenerazione della secret comporterà l'aggiornamento dell'app di autenticazione"
+ 2FA_LABEL: "Accesso a livello amministratore"
+ 2FA_FAILED: "Codice di autenticazione a 2 fattori non valido. Riprovare per favore..."
+ 2FA_ENABLED: "2FA abilitato"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "2FA segreto"
+ 2FA_SECRET_HELP: "Scandisci questo codice QR nella tua [Authenticator App](https://learn.getgrav.org/admin-panel/2fa#apps). È inoltre cosa buona fare il backup della secret in un luogo sicuro, nel caso in cui fosse necessario reinstallare la tua app. Controllare [Grav docs] (https://learn.getgrav.org/admin-panel/2fa) per ulteriori informazioni "
+ 2FA_REGENERATE: "Rigenera"
+ FORCE_LOWERCASE_URLS: "Forza URL minuscoli"
+ FORCE_LOWERCASE_URLS_HELP: "Per impostazione predefinita Grav imposterà tutti gli slugs e le routes in minuscolo. Con tale impostazione su false, possono essere utilizzati slugs e routes maiuscoli"
+ INTL_ENABLED: "Integrazione modulo Intl"
+ INTL_ENABLED_HELP: "Utilizza il modulo PHP Intl e raccogli per ordinare le collezioni basate su UTF8"
+ VIEW_SITE_TIP: "Visualizza sito"
+ TOOLS_DIRECT_INSTALL_TITLE: "Installazione diretta deii pacchetti di Grav"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "Installare il pacchetto tramite upload via Direct ZIP"
+ TOOLS_DIRECT_INSTALL_UPLOAD_DESC: "Con questo metodo si può facilmente installare un valido tema , plugin , o anche un aggiornamento pacchetto Zip di Grav . Questo pacchetto non deve essere registrato tramite GPM e consente facilmente il rollback a una versione o insallazione precedente per il test."
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "Installare il pacchetto tramite riferimento URL remoto"
+ TOOLS_DIRECT_INSTALL_URL_DESC: "In alternativa si può anche fare riferimento ad una URL completa per il file ZIP del pacchetto e installarlo tramite questa URL remota."
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Carica e installa"
+ ROUTE_OVERRIDES: "Override della route"
+ ROUTE_DEFAULT: "Route di default"
+ ROUTE_CANONICAL: "Route canonica"
+ ROUTE_ALIASES: "Alias della route"
diff --git a/user/plugins/admin/languages/ja.yaml b/user/plugins/admin/languages/ja.yaml
new file mode 100644
index 0000000..1bbcebb
--- /dev/null
+++ b/user/plugins/admin/languages/ja.yaml
@@ -0,0 +1,645 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "これはベータリリースです。稼働中サイトでは、自己責任においてご利用ください。"
+ ADMIN_REPORT_ISSUE: "問題を見つけた場合は、GitHub にてご報告をお願いします。"
+ EMAIL_FOOTER: "Powered by Grav - The Modern Flat File CMS"
+ LOGIN_BTN: "ログイン"
+ LOGIN_BTN_FORGOT: "パスワードを紛失"
+ LOGIN_BTN_RESET: "パスワードをリセット"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "リセット手順を送信"
+ LOGIN_BTN_CLEAR: "フォームをクリア"
+ LOGIN_BTN_CREATE_USER: "ユーザーを作成"
+ LOGIN_LOGGED_IN: "正常にログインしました。"
+ LOGIN_FAILED: "ログインに失敗しました。"
+ LOGGED_OUT: "ログアウトしました。"
+ RESET_NEW_PASSWORD: "新パスワードを入力してください …"
+ RESET_LINK_EXPIRED: "リセットリンクが期限切れです。再試行してください。"
+ RESET_PASSWORD_RESET: "パスワードがリセットされました。"
+ RESET_INVALID_LINK: "無効なリセットリンクを使用しました。再試行してください。"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "パスワードのリセット手順を、メールで %s 宛に送信しました。"
+ FORGOT_FAILED_TO_EMAIL: "リセット手順のメール送信に失敗しました。後ほど再試行してください。"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "メールアドレスが設定されていないため、 %s のパスワードをリセットできません。"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "ユーザー名が %s のユーザーは存在しません。"
+ FORGOT_EMAIL_NOT_CONFIGURED: "このサイトはメールを送信できるように設定されていないため、パスワードのリセットができません。"
+ FORGOT_EMAIL_SUBJECT: "%s パスワードリセットのリクエスト"
+ FORGOT_EMAIL_BODY: "パスワードのリセット %1$s さん
%4$s でパスワードをリセットするリクエストがありました。
パスワードをリセットするには、このリンクをクリックしてください。
あるいは、次の URL をブラウザのアドレスバーにコピーしてください:
%2$s
=========== %3$s
"
+ MANAGE_PAGES: "ページの管理"
+ CONFIGURATION: "設定"
+ PAGES: "ページ"
+ PLUGINS: "プラグイン"
+ PLUGIN: "プラグイン"
+ THEMES: "テーマ"
+ LOGOUT: "ログアウト"
+ BACK: "戻る"
+ NEXT: "次へ"
+ PREVIOUS: "前へ"
+ ADD_PAGE: "ページの追加"
+ ADD_MODULAR: "モジュラーの追加"
+ MOVE: "移動"
+ DELETE: "削除"
+ VIEW: "参照"
+ SAVE: "保存"
+ NORMAL: "通常"
+ EXPERT: "上級者向け"
+ EXPAND_ALL: "すべて展開"
+ COLLAPSE_ALL: "すべて折りたたむ"
+ ERROR: "エラー"
+ CLOSE: "閉じる"
+ CANCEL: "キャンセル"
+ CONTINUE: "続ける"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "ご確認ください"
+ MODAL_CHANGED_DETECTED_TITLE: "変更が見つかりました"
+ MODAL_CHANGED_DETECTED_DESC: "変更が保存されていません。本当に、保存せずに移動してもよろしいですか?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "ご確認ください"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "本当に、このファイルを削除してもよろしいですか? この操作は元に戻すことができません。"
+ ADD_FILTERS: "絞り込み条件を追加"
+ SEARCH_PAGES: "ページを検索"
+ VERSION: "バージョン"
+ WAS_MADE_WITH: "Was made with"
+ BY: "By"
+ UPDATE_THEME: "テーマを更新"
+ UPDATE_PLUGIN: "プラグインを更新"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "のこのテーマが入手可能です。"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "のこのプラグインが入手可能です。"
+ AUTHOR: "作者"
+ HOMEPAGE: "ホームページ"
+ DEMO: "デモ"
+ BUG_TRACKER: "バグトラッカー"
+ KEYWORDS: "キーワード"
+ LICENSE: "ライセンス"
+ DESCRIPTION: "説明"
+ README: "Readme"
+ REMOVE_THEME: "テーマを削除"
+ INSTALL_THEME: "テーマをインストール"
+ THEME: "テーマ"
+ BACK_TO_THEMES: "テーマに戻る"
+ BACK_TO_PLUGINS: "プラグインに戻る"
+ CHECK_FOR_UPDATES: "更新を確認"
+ ADD: "追加"
+ CLEAR_CACHE: "キャッシュをクリア"
+ CLEAR_CACHE_ALL_CACHE: "すべてのキャッシュ"
+ CLEAR_CACHE_ASSETS_ONLY: "アセットのみ"
+ CLEAR_CACHE_IMAGES_ONLY: "画像のみ"
+ CLEAR_CACHE_CACHE_ONLY: "キャッシュのみ"
+ CLEAR_CACHE_TMP_ONLY: "一時ファイルのみ"
+ DASHBOARD: "ダッシュボード"
+ UPDATES_AVAILABLE: "更新可能"
+ DAYS: "日"
+ UPDATE: "更新"
+ BACKUP: "バックアップ"
+ STATISTICS: "統計"
+ TODAY: "今日"
+ WEEK: "週"
+ MONTH: "月"
+ LATEST_PAGE_UPDATES: "最新のページ更新"
+ MAINTENANCE: "メンテナンス"
+ UPDATED: "更新済み"
+ MON: "月"
+ TUE: "火"
+ WED: "水"
+ THU: "木"
+ FRI: "金"
+ SAT: "土"
+ SUN: "日"
+ COPY: "コピー"
+ EDIT: "編集"
+ CREATE: "作成"
+ GRAV_ADMIN: "Grav 管理"
+ GRAV_OFFICIAL_PLUGIN: "Grav 公式プラグイン"
+ GRAV_OFFICIAL_THEME: "Grav 公式テーマ"
+ PLUGIN_SYMBOLICALLY_LINKED: "このプラグインは、シンボルによってリンクされています。更新は検出されません。"
+ THEME_SYMBOLICALLY_LINKED: "このテーマは、シンボルによってリンクされています。更新は検出されません。"
+ REMOVE_PLUGIN: "プラグインを削除"
+ INSTALL_PLUGIN: "プラグインをインストール"
+ AVAILABLE: "利用可能"
+ INSTALLED: "インストール済み"
+ INSTALL: "インストール"
+ ACTIVE_THEME: "アクティブなテーマ"
+ SWITCHING_TO: "次に切り替え"
+ SWITCHING_TO_DESCRIPTION: "別のテーマに変更することにより、すべてのレイアウトページがサポートされている保証がないため、ページ読み込み時にエラーが発生する場合があります。"
+ SWITCHING_TO_CONFIRMATION: "続行して、次のテーマに切り替えますか"
+ CREATE_NEW_USER: "新しいユーザーを作成"
+ REMOVE_USER: "ユーザーを削除"
+ ACCESS_DENIED: "アクセスが拒否されました"
+ ACCOUNT_NOT_ADMIN: "あなたのアカウントには管理者アクセス権がありません。"
+ PHP_INFO: "PHP 情報"
+ INSTALLER: "インストーラー"
+ AVAILABLE_THEMES: "利用可能なテーマ"
+ AVAILABLE_PLUGINS: "利用可能なプラグイン"
+ INSTALLED_THEMES: "インストール済みテーマ"
+ INSTALLED_PLUGINS: "インストール済みプラグイン"
+ BROWSE_ERROR_LOGS: "エラーログを閲覧"
+ SITE: "サイト"
+ INFO: "情報"
+ SYSTEM: "システム"
+ USER: "ユーザー"
+ ADD_ACCOUNT: "アカウントを追加"
+ SWITCH_LANGUAGE: "言語を切り替え"
+ SUCCESSFULLY_ENABLED_PLUGIN: "プラグインは正常に有効化されました。"
+ SUCCESSFULLY_DISABLED_PLUGIN: "プラグインは正常に無効化されました。"
+ SUCCESSFULLY_CHANGED_THEME: "デフォルトテーマは正常に変更されました。"
+ INSTALLATION_FAILED: "インストールに失敗しました。"
+ INSTALLATION_SUCCESSFUL: "インストールが成功しました。"
+ UNINSTALL_FAILED: "アンインストールに失敗しました。"
+ UNINSTALL_SUCCESSFUL: "アンインストールが成功しました。"
+ SUCCESSFULLY_SAVED: "正常に保存されました。"
+ SUCCESSFULLY_COPIED: "正常にコピーされました。"
+ REORDERING_WAS_SUCCESSFUL: "並べ替えに成功しました。"
+ SUCCESSFULLY_DELETED: "正常に削除されました。"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "正常に言語が切り替えられました。"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "タスクを実行するためのアクセス権が不足しています。"
+ CACHE_CLEARED: "キャッシュがクリアされました。"
+ METHOD: "方法"
+ ERROR_CLEARING_CACHE: "キャッシュのクリア中にエラーが発生しました。"
+ AN_ERROR_OCCURRED: "エラーが発生しました。"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "バックアップのダウンロード準備ができました。"
+ DOWNLOAD_BACKUP: "バックアップをダウンロード"
+ PAGES_FILTERED: "ページが絞り込まれました。"
+ NO_PAGE_FOUND: "ページがありません。"
+ INVALID_PARAMETERS: "無効なパラメータ"
+ NO_FILES_SENT: "ファイルが送信されませんでした。"
+ EXCEEDED_FILESIZE_LIMIT: "PHP 設定ファイルのサイズ上限を超過しました"
+ UNKNOWN_ERRORS: "不明なエラー"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Grav 設定ファイルのサイズ上限を超過しました"
+ UNSUPPORTED_FILE_TYPE: "対応していないファイルタイプです。"
+ FAILED_TO_MOVE_UPLOADED_FILE: "アップロードされたファイルの移動に失敗しました。"
+ FILE_UPLOADED_SUCCESSFULLY: "ファイルが正常にアップロードされました。"
+ FILE_DELETED: "ファイルが削除されました。"
+ FILE_COULD_NOT_BE_DELETED: "ファイルを削除できませんでした。"
+ FILE_NOT_FOUND: "ファイルが見つかりませんでした。"
+ NO_FILE_FOUND: "ファイルはありません。"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Gravは正常に更新されました"
+ GRAV_UPDATE_FAILED: "Grav の更新に失敗しました。"
+ EVERYTHING_UPDATED: "すべて更新されました。"
+ UPDATES_FAILED: "更新に失敗しました。"
+ AVATAR_BY: "Avatar by"
+ AVATAR_UPLOAD_OWN: "または、ファイルをアップロード..."
+ LAST_BACKUP: "前回のバックアップ"
+ FULL_NAME: "フルネーム"
+ USERNAME: "ユーザー名"
+ EMAIL: "メール"
+ USERNAME_EMAIL: "ユーザー名またはメールアドレス"
+ PASSWORD: "パスワード"
+ PASSWORD_CONFIRM: "パスワードの確認"
+ TITLE: "タイトル"
+ LANGUAGE: "言語"
+ ACCOUNT: "アカウント"
+ EMAIL_VALIDATION_MESSAGE: "有効なメールアドレスである必要があります。"
+ PASSWORD_VALIDATION_MESSAGE: "パスワードは、少なくとも 1 つの数字、大文字、小文字を含む、8 文字以上である必要があります。"
+ LANGUAGE_HELP: "お好きな言語を設定します。"
+ MEDIA: "メディア"
+ DEFAULTS: "デフォルト"
+ SITE_TITLE: "サイトタイトル"
+ SITE_TITLE_PLACEHOLDER: "サイト全体のタイトル"
+ SITE_TITLE_HELP: "サイトのデフォルトのタイトルです。テーマで使用される場合があります。"
+ SITE_DEFAULT_LANG: "既定の言語"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "テーマの タグに使用する既定の言語"
+ SITE_DEFAULT_LANG_HELP: "テーマの タグに使用する既定の言語"
+ DEFAULT_AUTHOR: "デフォルトの作者"
+ DEFAULT_AUTHOR_HELP: "デフォルトの作者です。テーマやページコンテンツで使用される場合があります。"
+ DEFAULT_EMAIL: "デフォルトのメール"
+ DEFAULT_EMAIL_HELP: "テーマやページで参照する、デフォルトのメールです。"
+ TAXONOMY_TYPES: "タクソノミータイプ"
+ TAXONOMY_TYPES_HELP: "ページでタクソノミーを使用したい場合、ここでタクソノミータイプを定義する必要があります。"
+ PAGE_SUMMARY: "ページの概要"
+ ENABLED: "有効"
+ ENABLED_HELP: "ページの概要 (概要はページコンテンツと同じものを返します) を有効にします。"
+ 'YES': "はい"
+ 'NO': "いいえ"
+ SUMMARY_SIZE: "概要のサイズ"
+ SUMMARY_SIZE_HELP: "コンテンツの概要として使用する 1 ページの文字数です。"
+ FORMAT: "フォーマット"
+ FORMAT_HELP: "短い = 区切り文字が現れるかサイズに達するか、どちらか最初を使用します。長い = 概要の区切り文字は無視されます。"
+ SHORT: "短い"
+ LONG: "長い"
+ DELIMITER: "区切り文字"
+ DELIMITER_HELP: "概要の区切り文字 (デフォルトは\"===\") です。"
+ METADATA: "メタデータ"
+ METADATA_HELP: "ページで優先設定された場合を除き、すべてのページに表示されるデフォルトのメタデータ値です。"
+ NAME: "名前"
+ CONTENT: "コンテンツ"
+ REDIRECTS_AND_ROUTES: "リダイレクトとルート"
+ CUSTOM_REDIRECTS: "カスタムリダイレクト"
+ CUSTOM_REDIRECTS_HELP: "他のページへリダイレクトするためのルートです。標準的な正規表現での置換が有効です。"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/your/redirect"
+ CUSTOM_ROUTES: "カスタムルート"
+ CUSTOM_ROUTES_HELP: "他のページに対するエイリアスへのルートです。標準的な正規表現での置換が有効です。"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/your/route"
+ FILE_STREAMS: "ファイルストリーム"
+ DEFAULT: "デフォルト"
+ PAGE_MEDIA: "ページメディア"
+ OPTIONS: "オプション"
+ PUBLISHED: "公開"
+ PUBLISHED_HELP: "明示的に「公開」を設定しない場合、デフォルトでページは公開されます。「公開」を「いいえ」に設定、または「公開日」を指定することで公開を将来にすることができます。また、「公開終了日」を指定することで公開の終了を設定できます。"
+ DATE: "日付"
+ DATE_HELP: "「日付」変数は、明確に設定された日付とこのページを関連付けることができます。"
+ PUBLISHED_DATE: "公開日"
+ PUBLISHED_DATE_HELP: "自動的に公開するための日付を設定できます。"
+ UNPUBLISHED_DATE: "公開終了日"
+ UNPUBLISHED_DATE_HELP: "自動的に公開を終了するための日付を設定できます。"
+ ROBOTS: "Robots"
+ TAXONOMIES: "タクソノミー"
+ TAXONOMY: "タクソノミー"
+ ADVANCED: "高度"
+ SETTINGS: "設定"
+ FOLDER_NUMERIC_PREFIX: "フォルダーの数値プレフィックス"
+ FOLDER_NUMERIC_PREFIX_HELP: "手作業での並べ替えや、見やすくするために先頭に付加する数値です。"
+ FOLDER_NAME: "フォルダー名"
+ FOLDER_NAME_HELP: "このページのファイルシステムに格納されるフォルダー名です。"
+ PARENT: "親"
+ DEFAULT_OPTION_ROOT: "- ルート -"
+ DEFAULT_OPTION_SELECT: "- 選択 -"
+ DISPLAY_TEMPLATE: "表示テンプレート"
+ DISPLAY_TEMPLATE_HELP: "ページをレンダリングする twig テンプレートを示すページタイプです。"
+ BODY_CLASSES: "Body Classe"
+ ORDERING: "並び換え"
+ PAGE_ORDER: "ページ順序"
+ OVERRIDES: "置き換え"
+ MENU: "メニュー"
+ MENU_HELP: "メニューで使用される文字列です。設定しない場合、タイトルが使用されます。"
+ SLUG: "スラッグ"
+ SLUG_HELP: "スラッグ変数は、ページの URL 部分を明確に設定できるようにします。"
+ SLUG_VALIDATE_MESSAGE: "スラッグには、英数小文字とダッシュだけを使用できます。"
+ PROCESS: "プロセス"
+ PROCESS_HELP: "ページの処理方法を制御します。全体ではなく、ページごとに設定できます。"
+ DEFAULT_CHILD_TYPE: "デフォルトの子タイプ"
+ USE_GLOBAL: "全体設定を使用"
+ ROUTABLE: "アクセス可能"
+ ROUTABLE_HELP: "このページが URL によってアクセス可能かどうかを決定します。"
+ CACHING: "キャッシュ"
+ VISIBLE: "表示可能"
+ VISIBLE_HELP: "ページがナビゲーションに表示されるかを決定します。"
+ DISABLED: "無効"
+ ITEMS: "アイテム"
+ ORDER_BY: "順序:"
+ ORDER: "順序"
+ FOLDER: "フォルダー"
+ ASCENDING: "昇順"
+ DESCENDING: "降順"
+ ADD_MODULAR_CONTENT: "モジュラーコンテンツを追加"
+ PAGE_TITLE: "ページタイトル"
+ PAGE_TITLE_HELP: "ページのタイトルです。"
+ PAGE: "ページ"
+ MODULAR_TEMPLATE: "モジュラーテンプレート"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "ファイル名"
+ PARENT_PAGE: "親ページ"
+ HOME_PAGE: "ホームページ"
+ HOME_PAGE_HELP: "デフォルトのランディングページとして使用するページです。"
+ DEFAULT_THEME: "デフォルトテーマ"
+ DEFAULT_THEME_HELP: "デフォルトで使用するテーマ (デフォルトは Antimatter) を設定します 。"
+ TIMEZONE: "タイムゾーン"
+ TIMEZONE_HELP: "サーバーのデフォルトタイムゾーンを置き換えます。"
+ SHORT_DATE_FORMAT: "短い日付フォーマット"
+ SHORT_DATE_FORMAT_HELP: "テーマで使用される短い日付フォーマットを設定します。"
+ LONG_DATE_FORMAT: "長い日付フォーマット"
+ LONG_DATE_FORMAT_HELP: "テーマで使用される長い日付フォーマットを設定します。"
+ DEFAULT_ORDERING: "デフォルトの順序"
+ DEFAULT_ORDERING_HELP: "優先設定をされた場合を除き、ページの一覧はこの順序を使用して表示されます。"
+ DEFAULT_ORDERING_DEFAULT: "デフォルト - フォルダ名を基準にする"
+ DEFAULT_ORDERING_FOLDER: "フォルダ - プレフィックスなしのフォルダ名を基準にする"
+ DEFAULT_ORDERING_TITLE: "タイトル - ヘッダー内のタイトルフィールド基準にする"
+ DEFAULT_ORDERING_DATE: "日付 - ヘッダー内の日付フィールドを基準にする"
+ DEFAULT_ORDER_DIRECTION: "デフォルトの順序"
+ DEFAULT_ORDER_DIRECTION_HELP: "ページの一覧の順序です。"
+ DEFAULT_PAGE_COUNT: "デフォルトのページ数"
+ DEFAULT_PAGE_COUNT_HELP: "ページの一覧でのデフォルトの最大ページ数です。"
+ DATE_BASED_PUBLISHING: "日付に基づく公開"
+ DATE_BASED_PUBLISHING_HELP: "設定された日付に基づき、自動的に投稿を公開 (または公開終了) します。"
+ EVENTS: "イベント"
+ EVENTS_HELP: "特定のイベントを有効または無効にします。無効にするとプラグインを断絶する (フックさせない) ことができます。"
+ REDIRECT_DEFAULT_ROUTE: "デフォルト経路へリダイレクト"
+ REDIRECT_DEFAULT_ROUTE_HELP: "自動的にページのデフォルト経路へリダイレクトします。"
+ LANGUAGES: "言語"
+ SUPPORTED: "対応"
+ SUPPORTED_HELP: "コンマで区切った 2 文字の言語コードのリスト (例: 'en,fr,de') です。"
+ TRANSLATIONS_ENABLED: "翻訳を有効化"
+ TRANSLATIONS_ENABLED_HELP: "Grav、プラグイン、エクステンションでの翻訳をサポートします。"
+ TRANSLATIONS_FALLBACK: "翻訳の次候補"
+ TRANSLATIONS_FALLBACK_HELP: "アクティブな言語が存在しない場合に、サポートする翻訳の次候補を使用します。"
+ ACTIVE_LANGUAGE_IN_SESSION: "セッション内のアクティブ言語"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "セッション内にアクティブ言語を格納します。"
+ HTTP_HEADERS: "HTTP ヘッダー"
+ EXPIRES: "Expires"
+ EXPIRES_HELP: "expires ヘッダーを設定します。値は秒です。"
+ CACHE_CONTROL: "HTTP キャッシュコントロール"
+ CACHE_CONTROL_HELP: "`no-cache, no-store, must-revalidate`のように有効なキャッシュコントロールを設定します。"
+ LAST_MODIFIED: "Last modified"
+ LAST_MODIFIED_HELP: "プロキシーやブラウザのキャッシュの最適化に役立つ、Last-Modified ヘッダーを設定します。"
+ ETAG: "ETag"
+ ETAG_HELP: "ページがいつ修正されたかを確認するのに役立つ、ETag ヘッダーを設定します。"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "プロキシーや CDN のキャッシュに役立つ、Vary: Accept Encoding ヘッダーを設定します。"
+ MARKDOWN_EXTRA_HELP: "Markdown Extra (https://michelf.ca/projects/php-markdown/extra/) のデフォルトでのサポートを有効にします。"
+ AUTO_LINE_BREAKS: "自動改行"
+ AUTO_LINE_BREAKS_HELP: "markdown で自動改行のサポートを有効にします。"
+ AUTO_URL_LINKS: "自動 URL リンク"
+ AUTO_URL_LINKS_HELP: "URL から HTML のハイパーリンクへの自動変換を有効にします。"
+ ESCAPE_MARKUP: "マークアップのエスケープ"
+ ESCAPE_MARKUP_HELP: "マークアップタグを HTML エンティティへエスケープします。"
+ CACHING_HELP: "Grav のキャッシュを有効/無効にする、全体的な ON/OFF スイッチです。"
+ CACHE_CHECK_METHOD: "キャッシュの確認方法"
+ CACHE_CHECK_METHOD_HELP: "ページファイルが修正されたかどうかを Grav が確認するために使用する方法を選択します。"
+ CACHE_DRIVER: "キャッシュドライバー"
+ CACHE_DRIVER_HELP: "Grav が使用すべきキャッシュドライバーを選択します。「自動検出」は自動的に最適なものを探します。"
+ CACHE_PREFIX: "キャッシュプレフィックス"
+ CACHE_PREFIX_HELP: "Grav キーの一部となる識別子です。よくわからない場合は、変更しないでください。"
+ CACHE_PREFIX_PLACEHOLDER: "ベースURLから作成(ランダム文字列で上書き)"
+ LIFETIME: "存続時間"
+ LIFETIME_HELP: "キャッシュの存続時間を秒単位 (0 = 無限) で設定します。"
+ GZIP_COMPRESSION: "Gzip 圧縮"
+ GZIP_COMPRESSION_HELP: "パフォーマンスを上げるために Grav ページの GZip 圧縮を有効にします。"
+ TWIG_TEMPLATING: "Twig テンプレート"
+ TWIG_CACHING: "Twig キャッシュ"
+ TWIG_CACHING_HELP: "Twig のキャッシュメカニズムをコントロールします。最適なパフォーマンスにするには、有効のままにしておきます。"
+ TWIG_DEBUG: "Twig デバッグ"
+ TWIG_DEBUG_HELP: "Twig デバッガーエクステンションを読み込むかどうかを選択します。"
+ DETECT_CHANGES: "変更の検出"
+ DETECT_CHANGES_HELP: "Twig テンプレートの変更を検出した場合、自動的に Twig キャッシュを再コンパイルします。"
+ AUTOESCAPE_VARIABLES: "変数の自動エスケープ"
+ AUTOESCAPE_VARIABLES_HELP: "すべての変数を自動的にエスケープします。有効にした場合、おそらくサイトは正しく表示されなくなります。"
+ ASSETS: "アセット"
+ CSS_PIPELINE: "CSS パイプライン"
+ CSS_PIPELINE_HELP: "CSS パイプラインは、複数の CSS リソースを 1 つのファイルに結合します。"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "CSSパイプラインに外部CSSを含める"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "外部URLはファイル相対参照を含む場合があり、パイプラインすべきではありません"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "最初にCSSパイプラインをレンダリングします"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "最初にCSSパイプラインをレンダリングする場合、他のCSS参照は含まれません"
+ CSS_MINIFY: "CSS の圧縮"
+ CSS_MINIFY_HELP: "パイプライン中に CSS を圧縮します。"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "CSS 圧縮を Windows 用に置き換える"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Windows プラットフォーム用に圧縮を置き換えます。デフォルトでは ThreadStackSize のために False です。"
+ CSS_REWRITE: "CSS の書き換え"
+ CSS_REWRITE_HELP: "パイプライン中にすべての CSS の相対パスを書き換えます。"
+ JAVASCRIPT_PIPELINE: "JavaScript パイプライン"
+ JAVASCRIPT_PIPELINE_HELP: "JS パイプラインは、複数の JS リソースを 1 つのファイルに結合します。"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "JS パイプラインに外部 JS を含める"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "外部URLはファイル相対参照を含む場合があり、パイプライン処理すべきではありません"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "最初に JS パイプラインをレンダリングします"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "最初に JS パイプラインをレンダリングする場合、他の JS 参照は含まれません"
+ JAVASCRIPT_MINIFY: "JavaScript の圧縮"
+ JAVASCRIPT_MINIFY_HELP: "パイプライン中に JS を圧縮します。"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "アセットでタイムスタンプを有効にする"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "アセットのタイムスタンプを有効にします。"
+ COLLECTIONS: "コレクション"
+ ERROR_HANDLER: "エラー処理"
+ DISPLAY_ERRORS: "エラーを表示"
+ DISPLAY_ERRORS_HELP: "完全なバックトレーススタイルのエラーページを表示します。"
+ LOG_ERRORS: "エラーを記録"
+ LOG_ERRORS_HELP: "エラーを /logs フォルダーに記録します。"
+ DEBUGGER: "デバッガー"
+ DEBUGGER_HELP: "Grav デバッガーと以下の設定を有効にします。"
+ DEBUG_TWIG: "Twig のデバッグ"
+ DEBUG_TWIG_HELP: "Twig テンプレートのデバッグを有効にします。"
+ SHUTDOWN_CLOSE_CONNECTION: "シャットダウン時に接続を閉じる"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "onShutdown() をコールする前に接続を閉じます。デバッグ中は無効にします。"
+ DEFAULT_IMAGE_QUALITY: "デフォルトの画像品質"
+ DEFAULT_IMAGE_QUALITY_HELP: "画像のリサンプリングやキャッシュするときに使用する、デフォルトの画像品質 (85%) です。"
+ CACHE_ALL: "すべての画像をキャッシュ"
+ CACHE_ALL_HELP: "メディア操作がない場合でも、Grav のキャッシュシステム経由ですべての画像を実行します。"
+ IMAGES_DEBUG: "デバッグで透過画像"
+ IMAGES_DEBUG_HELP: "例えばレティナディスプレイで作業する場合、後ろの画像の上に透過して画像を表示します"
+ UPLOAD_LIMIT: "ファイルのアップロード制限"
+ UPLOAD_LIMIT_HELP: "アップロードの最大サイズをバイト単位 (0 = 無制限) で設定します。"
+ ENABLE_MEDIA_TIMESTAMP: "メディアのタイムスタンプを有効にする"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "最終更新日をもとに各メディアアイテムにタイムスタンプを付加します。"
+ SESSION: "セッション"
+ SESSION_ENABLED_HELP: "Grav 内でセッションのサポートを有効にします。"
+ TIMEOUT: "タイムアウト"
+ TIMEOUT_HELP: "セッションのタイムアウトを秒単位で設定します。"
+ SESSION_NAME_HELP: "セッションクッキーの名前を構成するために使用する識別子です。"
+ ABSOLUTE_URLS: "絶対 URL"
+ ABSOLUTE_URLS_HELP: "`base_url` に絶対 URL を返すか、相対 URL を返すかを決定します。"
+ PARAMETER_SEPARATOR: "パラメーターの区切り文字"
+ PARAMETER_SEPARATOR_HELP: "渡されるパラメーターの区切り文字です。Windows 上の Apache 用に変更することができます。"
+ TASK_COMPLETED: "タスクが完了しました。"
+ EVERYTHING_UP_TO_DATE: "すべて最新です。"
+ UPDATES_ARE_AVAILABLE: "アップデートが可能です"
+ IS_AVAILABLE_FOR_UPDATE: "が更新可能です。"
+ IS_NOW_AVAILABLE: "が入手可能です"
+ CURRENT: "現在"
+ UPDATE_GRAV_NOW: "すぐ Grav を更新する"
+ GRAV_SYMBOLICALLY_LINKED: "Grav は、シンボルによってリンクされています。 アップグレードは利用できません。"
+ UPDATING_PLEASE_WAIT: "更新中... しばらくお待ちください。ダウンロード中"
+ OF_THIS: "のこの"
+ OF_YOUR: "の"
+ HAVE_AN_UPDATE_AVAILABLE: "に更新があります。"
+ SAVE_AS: "名前をつけて保存"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "本当に、このページとすべての子ページを削除しますか? ページが他の言語に翻訳されている場合は、それらは保持され、個別に削除する必要があります。そうでない場合は、ページフォルダーは子ページと一緒に削除されます。この操作は、元に戻すことができません。"
+ AND: "および"
+ UPDATE_AVAILABLE: "更新が可能です。"
+ METADATA_KEY: "キー (例 'Keywords')"
+ METADATA_VALUE: "値 (例 'Blog, Grav')"
+ USERNAME_HELP: "ユーザー名は、小文字、数字、アンダースコア、ハイフンを含む 3 ~ 16 文字である必要があります。大文字、空白、特殊文字は使用できません。"
+ FULLY_UPDATED: "すべて更新済み"
+ SAVE_LOCATION: "保存先"
+ PAGE_FILE: "ページテンプレート"
+ PAGE_FILE_HELP: "ページファイル名及びこのページに対するデフォルトの表示テンプレート"
+ NO_USER_ACCOUNTS: "ユーザーアカウントがありません。はじめに 1 つ作成してください。"
+ REDIRECT_TRAILING_SLASH: "リダイレクト時の URL 末尾のスラッシュ"
+ REDIRECT_TRAILING_SLASH_HELP: "透過的に URL 末尾のスラッシュを処理するのではなく、301 リダイレクトを実行します。"
+ DEFAULT_DATE_FORMAT: "ページの日付フォーマット"
+ DEFAULT_DATE_FORMAT_HELP: "Grav で使用されるページの日付フォーマットです。デフォルトでは自動的に日付フォーマットを推測しますが、PHP の date 関数で使用されるフォーマット (例 Y-m-d H:i) を指定することもできます。"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "自動的に推測する"
+ IGNORE_FILES: "無視するファイル"
+ IGNORE_FILES_HELP: "ページの処理中に無視する特定のファイルです。"
+ IGNORE_FOLDERS: "無視するフォルダー"
+ IGNORE_FOLDERS_HELP: "ページの処理中に無視する特定のフォルダーです。"
+ HTTP_ACCEPT_LANGUAGE: "ブラウザーから言語を設定する"
+ HTTP_ACCEPT_LANGUAGE_HELP: "有効にすると、ブラウザーの `http_accept_language`タグに基づく言語の設定を試みます。"
+ OVERRIDE_LOCALE: "言語の置き換え"
+ OVERRIDE_LOCALE_HELP: "現在の言語に基づいて、PHP のロケール設定を置き換えます。"
+ REDIRECT: "ページのリダイレクト"
+ REDIRECT_HELP: "このページのリダイレクト先のページルート、または外部 URL を入力します。例えば、`/some/route` や `http://somesite.com` です。"
+ PLUGIN_STATUS: "プラグインの状態"
+ INCLUDE_DEFAULT_LANG: "デフォルト言語を付加"
+ INCLUDE_DEFAULT_LANG_HELP: "有効にすると、デフォルト言語のすべての URL の 先頭に、デフォルト言語を付加します (例 `/en/blog/my-post`)。"
+ ALLOW_URL_TAXONOMY_FILTERS: "URL タクソノミーフィルター"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "`/taxonomy:value` でページベースの収集を絞り込むことができます。"
+ REDIRECT_DEFAULT_CODE: "デフォルトのリダイレクトコード"
+ REDIRECT_DEFAULT_CODE_HELP: "リダイレクトに使用する HTTP ステータスコードです。"
+ IGNORE_HIDDEN: "無視する隠しファイル"
+ IGNORE_HIDDEN_HELP: "ドットで始まるすべてのファイルやフォルダー無視します。"
+ WRAPPED_SITE: "ラップドサイト"
+ WRAPPED_SITE_HELP: "別のプラットフォームで Grav がラップされているかどうかを、テーマやプラグインが識別できるようにします。"
+ FALLBACK_TYPES: "許可するファイルタイプ候補"
+ FALLBACK_TYPES_HELP: "ページ経由でアクセスする場合に、許可するファイルタイプ。通常はサポートされているメディアタイプです。"
+ INLINE_TYPES: "インラインファイルタイプ候補"
+ INLINE_TYPES_HELP: "ダウンロードではなく、インラインで表示するファイルタイプの一覧"
+ APPEND_URL_EXT: "URL に拡張子を付加する"
+ APPEND_URL_EXT_HELP: "ページの URL に好みの拡張子を付加します。 この設定により、Grav が `<テンプレート>.<拡張子>.twig` テンプレートを探すことに注意してください。"
+ PAGE_MODES: "ページモード"
+ PAGE_TYPES: "ページタイプ"
+ ACCESS_LEVELS: "アクセスレベル"
+ GROUPS: "グループ"
+ GROUPS_HELP: "ユーザーの役割別のグループ一覧"
+ ADMIN_ACCESS: "管理者アクセス権"
+ SITE_ACCESS: "サイトアクセス権"
+ INVALID_SECURITY_TOKEN: "無効なセキュリティトークンです。"
+ ACTIVATE: "アクティベート"
+ TWIG_UMASK_FIX: "umask を変更"
+ TWIG_UMASK_FIX_HELP: "デフォルトで Twig はキャッシュフォルダーを 0755 として作成しますが、有効にすると 0775 に切り替えます。"
+ CACHE_PERMS: "キャッシュのパーミッション"
+ CACHE_PERMS_HELP: "キャッシュフォルダーのデフォルトのパーミッションです。セットアップに応じて、通常は 0755 または 0775 です。"
+ REMOVE_SUCCESSFUL: "正常に削除されました。"
+ REMOVE_FAILED: "削除に失敗しました。"
+ HIDE_HOME_IN_URLS: "ホームのルートURLを非表示"
+ HIDE_HOME_IN_URLS_HELP: "ホーム以下のページでホームのルートURLを表示しないようになります"
+ TWIG_FIRST: "Twig を最初に処理する"
+ TWIG_FIRST_HELP: "Twig ページ処理を有効にした場合は、Markdown の前後に Twig を設定できます。"
+ SESSION_SECURE: "安全"
+ SESSION_SECURE_HELP: "有効の場合、このクッキーの通信は、暗号化された送信の上で行われる必要があることを示します。警告: HTTPS 上だけで稼働するサイトだけで有効にしてください。"
+ SESSION_HTTPONLY: "HTTP のみ"
+ SESSION_HTTPONLY_HELP: "有効の場合、クッキーが HTTP 上だけで使用される必要があり、JavaScript による変更は認められないことを示します。"
+ REVERSE_PROXY: "リバースプロキシ"
+ REVERSE_PROXY_HELP: "リバースプロキシの背後にある不適切なポートを含むURLに問題がある場合は、これを有効にします"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "無効な frontmatter を保存できませんでした。"
+ ADD_FOLDER: "フォルダーの追加"
+ PROXY_URL: "プロキシURL"
+ PROXY_URL_HELP: "プロキシのホストか IP 、ポートを入力"
+ NOTHING_TO_SAVE: "保存するものがありません"
+ FILE_ERROR_ADD: "ファイルの追加中にエラーが発生しました。"
+ FILE_ERROR_UPLOAD: "ファイルのアップロード中にエラーが発生しました。"
+ FILE_UNSUPPORTED: "対応していないファイルタイプ"
+ ADD_ITEM: "アイテムを追加"
+ FILE_TOO_LARGE: "このファイルは大きすぎるためアップロードできません。PHP 設定によると最大サイズ は %s です。PHP 設定にて `post_max_size` を大きくしてください。"
+ INSTALLING: "インストール中"
+ LOADING: "読み込み中..."
+ DEPENDENCIES_NOT_MET_MESSAGE: "以下の依存関係を先に満たすことが必要です:"
+ ERROR_INSTALLING_PACKAGES: "パッケージのインストール中に生じたエラー"
+ INSTALLING_DEPENDENCIES: "依存関係のインストール..."
+ INSTALLING_PACKAGES: "パッケージをインストール中"
+ PACKAGES_SUCCESSFULLY_INSTALLED: "パッケージは正常にインストールされました。"
+ READY_TO_INSTALL_PACKAGES: "パッケージをインストールする準備ができました"
+ PACKAGES_NOT_INSTALLED: "パッケージはインストールさていません"
+ PACKAGES_NEED_UPDATE: "パッケージはすでにインストールされていますが、古いようです"
+ REMOVE_THE: "%s を削除"
+ CONFIRM_REMOVAL: "%s を削除してもよろしいですか?"
+ REMOVED_SUCCESSFULLY: "%s の削除が正常におこなわれました"
+ ERROR_REMOVING_THE: "%s の削除中にエラーが発生しました"
+ READY_TO_UPDATE_PACKAGES: "パッケージを更新する準備ができました"
+ ERROR_UPDATING_PACKAGES: "パッケージを更新中にエラーが発生しました"
+ UPDATING_PACKAGES: "パッケージを更新中..."
+ PACKAGES_SUCCESSFULLY_UPDATED: "パッケージは正常に更新されました。"
+ UPDATING: "更新中"
+ GPM_RELEASES: "GPM のリリース"
+ GPM_RELEASES_HELP: "ベータ版またはテスト版をインストールするのには 「Testing」を選択してください"
+ AUTO: "自動"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "安定版"
+ TESTING: "Testing"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "パッケージ %s が正常にインストールされました"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "200ページ以上あるため管理画面経由で並び替えをサポートできません"
+ DROP_FILES_HERE_TO_UPLOAD: "ここにファイルをドロップするかこのエリアをクリックしてください "
+ INSERT: "挿入"
+ UNDO: "取り消す"
+ REDO: "やり直す"
+ HEADERS: "ヘッダー"
+ BOLD: "太字"
+ ITALIC: "斜体"
+ STRIKETHROUGH: "取り消し線"
+ SUMMARY_DELIMITER: "概要のデリミター"
+ LINK: "リンク"
+ IMAGE: "画像"
+ BLOCKQUOTE: "引用"
+ UNORDERED_LIST: "順不同リスト"
+ ORDERED_LIST: "順序付きリスト"
+ EDITOR: "エディタ"
+ PREVIEW: "プレビュー"
+ FULLSCREEN: "フルスクリーン"
+ MODULAR: "モジュラー"
+ NON_ROUTABLE: "非アクセス可能"
+ NON_MODULAR: "非モジュール"
+ NON_VISIBLE: "非表示"
+ NON_PUBLISHED: "非公開"
+ PUBLISHING: "公開中"
+ NOTIFICATIONS: "通知"
+ MEDIA_TYPES: "メディアタイプ"
+ IMAGE_OPTIONS: "画像オプション"
+ MIME_TYPE: "MIMEタイプ"
+ THUMB: "サムネイル"
+ TYPE: "種別"
+ FILE_EXTENSION: "ファイルの拡張子"
+ MEMCACHE_SERVER: "Memcache サーバー"
+ MEMCACHE_SERVER_HELP: "Memcache サーバーアドレス"
+ MEMCACHE_PORT: "Memcache ポート"
+ MEMCACHE_PORT_HELP: "Memcache サーバーポート"
+ MEMCACHED_SERVER: "Memcache サーバー"
+ MEMCACHED_SERVER_HELP: "Memcached サーバーアドレス"
+ MEMCACHED_PORT: "Memcached ポート"
+ MEMCACHED_PORT_HELP: "Memcached サーバーポート"
+ REDIS_SERVER: "Redis サーバー"
+ REDIS_SERVER_HELP: "Redis サーバーアドレス"
+ REDIS_PORT: "Redis ポート"
+ REDIS_PORT_HELP: "Redis サーバーポート"
+ REDIS_PASSWORD: "Redis パスワード/認証"
+ ALL: "すべて"
+ FROM: "from"
+ TO: "to"
+ RELEASE_DATE: "公開日時"
+ SORT_BY: "並べ替え:"
+ RESOURCE_FILTER: "フィルター..."
+ FORCE_SSL: "強制 SSL"
+ NEWS_FEED: "ニュースフィード"
+ EXTERNAL_URL: "外部 URL"
+ SESSION_PATH: "セッションデータのパス"
+ CUSTOM_BASE_URL: "カスタムベース URL"
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'ファイル %s をアップロードできません: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'ファイル %s を「%s」に移動できません。'
+ DROPZONE_CANCEL_UPLOAD: 'アップロードのキャンセル'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'このアップロードをキャンセルしてもよろしいですか?'
+ DROPZONE_FILE_TOO_BIG: 'ファイルがあまりにも大きいです({{filesize}}MiB)。最大ファイルサイズ: {{maxFilesize}}MiB。'
+ DROPZONE_INVALID_FILE_TYPE: "この種類のファイルはアップロードできません。"
+ DROPZONE_MAX_FILES_EXCEEDED: "これ以上ファイルをアップロードすることはできません。"
+ DROPZONE_REMOVE_FILE: "ファイルを削除"
+ DROPZONE_RESPONSE_ERROR: "サーバーは {{statusCode}} コードで応答しました。"
+ PREMIUM_PRODUCT: "プレミアム"
+ UPLOAD_ERR_NO_TMP_DIR: "テンポラリーフォルダが見つかりません"
+ ERROR_FULL_BACKTRACE: "フルバックトレースエラー"
+ ERROR_SIMPLE: "シンプルエラー"
+ ERROR_SYSTEM: "システムエラー"
+ IMAGES_AUTO_FIX_ORIENTATION: "向きを自動的に修正します"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "自動的に画像の向きを Exif データをもとに修正します"
+ REDIS_SOCKET: "Redis ソケット"
+ REDIS_SOCKET_HELP: "Redis ソケット"
+ NOT_SET: "未設定"
+ PERMISSIONS: "パーミッション"
+ NEVER_CACHE_TWIG: "Twig をキャッシュしない"
+ ALLOW_WEBSERVER_GZIP: "ウェブサーバ Gzip を許可する"
+ CLEAR_IMAGES_BY_DEFAULT: "デフォルトで画像キャッシュをクリア"
+ REINSTALL_PLUGIN: "プラグインを再インストール"
+ REINSTALL_THEME: "テーマを再インストール"
+ REINSTALL_THE: "%s を再インストール"
+ CONFIRM_REINSTALL: "%s を再インストールしてもよろしいですか?"
+ REINSTALLED_SUCCESSFULLY: "%s の再インストールが正常におこなわれました"
+ ERROR_REINSTALLING_THE: "%s の再インストール中にエラーが発生しました"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "パッケージ %s が正常に再インストールされました"
+ REINSTALLATION_FAILED: "再インストールに失敗しました"
+ TOOLS: "ツール"
+ DIRECT_INSTALL: "直接インストール"
+ NO_PACKAGE_NAME: "パッケージ名が指定されていません"
+ PACKAGE_EXTRACTION_FAILED: "パッケージの解凍に失敗しました"
+ NOT_VALID_GRAV_PACKAGE: "有効な Grav パッケージではありません"
+ NAME_COULD_NOT_BE_DETERMINED: "ネームを判別できませんでした。"
+ CANNOT_OVERWRITE_SYMLINKS: "シンボリックリンクを上書きできません"
+ ZIP_PACKAGE_NOT_FOUND: "ZIP パッケージが見つかりませんでした"
+ GPM_OFFICIAL_ONLY: "公式の GPM のみ"
+ GPM_OFFICIAL_ONLY_HELP: "公式の GPM のみリポジトリから直接インストールをできます"
+ SORTABLE_PAGES: "並べ替え可能なページ:"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "子ページの表示順序"
+ PWD_PLACEHOLDER: "最短 8 文字で複雑な文字列にしてください"
+ PWD_REGEX: "パスワードの正規表現"
+ USERNAME_PLACEHOLDER: "小文字のみ、例「admin」"
+ USERNAME_REGEX: "ユーザー名の正規表現"
+ USERNAME_REGEX_HELP: "デフォルト: 小文字の英数字、ダッシュ(-)およびアンダースコア(_)のみ。3〜6文字"
+ ENABLE_AUTO_METADATA: "Exif からメタデータを自動化"
+ ENABLE_AUTO_METADATA_HELP: "Exif 情報を持つ画像にメタデータのファイルを自動的に生成します。"
+ 2FA_TITLE: "二段階認証"
+ 2FA_FAILED: "無効の二段階認証コードです、もう一度試してください..."
+ 2FA_ENABLED: "二段階認証を有効化しました"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "二段階認証"
+ 2FA_REGENERATE: "再生成"
diff --git a/user/plugins/admin/languages/ko.yaml b/user/plugins/admin/languages/ko.yaml
new file mode 100644
index 0000000..0216898
--- /dev/null
+++ b/user/plugins/admin/languages/ko.yaml
@@ -0,0 +1,108 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "이것은 베타입니다! 개인적으로 사용할시 위험할수있습니다..."
+ ADMIN_REPORT_ISSUE: "문제가 있습니까? GitHub에 신고해주세요."
+ EMAIL_FOOTER: " Grav에 의해 구동 -현대 플랫 파일 CMS"
+ LOGIN_BTN: "로그인"
+ LOGIN_BTN_FORGOT: "잃어버림"
+ LOGIN_BTN_RESET: "비밀번호 초기화"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "초기화지침 보내기"
+ LOGIN_BTN_CLEAR: "클리어 폼"
+ LOGIN_BTN_CREATE_USER: "사용자 새로 만들기"
+ LOGIN_LOGGED_IN: "성공적으로 로그인이 되었습니다."
+ LOGIN_FAILED: "로그인에 실패했습니다."
+ LOGGED_OUT: "로그아웃 되었습니다."
+ RESET_LINK_EXPIRED: "초기화 링크 만료, 다시시도 하십시오."
+ RESET_PASSWORD_RESET: "비밀번호 변경"
+ RESET_INVALID_LINK: "잘못된 초기화 링크, 다시시도 하십시오."
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "비밀번호 초기화 인증키가 이메일에 전송되었습니다."
+ FORGOT_FAILED_TO_EMAIL: "이메일 인증실패, 나중에 다시시도 하십시오."
+ FORGOT_EMAIL_NOT_CONFIGURED: "암호를 초기화 할수 없습니다. 이 사이트는 이메일인증키가 보내지도록 구성되어 있지 않습니다"
+ MANAGE_PAGES: "패이지 관리"
+ CONFIGURATION: "구성"
+ PAGES: "페이지"
+ PLUGINS: "플러그인"
+ PLUGIN: "플러그인"
+ THEMES: "테마"
+ LOGOUT: "로그아웃"
+ BACK: "뒤로"
+ ADD_PAGE: "페이지 추가"
+ ADD_MODULAR: "모듈 추가"
+ MOVE: "더보기"
+ DELETE: "삭제"
+ SAVE: "저장"
+ NORMAL: "표준"
+ EXPERT: "전문가"
+ EXPAND_ALL: "확장"
+ COLLAPSE_ALL: "모두 축소"
+ ERROR: "오류"
+ CLOSE: "닫기"
+ CANCEL: "취소"
+ CONTINUE: "연결"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "필수"
+ MODAL_CHANGED_DETECTED_TITLE: "변경 감지"
+ MODAL_CHANGED_DETECTED_DESC: "저장되어 있지않은 내용이 있습니다. 그래도 종료할까요?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "확인 필수"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "파일을 삭제할까요? 이 작업은 절대로 취소할수 없습니다."
+ ADD_FILTERS: "필터 추가"
+ SEARCH_PAGES: "페이지 검색"
+ VERSION: "버전"
+ UPDATE_THEME: "테마 업데이트"
+ UPDATE_PLUGIN: "플러그인 업데이트"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "이 테마는 지금 사용할수 있습니다"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "이 플러그인은 지금 사용할수 있습니다"
+ AUTHOR: "저자"
+ HOMEPAGE: "홈페이지"
+ DEMO: "데모"
+ BUG_TRACKER: "버그 트래커"
+ KEYWORDS: "키워드"
+ LICENSE: "특허"
+ README: "추가정보"
+ REMOVE_THEME: "테마 제거"
+ INSTALL_THEME: "테마 설치"
+ THEME: "테마"
+ BACK_TO_THEMES: "이전 테마"
+ BACK_TO_PLUGINS: "이전 플러그인"
+ CHECK_FOR_UPDATES: "업데이트 체크"
+ ADD: "추가"
+ DAYS: "날"
+ UPDATE: "업데이트"
+ BACKUP: "백업"
+ STATISTICS: "통계"
+ TODAY: "오늘"
+ WEEK: "주"
+ MONTH: "달"
+ LATEST_PAGE_UPDATES: "전 페이지 업데이트"
+ MON: "월"
+ TUE: "화"
+ WED: "수"
+ THU: "목"
+ FRI: "금"
+ SAT: "토"
+ SUN: "일"
+ COPY: "복사"
+ EDIT: "편집"
+ CREATE: "새로 만들기"
+ INSTALL: "설치"
+ CREATE_NEW_USER: "사용자 새로만들기"
+ SYSTEM: "시스템"
+ USER: "사용자"
+ NO_PAGE_FOUND: "페이지 오류"
+ FULL_NAME: "이름"
+ USERNAME: "닉네임"
+ EMAIL: "이메일"
+ PASSWORD: "비밀번호"
+ TITLE: "제목"
+ LANGUAGE: "언어"
+ ACCOUNT: "성공"
+ MEDIA: "미디어"
+ SITE_TITLE: "사이트 제목"
+ SITE_TITLE_PLACEHOLDER: "사이트 전체제목"
+ NAME: "이름"
+ CONTENT: "컨텐츠"
+ OPTIONS: "옵션"
+ DATE: "정보"
+ ROBOTS: "로봇"
+ SETTINGS: "설정"
+ FOLDER_NAME: "폴더 제목"
+ MENU: "메뉴"
diff --git a/user/plugins/admin/languages/lt.yaml b/user/plugins/admin/languages/lt.yaml
new file mode 100644
index 0000000..42a5066
--- /dev/null
+++ b/user/plugins/admin/languages/lt.yaml
@@ -0,0 +1,363 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Tai Grav administravimo įskiepio beta versija! Naudokite atsargiai..."
+ ADMIN_REPORT_ISSUE: "Radote klaidą? Prašome pranešti apie ją GitHub platformoje."
+ EMAIL_FOOTER: "Naudojama Grav - Moderni TVS, nenaudojanti duomenų bazės"
+ LOGIN_BTN: "Prisijungti"
+ LOGIN_BTN_FORGOT: "Pamiršau"
+ LOGIN_BTN_RESET: "Atstatyti slaptažodį"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Siųsti atstatymo instrukcijas"
+ LOGIN_BTN_CLEAR: "Išvalyti formą"
+ LOGIN_BTN_CREATE_USER: "Sukurti vartotoją"
+ LOGIN_LOGGED_IN: "Jūs sėkmingai prisjungėte"
+ LOGIN_FAILED: "Prisijungti nepavyko"
+ LOGGED_OUT: "Jūs sėkmingai atsijungėte"
+ RESET_NEW_PASSWORD: "Įveskite naują slaptažodį;"
+ RESET_LINK_EXPIRED: "Atstatymo nuoroda nebegalioja, prašome bandyti dar kartą"
+ RESET_PASSWORD_RESET: "Slaptažodis buvo atnaujintas"
+ RESET_INVALID_LINK: "Panaudota neteisinga atstatymo nuoroda, prašome meginti dar kartą"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Instrukcijos kaip atnaujinti slaptažodį buvo nusiųstos į jūsų el. paštą adresu %s"
+ FORGOT_FAILED_TO_EMAIL: "Įvyko klaida siunčiant instrukcijas, prašome meginti dar kartą"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Negalima atnaujinti slaptažodžio, %s, nepateiktas el. pašto adresas"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Vartotojas su slapyvardžiu %s neegzistuoja"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Negalima atnaujinti slaptažodžio. svetainėje nesukonfigūruotas el. laiškų siuntimas"
+ FORGOT_EMAIL_SUBJECT: "%s slaptažodžio atstatymo užsakymas"
+ FORGOT_EMAIL_BODY: "Slaptažodžio atstatymas Sveiki, %1$s,
Svetainėje %4$s buvo užsakytas jūsų slaptažodžio atstatymas.
Nuspauskite šį mygtuką, jei norite atstatyti slaptažodį
Arba nukopijuokite į naršyklės adreso įrašymo laukelį šį adresą:
%2$s
Su pagarba, %3$s
"
+ MANAGE_PAGES: "Tvarkyti puslapius"
+ CONFIGURATION: "Konfigūracija"
+ PAGES: "Puslapiai"
+ PLUGINS: "Įskiepiai"
+ PLUGIN: "Įskiepis"
+ THEMES: "Temos"
+ LOGOUT: "Atsijungti"
+ BACK: "Atgal"
+ NEXT: "Sekantis"
+ PREVIOUS: "Ankstesnis"
+ ADD_PAGE: "Pridėti puslapį"
+ ADD_MODULAR: "Priedėti modulinį puslapį"
+ MOVE: "Perkelti"
+ DELETE: "Ištrinti"
+ VIEW: "Peržiūrėti"
+ SAVE: "Išsaugoti"
+ NORMAL: "Paprastas"
+ EXPERT: "Išplėstinis"
+ EXPAND_ALL: "Išskleisti visus"
+ COLLAPSE_ALL: "Sutraukti visus"
+ ERROR: "Klaida"
+ CLOSE: "Uždaryti"
+ CANCEL: "Atšaukti"
+ CONTINUE: "Tęsti"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Patvirtinimas būtinas"
+ MODAL_CHANGED_DETECTED_TITLE: "Aptikta pakeitimų"
+ MODAL_CHANGED_DETECTED_DESC: "Liko neišsaugotų pakeitimų. Ar tikrai norite išeiti jų neišsaugojus?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Patvirtinimas būtinas"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Ar tikrai norite panaikinti šį failą? Šio veiksmo nebus galima atšaukti."
+ ADD_FILTERS: "Pridėti filtrų"
+ SEARCH_PAGES: "Paieškos puslapiai"
+ VERSION: "Versija"
+ WAS_MADE_WITH: "Sukūrė"
+ BY: "Nuo"
+ UPDATE_THEME: "Atnaujinti temą"
+ UPDATE_PLUGIN: "Atnaujinti įskiepį"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: ", šio šablono, jau yra prieinama"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: ", šio papildinio, jau yra prieinama"
+ AUTHOR: "Autorius"
+ HOMEPAGE: "Pagrindinis puslapis"
+ DEMO: "Demo"
+ BUG_TRACKER: "Klaidų sekimas"
+ KEYWORDS: "Raktažodžiai"
+ LICENSE: "Licenzija"
+ DESCRIPTION: "Aprašymas"
+ README: "Informacija"
+ REMOVE_THEME: "Ištrinti temą"
+ INSTALL_THEME: "Įdiegti temą"
+ THEME: "Tema"
+ BACK_TO_THEMES: "Atgal į temas"
+ BACK_TO_PLUGINS: "Atgal į įskiepius"
+ CHECK_FOR_UPDATES: "Tikrinti atnaujinimus"
+ ADD: "Pridėti"
+ CLEAR_CACHE: "Išvalyti podėlį"
+ CLEAR_CACHE_ALL_CACHE: "Visą podėlį"
+ CLEAR_CACHE_ASSETS_ONLY: "Tik resursus"
+ CLEAR_CACHE_IMAGES_ONLY: "Tik nuotraukos"
+ CLEAR_CACHE_CACHE_ONLY: "Tik laikinąją atmintį"
+ CLEAR_CACHE_TMP_ONLY: "Tik laikinus"
+ DASHBOARD: "Darbastalis"
+ UPDATES_AVAILABLE: "Galimi atnaujinimai"
+ DAYS: "Dienos"
+ UPDATE: "Atnaujinti"
+ BACKUP: "Atsarginė kopija"
+ STATISTICS: "Statistika"
+ TODAY: "Šiandien"
+ WEEK: "Savaitė"
+ MONTH: "Mėnuo"
+ LATEST_PAGE_UPDATES: "Paskutiniai puslapių pakeitimai"
+ MAINTENANCE: "Priežiūra"
+ UPDATED: "Atnaujinta"
+ MON: "Pir"
+ TUE: "Ant"
+ WED: "Tre"
+ THU: "Ket"
+ FRI: "Pen"
+ SAT: "Šeš"
+ SUN: "Sek"
+ COPY: "Kopijuoti"
+ EDIT: "Redaguoti"
+ CREATE: "Sukurti"
+ GRAV_ADMIN: "Grav administravimas"
+ GRAV_OFFICIAL_PLUGIN: "Grav oficialus įskiepis"
+ GRAV_OFFICIAL_THEME: "Grav oficiali tema"
+ PLUGIN_SYMBOLICALLY_LINKED: "Šis papildinys yra simboliškai susietas. Atnaujinimai nebus galimi."
+ THEME_SYMBOLICALLY_LINKED: "Šis šablonas yra simboliškai susietas. Atnaujinimai nebus galimi"
+ REMOVE_PLUGIN: "Pašalinti papildinį"
+ INSTALL_PLUGIN: "Įdiegti papildinį"
+ AVAILABLE: "Galimas"
+ INSTALLED: "Įdiegtas"
+ INSTALL: "Įdiegti"
+ ACTIVE_THEME: "Aktyvi tema"
+ SWITCHING_TO: "Perjungiama į"
+ SWITCHING_TO_DESCRIPTION: "Perėjus į kitą temą, nėra jokių garantijų, kad bus palaikomi visi puslapių išdėstymai, gali atsirasti klaidų, bandant atvaizduoti minėtus puslapius."
+ SWITCHING_TO_CONFIRMATION: "Ar norite tęsti ir įjungti naują temą"
+ CREATE_NEW_USER: "Sukurti naują vartotoją"
+ REMOVE_USER: "Pašalinti vartotoją"
+ ACCESS_DENIED: "Priėjimas uždraustas"
+ ACCOUNT_NOT_ADMIN: "jūsų vartotojas neturi administratoriaus teisių"
+ PHP_INFO: "PHP informacija"
+ INSTALLER: "Įdiegėjas"
+ AVAILABLE_THEMES: "Galimos temos"
+ AVAILABLE_PLUGINS: "Galimi įskiepiai"
+ INSTALLED_THEMES: "Įdiegtos temos"
+ INSTALLED_PLUGINS: "Įdiegti įskiepiai"
+ BROWSE_ERROR_LOGS: "Naršyti klaidų žurnalą"
+ SITE: "Svetainė"
+ INFO: "Informacija"
+ SYSTEM: "Sistema"
+ USER: "Vartotojas"
+ ADD_ACCOUNT: "Pridėti paskyrą"
+ SWITCH_LANGUAGE: "Pakeisti kalbą"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Sėkmingai įjungėte įskiepį"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Sėkmingai išjungėte įskiepį"
+ SUCCESSFULLY_CHANGED_THEME: "Sėkmingai pakeitėte numatytąją temą"
+ INSTALLATION_FAILED: "Diegimas nepavyko"
+ INSTALLATION_SUCCESSFUL: "Sėkmingai įdiegta"
+ UNINSTALL_FAILED: "Pašalinti nepavyko"
+ UNINSTALL_SUCCESSFUL: "Sėkmingai pašalinote"
+ SUCCESSFULLY_SAVED: "Sėkmingai išsaugojote"
+ SUCCESSFULLY_COPIED: "Sėkmingai nukopijuotas"
+ REORDERING_WAS_SUCCESSFUL: "Pertvarkyta sėkmingai"
+ SUCCESSFULLY_DELETED: "Sėkmingai pašalintas"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Kalba pakeista sėkmingai"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Neturite leidimo šiai užduočiai"
+ CACHE_CLEARED: "Laikinoji atmintis išvalyta"
+ METHOD: "Būdas"
+ ERROR_CLEARING_CACHE: "Klaida bandant išvalyti laikinąją atmintį"
+ AN_ERROR_OCCURRED: "Įvyko klaida"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Atsarginė kopija paruošta atsisiuntimui"
+ DOWNLOAD_BACKUP: "Atsiųsti atsarginę kopiją"
+ PAGES_FILTERED: "Puslapiai išfiltruoti"
+ NO_PAGE_FOUND: "Puslapis nerastas"
+ INVALID_PARAMETERS: "Neteisingi parametrai"
+ NO_FILES_SENT: "Failai neįkelti"
+ EXCEEDED_FILESIZE_LIMIT: "Viršyta PHP konfigūracijos failo dydžio riba"
+ UNKNOWN_ERRORS: "Neatpažinta klaida"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Viršyta Grav konfigūracijos failo dydžio riba"
+ UNSUPPORTED_FILE_TYPE: "Nepalaikomas failo tipas"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Nepavyko perkelti įkelto failo"
+ FILE_UPLOADED_SUCCESSFULLY: "Failas įkeltas sėkmingai"
+ FILE_DELETED: "Failas ištrintas"
+ FILE_COULD_NOT_BE_DELETED: "Failas negali būti pašalintas"
+ FILE_NOT_FOUND: "Nerastas failas"
+ NO_FILE_FOUND: "Nerasta jokių failų"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav buvo sėkmingai atnaujintas į"
+ GRAV_UPDATE_FAILED: "Grav atnaujinimas nepavyko"
+ EVERYTHING_UPDATED: "Viskas atnaujinta"
+ UPDATES_FAILED: "Atnaujinimas nepavyko"
+ AVATAR_BY: "Avataras iš"
+ AVATAR_UPLOAD_OWN: "Arba įkelkite savo..."
+ LAST_BACKUP: "Paskutinė atsarginė kopija"
+ FULL_NAME: "Vardas ir pavardė"
+ USERNAME: "Vartotojo vardas"
+ EMAIL: "El. paštas"
+ USERNAME_EMAIL: "Vartotojo vardas arba el. paštas"
+ PASSWORD: "Slaptažodis"
+ PASSWORD_CONFIRM: "Patvirtinti slaptažodį"
+ TITLE: "Antraštė"
+ LANGUAGE: "Kalba"
+ ACCOUNT: "Paskyra"
+ EMAIL_VALIDATION_MESSAGE: "Turi būti galiojantis el. pašto adresas"
+ PASSWORD_VALIDATION_MESSAGE: "Slaptažodyje turi būti bent vienas skaičius ir vienas didžiųjų ir mažoji raidė ir bent 8 ar daugiau simbolių"
+ LANGUAGE_HELP: "Pagrindinės kalbos nustatymas"
+ MEDIA: "Medija"
+ DEFAULTS: "Pagal nutylėjimą"
+ SITE_TITLE: "Svetainės pavadinimas"
+ SITE_TITLE_PLACEHOLDER: "Platus svetainės pavadinimas"
+ SITE_TITLE_HELP: "Numatytasis pavadinimas, dažnai vartojamas temos"
+ SITE_DEFAULT_LANG: "Numatytoji kalba"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Numatytoji kalba, kuri bus naudojama žymei"
+ SITE_DEFAULT_LANG_HELP: "Numatytoji kalba, kuri bus naudojama žymei"
+ DEFAULT_AUTHOR: "Numatytasis autorius"
+ DEFAULT_AUTHOR_HELP: "Numatytasis autoriaus vardą, dažnai naudojamas temos arba rulonais"
+ DEFAULT_EMAIL: "Numatytasis el. pašto"
+ DEFAULT_EMAIL_HELP: "Numatytais el. paštas naudojimui temose bei puslapiuose"
+ TAXONOMY_TYPES: "Taksonomijos tipai"
+ TAXONOMY_TYPES_HELP: "Taksonomijos tipai turi būti apibrėžti čia, jei norite naudoti juos puslapiuose"
+ PAGE_SUMMARY: "Puslapio santrauka"
+ ENABLED: "Įjungtas"
+ ENABLED_HELP: "Įjungti puslapio santrauką (santrauka grąžina tą patį kaip ir puslapio tekstas)"
+ 'YES': "Taip"
+ 'NO': "Ne"
+ SUMMARY_SIZE: "Santraukos dydis"
+ SUMMARY_SIZE_HELP: "Simbolių kiekis puslapio santraukai"
+ FORMAT: "Formatas"
+ SHORT: "Trumpas"
+ LONG: "Ilgas"
+ DELIMITER: "Skirtukas"
+ DELIMITER_HELP: "Santraukos atskyrimo ženklas (numatytasis '===')"
+ METADATA: "Meta duomenys"
+ NAME: "Vardas"
+ CONTENT: "Turinys"
+ REDIRECTS_AND_ROUTES: "Nukreipimai ir maršrutai"
+ CUSTOM_REDIRECTS: "Individualūs nukreipimai"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/tavo/adresas"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/tavo/nukreipimas"
+ CUSTOM_ROUTES: "Individualūs maršrutai"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/tavo/adresas"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/tavo/maršrutas"
+ FILE_STREAMS: "Failų srautai"
+ DEFAULT: "Numatytas"
+ PAGE_MEDIA: "Puslapio medija"
+ OPTIONS: "Nustatymai"
+ PUBLISHED: "Paskelbtas"
+ DATE: "Data"
+ PUBLISHED_DATE: "Publikavimo data"
+ ROBOTS: "Robotai"
+ TAXONOMIES: "Taksonomijos"
+ TAXONOMY: "Taksonomija"
+ ADVANCED: "Išplėst. nust."
+ SETTINGS: "Nustatymai"
+ FOLDER_NUMERIC_PREFIX: "Skaitinis aplanko priešdėlis"
+ FOLDER_NAME: "Aplanko pavadinimas"
+ PARENT: "Pagrindinis"
+ DEFAULT_OPTION_ROOT: "Pradžia"
+ DEFAULT_OPTION_SELECT: "- Pasirinkti -"
+ DISPLAY_TEMPLATE: "Rodyti Šabloną"
+ ORDERING: "Rikiavimas"
+ PAGE_ORDER: "Puslapio rikiavimas"
+ OVERRIDES: "Perrašymai"
+ MENU: "Meniu"
+ PROCESS: "Apdoroti"
+ CACHING: "Podėlis"
+ VISIBLE: "Matomas"
+ DISABLED: "Išjungta"
+ ITEMS: "Elementai"
+ ORDER_BY: "Rūšiuoti pagal"
+ ORDER: "Rikiuoti"
+ FOLDER: "Aplankas"
+ ASCENDING: "Didėjimo tvarka"
+ DESCENDING: "Mažėjimo tvarka"
+ ADD_MODULAR_CONTENT: "Pridėti modulinį turinį"
+ PAGE_TITLE: "Puslapio pavadinimas"
+ PAGE_TITLE_HELP: "Puslapio pavadinimas"
+ PAGE: "Puslapis"
+ MODULAR_TEMPLATE: "Modulinis šablonas"
+ FRONTMATTER: "Įžanginė konfigūracija"
+ FILENAME: "Failo vardas"
+ PARENT_PAGE: "Pagrindinis puslapis"
+ HOME_PAGE: "Pradinis puslapis"
+ HOME_PAGE_HELP: "Puslapis, kuris Grav bus naudojamas kaip numatytasis 'landing' puslapis"
+ DEFAULT_THEME: "Numatytoji tema"
+ DEFAULT_THEME_HELP: "Nustatyti numatytąją temą, kurią naudos Grav (Numatytoji: Antimatter)"
+ TIMEZONE: "Laiko juosta"
+ TIMEZONE_HELP: "Nepaisyti numatytojo laiko juostos serverio"
+ SHORT_DATE_FORMAT: "Trumpas datos formatas"
+ LONG_DATE_FORMAT: "Ilgas datos formatas"
+ DEFAULT_ORDERING: "Numatytasis rikiavimas"
+ DEFAULT_PAGE_COUNT: "Numatytasis puslapių skaičius"
+ EVENTS: "Įvykiai"
+ LANGUAGES: "Kalbos"
+ SUPPORTED: "Palaikomi"
+ TRANSLATIONS_ENABLED: "Vertimai įjungti"
+ EXPIRES: "Galioja iki"
+ LAST_MODIFIED: "Paskutinių keitimų data"
+ ETAG: "ETag"
+ AUTO_LINE_BREAKS: "Automatiniai eilučių perkėlimai"
+ GZIP_COMPRESSION: "Gzip suspaudimas"
+ TWIG_CACHING: "Twig laikinoji atmintis"
+ CSS_PIPELINE: "CSS sutraukimas"
+ CSS_PIPELINE_HELP: "CSS sutraukimas yra keleto CSS failų sujungimas į vieną"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "Pirmiausia atvaizduoti sutraukta CSS"
+ CSS_MINIFY: "CSS sumažinimas"
+ CSS_REWRITE: "CSS perrašymas"
+ CSS_REWRITE_HELP: "Perrašyti reliatyvias nuorodas CSS faile, sutraukimo metu"
+ COLLECTIONS: "Kolekcijos"
+ ERROR_HANDLER: "Klaidų apdorojimas"
+ DISPLAY_ERRORS: "Rodyti klaidas"
+ LOG_ERRORS: "Registruoti klaidas"
+ LOG_ERRORS_HELP: "Registruoti klaidas į /logs aplanką"
+ SESSION: "Sesija"
+ EVERYTHING_UP_TO_DATE: "Šiuo metu atnaujinimų nėra"
+ UPDATES_ARE_AVAILABLE: "pasiekiami(as) atnaujinimai(as)"
+ IS_AVAILABLE_FOR_UPDATE: "gali būti atnaujintas"
+ UPDATE_GRAV_NOW: "Atnaujinti Grav dabar"
+ UPDATING_PLEASE_WAIT: "Atnaujinama... prašome palaukti, siunčiama"
+ SAVE_AS: "Išsaugoti kaip"
+ AND: "ir"
+ UPDATE_AVAILABLE: "Galimas atnaujinimas"
+ METADATA_KEY: "Raktas (pvz \"raktiniai žodžiai)"
+ METADATA_VALUE: "Reikšmė (pvz \"Blogas, Grav\")"
+ USERNAME_HELP: "Vartotojo vardas turi būti nuo 3 iki 16 simbolių, įskaitant mažąsias raides, skaičius, pabraukimus ir brūkšnelius. Didžiosios raidės, tarpai ir specialūs simboliai yra neleistini"
+ FULLY_UPDATED: "Pilnai atnaujintas"
+ SAVE_LOCATION: "Išsaugojimo vieta"
+ PAGE_FILE: "Puslapio Šablonas"
+ GROUPS: "Grupės"
+ REMOVE_SUCCESSFUL: "Sėkmingai pašalinta"
+ REMOVE_FAILED: "Nepavyko pašalinti"
+ SESSION_SECURE: "Saugus"
+ SESSION_HTTPONLY: "Tik HTTP"
+ ADD_FOLDER: "Pridėti aplanką"
+ FILE_UNSUPPORTED: "Nepalaikomas failo tipas"
+ ADD_ITEM: "Pridėti elementą"
+ INSTALLING: "Įdiegiamas"
+ LOADING: "Kraunama.."
+ REMOVE_THE: "Pašalinti %s"
+ REMOVED_SUCCESSFULLY: "%s sėkmingai pašalinta"
+ ERROR_REMOVING_THE: "Klaida bandant pašalinti %s"
+ PACKAGES_SUCCESSFULLY_UPDATED: "Paketas(-ai) sėkmingai atnaujinti."
+ UPDATING: "Atnaujinama"
+ GPM_RELEASES: "GPM versijos"
+ CURL: "cURL"
+ STABLE: "Stabilus"
+ INSERT: "Įterpti"
+ UNDO: "Anuliuoti"
+ REDO: "Atšaukti anuliavimą"
+ HEADERS: "Antraštės"
+ BOLD: "Paryškintas"
+ ITALIC: "Pasviręs"
+ STRIKETHROUGH: "Perbrauktas"
+ LINK: "Nuoroda"
+ IMAGE: "Paveiksliukas"
+ BLOCKQUOTE: "Įtrauka"
+ UNORDERED_LIST: "Nesurikiuotas sąrašas"
+ ORDERED_LIST: "Surikiuotas sąrašas"
+ EDITOR: "Redaktorius"
+ PREVIEW: "Peržiūrėti"
+ FULLSCREEN: "Per visą ekraną"
+ CHARACTERS: "simboliai"
+ MIME_TYPE: "Mime tipas"
+ FILE_EXTENSION: "Failo plėtinys"
+ REDIS_SERVER: "Redis serveris"
+ REDIS_SERVER_HELP: "Redis serverio adresas"
+ REDIS_PORT: "Redis prievadas"
+ REDIS_PORT_HELP: "Redis serverio prievadas"
+ FROM: "nuo"
+ TO: "kam"
+ SORT_BY: "Rikiuoti pagal"
+ RESOURCE_FILTER: "Filtruoti..."
+ FORCE_SSL: "Priverstinis SSL"
+ NEWS_FEED: "Naujienų srautas"
+ EXTERNAL_URL: "Išorinė nuoroda"
+ DROPZONE_CANCEL_UPLOAD: 'Atšaukti įkėlimą'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Ar tikrai norite atšaukti šį įkėlimą?'
+ UPLOAD_ERR_NO_TMP_DIR: "Nėra laikinojo katalogo"
+ ERROR_SIMPLE: "Paprasta klaida"
+ ERROR_SYSTEM: "Sistemos klaida"
diff --git a/user/plugins/admin/languages/nl.yaml b/user/plugins/admin/languages/nl.yaml
new file mode 100644
index 0000000..c92902f
--- /dev/null
+++ b/user/plugins/admin/languages/nl.yaml
@@ -0,0 +1,737 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Dit is een bèta versie! Gebruik in een productieomgeving is op eigen risico..."
+ ADMIN_REPORT_ISSUE: "Kom je een probleem tegen? Meld het op GitHub."
+ EMAIL_FOOTER: "Mede mogelijk gemaakt door 'Grav ' - het moderne flat file CMS"
+ LOGIN_BTN: "Log in"
+ LOGIN_BTN_FORGOT: "Vergeten"
+ LOGIN_BTN_RESET: "Wachtwoord resetten"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Stuur instructies om het wachtwoord opnieuw in te stellen"
+ LOGIN_BTN_CLEAR: "Wis alles"
+ LOGIN_BTN_CREATE_USER: "Gebruiker toevoegen"
+ LOGIN_LOGGED_IN: "Je bent ingelogd"
+ LOGIN_FAILED: "Inloggen is mislukt"
+ LOGGED_OUT: "Je bent uitgelogd"
+ RESET_NEW_PASSWORD: "Voer een nieuw wachtwoord in …"
+ RESET_LINK_EXPIRED: "De link om je wachtwoord opnieuw in te stellen is verlopen. Probeer het opnieuw"
+ RESET_PASSWORD_RESET: "Wachtwoord is opnieuw ingesteld"
+ RESET_INVALID_LINK: "Ongeldige link voor het opnieuw instellen van het wachtwoord gebruikt. Probeer het opnieuw"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Instructies om het wachtwoord opnieuw in te stellen zijn verstuurd naar je e-mailadres"
+ FORGOT_FAILED_TO_EMAIL: "Het versturen van de wachtwoordinstructies is mislukt, probeer het later opnieuw"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Wachtwoord kan niet worden hersteld voor %s, er is geen e-mailadres ingesteld"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Gebruiker met gebruikersnaam %s bestaat niet"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Kan het wachtwoord niet herstellen. Deze site is niet geconfigureerd voor het verzenden van e-mails"
+ FORGOT_EMAIL_SUBJECT: "%s Wachtwoord Herstellen Aanvraag"
+ FORGOT_EMAIL_BODY: "Wachtwoord Herstellen Beste %1$s,
Wij hebben een aanvraag ontvangen op %4$s om je wachtwoord opnieuw in te stellen.
Klik hier om je wachtwoord opnieuw in te stellen.
Als alternatief kan je de volgende URL kopiëren in de adresbalk van je browser:
%2$s
Met vriendelijke groeten, %3$s
"
+ MANAGE_PAGES: "Beheer pagina's"
+ CONFIGURATION: "Instellingen"
+ PAGES: "Pagina's"
+ PLUGINS: "Plugins"
+ PLUGIN: "Plugin"
+ THEMES: "Thema's"
+ LOGOUT: "Uitloggen"
+ BACK: "Terug"
+ NEXT: "Volgende"
+ PREVIOUS: "Vorige"
+ ADD_PAGE: "Pagina toevoegen"
+ ADD_MODULAR: "Modular toevoegen"
+ MOVE: "Verplaatsen"
+ DELETE: "Verwijderen"
+ UNSET: "Niet ingesteld"
+ VIEW: "Bekijken"
+ SAVE: "Opslaan"
+ NORMAL: "Normaal"
+ EXPERT: "Geavanceerd"
+ EXPAND_ALL: "Alles uitklappen"
+ COLLAPSE_ALL: "Alles inklappen"
+ ERROR: "Fout"
+ CLOSE: "Sluiten"
+ CANCEL: "Annuleer"
+ CONTINUE: "Verder"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Bevestiging vereist"
+ MODAL_CHANGED_DETECTED_TITLE: "Wijzigingen gedetecteerd"
+ MODAL_CHANGED_DETECTED_DESC: "Je hebt niet bewaarde wijzigingen. Wil je doorgaan zonder ze te op te slaan?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Bevestiging vereist"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Weet je zeker dat je dit bestand wilt verwijderen? Dit kan niet ongedaan worden gemaakt."
+ ADD_FILTERS: "Filters toevoegen"
+ SEARCH_PAGES: "Pagina's zoeken"
+ VERSION: "Versie"
+ WAS_MADE_WITH: "Gemaakt met"
+ BY: "Door"
+ UPDATE_THEME: "Theme bijwerken"
+ UPDATE_PLUGIN: "Plugin bijwerken"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "van deze theme is nu beschikbaar"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "van deze plugin is nu beschikbaar"
+ AUTHOR: "Auteur"
+ HOMEPAGE: "Homepage"
+ DEMO: "Demo"
+ BUG_TRACKER: "Bug Tracker"
+ KEYWORDS: "Trefwoorden"
+ LICENSE: "Licentie"
+ DESCRIPTION: "Omschrijving"
+ README: "Leesmij"
+ REMOVE_THEME: "Verwijder Thema"
+ INSTALL_THEME: "Installeer Thema"
+ THEME: "Thema"
+ BACK_TO_THEMES: "Terug naar Thema's"
+ BACK_TO_PLUGINS: "Terug naar Plugins"
+ CHECK_FOR_UPDATES: "Controleren op updates"
+ ADD: "Toevoegen"
+ CLEAR_CACHE: "Cache opschonen"
+ CLEAR_CACHE_ALL_CACHE: "Alle Cache"
+ CLEAR_CACHE_ASSETS_ONLY: "Alleen Assets"
+ CLEAR_CACHE_IMAGES_ONLY: "Aleen Beeld"
+ CLEAR_CACHE_CACHE_ONLY: "Alleen Cache"
+ CLEAR_CACHE_TMP_ONLY: "Alleen Tijdelijk"
+ DASHBOARD: "Dashboard"
+ UPDATES_AVAILABLE: "Updates beschikbaar"
+ DAYS: "Dagen"
+ UPDATE: "Bijwerken"
+ BACKUP: "Reservekopie"
+ BACKUPS: "Back-ups"
+ BACKUP_NOW: "Nu back-up maken"
+ BACKUPS_STATS: "Back-up Statistieken"
+ BACKUPS_HISTORY: "Back-up Historiek"
+ BACKUPS_PURGE_CONFIG: "Schoonmaakconfiguratie Back-uppen"
+ BACKUPS_PROFILES: "Profielen Back-uppen"
+ BACKUPS_COUNT: "Aantal Back-ups"
+ BACKUPS_PROFILES_COUNT: "Aantal Profielen"
+ BACKUPS_TOTAL_SIZE: "Gebruikte Ruimte"
+ BACKUPS_NEWEST: "Nieuwste back-up"
+ BACKUPS_OLDEST: "Oudste Back-up"
+ BACKUPS_PURGE: "Schoonmaken"
+ STATISTICS: "Statistieken"
+ TODAY: "Vandaag"
+ WEEK: "Week"
+ MONTH: "Maand"
+ LATEST_PAGE_UPDATES: "Recente Pagina Updates"
+ MAINTENANCE: "Onderhoud"
+ UPDATED: "Bijgewerkt"
+ MON: "Ma"
+ TUE: "Di"
+ WED: "Wo"
+ THU: "Do"
+ FRI: "Vr"
+ SAT: "Za"
+ SUN: "Zo"
+ COPY: "Kopieer"
+ EDIT: "Bewerk"
+ CREATE: "Maak"
+ GRAV_ADMIN: "Grav beheer"
+ GRAV_OFFICIAL_PLUGIN: "Officiële Grav Plugin"
+ GRAV_OFFICIAL_THEME: "Officiële Grav Thema"
+ PLUGIN_SYMBOLICALLY_LINKED: "Deze plugin is symbolisch gekoppeld. Updates worden niet gedetecteerd."
+ THEME_SYMBOLICALLY_LINKED: "Deze theme is symbolisch gekoppeld. Updates worden niet gedetecteerd"
+ REMOVE_PLUGIN: "Plugin verwijderen"
+ INSTALL_PLUGIN: "Installeer Plugin"
+ AVAILABLE: "Beschikbaar"
+ INSTALLED: "Geïnstalleerd"
+ INSTALL: "Installeren"
+ ACTIVE_THEME: "Actief thema"
+ SWITCHING_TO: "Overschakelen naar"
+ SWITCHING_TO_DESCRIPTION: "Door over te schakelen naar een ander thema, is er geen garantie dat alle pagina's van de layout worden ondersteund. Er worden mogelijk fouten veroorzaakt wanneer wordt geprobeerd om de genoemde pagina's te laden."
+ SWITCHING_TO_CONFIRMATION: "Wilt u doorgaan en overschakelen naar het thema"
+ CREATE_NEW_USER: "Nieuwe gebruiker aanmaken"
+ REMOVE_USER: "Gebruiker verwijderen"
+ ACCESS_DENIED: "Toegang geweigerd"
+ ACCOUNT_NOT_ADMIN: "Je account heeft geen Administrator rechten"
+ PHP_INFO: "PHP Info"
+ INSTALLER: "Installatie programma"
+ AVAILABLE_THEMES: "Beschikbare thema's"
+ AVAILABLE_PLUGINS: "Beschikbare plugins"
+ INSTALLED_THEMES: "Geïnstalleerde thema's"
+ INSTALLED_PLUGINS: "Geïnstalleerde plugins"
+ BROWSE_ERROR_LOGS: "Blader door foutenlogboek"
+ SITE: "Website"
+ INFO: "Informatie"
+ SYSTEM: "Systeem"
+ USER: "Gebruiker"
+ ADD_ACCOUNT: "Account toevoegen"
+ SWITCH_LANGUAGE: "Wissel van taal"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Plugin met succes ingeschakeld"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Plugin met succes uitgeschakeld"
+ SUCCESSFULLY_CHANGED_THEME: "Thema met succes veranderd"
+ INSTALLATION_FAILED: "Installatie mislukt"
+ INSTALLATION_SUCCESSFUL: "Installatie geslaagd"
+ UNINSTALL_FAILED: "Verwijderen mislukt"
+ UNINSTALL_SUCCESSFUL: "Verwijderen geslaagd"
+ SUCCESSFULLY_SAVED: "Succesvol opgeslagen"
+ SUCCESSFULLY_COPIED: "Succesvol gekopieerd"
+ REORDERING_WAS_SUCCESSFUL: "Opnieuw ordenen gelukt"
+ SUCCESSFULLY_DELETED: "Verwijderen geslaagd"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Taal met succes veranderd"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "U heeft onvoldoende rechten voor deze taak"
+ CACHE_CLEARED: "Cache gewist"
+ METHOD: "Methode"
+ ERROR_CLEARING_CACHE: "Fout bij leegmaken van de cache"
+ AN_ERROR_OCCURRED: "Er heeft zich een fout voorgedaan"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Je backup is klaar om te downloaden"
+ DOWNLOAD_BACKUP: "Download backup"
+ PAGES_FILTERED: "Pagina's zijn gefilterd"
+ NO_PAGE_FOUND: "Geen pagina gevonden"
+ INVALID_PARAMETERS: "Ongeldige parameters"
+ NO_FILES_SENT: "Geen bestanden verzonden"
+ EXCEEDED_FILESIZE_LIMIT: "Limiet voor de grootte van het PHP configuratiebestand overschreden"
+ UNKNOWN_ERRORS: "Onbekende fouten"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Limiet voor de grootte van het Grav configuratiebestand overschreden"
+ UNSUPPORTED_FILE_TYPE: "Niet-ondersteund bestandstype"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Het is niet gelukt om het geüploade bestand te verplaatsen."
+ FILE_UPLOADED_SUCCESSFULLY: "Bestand met succes geüpload"
+ FILE_DELETED: "Bestand verwijderd"
+ FILE_COULD_NOT_BE_DELETED: "Bestand kon niet worden verwijderd"
+ FILE_NOT_FOUND: "Bestand niet gevonden"
+ NO_FILE_FOUND: "Geen bestand gevonden"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav is met succes bijgewerkt naar"
+ GRAV_UPDATE_FAILED: "Grav update is mislukt"
+ EVERYTHING_UPDATED: "Alles is bijgewerkt"
+ UPDATES_FAILED: "Updates mislukt"
+ AVATAR_BY: "Avatar door"
+ AVATAR_UPLOAD_OWN: "Of upload uw eigen.."
+ LAST_BACKUP: "Laatste backup"
+ FULL_NAME: "Volledige naam"
+ USERNAME: "Gebruikersnaam"
+ EMAIL: "E-mailadres"
+ USERNAME_EMAIL: "Gebruikersnaam of e-mail"
+ PASSWORD: "Wachtwoord"
+ PASSWORD_CONFIRM: "Bevestig wachtwoord"
+ TITLE: "Titel"
+ LANGUAGE: "Taal"
+ ACCOUNT: "Account"
+ EMAIL_VALIDATION_MESSAGE: "Moet een geldig e-mailadres zijn"
+ PASSWORD_VALIDATION_MESSAGE: "Wachtwoord moet ten minste één nummer, één hoofdletter, één kleine letter en minstens 8 of meer tekens bevatten"
+ LANGUAGE_HELP: "Favoriete taal instellen"
+ MEDIA: "Media"
+ DEFAULTS: "Standaardwaarden"
+ SITE_TITLE: "Titel van de site"
+ SITE_TITLE_PLACEHOLDER: "Site titel plaatshouder"
+ SITE_TITLE_HELP: "Standaard titel voor uw website, wordt veel gebruikt in thema's"
+ SITE_DEFAULT_LANG: "Standaard taal"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Standaard taal dat gebruikt wordt door de tag van dit thema"
+ SITE_DEFAULT_LANG_HELP: "Standaard taal te gebruiken door thema's tag"
+ DEFAULT_AUTHOR: "Standaard auteur"
+ DEFAULT_AUTHOR_HELP: "Standaard auteursnaam, meestal gebruikt in thema's of pagina-inhoud"
+ DEFAULT_EMAIL: "Standaard e-mail"
+ DEFAULT_EMAIL_HELP: "Een standaard e-mailadres als referentie in thema's of pagina's"
+ TAXONOMY_TYPES: "Taxonomie types"
+ TAXONOMY_TYPES_HELP: "Taxonomie types dienen hier bepaald te worden indien u deze wil gebruiken in pagina's"
+ PAGE_SUMMARY: "Pagina samenvatting"
+ ENABLED: "Ingeschakeld"
+ ENABLED_HELP: "Activeer samenvatting van pagina's (de samenvatting geeft hetzelfde weer als de inhoud van de pagina)"
+ 'YES': "Ja"
+ 'NO': "Nee"
+ SUMMARY_SIZE: "Samenvatting grootte"
+ SUMMARY_SIZE_HELP: "Het aantal karakters van een pagina die gebruikt worden als samenvatting van de inhoud"
+ FORMAT: "Type"
+ FORMAT_HELP: "kort = gebruik het eerste scheidingsteken of grootte = scheidingsteken van de samenvatting zal genegeerd worden"
+ SHORT: "Kort"
+ LONG: "Lang"
+ DELIMITER: "Scheidingsteken"
+ DELIMITER_HELP: "Het scheidingsteken van de samenvatting (standaard '===')"
+ METADATA: "Metadata"
+ METADATA_HELP: "Standaard metadata waardes die weergegeven worden op iedere pagina tenzij deze overschreven worden door de pagina zelf"
+ NAME: "Naam"
+ CONTENT: "Inhoud"
+ SIZE: "Grootte"
+ ACTION: "Actie"
+ REDIRECTS_AND_ROUTES: "Redirects & Routes"
+ CUSTOM_REDIRECTS: "Aangepaste omleidingen"
+ CUSTOM_REDIRECTS_HELP: "Routes om te verwijzen naar andere pagina's. Standaard Regex vervanging is geldig"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/uw/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/uw/doorverwijzing/"
+ CUSTOM_ROUTES: "Aangepaste Routes"
+ CUSTOM_ROUTES_HELP: "routes naar aliassen van andere pagina's. Standaard regex vervangingen zijn geldig"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/uw/alias/"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/uw/route"
+ FILE_STREAMS: "File Streams"
+ DEFAULT: "Standaard"
+ PAGE_MEDIA: "Pagina Media"
+ OPTIONS: "Opties"
+ PUBLISHED: "Gepubliceerd"
+ PUBLISHED_HELP: "Een pagina wordt standaard gepubliceerd tenzij u expliciet published: false meegeeft of indien u de publish_date in de toekomst zet of de unpublish_date in het verleden zet"
+ DATE: "Datum"
+ DATE_HELP: "De datum variabele staat u toe om specifiek een datum in te stellen die geassocieerd is met deze pagina."
+ PUBLISHED_DATE: "Datum van publicatie"
+ PUBLISHED_DATE_HELP: "Kan een datum voorzien om een automatische publicatie te activeren."
+ UNPUBLISHED_DATE: "Datum van niet-publicatie"
+ UNPUBLISHED_DATE_HELP: "Kan een datum voorzien om een automatische de-publicatie te activeren."
+ ROBOTS: "Robots"
+ TAXONOMIES: "Taxonomies"
+ TAXONOMY: "Taxonomie"
+ ADVANCED: "Geavanceerd"
+ SETTINGS: "Instellingen"
+ FOLDER_NUMERIC_PREFIX: "Numeriek voorvoegsel map"
+ FOLDER_NUMERIC_PREFIX_HELP: "Numeriek voorvoegsel dat manuele sortering en zichtbaarheid voorziet"
+ FOLDER_NAME: "Naam van map"
+ FOLDER_NAME_HELP: "De mapnaam dat in het bestandssysteem opgeslaan wordt voor deze pagina"
+ PARENT: "Bovenliggend"
+ DEFAULT_OPTION_ROOT: "-Root-"
+ DEFAULT_OPTION_SELECT: "-Selecteer-"
+ DISPLAY_TEMPLATE: "Toon template"
+ DISPLAY_TEMPLATE_HELP: "Het paginatype dat vertaald in welke twig template de pagina rendert"
+ BODY_CLASSES: "Body klassen"
+ ORDERING: "Volgorde"
+ PAGE_ORDER: "Pagina volgorde"
+ OVERRIDES: "Overschrijvingen"
+ MENU: "Menu"
+ MENU_HELP: "De string zal gebruikt worden in een menu. Indien niet ingesteld zal de titel gebruikt worden."
+ SLUG: "Slug"
+ SLUG_HELP: "De slug variabele laat u toe om een gedeelte van de pagina in de URL te plaatsen"
+ SLUG_VALIDATE_MESSAGE: "De slug mag enkel kleine letters, alfanumerieke karakters en koppeltekens bevatten"
+ PROCESS: "Verwerk"
+ PROCESS_HELP: "Bepaal hoe pagina's verwerkt worden. Kan per pagina ingesteld worden in plaats van globaal"
+ DEFAULT_CHILD_TYPE: "Standaard Child type"
+ USE_GLOBAL: "Gebruik globaal"
+ ROUTABLE: "Routeerbaar"
+ ROUTABLE_HELP: "Indien deze pagina bereikbaar is via een link"
+ CACHING: "Cachen"
+ VISIBLE: "Zichtbaar"
+ VISIBLE_HELP: "Bepaalt of een pagina zichtbaar is in de navigatie."
+ DISABLED: "Uitgeschakeld"
+ ITEMS: "Items"
+ ORDER_BY: "Sorteer op"
+ ORDER: "Volgorde"
+ FOLDER: "Map"
+ ASCENDING: "Oplopend"
+ DESCENDING: "Aflopend"
+ ADD_MODULAR_CONTENT: "Modulaire inhoud toevoegen"
+ PAGE_TITLE: "Pagina titel"
+ PAGE_TITLE_HELP: "De titel van de pagina"
+ PAGE: "Pagina"
+ MODULAR_TEMPLATE: "Modulair sjabloon"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Bestandsnaam"
+ PARENT_PAGE: "Bovenliggende pagina"
+ HOME_PAGE: "Homepage"
+ HOME_PAGE_HELP: "De beginpagina van de site"
+ DEFAULT_THEME: "Standaard thema"
+ DEFAULT_THEME_HELP: "Stel het standaard thema in voor Grav (standaard is Antimatter geactiveerd)"
+ TIMEZONE: "Tijdzone"
+ TIMEZONE_HELP: "Overschrijf de standaard tijdzonde van de server"
+ SHORT_DATE_FORMAT: "Korte datum notatie"
+ SHORT_DATE_FORMAT_HELP: "Instellen van de korte datum notatie die kan gebruikt worden door thema's"
+ LONG_DATE_FORMAT: "Lange datumnotatie"
+ LONG_DATE_FORMAT_HELP: "Instellen van de lange datum notatie die kan gebruikt worden door thema's"
+ DEFAULT_ORDERING: "Standaard volgorde"
+ DEFAULT_ORDERING_HELP: "Pagina's in een lijst zullen weergegeven worden in deze volgorde tenzij deze overschreven wordt"
+ DEFAULT_ORDERING_DEFAULT: "Standaard - gebaseerd op mapnaam"
+ DEFAULT_ORDERING_FOLDER: "Map - gebaseerd op mapnaam zonder voorvoegsel"
+ DEFAULT_ORDERING_TITLE: "Titel - gebaseerd op titel veld in de header"
+ DEFAULT_ORDERING_DATE: "Datum - gebaseerd op datum veld in de header"
+ DEFAULT_ORDER_DIRECTION: "Standaard sorteer volgorde"
+ DEFAULT_ORDER_DIRECTION_HELP: "De richting van pagina's in een lijst"
+ DEFAULT_PAGE_COUNT: "Standaard aantal pagina's"
+ DEFAULT_PAGE_COUNT_HELP: "Standaard maximum aantal pagina's in een lijst"
+ DATE_BASED_PUBLISHING: "Publiceren op basis van datum"
+ DATE_BASED_PUBLISHING_HELP: "Automatische (niet-)publicatie van artikels gebaseerd op hun datum"
+ EVENTS: "Gebeurtenissen"
+ EVENTS_HELP: "Inschakelen of uitschakelen van specifieke gebeurtenissen. Het uitschakelen ervan kan plugins breken"
+ REDIRECT_DEFAULT_ROUTE: "Omleiden van standaard route"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Stuur automatisch door naar de standaard route van een pagina"
+ LANGUAGES: "Talen"
+ SUPPORTED: "Ondersteund"
+ SUPPORTED_HELP: "Door komma's gescheiden lijst van tweeletterige taalcodes (bijvoorbeeld 'en,fr,de')"
+ TRANSLATIONS_ENABLED: "Vertalingen ingeschakeld"
+ TRANSLATIONS_ENABLED_HELP: "Ondersteun vertalingen in Grav, Plugins en Extensies"
+ TRANSLATIONS_FALLBACK: "Noodoplossing vertalingen"
+ TRANSLATIONS_FALLBACK_HELP: "Terugvallen op ondersteunde vertaling als de actieve taal niet bestaat"
+ ACTIVE_LANGUAGE_IN_SESSION: "Actieve taal in sessie"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "De actieve taal opslaan in de sessie"
+ HTTP_HEADERS: "HTTP Headers"
+ EXPIRES: "Verloopt"
+ EXPIRES_HELP: "Stel de expires header in. De waarde is in seconden."
+ CACHE_CONTROL: "HTTP Cache-Control"
+ CACHE_CONTROL_HELP: "Zet naar een geldige cache-control waarde zoals `no-cache, no-store, must-revalidate`"
+ LAST_MODIFIED: "Laatst gewijzigd"
+ LAST_MODIFIED_HELP: "Stel de last modified header in die kan helpen voor het optimaliseren van proxy en browser caching"
+ ETAG: "ETag"
+ ETAG_HELP: "Stel de etag header in die kan helpen identificeren wanneer een pagina aangepast is"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Stel de 'Vary: Accept Encoding' header in die kan helpen bij proxy en CDN caching"
+ MARKDOWN: "Markdown"
+ MARKDOWN_EXTRA: "Markdown Extra"
+ MARKDOWN_EXTRA_HELP: "Activeer standaard ondersteuning voor Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ MARKDOWN_EXTRA_ESCAPE_FENCES: "Escape HTML elementen in Markdown Extra omheiningen"
+ MARKDOWN_EXTRA_ESCAPE_FENCES_HELP: "Escapes HTML elementen in Markdown Extra omheiningen"
+ AUTO_LINE_BREAKS: "Automatische regeleinden"
+ AUTO_LINE_BREAKS_HELP: "Activeer ondersteuning voor automatische regeleinden in markdown"
+ AUTO_URL_LINKS: "Automatische URL koppelingen"
+ AUTO_URL_LINKS_HELP: "Activeer automatische conversie van URLs naar HTML hyperlinks"
+ ESCAPE_MARKUP: "Markup escapen"
+ ESCAPE_MARKUP_HELP: "Markup tags escapen naar HTML entiteiten"
+ CACHING_HELP: "Globale AAN/UIT schakelaar om Grav caching in/uit te schakelen"
+ CACHE_CHECK_METHOD: "Cache check methode"
+ CACHE_CHECK_METHOD_HELP: "Selecteer de methode die Grav gebruikt om te controleren of bestanden van een pagina aangepast zijn."
+ CACHE_DRIVER: "Cache driver"
+ CACHE_DRIVER_HELP: "Selecteer de cache driver die Grav moet gebruiken. 'Auto Detect' probeert de beste te vinden voor jou"
+ CACHE_PREFIX: "Cache voorvoegsel"
+ CACHE_PREFIX_HELP: "Identificatie voor een gedeelte van de Grav key. Wijzig dit niet indien u weet waar u mee bezig bent."
+ CACHE_PREFIX_PLACEHOLDER: "Afgeleid van de basis URL (overschrijf deze door een willekeurige tekst in te geven)"
+ LIFETIME: "Levensduur"
+ LIFETIME_HELP: "Stel de levensduur van de cache in, in seconden. 0 = oneindig"
+ GZIP_COMPRESSION: "Gzip compressie"
+ GZIP_COMPRESSION_HELP: "Gzip compressie activeren voor betere prestaties."
+ TWIG_TEMPLATING: "Twig templating"
+ TWIG_CACHING: "Twig caching"
+ TWIG_CACHING_HELP: "Controleer het Twig caching mechanisme. Laat dit ingeschakeld voor de beste prestaties."
+ TWIG_DEBUG: "Twig Debug"
+ TWIG_DEBUG_HELP: "Voorzie de mogelijkheid om de Twig Debugger extensie niet in te laden"
+ DETECT_CHANGES: "Detecteer wijzigingen"
+ DETECT_CHANGES_HELP: "Twig zal automatisch de Twig cache hercompileren zodra het wijzigingen in de Twig templates detecteert"
+ AUTOESCAPE_VARIABLES: "Autoescape variabelen"
+ AUTOESCAPE_VARIABLES_HELP: "Autoescape alle variabelen. Dit zal hoogstwaarschijnlijk uw site breken"
+ ASSETS: "Assets"
+ CSS_PIPELINE: "CSS pipeline"
+ CSS_PIPELINE_HELP: "De CSS pipeline is de éénmaking van meerdere CSS-bronnen naar één bestand"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Opnemen van externe bestanden in CSS pipeline"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Externe URLs hebben soms relatieve bestandsverwijzingen en mogen niet in pipeline opgenomen worden"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "CSS pipeline render eerst"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Render de CSS pipeline voor andere CSS referenties die niet zijn opgenomen"
+ CSS_MINIFY: "CSS verkleinen"
+ CSS_MINIFY_HELP: "CSS verkleinen tijdens het pipelinen"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "CSS verkleinen Windows overschrijving"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Overschrijven voor Windows platformen beperken. Standaardwaarde is False door de ThreadStackSize"
+ CSS_REWRITE: "CSS herschrijven"
+ CSS_REWRITE_HELP: "Herschrijf alle URL gerelateerde CSS tijdens het pipelinen"
+ JAVASCRIPT_PIPELINE: "JavaScript pipeline"
+ JAVASCRIPT_PIPELINE_HELP: "De JavaScript pipeline is de éénmaking van meerdere JavaScript-bronnen in één bestand"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Opnemen van externe bestanden in JavaScript pipeline"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Externe URLs hebben soms relatieve bestandsverwijzingen en mogen niet in de pipeline opgenomen worden"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "JavaScript pipeline render eerst"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Render eerst de JavaScript pipeline voor andere JavaScript referenties die niet zijn opgenomen"
+ JAVASCRIPT_MINIFY: "JavaScript verkleinen"
+ JAVASCRIPT_MINIFY_HELP: "JavaScript verkleinen tijdens het pipelinen"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Tijdstempels activeren op assets"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Tijdstempels activeren op assets"
+ COLLECTIONS: "Collecties"
+ ERROR_HANDLER: "Foutafhandeling"
+ DISPLAY_ERRORS: "Errors weergeven"
+ DISPLAY_ERRORS_HELP: "Volledige backtrace-stijl foutpagina weergeven"
+ LOG_ERRORS: "Errors bewaren"
+ LOG_ERRORS_HELP: "Errors in de /logs map bewaren"
+ DEBUGGER: "Debugger"
+ DEBUGGER_HELP: "Grav Debugger activeren met de volgende instellingen"
+ DEBUG_TWIG: "Twig Debuggen"
+ DEBUG_TWIG_HELP: "Debuggen van Twig templates activeren"
+ SHUTDOWN_CLOSE_CONNECTION: "Connectie afsluiten"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "De verbinding afsluiten voor het oproepen van onShutdown(). False voor debuggen"
+ DEFAULT_IMAGE_QUALITY: "Standaard afbeeldingskwaliteit"
+ DEFAULT_IMAGE_QUALITY_HELP: "Standaardkwaliteit die gebruikt zal worden bij het resamplen of cachen van afbeeldingen (85%)"
+ CACHE_ALL: "Alle afbeeldingen cachen"
+ CACHE_ALL_HELP: "Verwerk alle afbeeldingen met het Grav cache systeem, zelfs als er geen aanpassingen aan gebeurd zijn"
+ IMAGES_DEBUG: "Debug watermerk op afbeelding"
+ IMAGES_DEBUG_HELP: "Bedek de afbeeldingen met een laag die de pixel diepte van de afbeelding weergeeft wanneer er met bijvoorbeeld retina gewerkt wordt"
+ UPLOAD_LIMIT: "Bestand upload limiet"
+ UPLOAD_LIMIT_HELP: "Stel de maximum upload grootte in, in bytes. (0 is onbeperkt)"
+ ENABLE_MEDIA_TIMESTAMP: "Tijdstempels op media activeren"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Voeg een tijdstempel toe, gebaseerd op het tijdstip van de laatste wijziging van elk media-item"
+ SESSION: "Sessie"
+ SESSION_ENABLED_HELP: "Sessie ondersteuning activeren in Grav"
+ TIMEOUT: "Timeout"
+ TIMEOUT_HELP: "Sessie timeout instellen in seconden"
+ SESSION_NAME_HELP: "Een id die gebruikt wordt om de naam van de sessie cookie op te bouwen"
+ ABSOLUTE_URLS: "Absolute URLs"
+ ABSOLUTE_URLS_HELP: "Absolute of relatieve URLs voor de 'base_url'"
+ PARAMETER_SEPARATOR: "Parameter scheidingsteken"
+ PARAMETER_SEPARATOR_HELP: "Scheidingsteken voor doorgegeven van parameters die gewijzigd kunnen worden voor Apache op Windows"
+ TASK_COMPLETED: "Taak uitgevoerd"
+ EVERYTHING_UP_TO_DATE: "Alles is up-to-date"
+ UPDATES_ARE_AVAILABLE: "update(s) beschikbaar"
+ IS_AVAILABLE_FOR_UPDATE: "is beschikbaar voor update"
+ IS_NOW_AVAILABLE: "is nu beschikbaar"
+ CURRENT: "Huidige"
+ UPDATE_GRAV_NOW: "Grav nu updaten"
+ GRAV_SYMBOLICALLY_LINKED: "Grav is symbolisch gekoppeld. Upgraden is niet mogelijk"
+ UPDATING_PLEASE_WAIT: "Aan het updaten.. even geduld a.u.b., aan het downloaden"
+ OF_THIS: "van dit"
+ OF_YOUR: "van uw"
+ HAVE_AN_UPDATE_AVAILABLE: "hebben een update beschikbaar"
+ SAVE_AS: "Opslaan als"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Bent u zeker dat u deze pagina en subpagina's wil verwijderen? Indien deze pagina vertaald is in andere talen dan blijven deze vertalingen behouden en dienen deze afzonderlijk gewist te worden. Anders zal de paginamap met alle subpagina's verwijderd worden. Deze actie kan niet ongedaan gemaakt worden."
+ AND: "en"
+ UPDATE_AVAILABLE: "Update beschikbaar"
+ METADATA_KEY: "Key (bvb. 'Zoektermen')"
+ METADATA_VALUE: "Value (bvb. 'Blog, Grav')"
+ USERNAME_HELP: "De gebruikersnaam dient tussen 3 en 16 karakters lang te zijn. Inclusief kleine letters, nummers, underscores en koppeltekens. Grote letters, spaties en speciale karakters zijn niet toegestaan"
+ FULLY_UPDATED: "Volledig bijgewerkt"
+ SAVE_LOCATION: "Opslaglocatie"
+ PAGE_FILE: "Pagina sjabloon"
+ PAGE_FILE_HELP: "Template bestandsnaam en wordt standaard het template voor deze pagina"
+ NO_USER_ACCOUNTS: "Geen gebruikersaccounts gevonden, gelieve er eerst één aan te maken..."
+ REDIRECT_TRAILING_SLASH: "Verwijs nakomende slash"
+ REDIRECT_TRAILING_SLASH_HELP: "Voer een 301-omleiding uit in plaats van het transparant afhandelen van nakomende slash URIs."
+ DEFAULT_DATE_FORMAT: "Datum formaat van pagina"
+ DEFAULT_DATE_FORMAT_HELP: "Datumformaat voor pagina's gebruikt door Grav. Grav zal standaard proberen het datum formaat te raden, u kan echter een formaat instellen volgens de PHP datum syntax (bvb.: Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Raad automatisch"
+ IGNORE_FILES: "Negeer bestanden"
+ IGNORE_FILES_HELP: "Specifieer bestanden die genegeerd zullen worden bij het verwerken van pagina's"
+ IGNORE_FOLDERS: "Negeer mappen"
+ IGNORE_FOLDERS_HELP: "Specifieke mappen die genegeerd zullen worden bij het verwerken van pagina's"
+ HTTP_ACCEPT_LANGUAGE: "Taal van browser instellen"
+ HTTP_ACCEPT_LANGUAGE_HELP: "U kan ervoor kiezen om de taal in te stellen gebaseerd op de 'http_accept_language' header tag in de browser"
+ OVERRIDE_LOCALE: "Overschrijf 'locale'"
+ OVERRIDE_LOCALE_HELP: "Overschrijf de 'locale' instelling in PHP gebaseerd op de huidige taal"
+ REDIRECT: "Pagina doorverwijzing"
+ REDIRECT_HELP: "Voer een pagina route of externe URL in, om deze pagina naar te verwijzen. bvb. '/een/route' of 'http://somesite.com'"
+ PLUGIN_STATUS: "Plugin status"
+ INCLUDE_DEFAULT_LANG: "Standaardtaal meerekenen"
+ INCLUDE_DEFAULT_LANG_HELP: "Dit zal aan alle URLs in de standaardtaal, de standaardtaal toevoegen. bvb. 'nl/blog/mijn-artikel'"
+ ALLOW_URL_TAXONOMY_FILTERS: "URL taxonomie filters"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Met collecties gebaseerd op pagina's kan u filteren via '/taxonomie:waarde'."
+ REDIRECT_DEFAULT_CODE: "Standaard doorverwijscode"
+ REDIRECT_DEFAULT_CODE_HELP: "De HTTP status code die gebruikt zal worden voor doorverwijzingen"
+ IGNORE_HIDDEN: "Negeer verborgen"
+ IGNORE_HIDDEN_HELP: "Negeer alle bestanden en mappen die beginnen met een PUNT"
+ WRAPPED_SITE: "Omhulde site"
+ WRAPPED_SITE_HELP: "Voor /themes/plugins om te weten of Grav in een ander platform verwikkeld is"
+ FALLBACK_TYPES: "Toegestane types om op terug te vallen"
+ FALLBACK_TYPES_HELP: "Toegestane bestandstypes die gevonden kunnen worden als ze opgeroepen worden via pagina route. Verwijst standaard naar ieder ondersteund mediatype."
+ INLINE_TYPES: "Inline types om op terug te vallen"
+ INLINE_TYPES_HELP: "Lijst met bestandstypes die inline weergegeven worden in plaats van gedownload te worden"
+ APPEND_URL_EXT: "URL extensie toevoegen"
+ APPEND_URL_EXT_HELP: "Voegt een aangepaste extensie toe aan de pagina URLs. Let op, dit betekent dat Grav zal zoeken naar de '..twig' template"
+ PAGE_MODES: "Pagina modes"
+ PAGE_TYPES: "Pagina types"
+ ACCESS_LEVELS: "Toegangsniveaus"
+ GROUPS: "Groepen"
+ GROUPS_HELP: "Lijst van groepen waar de gebruiker deel van uitmaakt"
+ ADMIN_ACCESS: "Beheerder toegang"
+ SITE_ACCESS: "Toegang tot de site"
+ INVALID_SECURITY_TOKEN: "Ongeldige Security Token"
+ ACTIVATE: "Activeer"
+ TWIG_UMASK_FIX: "Umask fix"
+ TWIG_UMASK_FIX_HELP: "Standaard maakt Twig gecachete bestanden aan zoals 0755, de fix wijzigt dit naar 0775"
+ CACHE_PERMS: "Cache toestemmingen"
+ CACHE_PERMS_HELP: "Standaard cache map machtigingen. Doorgaans 0755 of 0775 afhankelijk van de configuratie"
+ REMOVE_SUCCESSFUL: "Met succes verwijderd"
+ REMOVE_FAILED: "Verwijderen mislukt"
+ HIDE_HOME_IN_URLS: "Verberg home route in URLs"
+ HIDE_HOME_IN_URLS_HELP: "Zorgt ervoor dat de standaard routes voor alle pagina's onder home niet verwijzen naar de gewone route voor home"
+ TWIG_FIRST: "Twig eerst verwerken"
+ TWIG_FIRST_HELP: "Indien u Twig pagina verwerking geactiveerd heeft dan kan u Twig configureren om te verwerken voor of na Markdown"
+ SESSION_SECURE: "Veilig"
+ SESSION_SECURE_HELP: "Als de waarde true is, dan wordt er aangegeven dat communicatie voor deze cookie over een beveiligde verbinding zal gebeuren. WAARSCHUWING: activeer deze optie enkel voor websites die enkel over HTTPS lopen"
+ SESSION_HTTPONLY: "HTTP Only"
+ SESSION_HTTPONLY_HELP: "Indien de waarde true is, geef dan aan, dat cookies enkel over HTTP gebruikt zullen worden en dat JavaScript wijzigingen niet toegestaan zijn"
+ REVERSE_PROXY: "Reverse Proxy"
+ REVERSE_PROXY_HELP: "Activeer dit indien u zich achter een reverse proxy bevind en indien u problemen heeft met URLs die ongeldige poorten bevatten"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Ongeldige Frontmatter, opslaan mislukt"
+ ADD_FOLDER: "Map toevoegen"
+ PROXY_URL: "Proxy URL"
+ PROXY_URL_HELP: "Geef de proxy HOST of IP en POORT in"
+ NOTHING_TO_SAVE: "Niets om op te slaan"
+ FILE_ERROR_ADD: "Er is een fout opgetreden tijdens het toevoegen van het bestand"
+ FILE_ERROR_UPLOAD: "Er is een fout opgetreden tijdens het uploaden van het bestand"
+ FILE_UNSUPPORTED: "Niet ondersteund bestandstype "
+ ADD_ITEM: "Toevoegen"
+ FILE_TOO_LARGE: "Het bestand is te groot om upgeload te worden, maximaal toegestane grootte is %s volgens uw PHP instellingen. Verhoog uw 'post_max_size' PHP instelling"
+ INSTALLING: "Bezig met installeren"
+ LOADING: "Laden.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "De volgende afhankelijkheden moeten eerst worden voldaan:"
+ ERROR_INSTALLING_PACKAGES: "Fout tijdens het installeren van pakket(ten)"
+ INSTALLING_DEPENDENCIES: "Afhankelijkheden installeren..."
+ INSTALLING_PACKAGES: "Pakket(ten) wordt/worden geïnstalleerd.."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Pakket(ten) succesvol geïnstalleerd."
+ READY_TO_INSTALL_PACKAGES: "Klaar om pakket(ten) te installeren"
+ PACKAGES_NOT_INSTALLED: "Pakket(ten) niet geïnstalleerd"
+ PACKAGES_NEED_UPDATE: "Pakket(ten) reeds geïnstalleerd, maar updates zijn nodig"
+ PACKAGES_SUGGESTED_UPDATE: "Pakketten reeds geïnstalleerd en versie is in orde, maar ze zullen worden bijgewerkt om u up-to-date te houden"
+ REMOVE_THE: "Verwijder de %s"
+ CONFIRM_REMOVAL: "Weet je zeker dat je deze %s wilt verwijderen"
+ REMOVED_SUCCESSFULLY: "%s succesvol verwijderd"
+ ERROR_REMOVING_THE: "Fout tijdens het verwijderen van de %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "De %s vereist de volgende afhankelijkheden die niet benodigd zijn voor andere geïnstalleerde pakketten. Als die niet gebruikt worden, kunt u ze rechtstreeks hier vandaan verwijderen."
+ READY_TO_UPDATE_PACKAGES: "Klaar om pakket(ten) bij te werken"
+ ERROR_UPDATING_PACKAGES: "Fout tijdens het bijwerken van pakket(ten)"
+ UPDATING_PACKAGES: "Bijwerken van pakket(ten).."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Pakket(ten) succesvol bijgewerkt."
+ UPDATING: "Bijwerken"
+ GPM_RELEASES: "GPM Releases"
+ GPM_RELEASES_HELP: "Kies 'Testen' om Beta/Test versies te installeren"
+ GPM_METHOD: "Externe Fetch methode"
+ GPM_METHOD_HELP: "Wanneer ingesteld op Auto, zal Grav bepalen of 'fopen' beschikbaar en bruikbaar is, anders zal het terugvallen op 'cURL'. Om één van beide af te dwingen kan je deze selecteren."
+ GPM_VERIFY_PEER: "Externe controle (SSL)"
+ GPM_VERIFY_PEER_HELP: "Sommige providers kunnen het getgrav.org SSL-certificaat niet verifiëren, waardoor de GPM niet werkt. Als dit het geval is, kan het uitschakelen van deze instelling helpen"
+ AUTO: "Automatisch"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stabiel"
+ TESTING: "Testen"
+ FRONTMATTER_PROCESS_TWIG: "Verwerk Frontmatter Twig"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Wanneer ingeschakeld, kunt u Twig config variabelen gebruiken in Frontmatter op de pagina"
+ FRONTMATTER_IGNORE_FIELDS: "Negeer Frontmatter velden"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Bepaalde Frontmatter velden kunnen Twig bevatten maar moeten niet verwerkt worden, zoals 'forms'"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Pakket %s succesvol geïnstalleerd"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Volgorde bepaald door bovenliggende pagina, volgorde uitgeschakeld"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Pagina is niet zichtbaar, sorteren gedeactiveerd"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "Volgordebepaling via de admin is niet beschikbaar bij meer dan 200 items"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Pagina volgorde is uitgeschakeld voor deze pagina aangezien Folder Numeric Prefix niet ingeschakeld is"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "Let op: U kunt geen media toevoegen voordat u de pagina heeft opgeslagen. Klik op 'opslaan' bovenaan de pagina"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "Let op: De pagina moet worden opgeslagen voordat u bestanden kunt uploaden."
+ DROP_FILES_HERE_TO_UPLOAD: "Sleep je bestanden hier of klik in deze regio "
+ INSERT: "Invoegen"
+ UNDO: "Ongedaan maken"
+ REDO: "Herhalen"
+ HEADERS: "Koppen"
+ BOLD: "Vet"
+ ITALIC: "Cursief"
+ STRIKETHROUGH: "Doorhalen"
+ SUMMARY_DELIMITER: "Scheidingsteken voor samenvatting"
+ LINK: "Link"
+ IMAGE: "Afbeelding"
+ BLOCKQUOTE: "Citaatblok"
+ UNORDERED_LIST: "Ongeordende lijst"
+ ORDERED_LIST: "Geordende lijst"
+ EDITOR: "Bewerker"
+ PREVIEW: "Voorbeeld"
+ FULLSCREEN: "Volledig scherm"
+ MODULAR: "Modulair"
+ NON_ROUTABLE: "Niet routable"
+ NON_MODULAR: "Niet modulair"
+ NON_VISIBLE: "Niet zichtbaar"
+ NON_PUBLISHED: "Niet gepubliceerd"
+ CHARACTERS: "Tekens"
+ PUBLISHING: "Publiceren"
+ NOTIFICATIONS: "Notificaties"
+ MEDIA_TYPES: "Media types"
+ IMAGE_OPTIONS: "Afbeelding opties"
+ MIME_TYPE: "MIME type"
+ THUMB: "Miniatuur"
+ TYPE: "Type"
+ FILE_EXTENSION: "Bestandsextensie"
+ LEGEND: "Paginalegenda"
+ MEMCACHE_SERVER: "Memcache-server"
+ MEMCACHE_SERVER_HELP: "Het Memcache-serveradres"
+ MEMCACHE_PORT: "Memcache-poort"
+ MEMCACHE_PORT_HELP: "De Memcache-serverpoort"
+ MEMCACHED_SERVER: "Memcached-server"
+ MEMCACHED_SERVER_HELP: "Het Memcached-serveradres"
+ MEMCACHED_PORT: "Memcached-poort"
+ MEMCACHED_PORT_HELP: "De Memcached-serverpoort"
+ REDIS_SERVER: "Redis-server"
+ REDIS_SERVER_HELP: "Het Redis-serveradres"
+ REDIS_PORT: "Redis-poort"
+ REDIS_PORT_HELP: "De Redis-serverpoort"
+ REDIS_PASSWORD: "Redis paswoord/geheim"
+ ALL: "Alle"
+ FROM: "van"
+ TO: "aan"
+ RELEASE_DATE: "Verschijningsdatum"
+ SORT_BY: "Sorteren op"
+ RESOURCE_FILTER: "Filter..."
+ FORCE_SSL: "SSL forceren"
+ FORCE_SSL_HELP: "Globaal SSL forceren, als dit is ingeschakeld, word al het verkeer via HTTP door Grav omgeleid naar HTTPS"
+ NEWS_FEED: "Nieuws Feed"
+ EXTERNAL_URL: "Externe URL"
+ SESSION_PATH: "Folder voor sessies"
+ SESSION_PATH_HELP: "Alleen gebruiken als u een aangepaste URL wil (u herschrijft het domein / submap van de website)"
+ CUSTOM_BASE_URL: "Aangepaste basis-URL"
+ CUSTOM_BASE_URL_HELP: "Gebruiken als u het domein van de website wilt overschrijven. Of gebruik wilt maken van een andere submap dan degene die standaard gebruikt wordt door Grav. Voorbeeld: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'Mag geen gebruik maken van %s buiten de pagina''s.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Kan het bestand %s niet uploaden: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'Kan het bestand %s niet verplaatsen naar %s'
+ DROPZONE_CANCEL_UPLOAD: 'Annuleer upload'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Weet u zeker dat u deze upload wilt annuleren?'
+ DROPZONE_DEFAULT_MESSAGE: 'Sleep je bestanden hier of klik in deze regio '
+ DROPZONE_FALLBACK_MESSAGE: 'Uw browser ondersteunt deze methode van uploaden niet.'
+ DROPZONE_FALLBACK_TEXT: 'Gebruik onderstaand fallback formulier om uw bestanden te uploaden, zoals in de goede oude tijd.'
+ DROPZONE_FILE_TOO_BIG: 'Bestand is te groot ({{filesize}}MiB). Max bestandsgrootte: {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "U kunt geen bestanden van dit type uploaden."
+ DROPZONE_MAX_FILES_EXCEEDED: "U kunt niet nog meer bestanden uploaden."
+ DROPZONE_REMOVE_FILE: "Verwijder bestand"
+ DROPZONE_RESPONSE_ERROR: "Server reageerde met code {{statusCode}}."
+ PREMIUM_PRODUCT: "Premium"
+ ADMIN_CACHING: "Admin Caching inschakelen"
+ ADMIN_CACHING_HELP: "Caching van de admin kan onafhankelijk van de front-end site beheerd worden"
+ DESTINATION_NOT_SPECIFIED: "Bestemming niet gespecificeerd"
+ UPLOAD_ERR_NO_TMP_DIR: "Tijdelijke map ontbreekt"
+ SESSION_SPLIT: "Sessie Split"
+ SESSION_SPLIT_HELP: "Onafhankelijke sessie-splits tussen site en andere plugins (zoals admin)"
+ ERROR_FULL_BACKTRACE: "Volledige Backtrace fout"
+ ERROR_SIMPLE: "Simpele fout"
+ ERROR_SYSTEM: "Systeemfout"
+ IMAGES_AUTO_FIX_ORIENTATION: "Herstel oriëntatie automatisch"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Herstel de oriëntatie van de afbeelding automatisch op basis van Exif data"
+ REDIS_SOCKET: "Redis socket"
+ REDIS_SOCKET_HELP: "De Redis socket"
+ NOT_SET: "Niet ingesteld"
+ PERMISSIONS: "Rechten"
+ NEVER_CACHE_TWIG: "Nooit Twig cachen"
+ NEVER_CACHE_TWIG_HELP: "Cache enkel de inhoud en verwerk Twig telkens voor pagina's. Negeert twig_first instelling."
+ ALLOW_WEBSERVER_GZIP: "Sta WebServer Gzip toe"
+ ALLOW_WEBSERVER_GZIP_HELP: "Standaard uitgeschakeld. Indien ingeschakeld, zal WebServer-geconfigureerde Gzip / deflate compressie werken, maar http-verbinding zal niet worden gesloten voordat onShutDown() event waardoor het laden van pagina's traag zal worden"
+ OFFLINE_WARNING: "Kan geen verbinding met de GPM maken"
+ CLEAR_IMAGES_BY_DEFAULT: "Image cache standaard verwijderen"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "Standaard worden alle afbeeldingen bij alle cache clears verwijderd, dit kan uitgeschakeld worden"
+ CLI_COMPATIBILITY: "CLI-compatibiliteit"
+ CLI_COMPATIBILITY_HELP: "Zorgt ervoor dat alleen niet-vluchtige Cache drivers gebruikt worden (bestand, redis, memcache, enz.)"
+ REINSTALL_PLUGIN: "Plugin herinstalleren"
+ REINSTALL_THEME: "Thema herinstalleren"
+ REINSTALL_THE: "%s herinstalleren"
+ CONFIRM_REINSTALL: "Weet je zeker dat je %s opnieuw wil installeren?"
+ REINSTALLED_SUCCESSFULLY: "%s is succesvol opnieuw geïnstalleerd"
+ ERROR_REINSTALLING_THE: "Fout opgetreden bij het herinstalleren van %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Pakket %s succesvol geïnstalleerd"
+ REINSTALLATION_FAILED: "Installatie mislukt"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "De geïnstalleerde versie is niet de nieuwste versie. Door op 'doorgaan' te klikken, wordt de huidige versie verwijderd en de nieuwste versie geïnstalleerd"
+ TOOLS: "Gereedschap"
+ DIRECT_INSTALL: "Direct installeren"
+ NO_PACKAGE_NAME: "De naam van het pakket is niet vermeld"
+ PACKAGE_EXTRACTION_FAILED: "Pakket uitpakken is mislukt"
+ NOT_VALID_GRAV_PACKAGE: "Dit is geen geldig Grav pakket"
+ NAME_COULD_NOT_BE_DETERMINED: "Naam kon niet worden vastgesteld"
+ CANNOT_OVERWRITE_SYMLINKS: "Symlinks kunnen niet overschreven worden."
+ ZIP_PACKAGE_NOT_FOUND: "ZIP bestand kan niet gevonden worden"
+ GPM_OFFICIAL_ONLY: "Alleen officiële GPM"
+ GPM_OFFICIAL_ONLY_HELP: "Sta directe installaties alleen toe van de officiële GPM repository."
+ NO_CHILD_TYPE: "Er is geen child type voor deze raw route"
+ SORTABLE_PAGES: "Sorteerbare pagina's:"
+ UNSORTABLE_PAGES: "Niet sorteerbare pagina's:"
+ ADMIN_SPECIFIC_OVERRIDES: "Admin specifieke overschrijvingen"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Volgorde van weergave"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "De volgorde van de onderliggende pagina's moeten worden weergegeven in 'Pagina's' van de Admin plugin"
+ PWD_PLACEHOLDER: "Complexe string van minstens 8 tekens lang"
+ PWD_REGEX: "Wachtwoord Regex"
+ PWD_REGEX_HELP: "Wachtwoord moet ten minste één nummer, één hoofdletter, één kleine letter en minstens 8 of meer tekens bevatten"
+ USERNAME_PLACEHOLDER: "Enkel kleine tekens, bijvoorbeeld 'admin'"
+ USERNAME_REGEX: "Gebruikersnaam Regex"
+ USERNAME_REGEX_HELP: "Standaard: enkel kleine letters, tekens, cijfers, streepjes en onderstrepingstekens. 3 - 16 tekens"
+ CONTENT_PADDING: "Opvulling van inhoud"
+ CONTENT_PADDING_HELP: "Uit- of aanzetten van content padding rond de content om meer ruimte te creëren"
+ ENABLE_AUTO_METADATA: "Automatische metadata van Exif"
+ ENABLE_AUTO_METADATA_HELP: "Genereer metadata bestanden automatisch voor afbeeldingen met exif informatie"
+ 2FA_TITLE: "2-Factor Authenticatie"
+ 2FA_INSTRUCTIONS: "##### 2-Factor Authenticatie\nJe hebt **2FA** aanstaan op dit account. Gebruik je **2FA** App om de huidige **6-digit code** te gebruiken om het login proces te voltooien"
+ 2FA_REGEN_HINT: "Het hergenereren van het geheim zal vereisen dat je de Authenticatie App moet updaten"
+ 2FA_LABEL: "Admin Toegang"
+ 2FA_FAILED: "Ongeldige 2-Factor Autenticatie code, probeer het opnieuw.."
+ 2FA_ENABLED: "2FA Ingeschakeld"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "2FA Geheim"
+ 2FA_SECRET_HELP: "Scan deze QR code in op je [Authenticatie App](https://learn.getgrav.org/admin-panel/2fa#apps). Het is ook een goed idee om van het geheim een backup te nemen op een veilige lokatie, in geval dat je de app zou moeten herinstalleren. Check de [Grav documentatie](https://learn.getgrav.org/admin-panel/2fa) voor meer informatie"
+ 2FA_REGENERATE: "Hergenereer"
+ FORCE_LOWERCASE_URLS: "Forceer 'lowercase' URLs"
+ FORCE_LOWERCASE_URLS_HELP: "Standaard zal Grav alle slugs en routes in 'lowercase' zetten. Met dit uitgeschakeld te zetten, zullen 'uppercase' slugs en routes gebruikt kunnen worden"
+ INTL_ENABLED: "Intl module integratie"
+ INTL_ENABLED_HELP: "Gebruik Intl PHP module en vergelijk om UTF8 gebaseerde collecties te sorteren"
+ VIEW_SITE_TIP: "Bekijk site"
+ TOOLS_DIRECT_INSTALL_TITLE: "Snelle installatie van Grav packages"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "Installeer package door een ZIP te uploaden"
+ TOOLS_DIRECT_INSTALL_UPLOAD_DESC: "U kunt gemakkelijk een geldig Grav thema of plugin installeren, of zelfs Grav updaten door de ZIP package met deze methode. Deze package moet niet geregistreerd worden door de GPM en laat toe dat je makkelijk kunt terugrollen naar een eerdere versie of installatie om te testen."
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "Installeer package via URL referentie"
+ TOOLS_DIRECT_INSTALL_URL_DESC: "Als alternatief kunt u ook een volledige URL refereren naar het ZIP bestand van de package en het installeren via die URL."
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Upload en installeer"
+ ROUTE_OVERRIDES: "Route overschrijvingen"
+ ROUTE_DEFAULT: "Standaard Route"
+ ROUTE_CANONICAL: "Oorspronkelijke Route"
+ ROUTE_ALIASES: "Route pseudoniemen"
+ OPEN_NEW_TAB: "Openen in nieuwe tab"
+ SCHEDULER_NOT_ENABLED: "Niet Ingeschakeld"
+ SECURITY: "Veiligheid"
+ XSS_SECURITY: "XSS Veiligheid voor Inhoud"
+ XSS_WHITELIST_PERMISSIONS: "Whitelist Machtigingen"
+ XSS_MOZ_BINDINGS: "Filter Moz bindingen"
+ XSS_HTML_INLINE_STYLES: "Filter HTML inline-stijlen"
+ XSS_DANGEROUS_TAGS: "Filter gevaarlijke HTML-codes"
+ XSS_DANGEROUS_TAGS_LIST: "Lijst van gevaarlijke HTML-tags"
+ UPLOADS_DANGEROUS_EXTENSIONS: "Gevaarlijke Extensies"
+ REPORTS: "Rapportages"
diff --git a/user/plugins/admin/languages/no.yaml b/user/plugins/admin/languages/no.yaml
new file mode 100644
index 0000000..689a6fc
--- /dev/null
+++ b/user/plugins/admin/languages/no.yaml
@@ -0,0 +1,686 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Dette er en betaversjon! Bruk denne i produksjon på egen risiko..."
+ ADMIN_REPORT_ISSUE: "Oppdaget et problem? Vennligst rapport det på GitHub."
+ EMAIL_FOOTER: "Drevet av Grav - Det moderne flat file CMSet"
+ LOGIN_BTN: "Logg inn"
+ LOGIN_BTN_FORGOT: "Glemt"
+ LOGIN_BTN_RESET: "Nullstill passord"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Sende tilbakestillingsintruksjoner"
+ LOGIN_BTN_CLEAR: "Tøm skjema"
+ LOGIN_BTN_CREATE_USER: "Opprett bruker"
+ LOGIN_LOGGED_IN: "Innloggingen var vellykket"
+ LOGIN_FAILED: "Innloggingen feilet"
+ LOGGED_OUT: "Du har nå logget ut"
+ RESET_NEW_PASSWORD: "Skriv inn et nytt passord …"
+ RESET_LINK_EXPIRED: "Tilbakestillingslenken er utløpt, vennligst prøv igjen"
+ RESET_PASSWORD_RESET: "Passordet ble tilbakestilt"
+ RESET_INVALID_LINK: "Ugyldig tilbakestillingslenke, prøv igjen"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Instruksjonene for å tilbakestille passordet har blitt sendt via e-post til %s"
+ FORGOT_FAILED_TO_EMAIL: "Kunne ikke sende instruksjoner, prøv på nytt senere"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Kan ikke tilbakestille passord %s, ingen e-postadresse er oppgitt"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Brukeren med brukernavn %s eksisterer ikke"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Kan ikke tilbakestille passordet. Denne siden er ikke konfigurert til å sende e-post"
+ FORGOT_EMAIL_SUBJECT: "%s tilbakestillingsforespørsel for passord"
+ FORGOT_EMAIL_BODY: "Tilbakestilling av passord Kjære %1$s,
En forespørsel ble gjort på %4$s for å tilbakestille passordet.
Klikk her for å tilbakestille passordet
Eventuelt, kopier over følgende URL til din nettlesers adresselinje:
%2$s
Vennligst hilsen, %3$s
"
+ MANAGE_PAGES: "Administrer sider"
+ CONFIGURATION: "Konfigurasjon"
+ PAGES: "Sider"
+ PLUGINS: "Utvidelser"
+ PLUGIN: "Utvidelse"
+ THEMES: "Temaer"
+ LOGOUT: "Logg ut"
+ BACK: "Tilbake"
+ NEXT: "Neste"
+ PREVIOUS: "Forrige"
+ ADD_PAGE: "Legg til side"
+ ADD_MODULAR: "Legg til modulær"
+ MOVE: "Flytt"
+ DELETE: "Slett"
+ VIEW: "Visning"
+ SAVE: "Lagre"
+ NORMAL: "Normal"
+ EXPERT: "Ekspert"
+ EXPAND_ALL: "Utvid alle"
+ COLLAPSE_ALL: "Skjul alle"
+ ERROR: "Feil"
+ CLOSE: "Lukk"
+ CANCEL: "Avbryt"
+ CONTINUE: "Fortsett"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Bekreftelse påkrevd"
+ MODAL_CHANGED_DETECTED_TITLE: "Endringer oppdaget"
+ MODAL_CHANGED_DETECTED_DESC: "Du har ulagrede endringer. Er du sikker på at du vil forlate siden uten å lagre?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Bekreftelse påkrevd"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Er du sikker på at du vil slette denne filen? Denne handlingen kan ikke angres."
+ ADD_FILTERS: "Legg til filter"
+ SEARCH_PAGES: "Søk etter sider"
+ VERSION: "Versjon"
+ WAS_MADE_WITH: "Ble laget med"
+ BY: "Av"
+ UPDATE_THEME: "Oppdater tema"
+ UPDATE_PLUGIN: "Oppdater utvidelse"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "av dette temaet er nå tilgjengelig"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "av denne utvidelsen er nå tilgjengelig"
+ AUTHOR: "Forfatter"
+ HOMEPAGE: "Forside"
+ DEMO: "Demo"
+ BUG_TRACKER: "Bug tracker"
+ KEYWORDS: "Nøkkelord"
+ LICENSE: "Lisens"
+ DESCRIPTION: "Beskrivelse"
+ README: "Les meg"
+ REMOVE_THEME: "Fjern tema"
+ INSTALL_THEME: "Installer tema"
+ THEME: "Tema"
+ BACK_TO_THEMES: "Tilbake til temaer"
+ BACK_TO_PLUGINS: "Tilbake til plugins"
+ CHECK_FOR_UPDATES: "Se etter oppdateringer"
+ ADD: "Legg til"
+ CLEAR_CACHE: "Tøm cache"
+ CLEAR_CACHE_ALL_CACHE: "Alt mellomlager"
+ CLEAR_CACHE_ASSETS_ONLY: "Bare ressurser"
+ CLEAR_CACHE_IMAGES_ONLY: "Kun bilder"
+ CLEAR_CACHE_CACHE_ONLY: "Kun cache"
+ CLEAR_CACHE_TMP_ONLY: "Bare midlertidig"
+ DASHBOARD: "Dashbord"
+ UPDATES_AVAILABLE: "Oppdateringer tilgjengelig"
+ DAYS: "Dager"
+ UPDATE: "Oppdater"
+ BACKUP: "Sikkerhetskopi"
+ STATISTICS: "Statistikk"
+ TODAY: "I dag"
+ WEEK: "Uke"
+ MONTH: "Måned"
+ LATEST_PAGE_UPDATES: "Siste sideoppdateringer"
+ MAINTENANCE: "Vedlikehold"
+ UPDATED: "Oppdatert"
+ MON: "Man"
+ TUE: "Tir"
+ WED: "Ons"
+ THU: "Tor"
+ FRI: "Fre"
+ SAT: "Lør"
+ SUN: "Søn"
+ COPY: "Kopier"
+ EDIT: "Rediger"
+ CREATE: "Opprett"
+ GRAV_ADMIN: "Grav Admin"
+ GRAV_OFFICIAL_PLUGIN: "Grav offisiell plugin"
+ GRAV_OFFICIAL_THEME: "Grav offisielt tema"
+ PLUGIN_SYMBOLICALLY_LINKED: "Denne utvidelsen er symbolsk koblet. Oppdateringer vil ikke oppdages."
+ THEME_SYMBOLICALLY_LINKED: "Dette temaet er symbolsk koblet. Oppdateringer vil ikke oppdages"
+ REMOVE_PLUGIN: "Fjern plugin"
+ INSTALL_PLUGIN: "Installer plugin"
+ AVAILABLE: "Tilgjengelig"
+ INSTALLED: "Installert"
+ INSTALL: "Installer"
+ ACTIVE_THEME: "Aktivt tema"
+ SWITCHING_TO: "Bytter til"
+ SWITCHING_TO_DESCRIPTION: "Ved å bytte til et annet tema, er det ingen garanti for at alle layout-sidene støttes. Dette kan potensielt forårsake feil når du prøver å laste nevnte sider."
+ SWITCHING_TO_CONFIRMATION: "Vil du fortsette og bytte til temaet"
+ CREATE_NEW_USER: "Lag en ny bruker"
+ REMOVE_USER: "Fjern bruker"
+ ACCESS_DENIED: "Ingen tilgang"
+ ACCOUNT_NOT_ADMIN: "kontoen din har ikke administratorrettigheter"
+ PHP_INFO: "PHP Info"
+ INSTALLER: "Installatør"
+ AVAILABLE_THEMES: "Tilgjengelige temaer"
+ AVAILABLE_PLUGINS: "Tilgjengelige utvidelser"
+ INSTALLED_THEMES: "Installerte temaer"
+ INSTALLED_PLUGINS: "Installerte utvidelser"
+ BROWSE_ERROR_LOGS: "Bla igjennom feilmeldingsloggene"
+ SITE: "Nettsted"
+ INFO: "Info"
+ SYSTEM: "System"
+ USER: "Bruker"
+ ADD_ACCOUNT: "Ligg til konto"
+ SWITCH_LANGUAGE: "Bytt språk"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Utvidelsen ble aktivert"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Utvidelsen ble deaktivert"
+ SUCCESSFULLY_CHANGED_THEME: "Temaet ble endret"
+ INSTALLATION_FAILED: "Installasjonen mislyktes"
+ INSTALLATION_SUCCESSFUL: "Installasjonen var vellykket"
+ UNINSTALL_FAILED: "Avinstalleringen mislyktes"
+ UNINSTALL_SUCCESSFUL: "Avinstalleringen var vellykket"
+ SUCCESSFULLY_SAVED: "Lagret"
+ SUCCESSFULLY_COPIED: "Kopiert"
+ REORDERING_WAS_SUCCESSFUL: "Omorganiseringen var vellykket"
+ SUCCESSFULLY_DELETED: "Slettet"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Språket ble endret"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Du har ikke rettigheter til å gjøre dette"
+ CACHE_CLEARED: "Mellomlageret ble tømt"
+ METHOD: "Metode"
+ ERROR_CLEARING_CACHE: "Tømming av mellomlageret mislyktes"
+ AN_ERROR_OCCURRED: "En feil oppstod"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Sikkerhetskopien kan nå lastes ned"
+ DOWNLOAD_BACKUP: "Last ned sikkerhetskopi"
+ PAGES_FILTERED: "Filtrerte sider"
+ NO_PAGE_FOUND: "Ingen side ble funnet"
+ INVALID_PARAMETERS: "Ugyldige parametere"
+ NO_FILES_SENT: "Inge fil ble sendt"
+ EXCEEDED_FILESIZE_LIMIT: "Overgikk PHP-konfigurasjonens filstørrelsesbegrensning"
+ UNKNOWN_ERRORS: "Ukjente feil"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Overgikk Grav-konfigurasjonens filstørrelsesbegrensning"
+ UNSUPPORTED_FILE_TYPE: "Filtypen støttes ikke"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Kunne ikke flytte den opplastede filen."
+ FILE_UPLOADED_SUCCESSFULLY: "Filen ble vellykket opplastet"
+ FILE_DELETED: "Fil slettet"
+ FILE_COULD_NOT_BE_DELETED: "Filen kunne ikke slettes"
+ FILE_NOT_FOUND: "Fant ikke filen"
+ NO_FILE_FOUND: "Ingen filer funnet"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav ble oppdatert til"
+ GRAV_UPDATE_FAILED: "Grav-oppdatering mislyktes"
+ EVERYTHING_UPDATED: "Alt oppdatert"
+ UPDATES_FAILED: "Oppdateringer mislyktes"
+ AVATAR_BY: "Avatar av"
+ AVATAR_UPLOAD_OWN: "Eller last opp din egen..."
+ LAST_BACKUP: "Siste sikkerhetskopiering"
+ FULL_NAME: "Fullt navn"
+ USERNAME: "Brukernavn"
+ EMAIL: "E-post"
+ USERNAME_EMAIL: "Brukernavn eller E-post"
+ PASSWORD: "Passord"
+ PASSWORD_CONFIRM: "Bekreft passord"
+ TITLE: "Tittel"
+ LANGUAGE: "Språk"
+ ACCOUNT: "Konto"
+ EMAIL_VALIDATION_MESSAGE: "Må være en gyldig e-postadresse"
+ PASSWORD_VALIDATION_MESSAGE: "Passordet må inneholde minst ett tall og en stor og liten bokstav, samt inneholde minst 8 tegn"
+ LANGUAGE_HELP: "Angi favoritt språk"
+ MEDIA: "Media"
+ DEFAULTS: "Standard"
+ SITE_TITLE: "Nettstedets tittel"
+ SITE_TITLE_PLACEHOLDER: "Tittel for hele nettstedet"
+ SITE_TITLE_HELP: "Standardtittelen for nettsiden, brukes ofte i temaer"
+ SITE_DEFAULT_LANG: "Standardspråk"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Standardspråk som skal brukes av temaets -kode"
+ SITE_DEFAULT_LANG_HELP: "Standardspråk som skal brukes av temaets -kode"
+ DEFAULT_AUTHOR: "Forhåndsvalgt forfatter"
+ DEFAULT_AUTHOR_HELP: "En forhåndsvalgt forfatters navn, som ofte brukes i temaer eller sideinnhold"
+ DEFAULT_EMAIL: "Forhåndsvalgt e-post"
+ DEFAULT_EMAIL_HELP: "En forhåndsvalgt e-post, som referanse i temaer eller sider"
+ TAXONOMY_TYPES: "Taksonomityper"
+ TAXONOMY_TYPES_HELP: "Taksonomityper defineres her hvis du ønsker å bruke dem i sider"
+ PAGE_SUMMARY: "Sidesammendrag"
+ ENABLED: "Aktivert"
+ ENABLED_HELP: "Aktiver sidesammendrag (sammendraget gir det samme innholdet som på siden)"
+ 'YES': "Ja"
+ 'NO': "Nei"
+ SUMMARY_SIZE: "Sammendragslengde"
+ SUMMARY_SIZE_HELP: "Antall tegn fra en side som skal brukes i et sidesammendrag"
+ FORMAT: "Fomat"
+ FORMAT_HELP: "kort = bruk først forekomsten av skilletegn eller lengde; lang = skilletegn ignoreres"
+ SHORT: "Kort"
+ LONG: "Lang"
+ DELIMITER: "Skilletegn"
+ DELIMITER_HELP: "Skilletegn for sammendrag (standard er '===')"
+ METADATA: "Metadata"
+ METADATA_HELP: "Standard metadataverdier som vises på hver side med mindre de overstyres av siden"
+ NAME: "Navn"
+ CONTENT: "Innhold"
+ REDIRECTS_AND_ROUTES: "Viderekoblinger & Ruter"
+ CUSTOM_REDIRECTS: "Egendefinerte viderekoblinger"
+ CUSTOM_REDIRECTS_HELP: "ruter som omadresserer til andre sider. Standard Regex-erstatning er gyldig"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/ditt/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/din/omadressering"
+ CUSTOM_ROUTES: "Egendefinerte ruter"
+ CUSTOM_ROUTES_HELP: "ruter som er alias for andre sider. Standard Regex-erstatning er gyldig"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/ditt/alias/"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/din/rute"
+ FILE_STREAMS: "Filstrømmer"
+ DEFAULT: "Standard"
+ PAGE_MEDIA: "Side-Media"
+ OPTIONS: "Innstillinger"
+ PUBLISHED: "Publisert"
+ PUBLISHED_HELP: "Som standard publiseres siden, med mindre du eksplisitt angir published: false eller gjennom publish_date angir en dato i fremtiden, eller gjennom unpublish_date angir en dato i fortiden"
+ DATE: "Dato"
+ DATE_HELP: "Dato-variabelen lar deg spesifisere en dato knyttet til denne siden."
+ PUBLISHED_DATE: "Publisert Dato"
+ PUBLISHED_DATE_HELP: "Kan gi en dato for å automatisk aktivere publisering."
+ UNPUBLISHED_DATE: "Avpublisert Dato"
+ UNPUBLISHED_DATE_HELP: "Kan gi en dato for å automatisk aktivere avpublisering."
+ ROBOTS: "Roboter"
+ TAXONOMIES: "Taksonomier"
+ TAXONOMY: "Taksonomi"
+ ADVANCED: "Avansert"
+ SETTINGS: "Innstillinger"
+ FOLDER_NUMERIC_PREFIX: "Mappens Numeriske Prefiks"
+ FOLDER_NUMERIC_PREFIX_HELP: "Numerisk prefiks som angir manuell rangering og impliserer synlighet"
+ FOLDER_NAME: "Mappenavn"
+ FOLDER_NAME_HELP: "Mappenavnet som lagres i filsystemet for denne siden"
+ PARENT: "Forelder"
+ DEFAULT_OPTION_ROOT: "- Rot -"
+ DEFAULT_OPTION_SELECT: "- Valg -"
+ DISPLAY_TEMPLATE: "Vis mal"
+ DISPLAY_TEMPLATE_HELP: "Sidetype bestemmer hvilken twig mal som skal tegne ut siden"
+ BODY_CLASSES: "Body klasser"
+ ORDERING: "Sortering"
+ PAGE_ORDER: "Siderekkefølge"
+ OVERRIDES: "Overstyrer"
+ MENU: "Meny"
+ MENU_HELP: "Teksten som brukes i en meny. Hvis den ikke finnes, blir tittel brukt."
+ SLUG: "Kortversjon"
+ SLUG_HELP: "Kortversjonen brukes til sidens del av url-en"
+ SLUG_VALIDATE_MESSAGE: "Kortversjonen kan kun inneholde `små` bokstaver og bindestrek"
+ PROCESS: "Prosess"
+ PROCESS_HELP: "Kontroller hvordan sidene blir prosessert. Kan settes til pr side i stedet for globalt."
+ DEFAULT_CHILD_TYPE: "Standard type `barn`"
+ USE_GLOBAL: "Bruk globalt"
+ ROUTABLE: "Kan rutes til"
+ ROUTABLE_HELP: "Om denne siden skal være tilgjengelig via en url"
+ CACHING: "Hurtigbuffer"
+ VISIBLE: "Synlig"
+ VISIBLE_HELP: "Bestemmer om siden er synlig ved navigering."
+ DISABLED: "Deaktivert"
+ ITEMS: "Elementer"
+ ORDER_BY: "Sorter etter"
+ ORDER: "Sortering"
+ FOLDER: "Folder"
+ ASCENDING: "Stigende"
+ DESCENDING: "Synkende"
+ ADD_MODULAR_CONTENT: "Legg til modulært innhold"
+ PAGE_TITLE: "Sidetittel"
+ PAGE_TITLE_HELP: "Tittelen på siden"
+ PAGE: "Side"
+ MODULAR_TEMPLATE: "Modulær mal"
+ FRONTMATTER: "Ingress"
+ FILENAME: "Filnavn"
+ PARENT_PAGE: "`Foreldre`-side"
+ HOME_PAGE: "Hjemmeside"
+ HOME_PAGE_HELP: "Siden som Grav vil bruke som standard startside"
+ DEFAULT_THEME: "Standardtema"
+ DEFAULT_THEME_HELP: "Sett standardtema for Grav (standard er Antimatter)"
+ TIMEZONE: "Tidssone"
+ TIMEZONE_HELP: "Overstyrer standard tidssone på server"
+ SHORT_DATE_FORMAT: "Kort visningsformat for dato"
+ SHORT_DATE_FORMAT_HELP: "Sett kort visningsformat for dato som temaene kan bruke"
+ LONG_DATE_FORMAT: "Langt visningsformat for dato"
+ LONG_DATE_FORMAT_HELP: "Sett langt visningsformat for dato som temaene kan bruke"
+ DEFAULT_ORDERING: "Standardsortering"
+ DEFAULT_ORDERING_HELP: "Sider i en liste vil bli rendret i denne rekkefølgen med mindre den blir overskrevet"
+ DEFAULT_ORDERING_DEFAULT: "Standard - basert på foldernavn"
+ DEFAULT_ORDERING_FOLDER: "Folder - basert på foldernavn uten prefix"
+ DEFAULT_ORDERING_TITLE: "Tittel - basert på tittelfeltet i header"
+ DEFAULT_ORDERING_DATE: "Dato - basert på dato-feltet i topptekst"
+ DEFAULT_ORDER_DIRECTION: "Standard sorteringsmetode"
+ DEFAULT_ORDER_DIRECTION_HELP: "Retningen på sider i en liste"
+ DEFAULT_PAGE_COUNT: "Standard sidetelling"
+ DEFAULT_PAGE_COUNT_HELP: "Standard maksimalt antall sider i en liste"
+ DATE_BASED_PUBLISHING: "Dato-basert publisering"
+ DATE_BASED_PUBLISHING_HELP: "Automatisk (av)publisere innlegg basert på dato"
+ EVENTS: "Hendelser"
+ EVENTS_HELP: "Aktivere eller Deaktivere bestemte hendelser. Deaktivering kan ødelegge utvidelser"
+ REDIRECT_DEFAULT_ROUTE: "Omdirigere standardrute"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Automatisk omdirigere til en sides standardrute"
+ LANGUAGES: "Språk"
+ SUPPORTED: "Støttet"
+ SUPPORTED_HELP: "Kommadelt liste for tobokstavers språkkoder (for eksempel 'en,fr,de')"
+ TRANSLATIONS_ENABLED: "Oversettelser aktivert"
+ TRANSLATIONS_ENABLED_HELP: "Støtter oversettelser i Grav, plugins og extensions"
+ TRANSLATIONS_FALLBACK: "Oversettelsers reserveløsning"
+ TRANSLATIONS_FALLBACK_HELP: "Anvend reserveløsninger hvis aktivt språk ikke finnes"
+ ACTIVE_LANGUAGE_IN_SESSION: "Aktivt språk i økten"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Lagre aktivt språk i økten"
+ HTTP_HEADERS: "HTTP-topptekst"
+ EXPIRES: "Utløper"
+ EXPIRES_HELP: "Angir utløpt-topptekst. Verdien er i sekunder."
+ CACHE_CONTROL: "HTTP Cache-kontroll"
+ CACHE_CONTROL_HELP: "Angi en gyldig bufferstyrings verdi som \"ingen-cache, ingen-lagring, må re-validere\""
+ LAST_MODIFIED: "Sist endret"
+ LAST_MODIFIED_HELP: "Angir sist-endret-topptekst som kan bidra til å optimalisere mellomtjener og nettleser-hurtiglager"
+ ETAG: "ETag"
+ ETAG_HELP: "Angir etag-topptekst for å identifisere når en side er endret"
+ VARY_ACCEPT_ENCODING: "Godta variert koding"
+ VARY_ACCEPT_ENCODING_HELP: "Angir `Vary: Accept Encoding`-topptekst til støtte for mellomtjener og CDN-hurtiglager"
+ MARKDOWN_EXTRA: "Markdown ekstra"
+ MARKDOWN_EXTRA_HELP: "Aktivere standardstøtte for Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ AUTO_LINE_BREAKS: "Automatisk linjeskift"
+ AUTO_LINE_BREAKS_HELP: "Aktivere støtte for automatisk linjeskift i markdown"
+ AUTO_URL_LINKS: "Automatiske URL-lenker"
+ AUTO_URL_LINKS_HELP: "Aktiver automatisk konvertering av lenker til HTML-hyperlenker"
+ ESCAPE_MARKUP: "Verne kode"
+ ESCAPE_MARKUP_HELP: "Verne kode i HTML-enheter"
+ CACHING_HELP: "Global på/av-bryteren for å aktivere/deaktivere Grav hurtigbufring"
+ CACHE_CHECK_METHOD: "Cache kontrollmetode"
+ CACHE_CHECK_METHOD_HELP: "Velg metoden som Grav bruker for å kontrollere om sidefiler har blitt endret."
+ CACHE_DRIVER: "Cache driver"
+ CACHE_DRIVER_HELP: "Velg hvilken cache driver Grav skal bruke. 'Auto Detect' forsøker å finne den beste for deg"
+ CACHE_PREFIX: "Cache prefix"
+ CACHE_PREFIX_HELP: "En identifikator for deler av Grav key. Ikke bytt med mindre du vet hva du gjør."
+ CACHE_PREFIX_PLACEHOLDER: "Utledet fra base-url (overstyr ved å legge inn en tilfeldig streng)"
+ LIFETIME: "Levetid"
+ LIFETIME_HELP: "Setter cache levetid i sekunder. 0 = evig"
+ GZIP_COMPRESSION: "Gzip-kompresjon"
+ GZIP_COMPRESSION_HELP: "Aktiver gzip-kompresjon av Grav-siden for økt ytelse."
+ TWIG_TEMPLATING: "Twig-maler"
+ TWIG_CACHING: "Twig-caching"
+ TWIG_CACHING_HELP: "Kontroller Twigs cache-mekanisme. La denne være aktivert for best ytelse."
+ TWIG_DEBUG: "Twig feilsøking"
+ TWIG_DEBUG_HELP: "Tillater å velge bort Twigs feilsøkings-utvidelse."
+ DETECT_CHANGES: "Oppdag endringer"
+ DETECT_CHANGES_HELP: "Twig vil automatisk rekompilere bufferen hvis den oppdager forandringer i Twig-malene."
+ AUTOESCAPE_VARIABLES: "Auto-escape variabler"
+ AUTOESCAPE_VARIABLES_HELP: "Auto-escaper alle variabler. Dette kommer trolig til å bryte websidene dine."
+ ASSETS: "Ressurser"
+ CSS_PIPELINE: "Css pipeline"
+ CSS_PIPELINE_HELP: "Css-pipeline slår sammen flere css-ressurser til en fil."
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Inkluder eksterne ressurser i css-pipeline."
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Eksterne URLer har relative filreferanser og bør ikke brukes i pipelinen."
+ CSS_PIPELINE_BEFORE_EXCLUDES: "Prosesser css-pipeline først."
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Prosesser css-pipeline før andre css-referanser som ikke er inkludert."
+ CSS_MINIFY: "Css-minifisering"
+ CSS_MINIFY_HELP: "Minifiser css når pipeline kjøres."
+ CSS_MINIFY_WINDOWS_OVERRIDE: "Css minifiser Windows-overstyring."
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Minifiser overstyring for Windows OS. Er `false` som standard p.g.a. ThreadStackSize."
+ CSS_REWRITE: "Css-overskriving"
+ CSS_REWRITE_HELP: "Overskriv alle relative url-er i css når pipeline kjøres."
+ JAVASCRIPT_PIPELINE: "Javascript pipeline"
+ JAVASCRIPT_PIPELINE_HELP: "Js-pipeline er sammenslåing av flere js-ressurser til en fil."
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Inkluder eksterne ressurser i js-pipeline."
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Eksterne url-er har av og til relative fil-referanser og bør ikke inn i pipelinen."
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Prosesser js-pipeline først."
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Prosesser js-pipeline før andre js-referanser som ikke er inkludert."
+ JAVASCRIPT_MINIFY: "Javascript minifisering"
+ JAVASCRIPT_MINIFY_HELP: "Aktiver tidsstempel på ressurser."
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Aktiver timestamp på ressurser."
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Aktiver timestamp på ressurser."
+ COLLECTIONS: "Samlinger"
+ ERROR_HANDLER: "Feilhåndtering"
+ DISPLAY_ERRORS: "Visning av feil"
+ DISPLAY_ERRORS_HELP: "Vis full backtrace-style feilside."
+ LOG_ERRORS: "Logge feil"
+ LOG_ERRORS_HELP: "Logg feil til /logs-folder"
+ DEBUGGER: "Feilsøking"
+ DEBUGGER_HELP: "Aktiver Grav feilsøker og følgende innstillinger"
+ DEBUG_TWIG: "Feilsøk Twig"
+ DEBUG_TWIG_HELP: "Aktiver feilsøking av Twig-maler"
+ SHUTDOWN_CLOSE_CONNECTION: "Steng av lukk connection"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Lukk connection før onShutdown() kalles. `false` for feilsøking"
+ DEFAULT_IMAGE_QUALITY: "Standard bilde-kvalitet"
+ DEFAULT_IMAGE_QUALITY_HELP: "Standard bilde-kvalitet som brukes ved resampling eller caching av bilder (85%)"
+ CACHE_ALL: "Cache alle bilder"
+ CACHE_ALL_HELP: "Kjør alle bilder gjennom Gravs cache-system selv om de ikke har noen media-manipulasjoner"
+ IMAGES_DEBUG: "Visning av info ved bildebehandling"
+ IMAGES_DEBUG_HELP: "Legg et lag over bildet som viser pixel-dybden når man arbeider med retina f.eks."
+ UPLOAD_LIMIT: "Filopplastingsgrense"
+ UPLOAD_LIMIT_HELP: "Sett maksimum opplastingsstørrelse i bytes (0 is ubegrenset)"
+ ENABLE_MEDIA_TIMESTAMP: "Aktiver tidsstempel på media"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Legger til et tidsstempel basert på sist endret-dato på hvert media-ressurs"
+ SESSION: "Sesjon"
+ SESSION_ENABLED_HELP: "Aktiver sesjonsstøtte i Grav"
+ TIMEOUT: "Tidsutløp"
+ TIMEOUT_HELP: "Setter sesjonens tidsutløp i sekunder"
+ SESSION_NAME_HELP: "En identifikator som brukes til å lage navnet på sesjons-cookien"
+ ABSOLUTE_URLS: "Absolutte url-er"
+ ABSOLUTE_URLS_HELP: "Absolutte eller relative url-er for `base_url`"
+ PARAMETER_SEPARATOR: "Parameter-separator"
+ PARAMETER_SEPARATOR_HELP: "Separator for innsendte parametre som kan endres for Apache på Windows"
+ TASK_COMPLETED: "Oppgave fullført"
+ EVERYTHING_UP_TO_DATE: "Alt er oppdatert"
+ UPDATES_ARE_AVAILABLE: "oppdatering(er) er tilgjengelige"
+ IS_AVAILABLE_FOR_UPDATE: "er klar for oppdatering"
+ IS_NOW_AVAILABLE: "er nå tilgjengelig"
+ CURRENT: "Nåværende"
+ UPDATE_GRAV_NOW: "Oppdater Grav nå"
+ GRAV_SYMBOLICALLY_LINKED: "Grav er lenket med `symbolske` lenker. Oppgradering vil ikke være tilgjengelig"
+ UPDATING_PLEASE_WAIT: "Oppdaterer... vennligst vent, laster ned"
+ OF_THIS: "av dette"
+ OF_YOUR: "av din"
+ HAVE_AN_UPDATE_AVAILABLE: "har en oppdatering tilgjengelig"
+ SAVE_AS: "Lagre som"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Er du sikker på at du vil slette denne siden og alle dens barn? Hvis siden er oversatt til andre språk, vil oversettingene beholdes og må slettes separat. Ellers vil sidefolderen bli slettet sammen med dens undersider. Denne handlingen kan ikke gjøres om."
+ AND: "og"
+ UPDATE_AVAILABLE: "Oppdatering tilgjengelig"
+ METADATA_KEY: "Nøkkel (f.eks. 'Nøkkelord')"
+ METADATA_VALUE: "Verdi (f.eks. 'Blogg, Grav')"
+ USERNAME_HELP: "Brukernavn må være mellom 3 og 16 tegn, inkludert `små` bokstaver, tall, understrekning og bindestrek. `Store` bokstaver, mellomrom, og spesialtegn er ikke tillatt"
+ FULLY_UPDATED: "Fullt oppdatert"
+ SAVE_LOCATION: "Lagre sted"
+ PAGE_FILE: "Sidemal"
+ PAGE_FILE_HELP: "Navnet på sidemalen, og standard visningsmal for denne siden"
+ NO_USER_ACCOUNTS: "Ingen brukerkonto funnet, vennligst opprett en først..."
+ REDIRECT_TRAILING_SLASH: "Viderekoble avsluttende skråstrek"
+ REDIRECT_TRAILING_SLASH_HELP: "Gjør en 301 viderekobling heller enn å håndtere url-er med avsluttende skråstreker usynlig."
+ DEFAULT_DATE_FORMAT: "Datoformat for siden"
+ DEFAULT_DATE_FORMAT_HELP: "Datoformat for siden brukt av Grav. Som standard, forsøker Grav å gjette ditt datoformat, men du kan spesifisere et format med PHPs dato-syntaks (f.eks.: Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Gjett automatisk"
+ IGNORE_FILES: "Ignorer filer"
+ IGNORE_FILES_HELP: "Spesifikke filer som skal ignoreres når sidene prosesseres."
+ IGNORE_FOLDERS: "Ignorer foldere"
+ IGNORE_FOLDERS_HELP: "Spesifikke foldere som skal ignoreres når sidene prosesseres."
+ HTTP_ACCEPT_LANGUAGE: "Sett språk fra nettleser"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Du kan forsøke å sette språket basert på `http_accept_language` header-tagg i nettleseren."
+ OVERRIDE_LOCALE: "Overstyr språkvalg"
+ OVERRIDE_LOCALE_HELP: "Overstyr apråk-innstillingene i PHP basert på eksisterende språk."
+ REDIRECT: "Viderekobling av siden"
+ REDIRECT_HELP: "Legg inn en siderute eller ekstern url som denne siden skal viderekobles til. F.eks. `/hva/som/helst` or `http:/hvasomhelst.no`."
+ PLUGIN_STATUS: "Status på utvidelser"
+ INCLUDE_DEFAULT_LANG: "Inkluder standard-språk"
+ INCLUDE_DEFAULT_LANG_HELP: "Dette vil sette kode for standardspråk foran alle url-er i dette språket. F.eks. engelsk `/en/blog/my-post`."
+ ALLOW_URL_TAXONOMY_FILTERS: "Url taksonomi-filter"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Du kan filtrere side-baserte samlinger via `/taxonomy:value`."
+ REDIRECT_DEFAULT_CODE: "Standard viderekoblingskode"
+ REDIRECT_DEFAULT_CODE_HELP: "Http-statuskode som skal brukes til viderekobling."
+ IGNORE_HIDDEN: "Ignorer skjulte filer"
+ IGNORE_HIDDEN_HELP: "Ignorer alle filer og foldere som starter med et punktum."
+ WRAPPED_SITE: "Inkludert nettsted"
+ WRAPPED_SITE_HELP: "For at tema/utvidelser skal vite om Grav er inkludert i en annen løsning."
+ FALLBACK_TYPES: "Tillatte fallback-typer"
+ FALLBACK_TYPES_HELP: "Tillatte filtyper som kan bli funnet hvis de blir aksessert via en side-rute. Standard er alle støttede mediatyper."
+ INLINE_TYPES: "Fallback-typer i nettleser"
+ INLINE_TYPES_HELP: "En liste over fil-typer som skal bli vist i nettleseren i stedet for å bli lastet ned."
+ APPEND_URL_EXT: "Legg til url-utvidelser"
+ APPEND_URL_EXT_HELP: "Legger til en egendefinert utvidelse til sidens url. Dette betyr at Grav vil lette etter malen: `..twig`."
+ PAGE_MODES: "Sidemoduser"
+ PAGE_TYPES: "Sidetyper"
+ ACCESS_LEVELS: "Tilgangsnivåer"
+ GROUPS: "Grupper"
+ GROUPS_HELP: "Liste av grupepr som brukeren tilhører."
+ ADMIN_ACCESS: "Admin-tilgang"
+ SITE_ACCESS: "Nettsted-tilgang"
+ INVALID_SECURITY_TOKEN: "Ugyldig sikkerhetssymbol"
+ ACTIVATE: "Aktiver"
+ TWIG_UMASK_FIX: "Umask fiks"
+ TWIG_UMASK_FIX_HELP: "Som standard oppretter Twig-cachede filer som 0755, fiks bytter dette til 0775."
+ CACHE_PERMS: "Cache-rettigheter"
+ CACHE_PERMS_HELP: "Standard folder-rettigheter for cache. Normalt 0755 eller 0775 avhengig av oppsett."
+ REMOVE_SUCCESSFUL: "Fjerning var vellykket"
+ REMOVE_FAILED: "Fjerning feilet"
+ HIDE_HOME_IN_URLS: "Skjul hjem-rute i url-er"
+ HIDE_HOME_IN_URLS_HELP: "Sikrer at standard rute for alle sider under hjemsiden ikke refererer til hjemsidens vanlige rute."
+ TWIG_FIRST: "Prosesser Twig først"
+ TWIG_FIRST_HELP: "Hvis du aktiverte Twig sideprosessering, da kan du konfigurere Twig til å prosessere før eller etter markdown"
+ SESSION_SECURE: "Sikker sesjon"
+ SESSION_SECURE_HELP: "Hvis aktivert, så indikerer dette at kommunikasjonen for denne cookien må være via en kryptert overføring. ADVARSEL: Aktiver denne bare på nettsteder som kjøres utelukkende på https"
+ SESSION_HTTPONLY: "Kun http"
+ SESSION_HTTPONLY_HELP: "Hvis aktivert, så indikerer denne at cookier bare skal bli brukt over http, og javascript-endringer er ikke tillatt"
+ REVERSE_PROXY: "Revers-proxy"
+ REVERSE_PROXY_HELP: "Aktiver denne om du er bak en revers-proxy og du har problemer med url-er som inneholder ulovlige porter"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Ugyldig startfelt på siden, kunne ikke lagre"
+ ADD_FOLDER: "Legg til folder"
+ PROXY_URL: "Proxy url"
+ PROXY_URL_HELP: "Legg inn proxy-tjener eller ip og port"
+ NOTHING_TO_SAVE: "Ingenting å lagre"
+ FILE_ERROR_ADD: "Det oppstod en feil når filen skulle legges til"
+ FILE_ERROR_UPLOAD: "Det oppstod en feil ved opplasting av fil"
+ FILE_UNSUPPORTED: "Filtypen er ikke støttet"
+ ADD_ITEM: "Legg til"
+ FILE_TOO_LARGE: "Filen er for stor til å bli lastet opp, maksimum tillatte størrelse er %s i følge dine php-innstillinger. øk `post_max_size` php-innstilling"
+ INSTALLING: "Installerer"
+ LOADING: "Laster..."
+ DEPENDENCIES_NOT_MET_MESSAGE: "Følgende avhengighet må fullføres først:"
+ ERROR_INSTALLING_PACKAGES: "Det oppstod en feil ved installering av pakken(e)"
+ INSTALLING_DEPENDENCIES: "Installerer avhengigheter..."
+ INSTALLING_PACKAGES: "Installerer pakke(r)..."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Installering av pakke(r) var vellykket."
+ READY_TO_INSTALL_PACKAGES: "Klar til å installere pakken(e)"
+ PACKAGES_NOT_INSTALLED: "Pakkene er ikke installert"
+ PACKAGES_NEED_UPDATE: "Pakkene er allerede installert, men de er for gamle"
+ PACKAGES_SUGGESTED_UPDATE: "Pakkene er allerede installert, og versjonen er ok, men vil bli oppdatert for å holde løsningen oppdatert"
+ REMOVE_THE: "Fjern %s"
+ CONFIRM_REMOVAL: "Er du sikker på at du vil slette %s?"
+ REMOVED_SUCCESSFULLY: "Fjerningen av %s var vellykket"
+ ERROR_REMOVING_THE: "Fjerningen av %s var mislykket"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "%s krevde følgende avhengigheter som ikke er påkrevd av andre installerte pakker. Hvis du ikke bruker dem, kan du fjerne dem direkte herfra."
+ READY_TO_UPDATE_PACKAGES: "Klar til å oppdatere pakken(e)"
+ ERROR_UPDATING_PACKAGES: "Det oppstod en feil ved oppdatering av pakken(e)"
+ UPDATING_PACKAGES: "Oppdaterer pakke(r)..."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Oppdatering av pakken(e) var vellykket."
+ UPDATING: "Oppdaterer"
+ GPM_RELEASES: "GPM utgivelser"
+ GPM_RELEASES_HELP: "Velg 'Testing' for å installere beta- eller testversjoner"
+ GPM_METHOD: "Metode for fjernhenting"
+ GPM_METHOD_HELP: "Når denne er satt til auto, vil Grav sjekke om fopen er tilgjengelig og bruke den, ellers falle tilbake til curl. For å tvinge den eller andre, endre innstilling."
+ GPM_VERIFY_PEER: "Godkjenning (SSL)"
+ GPM_VERIFY_PEER_HELP: "Noen tilbydere feiler med å verifisere getgrav.orgs SSL-sertifikat, noe som gjør at GPM ikke virker. Hvis dette gjelder for deg, kan det hjelpe å slå av denne innstillingen"
+ AUTO: "Auto"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stabil"
+ TESTING: "Testing"
+ FRONTMATTER_PROCESS_TWIG: "Prosesser startfelt Twig"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Når denne er aktivert, kan du bruke Twig konfigurasjonsvariabler på sidens startfelter."
+ FRONTMATTER_IGNORE_FIELDS: "Ignorer startfelter"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Visse startfelter kan inneholde Twig men bør ikke bli prosessert, som f.eks. 'forms'."
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Installasjon av pakken %s var vellykket."
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Rekkefølge for `parent`-innstillinger, sortering inaktivert."
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Siden er ikke synlig, sortering inaktivert."
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "Sortering via admin er ikke støttet fordi det er mer enn 200 elementer på samme nivå."
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Side-sortering er inaktivert for denne siden fordi Numerisk folder-prefix er ikke aktivert."
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "MERK: Du kan ikke legge til media-filer før du lagrer siden. Bare klikk 'Lagre' på toppen."
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "MERK: Siden må lagres før du kan laste opp filer til den."
+ DROP_FILES_HERE_TO_UPLOAD: "Slipp filene dine her eller klikk i dette området ."
+ INSERT: "Legg inn"
+ UNDO: "Gjør om"
+ REDO: "Tilbakestill `Gjør om`"
+ HEADERS: "Overskrifter"
+ BOLD: "Halvfet"
+ ITALIC: "Kursiv"
+ STRIKETHROUGH: "Gjennomstrek"
+ SUMMARY_DELIMITER: "Summerings-skilletegn"
+ LINK: "Lenke"
+ IMAGE: "Bilde"
+ BLOCKQUOTE: "Blokksitat"
+ UNORDERED_LIST: "Punktliste"
+ ORDERED_LIST: "Nummerert liste"
+ EDITOR: "Editor"
+ PREVIEW: "Forhåndsvisning"
+ FULLSCREEN: "Fullskjerm"
+ MODULAR: "Modulær"
+ NON_ROUTABLE: "Ikke rutbar"
+ NON_MODULAR: "Ikke modulær"
+ NON_VISIBLE: "Ikke synlig"
+ NON_PUBLISHED: "Upublisert"
+ CHARACTERS: "tegn"
+ PUBLISHING: "Publisering"
+ NOTIFICATIONS: "Meldinger"
+ MEDIA_TYPES: "Mediatyper"
+ IMAGE_OPTIONS: "Bilde-tilvalg"
+ MIME_TYPE: "Mimetype"
+ THUMB: "Miniatyr"
+ TYPE: "Type"
+ FILE_EXTENSION: "Fil-endinger"
+ LEGEND: "Sideoverskrift"
+ MEMCACHE_SERVER: "Memcache-tjener"
+ MEMCACHE_SERVER_HELP: "Memcache tjeneradresse"
+ MEMCACHE_PORT: "Memcache port"
+ MEMCACHE_PORT_HELP: "Memcache tjener-port"
+ MEMCACHED_SERVER: "Memcached tjener"
+ MEMCACHED_SERVER_HELP: "Memcached tjeneradresse"
+ MEMCACHED_PORT: "Memcached port"
+ MEMCACHED_PORT_HELP: "Memcache tjener-port"
+ REDIS_SERVER: "Redis tjener"
+ REDIS_SERVER_HELP: "Redis tjeneradresse"
+ REDIS_PORT: "Redis port"
+ REDIS_PORT_HELP: "Redis tjener-port"
+ ALL: "Alle"
+ FROM: "fra"
+ TO: "til"
+ RELEASE_DATE: "Utgivelsesdato"
+ SORT_BY: "Sorter etter"
+ RESOURCE_FILTER: "Filter..."
+ FORCE_SSL: "Tving ssl"
+ FORCE_SSL_HELP: "Tving ssl globalt. Hvis denne er aktivert når nettstedet nås via http, Grav viderekobler til https-siden"
+ NEWS_FEED: "Nyhetsvarsling"
+ EXTERNAL_URL: "Ekstern url"
+ SESSION_PATH: "Sesjons-sti"
+ SESSION_PATH_HELP: "Brukes kun hvis du har valgt en egendefinert base-url (du overskriver nettstedets / underfolder)"
+ CUSTOM_BASE_URL: "Egendefinert base-url"
+ CUSTOM_BASE_URL_HELP: "Bruk hvis du ønsker å overskrive nettstedets domene eller å bruke en annen underfolder enn den som brukes av Grav. F. eks: http://localhost."
+ FILEUPLOAD_PREVENT_SELF: 'Kan ikke bruke "%s" utenfor sider.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Kan ikke laste opp fil %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'Kan ikke flytte fil %s til "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'Avbryt opplasting'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Er du sikker på at du vil avbryte denne opplastingen?'
+ DROPZONE_DEFAULT_MESSAGE: 'Slipp filene dine her eller klikk i dette området '
+ DROPZONE_FALLBACK_MESSAGE: 'Nettleseren din støtter ikke dra-og-slipp filopplasting.'
+ DROPZONE_FALLBACK_TEXT: 'Vennligst bruk skjemaet under for å laste opp filene dine.'
+ DROPZONE_FILE_TOO_BIG: 'Filen er for stor ({{filesize}}MiB). Maks filstørrelse: {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "Du kan ikke laste opp filer av denne typen."
+ DROPZONE_MAX_FILES_EXCEEDED: "Du kan ikke laste opp flere filer."
+ DROPZONE_REMOVE_FILE: "Fjern fil"
+ DROPZONE_RESPONSE_ERROR: "Tjeneren responderte med {{statusCode}}-kode."
+ PREMIUM_PRODUCT: "Premium"
+ ADMIN_CACHING: "Aktiver Admin-caching"
+ ADMIN_CACHING_HELP: "Caching i admin kan bli kontrollert uavhengig av frontend-nettstedet."
+ DESTINATION_NOT_SPECIFIED: "Målet er ikke spesifisert"
+ UPLOAD_ERR_NO_TMP_DIR: "Mangler en midlertidig folder"
+ SESSION_SPLIT: "Sesjons-splitt"
+ SESSION_SPLIT_HELP: "Uavhengig splitt-sesjoner mellom nettsted og andre utvidelser (som f. eks. admin)"
+ ERROR_FULL_BACKTRACE: "Fullt sporbar feil"
+ ERROR_SIMPLE: "Enkel feil"
+ ERROR_SYSTEM: "Systemfeil"
+ IMAGES_AUTO_FIX_ORIENTATION: "Korriger orientering automatisk"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Automatisk korriger bildeorientering basert på Exif data."
+ REDIS_SOCKET: "Redis socket"
+ REDIS_SOCKET_HELP: "Redis socket"
+ NOT_SET: "Ikke satt"
+ PERMISSIONS: "Tillatelser"
+ NEVER_CACHE_TWIG: "Aldri cache for Twig"
+ NEVER_CACHE_TWIG_HELP: "Kun cache for innhold og prosesser Twig hver gang for sider. Ignorerer twig_først-innstilling."
+ ALLOW_WEBSERVER_GZIP: "Tillat WebServer gzip"
+ ALLOW_WEBSERVER_GZIP_HELP: "Av som standard. Når aktivert vil webserver-konfigurert gzip/deflate-kompresjon virke, men http-forbindelse vil ikke bli lukket før onShutDown() event, noe som forårsaker tregere sidelasting."
+ OFFLINE_WARNING: "Forbindelse med GPM kan ikke bli opprettet."
+ CLI_COMPATIBILITY: "Cli-kompatibilitet"
+ CLI_COMPATIBILITY_HELP: "Forsikrer at kun ikke-volatile cache-drivere blir brukt (fil, redis, memcache, osv.)"
+ REINSTALL_PLUGIN: "Reinstaller utvidelse"
+ REINSTALL_THEME: "Reinstaller tema"
+ REINSTALL_THE: "Reinstaller %s"
+ CONFIRM_REINSTALL: "Er du sikker på at du vil reinstallere denne %s?"
+ REINSTALLED_SUCCESSFULLY: "Reinstallasjon av %s var vellykket."
+ ERROR_REINSTALLING_THE: "Reinstallasjon av %s feilet."
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Reinstallasjon av pakken %s var vellykket."
+ REINSTALLATION_FAILED: "Reinstallasjon feilet"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "Installert versjon er ikke siste utgivelse. Ved å klikke `fortsett` vil du fjerne nåværende versjon og installere den siste tilgjengelige utgivelsen."
+ TOOLS: "Verktøy"
+ DIRECT_INSTALL: "Direkte installering"
+ NO_PACKAGE_NAME: "Pakkenavnet er ikke spesifisert."
+ PACKAGE_EXTRACTION_FAILED: "Utpakking av pakke feilet."
+ NOT_VALID_GRAV_PACKAGE: "Ikke en gyldig Grav-pakke."
+ NAME_COULD_NOT_BE_DETERMINED: "Navnet kunne ikke bestemmes."
+ CANNOT_OVERWRITE_SYMLINKS: "Kan ikke overskrive symlinker."
+ ZIP_PACKAGE_NOT_FOUND: "Zip-pakken ble ikke funnet."
+ GPM_OFFICIAL_ONLY: "Kun offisiell GPM"
+ GPM_OFFICIAL_ONLY_HELP: "Bare tillat direkte installering fra det ofisielle GPM-oppbevaringssted."
+ NO_CHILD_TYPE: "Ingen `barne`-type for denne ruten."
+ SORTABLE_PAGES: "Sorterbare sider:"
+ ADMIN_SPECIFIC_OVERRIDES: "Admin-spesifikke overstyringer"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Rekkefølge på `barne`-visning"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "Rekkefølgen som `barn` av denne siden skal bli vist i sidevisningen til Admin-utvidelsen."
+ PWD_PLACEHOLDER: "kompleks streng minst 8 tegn lang"
+ PWD_REGEX: "Passord regex"
+ PWD_REGEX_HELP: "Som standard: Passord må innehold minst et tall og en `stor` og en `liten` bokstav. Og må være minst 8 tegn langt."
+ USERNAME_PLACEHOLDER: "kun `små` bokstaver, f.eks. 'admin'"
+ USERNAME_REGEX: "Brukernavn regex"
+ USERNAME_REGEX_HELP: "Som standard: Kun `små` bokstaver, tall, bindestrek og understrek. 3 - 16 tegn."
+ CONTENT_PADDING: "Innpakking av innhold"
+ CONTENT_PADDING_HELP: "Aktiver/inaktiver ekstra avstand rundt innholdet for å gi mer luft."
+ ENABLE_AUTO_METADATA_HELP: "Automatisk oppretting av metadatafiler for bilder med exif-informasjon"
+ 2FA_TITLE: "To-faktor autentisering"
+ 2FA_INSTRUCTIONS: "### To-faktor autentisering\nDu har **2FA** aktivert på denne kontoen. Bruk din **2FA** app for å angi gjeldende **6-sifret kode** for å fullføre påloggingsprosessen."
+ 2FA_LABEL: "Admin-tilgang"
+ 2FA_FAILED: "Ugyldig 2-faktor autentiseringskode. Vennligst prøv igjen..."
+ 2FA_ENABLED: "2FA aktivert"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "2FA hemmelig nøkkel"
+ 2FA_REGENERATE: "Opprett ny"
diff --git a/user/plugins/admin/languages/pl.yaml b/user/plugins/admin/languages/pl.yaml
new file mode 100644
index 0000000..9ddae3a
--- /dev/null
+++ b/user/plugins/admin/languages/pl.yaml
@@ -0,0 +1,687 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "To jest wersja Beta! Używasz jej w produkcji na własne ryzyko..."
+ ADMIN_REPORT_ISSUE: "Znalazłeś błąd? Zgłoś to na GitHub."
+ EMAIL_FOOTER: "Powered by Grav - The Modern Flat File CMS"
+ LOGIN_BTN: "Zaloguj"
+ LOGIN_BTN_FORGOT: "Zapomniałem"
+ LOGIN_BTN_RESET: "Zresetuj hasło"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Wyślij instrukcje resetowania hasła"
+ LOGIN_BTN_CLEAR: "Wyczyść formularz"
+ LOGIN_BTN_CREATE_USER: "Utwórz użytkownika"
+ LOGIN_LOGGED_IN: "Zostałeś pomyślnie zalogowany"
+ LOGIN_FAILED: "Logowanie nie powiodło się"
+ LOGGED_OUT: "Zostałeś wylogowany"
+ RESET_NEW_PASSWORD: "Proszę wprowadzić nowe hasło …"
+ RESET_LINK_EXPIRED: "Link resetujący wygasł, spróbuj ponownie"
+ RESET_PASSWORD_RESET: "Hasło zostało zresetowane"
+ RESET_INVALID_LINK: "Nieprawidłowy link resetujący hasło, spróbuj ponownie"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Instrukcje dotyczące resetowania hasła zostały wysłane poprzez e-mail do %s"
+ FORGOT_FAILED_TO_EMAIL: "Nie udało się wysłać instrukcji resetowania hasła, proszę spróbować ponownie później"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Nie można zresetować hasła dla %s, brak adresu email"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Użytkownik o nazwie %s nie istnieje"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Nie można zresetować hasła. Ta strona nie jest skonfigurowana do wysyłania e-maili"
+ FORGOT_EMAIL_SUBJECT: "%s Żądanie zresetowania hasła"
+ FORGOT_EMAIL_BODY: "Resetowanie hasła Drogi %1$s,
Wniosek został złożony w %4$s aby resetować hasło.
Kliknij tutaj, aby zresetować hasło
Można także skopiować następujący adres URL do pasku adresu przeglądarki:
%2$s
Z poważaniem, %3$s
"
+ MANAGE_PAGES: "Zarządzaj stronami"
+ CONFIGURATION: "Konfiguracja"
+ PAGES: "Strony"
+ PLUGINS: "Wtyczki"
+ PLUGIN: "Wtyczka"
+ THEMES: "Motywy"
+ LOGOUT: "Wyloguj się"
+ BACK: "Wstecz"
+ NEXT: "Następna"
+ PREVIOUS: "Poprzednia"
+ ADD_PAGE: "Dodaj stronę"
+ ADD_MODULAR: "Dodaj moduł"
+ MOVE: "Przenieś"
+ DELETE: "Usuń"
+ VIEW: "Widok"
+ SAVE: "Zapisz"
+ NORMAL: "Zwykły"
+ EXPERT: "Ekspert"
+ EXPAND_ALL: "Rozwiń wszystko"
+ COLLAPSE_ALL: "Zwiń wszystko"
+ ERROR: "Błąd"
+ CLOSE: "Zamknij"
+ CANCEL: "Anuluj"
+ CONTINUE: "Kontynuuj"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Wymagane potwierdzenie"
+ MODAL_CHANGED_DETECTED_TITLE: "Wykryto zmiany"
+ MODAL_CHANGED_DETECTED_DESC: "Masz niezapisane zmiany. Czy na pewno chcesz opuścić bez zapisywania?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Wymagane potwierdzenie"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Czy na pewno chcesz usunąć ten plik? Nie można cofnąć tej akcji."
+ ADD_FILTERS: "Dodaj filtry"
+ SEARCH_PAGES: "Przeszukaj strony"
+ VERSION: "Wersja"
+ WAS_MADE_WITH: "Został stworzony z"
+ BY: "Przez"
+ UPDATE_THEME: "Aktualizacja motywu"
+ UPDATE_PLUGIN: "Aktualizuj wtyczkę"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "tego motywu jest dostępna"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "tej wtyczki jest dostępna"
+ AUTHOR: "Autor"
+ HOMEPAGE: "Strona domowa"
+ DEMO: "Demo"
+ BUG_TRACKER: "Śledzenie błędów"
+ KEYWORDS: "Słowa kluczowe"
+ LICENSE: "Licencja"
+ DESCRIPTION: "Opis"
+ README: "Plik Readme"
+ REMOVE_THEME: "Usuń motyw"
+ INSTALL_THEME: "Zainstaluj motyw"
+ THEME: "Motyw"
+ BACK_TO_THEMES: "Powrót do motywów"
+ BACK_TO_PLUGINS: "Powrót do wtyczek"
+ CHECK_FOR_UPDATES: "Sprawdź aktualizacje"
+ ADD: "Dodaj"
+ CLEAR_CACHE: "Wyczyść pamięć podręczną"
+ CLEAR_CACHE_ALL_CACHE: "Cała pamięć podręczna"
+ CLEAR_CACHE_ASSETS_ONLY: "Tylko zasoby"
+ CLEAR_CACHE_IMAGES_ONLY: "Tylko obrazy"
+ CLEAR_CACHE_CACHE_ONLY: "Tylko pamięć podręczna"
+ CLEAR_CACHE_TMP_ONLY: "Tylko tymczasowe"
+ DASHBOARD: "Pulpit"
+ UPDATES_AVAILABLE: "Aktualizacje są dostępne"
+ DAYS: "Dni"
+ UPDATE: "Zaktualizuj"
+ BACKUP: "Kopia zapasowa"
+ STATISTICS: "Statystyki"
+ TODAY: "Dziś"
+ WEEK: "Tydzień"
+ MONTH: "Miesiąc"
+ LATEST_PAGE_UPDATES: "Najnowsze aktualizacje stron"
+ MAINTENANCE: "Konserwacja"
+ UPDATED: "Zaktualizowano"
+ MON: "Pon."
+ TUE: "Wt."
+ WED: "Śr."
+ THU: "Czw."
+ FRI: "Pt."
+ SAT: "Sob."
+ SUN: "Niedz."
+ COPY: "Kopiuj"
+ EDIT: "Edytuj"
+ CREATE: "Utwórz"
+ GRAV_ADMIN: "Grav Admin"
+ GRAV_OFFICIAL_PLUGIN: "Oficjalna wtyczka Grav"
+ GRAV_OFFICIAL_THEME: "Oficjalny motyw Grav"
+ PLUGIN_SYMBOLICALLY_LINKED: "Ta wtyczka jest dowiązana symbolicznie. Aktualizacje nie zostaną wykryte."
+ THEME_SYMBOLICALLY_LINKED: "Ten motyw jest dowiązany symbolicznie. Aktualizacje nie zostaną wykryte"
+ REMOVE_PLUGIN: "Usuń wtyczkę"
+ INSTALL_PLUGIN: "Zainstaluj wtyczkę"
+ AVAILABLE: "Dostępne"
+ INSTALLED: "Zainstalowane"
+ INSTALL: "Zainstaluj"
+ ACTIVE_THEME: "Aktywny motyw"
+ SWITCHING_TO: "Przełącz na"
+ SWITCHING_TO_DESCRIPTION: "Po przełączeniu na inny motyw, nie ma gwarancji, że wszystkie układy stron są wspierane, co może powodować błędy w wyświetlaniu tych stron."
+ SWITCHING_TO_CONFIRMATION: "Czy chcesz kontynuować i zmienić motyw"
+ CREATE_NEW_USER: "Utwórz nowego użytkownika"
+ REMOVE_USER: "Usuń użytkownika"
+ ACCESS_DENIED: "Dostęp zabroniony"
+ ACCOUNT_NOT_ADMIN: "twoje konto nie ma uprawnień administratora"
+ PHP_INFO: "PHP Info"
+ INSTALLER: "Instalator"
+ AVAILABLE_THEMES: "Dostępne motywy"
+ AVAILABLE_PLUGINS: "Dostępnych wtyczki"
+ INSTALLED_THEMES: "Zainstalowane motywy"
+ INSTALLED_PLUGINS: "Zainstalowane wtyczki"
+ BROWSE_ERROR_LOGS: "Przeglądaj dzienniki błędów"
+ SITE: "Strona"
+ INFO: "Info"
+ SYSTEM: "System"
+ USER: "Użytkownik"
+ ADD_ACCOUNT: "Dodaj konto"
+ SWITCH_LANGUAGE: "Zmień język"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Wtyczka pomyślnie włączona"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Wtyczka pomyślnie wyłączona"
+ SUCCESSFULLY_CHANGED_THEME: "Pomyślnie zmieniono domyślny motyw"
+ INSTALLATION_FAILED: "Instalacja nie powiodła się"
+ INSTALLATION_SUCCESSFUL: "Instalacja zakończona sukcesem"
+ UNINSTALL_FAILED: "Odinstalowanie nie powiodło się"
+ UNINSTALL_SUCCESSFUL: "Odinstalowanie powiodło się"
+ SUCCESSFULLY_SAVED: "Zapisano pomyślnie"
+ SUCCESSFULLY_COPIED: "Skopiowano pomyślnie"
+ REORDERING_WAS_SUCCESSFUL: "Pomyślnie zmieniono kolejność"
+ SUCCESSFULLY_DELETED: "Pomyślnie usunięto"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Pomyślnie zmieniono język"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Nie masz wystarczających uprawnień aby wykonać to zadanie"
+ CACHE_CLEARED: "Pamięć cache wyczyszczona"
+ METHOD: "Metoda"
+ ERROR_CLEARING_CACHE: "Błąd czyszczenia pamięci podręcznej"
+ AN_ERROR_OCCURRED: "Wystąpił błąd"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Kopia zapasowa jest gotowa do pobrania"
+ DOWNLOAD_BACKUP: "Ściągnij kopię zapasową"
+ PAGES_FILTERED: "Strony przefiltrowane"
+ NO_PAGE_FOUND: "Nie znaleziono strony"
+ INVALID_PARAMETERS: "Nieprawidłowe parametry"
+ NO_FILES_SENT: "Nie wysłano plików"
+ EXCEEDED_FILESIZE_LIMIT: "Przekroczono limit rozmiaru pliku konfiguracji PHP"
+ UNKNOWN_ERRORS: "Nieznany błąd"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Przekroczono limit rozmiaru pliku konfiguracji Grav"
+ UNSUPPORTED_FILE_TYPE: "Nieobsługiwany typ pliku"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Nie można przenieść przesłanego pliku."
+ FILE_UPLOADED_SUCCESSFULLY: "Plik został pomyślnie przesłany"
+ FILE_DELETED: "Plik usunięty"
+ FILE_COULD_NOT_BE_DELETED: "Plik nie może zostać usunięty"
+ FILE_NOT_FOUND: "Plik nie znaleziony"
+ NO_FILE_FOUND: "Nie znaleziono pliku"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav został pomyślnie zaktualizowany do"
+ GRAV_UPDATE_FAILED: "Aktualizacja Grav nie powiodła się"
+ EVERYTHING_UPDATED: "Wszystko zaktualizowane"
+ UPDATES_FAILED: "Aktualizacja nieudana"
+ AVATAR_BY: "Awatar z"
+ AVATAR_UPLOAD_OWN: "Lub prześlij swój..."
+ LAST_BACKUP: "Ostatnia kopia zapasowa"
+ FULL_NAME: "Pełne imię"
+ USERNAME: "Nazwa Użytkownika"
+ EMAIL: "Adres E-Mail"
+ USERNAME_EMAIL: "Nazwa użytkownika lub e-mail"
+ PASSWORD: "Hasło"
+ PASSWORD_CONFIRM: "Potwierdź hasło"
+ TITLE: "Tytuł"
+ LANGUAGE: "Język"
+ ACCOUNT: "Konto"
+ EMAIL_VALIDATION_MESSAGE: "Adres e-mail musi być poprawny"
+ PASSWORD_VALIDATION_MESSAGE: "Hasło musi zawierać co najmniej jedną cyfrę, jedną wielką i małą literę i co najmniej 8 lub więcej znaków"
+ LANGUAGE_HELP: "Ustaw ulubiony język"
+ MEDIA: "Media"
+ DEFAULTS: "Domyślne"
+ SITE_TITLE: "Tytuł strony"
+ SITE_TITLE_PLACEHOLDER: "Pełny tytuł strony"
+ SITE_TITLE_HELP: "Domyślny tytuł witryny, często używany w motywach"
+ SITE_DEFAULT_LANG: "Domyślny język"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Domyślny język używany przez tag motywu"
+ SITE_DEFAULT_LANG_HELP: "Domyślny język używany przez tag motywu"
+ DEFAULT_AUTHOR: "Domyślny autor"
+ DEFAULT_AUTHOR_HELP: "Domyślna nazwa autora, często używana w motywach lub zawartości strony"
+ DEFAULT_EMAIL: "Domyślny Email"
+ DEFAULT_EMAIL_HELP: "Domyślny e-mail dla motywów lub stron"
+ TAXONOMY_TYPES: "Typy taksonomii"
+ TAXONOMY_TYPES_HELP: "Typy taksonomii muszą być tutaj zdefiniowane, jeśli chcesz z nich korzystać na stronach"
+ PAGE_SUMMARY: "Podsumowanie strony"
+ ENABLED: "Włączony"
+ ENABLED_HELP: "Włącz podsumowanie strony (podsumowanie będzie pobrane z treści strony)"
+ 'YES': "Tak"
+ 'NO': "Nie"
+ SUMMARY_SIZE: "Rozmiar podsumowania"
+ SUMMARY_SIZE_HELP: "Ilość znaków z treści strony wykorzystana do utworzenia podsumowania"
+ FORMAT: "Format"
+ FORMAT_HELP: "krótki = użyj pierwsze wystąpienie ogranicznika albo rozmiar; długi = ogranicznik podsumowania zostanie zignorowany"
+ SHORT: "Krótkie"
+ LONG: "Długie"
+ DELIMITER: "Separator"
+ DELIMITER_HELP: "Znacznik podsumowania (domyślnie '===')"
+ METADATA: "Metadane"
+ METADATA_HELP: "Domyślne wartości metadanych, które będą wyświetlane na każdej stronie, chyba że są nadpisane przez ustawienia strony"
+ NAME: "Nazwa"
+ CONTENT: "Zawartość"
+ REDIRECTS_AND_ROUTES: "Przekierowania i Trasy"
+ CUSTOM_REDIRECTS: "Niestandardowe przekierowania"
+ CUSTOM_REDIRECTS_HELP: "trasy do przekierowania do innych stron. Można użyć wyrażenia regularnego regex"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/twój/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/twoje/przekierowanie"
+ CUSTOM_ROUTES: "Trasy niestandardowe"
+ CUSTOM_ROUTES_HELP: "trasy do aliasu do innych stron. Można użyć wyrażenia regularnego regex"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/twój/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/twoja/trasa"
+ FILE_STREAMS: "Strumień plików"
+ DEFAULT: "Domyślne"
+ PAGE_MEDIA: "Media strony"
+ OPTIONS: "Opcje"
+ PUBLISHED: "Opublikowana"
+ PUBLISHED_HELP: "Domyślnie strona jest opublikowana, chyba że ustawiono czas publikacji, albo czas odpublikowania lub odpublikowano stronę ręcznie"
+ DATE: "Data"
+ DATE_HELP: "Zmienna date pozwala ustawić datę związaną z tą stroną."
+ PUBLISHED_DATE: "Data publikacji"
+ PUBLISHED_DATE_HELP: "Można podać datę, kiedy automatycznie opublikować."
+ UNPUBLISHED_DATE: "Data odpublikowania"
+ UNPUBLISHED_DATE_HELP: "Można podać datę, kiedy automatycznie odpublikować."
+ ROBOTS: "Roboty"
+ TAXONOMIES: "Taksonomia"
+ TAXONOMY: "Taksonomia"
+ ADVANCED: "Zaawansowane"
+ SETTINGS: "Ustawienia"
+ FOLDER_NUMERIC_PREFIX: "Numeryczny prefiks folderu"
+ FOLDER_NUMERIC_PREFIX_HELP: "Numeryczny prefiks, umożliwia ręcznie ustawić kolejność, oraz wyznacza widoczność"
+ FOLDER_NAME: "Nazwa folderu"
+ FOLDER_NAME_HELP: "Nazwa folderu, w którym będzie przechowywana strona z jej zawartością"
+ PARENT: "Rodzic"
+ DEFAULT_OPTION_ROOT: "- Rdzeń -"
+ DEFAULT_OPTION_SELECT: "- Wybierz -"
+ DISPLAY_TEMPLATE: "Wyświetl szablon"
+ DISPLAY_TEMPLATE_HELP: "Typ strony, czyli który szablon Twig ma renderować stronę"
+ BODY_CLASSES: "Klasy"
+ ORDERING: "Sortowanie"
+ PAGE_ORDER: "Kolejność stron"
+ OVERRIDES: "Nadpisania"
+ MENU: "Menu"
+ MENU_HELP: "Napis do użycia w menu. Jeżeli nie jest podany, domyślnie zostanie użyty Tytuł."
+ SLUG: "Adres"
+ SLUG_HELP: "Pozwala ustawić część adresu URL który prowadzi do strony"
+ SLUG_VALIDATE_MESSAGE: "Adres musi zawierać tylko małe litery znaków alfanumerycznych i kresek"
+ PROCESS: "Przetwarzanie"
+ PROCESS_HELP: "Kontroluje jak strony są przetwarzane. Można ustawić dla strony, zamiast globalnie"
+ DEFAULT_CHILD_TYPE: "Domyślny typ dziecka"
+ USE_GLOBAL: "Użyj Globalny"
+ ROUTABLE: "Routing"
+ ROUTABLE_HELP: "Czy strona jest osiągalna za pomocą adresu URL"
+ CACHING: "Buforowanie"
+ VISIBLE: "Widoczna"
+ VISIBLE_HELP: "Określa, czy strona jest widoczna w obszarze nawigacji."
+ DISABLED: "Wyłączony"
+ ITEMS: "Elementy"
+ ORDER_BY: "Uporządkuj według"
+ ORDER: "Uporządkuj"
+ FOLDER: "Katalog"
+ ASCENDING: "Rosnąco"
+ DESCENDING: "Malejąco"
+ ADD_MODULAR_CONTENT: "Dodaj treści modułowe"
+ PAGE_TITLE: "Tytuł strony"
+ PAGE_TITLE_HELP: "Tytuł strony"
+ PAGE: "Strona"
+ MODULAR_TEMPLATE: "Szablon modułowy"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Nazwa pliku"
+ PARENT_PAGE: "Strona nadrzędna"
+ HOME_PAGE: "Strona główna"
+ HOME_PAGE_HELP: "Strona, którą Grav użyje jako stronę główną"
+ DEFAULT_THEME: "Domyślny motyw"
+ DEFAULT_THEME_HELP: "Ustaw domyślny motyw, który Grav ma używać (domyślnie Antimatter)"
+ TIMEZONE: "Strefa czasowa"
+ TIMEZONE_HELP: "Zastąp domyślną strefę czasową serwera"
+ SHORT_DATE_FORMAT: "Krótki format daty"
+ SHORT_DATE_FORMAT_HELP: "Ustaw format krótkiej daty, który może być używany przez motywy"
+ LONG_DATE_FORMAT: "Długi format daty"
+ LONG_DATE_FORMAT_HELP: "Ustaw format długiej daty, który może być używany przez motywy"
+ DEFAULT_ORDERING: "Domyślna kolejność"
+ DEFAULT_ORDERING_HELP: "Strony na liście będą renderowane w tej kolejności, chyba że jest ona zastępowana"
+ DEFAULT_ORDERING_DEFAULT: "Domyślnie - na podstawie nazwy folderu"
+ DEFAULT_ORDERING_FOLDER: "Katalog - na podstawie nazwy foldery (bez prefiksu)"
+ DEFAULT_ORDERING_TITLE: "Tytuł - na podstawie tytułu pola w nagłówku"
+ DEFAULT_ORDERING_DATE: "Data - na podstawie pola daty w nagłówku"
+ DEFAULT_ORDER_DIRECTION: "Domyślny kierunek numerowania"
+ DEFAULT_ORDER_DIRECTION_HELP: "Kierunek stron na liście"
+ DEFAULT_PAGE_COUNT: "Domyślna liczba stron"
+ DEFAULT_PAGE_COUNT_HELP: "Domyślna liczba stron na liście"
+ DATE_BASED_PUBLISHING: "Publikacja oparta na dacie"
+ DATE_BASED_PUBLISHING_HELP: "Automatycznie publikuj lub odpublikuj post na podstawie ich daty"
+ EVENTS: "Zdarzenia"
+ EVENTS_HELP: "Włączanie lub wyłączanie określonych zdarzeń. Wyłączanie może popsuć wtyczki"
+ REDIRECT_DEFAULT_ROUTE: "Przekieruj domyślną ścieżkę"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Automatyczne przekierowanie do domyślnej trasy dla tej strony"
+ LANGUAGES: "Języki"
+ SUPPORTED: "Obsługiwane"
+ SUPPORTED_HELP: "Dwu literowe kody języków oddzielone przecinkami (np. 'pl,en,de')"
+ TRANSLATIONS_ENABLED: "Tłumaczenia włączone"
+ TRANSLATIONS_ENABLED_HELP: "Włącza tłumaczenia Grav, wtyczek i rozszerzeń"
+ TRANSLATIONS_FALLBACK: "Tłumaczenia zastępcze"
+ TRANSLATIONS_FALLBACK_HELP: "Odnoś się do innych wspieranych tłumaczeń, jeśli brak tłumaczenia w aktywnym języku"
+ ACTIVE_LANGUAGE_IN_SESSION: "Aktywny język w sesji"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Przechowuj aktualny język w sesji"
+ HTTP_HEADERS: "Nagłówki HTTP"
+ EXPIRES: "Wygasa"
+ EXPIRES_HELP: "Po jakim czasie nagłówek wygasa. Wartość w sekundach."
+ CACHE_CONTROL: "Dyrektywa HTTP Cache-Control"
+ CACHE_CONTROL_HELP: "Należy wprowadzić prawidłową wartość cache-control, taką jak `no-cache, no-store, must-revalidate`"
+ LAST_MODIFIED: "Ostatnia modyfikacja"
+ LAST_MODIFIED_HELP: "Ustawia ostatni zmodyfikowany nagłówek, który może pomóc w optymalizacji serwera proxy i buforowania przeglądarki"
+ ETAG: "ETag"
+ ETAG_HELP: "Ustawia Etag w nagłówku, aby pomóc zidentyfikować kiedy strona została zmodyfikowana"
+ VARY_ACCEPT_ENCODING: "Odróżnia kodowanie plików"
+ VARY_ACCEPT_ENCODING_HELP: "Ustawia \"Vary: Accept Encoding\" w nagłówku w celu pomocy z proxy i cache'owaniem CDN"
+ MARKDOWN_EXTRA_HELP: "Włącz domyślną obsługę Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ AUTO_LINE_BREAKS: "Automatyczne przejścia do nowej linii"
+ AUTO_LINE_BREAKS_HELP: "Włącz obsługę dla automatycznych podziałów wierszy w markdown"
+ AUTO_URL_LINKS: "Automatyczne linki URL"
+ AUTO_URL_LINKS_HELP: "Włącz automatyczną konwersję adresów URL w hiperłącza HTML"
+ ESCAPE_MARKUP: "Składnia znaku ucieczki"
+ ESCAPE_MARKUP_HELP: "Ucieczka tagów w encjach HTML"
+ CACHING_HELP: "Włącza lub wyłącza buforowanie dla Grav"
+ CACHE_CHECK_METHOD: "Metoda wyboru pamięci podręcznej"
+ CACHE_CHECK_METHOD_HELP: "Wybierz metodę używaną przez Grav do sprawdzania, czy pliki strony zostały zmodyfikowane."
+ CACHE_DRIVER: "Sterownik pam. podręcznej"
+ CACHE_DRIVER_HELP: "Wybierz który sterownik pamięci podręcznej powinien zostać użyty. 'Auto' spróbuje znaleźć najlepszy"
+ CACHE_PREFIX: "Prefiks pam. podręcznej"
+ CACHE_PREFIX_HELP: "Identyfikator Grav. Nie zmieniać, chyba że wiesz co robisz."
+ LIFETIME: "Czas życia"
+ LIFETIME_HELP: "Ustaw okres życia plików pamięci podręcznej w sekundach. 0 = nieskończoność"
+ GZIP_COMPRESSION: "Kompresja Gzip"
+ GZIP_COMPRESSION_HELP: "Włącz kompresję GZip stron dla zwiększenia wydajności."
+ TWIG_TEMPLATING: "Szablony Twig"
+ TWIG_CACHING: "Buforowanie Twig"
+ TWIG_CACHING_HELP: "Kontroluj mechanizm buforowania szablonów Twig. Zostaw włączone dla najlepszej wydajności."
+ TWIG_DEBUG: "Debugowanie Twig"
+ TWIG_DEBUG_HELP: "Pozwala na nie wczytywanie debugera Twig"
+ DETECT_CHANGES: "Wykrywaj zmiany"
+ DETECT_CHANGES_HELP: "Twig automatycznie przekompiluje pliki pamięci podręcznej szablonów Twig jeśli wystąpiły w nich zmiany"
+ AUTOESCAPE_VARIABLES: "Znak ucieczki przed wartościami"
+ AUTOESCAPE_VARIABLES_HELP: "Wstawia znak ucieczki przed każdą definicją wartości. Włączenie tego zazwyczaj psuje wygląd strony internetowej"
+ ASSETS: "Zasoby"
+ CSS_PIPELINE: "Koncentrowanie CSS"
+ CSS_PIPELINE_HELP: "Koncentrowanie CSS jest złączeniem wielu zasobów CSS w jeden plik"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Umieść zewnętrzne w potoku CSS"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Zewnętrzne adresy URL mają czasem względne odwołania do plików i nie powinny być częścią potoku"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "Wyświetl najpierw potok CSS"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Wyświetl potok CSS przed jakimikolwiek innymi odniesieniami CSS które nie są włączone"
+ CSS_MINIFY: "Minifikuj CSS"
+ CSS_MINIFY_HELP: "Zmniejsz pliki CSS podczas koncentrowania"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "Zastąp zmniejszanie CSS pod Windows"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Zastępuje zmniejszanie na platformach Windows. Wyłączone domyślnie przez ThreadStackSize"
+ CSS_REWRITE: "Przepisz CSS"
+ CSS_REWRITE_HELP: "Przepisz każdy względny URL do CSS podczas koncentrowania"
+ JAVASCRIPT_PIPELINE: "Koncentrowanie JavaScript"
+ JAVASCRIPT_PIPELINE_HELP: "Koncentrowanie JavaScript jest złączeniem wielu zasobów JavaScript w jeden plik"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Umieść zewnętrzne w potoku JS"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Zewnętrzne adresy URL mają czasem względne odwołania do plików i nie powinny być częścią potoku"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Wyświetl najpierw potok JS"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Wyświetl potok JS przed jakimikolwiek innymi odniesieniami JS które nie są włączone"
+ JAVASCRIPT_MINIFY: "Minifikuj JavaScript"
+ JAVASCRIPT_MINIFY_HELP: "Zmniejsz pliki JS podczas koncentrowania"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Włącz sygnatury czasowe na zasobach"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Włącz sygnatury czasowe zasobów"
+ COLLECTIONS: "Kolekcje"
+ ERROR_HANDLER: "Obsługa błędów"
+ DISPLAY_ERRORS: "Wyświetlaj błędy"
+ DISPLAY_ERRORS_HELP: "Wyświetlanie pełnego backtrace przy błędzie strony"
+ LOG_ERRORS: "Rejestruj błędy"
+ LOG_ERRORS_HELP: "Rejestruj błędy do folderu /logs"
+ DEBUGGER: "Debuger"
+ DEBUGGER_HELP: "Włącz Grav debugger i następujące ustawienia"
+ DEBUG_TWIG: "Debuguj Twig"
+ DEBUG_TWIG_HELP: "Włącz debugowanie szablonów Twig"
+ SHUTDOWN_CLOSE_CONNECTION: "Wyłącz zamknięte połączenie"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Zamyka połączenie przed wywołaniem onShutdown(). Ustaw Nie do debugowania"
+ DEFAULT_IMAGE_QUALITY: "Domyślna jakość zdjęć"
+ DEFAULT_IMAGE_QUALITY_HELP: "Domyślna jakość obrazów podczas ich przetwarzania lub buforowania (85%)"
+ CACHE_ALL: "Buforuj wszystkie obrazy"
+ CACHE_ALL_HELP: "Umieść wszystkie obrazy do pamięci podręcznej, nawet jeśli obrazy nie były modyfikowane"
+ IMAGES_DEBUG: "Debugowanie obrazków"
+ IMAGES_DEBUG_HELP: "Pokazuje nakładkę na obrazkach, pokazującą wartość pikseli obrazu"
+ UPLOAD_LIMIT: "Limit wysyłanego pliku"
+ UPLOAD_LIMIT_HELP: "Maksymalny rozmiar przesyłanych plików w bajtach (0 = nieograniczony)"
+ ENABLE_MEDIA_TIMESTAMP: "Włącz sygnatury czasowe w mediach"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Dołącza sygnatury czasowe oparte na dacie ostatniej modyfikacji do każdego elementu mediów"
+ SESSION: "Sesja"
+ SESSION_ENABLED_HELP: "Włącz obsługę sesji w Grav"
+ TIMEOUT: "Limit czasu"
+ TIMEOUT_HELP: "Ustawia limit czasu sesji w sekundach"
+ SESSION_NAME_HELP: "Identyfikator używany do utworzenia nazwy pliku cookie"
+ ABSOLUTE_URLS: "Bezwzględne adresy URL"
+ ABSOLUTE_URLS_HELP: "Bezwzględne lub względne adresy URL dla \"base_url\""
+ PARAMETER_SEPARATOR: "Separator parametrów"
+ PARAMETER_SEPARATOR_HELP: "Separator dla parametrów, który może zostać zmieniony dla Apache w systemie Windows"
+ TASK_COMPLETED: "Zadanie zakończone"
+ EVERYTHING_UP_TO_DATE: "Wszystko jest aktualne"
+ UPDATES_ARE_AVAILABLE: "aktualizacja(/e/i) dostępna(/e/nych)"
+ IS_AVAILABLE_FOR_UPDATE: "ma dostępne aktualizacje"
+ IS_NOW_AVAILABLE: "jest już dostępny"
+ CURRENT: "Bieżący"
+ UPDATE_GRAV_NOW: "Zaktualizuj Grav"
+ GRAV_SYMBOLICALLY_LINKED: "Grav jest dowiązany symbolicznie. Aktualizacje nie zostaną wykryte"
+ UPDATING_PLEASE_WAIT: "Aktualizowanie... proszę czekać, trwa pobieranie"
+ OF_THIS: "z tego"
+ OF_YOUR: "z twoich"
+ HAVE_AN_UPDATE_AVAILABLE: "ma dostępną aktualizację"
+ SAVE_AS: "Zapisz jako"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Czy na pewno chcesz usunąć tą stronę i wszystkie jej podstrony? Jeżeli ta strona została przetłumaczona na inne języki, te tłumaczenia pozostaną i będą musiały zostać usunięte niezależnie. W przeciwnym wypadku, folder z tą stroną zostanie usunięty razem z jej podstronami. Nie można cofnąć tej czynności."
+ AND: "i"
+ UPDATE_AVAILABLE: "Dostępna aktualizacja"
+ METADATA_KEY: "Klucz (np. 'Keywords')"
+ METADATA_VALUE: "Wartość (np. \"Blog, Grav\")"
+ USERNAME_HELP: "Nazwa użytkownika powinna mieć od 3 do 16 znaków, włączając w to małe litery, cyfry, znaki podkreślenia i myślniki. Wielkie litery, spacje oraz znaki specjalne są niedozwolone"
+ FULLY_UPDATED: "Aktualny"
+ SAVE_LOCATION: "Lokalizacja pliku konfiguracji"
+ PAGE_FILE: "Szablon strony"
+ PAGE_FILE_HELP: "Nazwa pliku szablonu strony, oraz domyślnie wyświetlany szablon dla tej strony"
+ NO_USER_ACCOUNTS: "Brak użytkowników, proszę utworzyć pierwszego użytkownika..."
+ REDIRECT_TRAILING_SLASH: "Przekieruj końcowy ukośnik"
+ REDIRECT_TRAILING_SLASH_HELP: "Wykonaj przekierowanie 301, zamiast obsługiwać adresy URI z końcowym ukośnikiem."
+ DEFAULT_DATE_FORMAT: "Format daty"
+ DEFAULT_DATE_FORMAT_HELP: "Format daty dla strony używany przez Grav. Domyślnie, Grav spróbuje odgadnąć format daty, ale możesz określić swój format używając składni PHP (np.: H:i Y-m-d)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Zgadnij automatycznie"
+ IGNORE_FILES: "Ignoruj pliki"
+ IGNORE_FILES_HELP: "Określone pliki do zignorowania podczas przetwarzania strony"
+ IGNORE_FOLDERS: "Ignoruj foldery"
+ IGNORE_FOLDERS_HELP: "Określone foldery do zignorowania podczas przetwarzania strony"
+ HTTP_ACCEPT_LANGUAGE: "Pobierz język z przegladarki"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Możesz spróbować ustawić język oparty na tagu nagłówka \"http_accept_language\" w przeglądarce"
+ OVERRIDE_LOCALE: "Nadpisze lokalne"
+ OVERRIDE_LOCALE_HELP: "Zastąp ustawienia lokalne w PHP w oparciu o bieżący język"
+ REDIRECT: "Przekierowanie strony"
+ REDIRECT_HELP: "Podaj trasę do strony, albo zewnętrzny URL gdzie ma być przekierowana ta strona np. \"/jakaś/trasa\" albo \"http://innyadres.com\""
+ PLUGIN_STATUS: "Status wtyczki"
+ INCLUDE_DEFAULT_LANG: "Załącz domyślny język"
+ INCLUDE_DEFAULT_LANG_HELP: "Zostaną dołączone do adresów URL kody języka z domyślnie ustawionego języka (np 'pl/blog/moj-post')"
+ ALLOW_URL_TAXONOMY_FILTERS: "Filtry taksonomii adresów URL"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Zbiory stron pozwalaną na filtrowanie używając `/taxonomy:value`."
+ REDIRECT_DEFAULT_CODE: "Domyślny kod przekierowania"
+ REDIRECT_DEFAULT_CODE_HELP: "Kod odpowiedzi HTTP dla przekierowań"
+ IGNORE_HIDDEN: "Ignoruj ukryte"
+ IGNORE_HIDDEN_HELP: "Ignoruj wszystkie pliki i foldery, które zaczynają się kropką"
+ WRAPPED_SITE: "Edytowana strona"
+ WRAPPED_SITE_HELP: "Pozwala motywom/pluginom sprawdzić czy Grav znajduje się wewnątrz innej platformy"
+ FALLBACK_TYPES: "Dozwolone typy zastępcze"
+ INLINE_TYPES_HELP: "Lista typów plików, które powinny być wyświetlane zamiast pobierane"
+ APPEND_URL_EXT: "Zmień rozszerzenie URL"
+ APPEND_URL_EXT_HELP: "Doda niestandardowe rozszerzenie do adresów URL stron. To oznacza że Grav będzie szukał pliku szablonu wg. '..twig'"
+ PAGE_MODES: "Tryby stron"
+ PAGE_TYPES: "Typy stron"
+ ACCESS_LEVELS: "Poziomy dostępu"
+ GROUPS: "Grupy"
+ GROUPS_HELP: "Lista grup, których użytkownik jest częścią"
+ ADMIN_ACCESS: "Uprawnienia admina"
+ SITE_ACCESS: "Dostęp do witryny"
+ INVALID_SECURITY_TOKEN: "Nieprawidłowy token bezpieczeństwa"
+ ACTIVATE: "Aktywuj"
+ TWIG_UMASK_FIX: "Poprawka Umask"
+ TWIG_UMASK_FIX_HELP: "Domyślnie Twig tworzy pliki pamięci podręcznej z uprawnieniami 0755, włączenie tej opcji zmienia ich uprawnienia na 0775"
+ CACHE_PERMS: "Uprawnienia pamięci podręcznej"
+ CACHE_PERMS_HELP: "Domyślne uprawnienia folderów pamięci podręcznej. Zazwyczaj 0755 albo 0775 w zależności od konfiguracji"
+ REMOVE_SUCCESSFUL: "Usunięcie udane"
+ REMOVE_FAILED: "Usunięcie nie powiodło się"
+ HIDE_HOME_IN_URLS: "Ukryj ścieżkę do strony startowej"
+ HIDE_HOME_IN_URLS_HELP: "Gwarantuje, że domyślne trasy przekierowań stron pod stroną główną nie odnoszą się do typowej trasy przekierowań strony głównej"
+ TWIG_FIRST: "Najpierw przetwarzaj Twig"
+ TWIG_FIRST_HELP: "Jeśli włączyłeś przetwarzanie strony w Twig, możesz skonfigurować go aby przetworzył stronę przed albo po znacznikach markdown"
+ SESSION_SECURE: "Bezpieczeństwo"
+ SESSION_SECURE_HELP: "Jeśli prawda, wtedy komunikacja z ciasteczkiem musi być wykonana przez transmisję szyfrowaną. UWAGA: Włącz tą opcję tylko jeśli strona działa wyłącznie na HTTPS"
+ SESSION_HTTPONLY: "Tylko HTTP"
+ SESSION_HTTPONLY_HELP: "Jeśli prawda, wtedy ciasteczka mogą być używane tylko przez HTTP, a modyfikacje przez JavaScript nie będą dozwolone"
+ REVERSE_PROXY: "Odwrotne proxy"
+ REVERSE_PROXY_HELP: "Włącz tą opcję jeśli masz odwrotne proxy i masz problemy z adresami URL które zawierają niepoprawne porty"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Nieprawidłowy frontmatter, nie można zapisać"
+ ADD_FOLDER: "Dodaj folder"
+ PROXY_URL: "Adres URL proxy"
+ PROXY_URL_HELP: "Wpisz HOST albo IP proxy i PORT"
+ NOTHING_TO_SAVE: "Nie ma nic do zapisania"
+ FILE_ERROR_ADD: "Wystąpił błąd podczas próby dodania pliku"
+ FILE_ERROR_UPLOAD: "Wystąpił błąd podczas próby przesłania pliku"
+ FILE_UNSUPPORTED: "Nieobsługiwany typ pliku"
+ ADD_ITEM: "Dodaj element"
+ FILE_TOO_LARGE: "Ten plik jest zbyt duży, aby go przesłać; maksymalny dozwolony rozmiar to %s zgodnie z twoimi ustawieniami PHP. Zwiększ wartość `post_max_size` w swoich ustawieniach PHP"
+ INSTALLING: "Trwa instalacja"
+ LOADING: "Trwa ładowanie.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "Następujące zależności muszą być najpierw spełnione:"
+ ERROR_INSTALLING_PACKAGES: "Wystąpił błąd podczas instalacji paczek"
+ INSTALLING_DEPENDENCIES: "Trwa instalacja zależności..."
+ INSTALLING_PACKAGES: "Trwa instalacja paczek.."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Paczki zostały poprawnie zainstalowane."
+ READY_TO_INSTALL_PACKAGES: "Gotowy do instalacji paczek"
+ PACKAGES_NOT_INSTALLED: "Paczki nie zainstalowane"
+ PACKAGES_NEED_UPDATE: "Paczki są już zainstalowane, ale zbyt nieaktualne"
+ PACKAGES_SUGGESTED_UPDATE: "Paczki są już zainstalowane, w poprawnych wersjach, ale zostaną zaktualizowane do najnowszych wersji"
+ REMOVE_THE: "Usuń %s"
+ CONFIRM_REMOVAL: "Czy na pewno chcesz usunąć %s?"
+ REMOVED_SUCCESSFULLY: "%s została pomyślnie usunięta"
+ ERROR_REMOVING_THE: "Wystąpił błąd podczas usuwania %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "%s wymagał następujących zależności, które nie są wymagane przez inne zainstalowane paczki. Jeżeli ich nie używasz, możesz je bezpośrednio tutaj usunąć."
+ READY_TO_UPDATE_PACKAGES: "Gotowy do zaktualizowania paczek"
+ ERROR_UPDATING_PACKAGES: "Wystąpił błąd podczas aktualizacji pakietu/ów"
+ UPDATING_PACKAGES: "Trwa aktualizacja pakietów..."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Pomyślnie zaktualizowano pakiety."
+ UPDATING: "Aktualizowanie"
+ GPM_RELEASES: "Wydania GPM"
+ GPM_RELEASES_HELP: "Wybierz 'Testowe' aby zainstalować wersje beta lub testowe"
+ GPM_METHOD: "Metody zdalnego pobierania"
+ GPM_METHOD_HELP: "Gdy ustawione na Auto, Grav sprawdzi dostępność funkcji fopen i ją użyje jeśli jest dostępna, w przeciwnym wypadku używając cURL. Aby wymusić użycie jednego z nich, zmień to ustawienie."
+ GPM_VERIFY_PEER: "Zdalnie sprawdź certyfikat (SSL)"
+ GPM_VERIFY_PEER_HELP: "Niektórzy dostawcy nie przyjmują certyfikatu SSL getgrav.org, co uniemożliwia działanie GPM. Jeśli masz ten problem, wyłączenie tego ustawienia może pomóc"
+ AUTO: "Auto"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stabilne"
+ TESTING: "Testowe"
+ FRONTMATTER_PROCESS_TWIG: "Przetwarzaj Twig we frontmatter"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Gdy jest włączone, możesz używać zmiennych Twig we frontmatter dla strony"
+ FRONTMATTER_IGNORE_FIELDS: "Ignoruj pola frontmatter"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Niektóre pola frontmatter mogą zawierać Twig, ale nie powinny być przetwarzane, np. 'forms'"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Pomyślnie zainstalowano pakiet %s"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Sortowanie niemożliwe z powodu ustawień nadrzędnych"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Strona nie jest widoczna, sortowanie wyłączone"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "Sortowanie za pośrednictwem panelu admina nie jest obsługiwane, ponieważ istnieje ponad 200 stron rodzeństwa"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Sortowanie stron jest wyłączone dla tej strony, ponieważ Numeryczny prefiks folderu nie jest włączony"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "Uwaga: Nie można dodać mediów przed zapisaniem. Kliknij 'Zapisz' na górze strony"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "Uwaga: Należy zapisać stronę przed dodaniem plików."
+ DROP_FILES_HERE_TO_UPLOAD: "Upuść pliki tutaj lub kliknij ten przycisk "
+ INSERT: "Wstaw"
+ UNDO: "Cofnij"
+ REDO: "Ponów"
+ HEADERS: "Nagłówki"
+ BOLD: "Pogrubienie"
+ ITALIC: "Kursywa"
+ STRIKETHROUGH: "Przekreślenie"
+ SUMMARY_DELIMITER: "Znacznik podsumowania"
+ LINK: "Hiperłącze"
+ IMAGE: "Obraz"
+ BLOCKQUOTE: "Blok cytowany"
+ UNORDERED_LIST: "Lista punktowana"
+ ORDERED_LIST: "Lista numerowana"
+ EDITOR: "Edytor"
+ PREVIEW: "Podgląd"
+ FULLSCREEN: "Pełny ekran"
+ MODULAR: "Modułowa"
+ NON_ROUTABLE: "Bez routingu"
+ NON_MODULAR: "Niemodułowa"
+ NON_VISIBLE: "Niewidoczna"
+ NON_PUBLISHED: "Nieopublikowana"
+ CHARACTERS: "znaków"
+ PUBLISHING: "Publikacja"
+ NOTIFICATIONS: "Powiadomienia"
+ MEDIA_TYPES: "Typy mediów"
+ IMAGE_OPTIONS: "Opcje obrazu"
+ MIME_TYPE: "Typ MIME"
+ THUMB: "Miniatura"
+ TYPE: "Typ"
+ FILE_EXTENSION: "Rozszerzenie pliku"
+ LEGEND: "Legenda stron"
+ MEMCACHE_SERVER: "Serwer Memcache"
+ MEMCACHE_SERVER_HELP: "Adres serwera Memcache"
+ MEMCACHE_PORT: "Port Memcache"
+ MEMCACHE_PORT_HELP: "Port serwera Memcache"
+ MEMCACHED_SERVER: "Serwer Memcached"
+ MEMCACHED_SERVER_HELP: "Adres serwera Memcached"
+ MEMCACHED_PORT: "Port Memcached"
+ MEMCACHED_PORT_HELP: "Port serwera Memcached"
+ REDIS_SERVER: "Serwer Redis"
+ REDIS_SERVER_HELP: "Adres serwera Redis"
+ REDIS_PORT: "Port Redis"
+ REDIS_PORT_HELP: "Port serwera Redis"
+ REDIS_PASSWORD: "Hasło/sekret Redis"
+ ALL: "Wszystkie"
+ FROM: "z"
+ TO: "do"
+ RELEASE_DATE: "Data wydania"
+ SORT_BY: "Sortuj według"
+ RESOURCE_FILTER: "Filtr..."
+ FORCE_SSL: "Wymuś SSL"
+ FORCE_SSL_HELP: "Globalnie wymusza użycie SSL, jeśli włączone, Grav przekierowuje połączenia ze stroną przy użyciu HTTP na HTTPS"
+ NEWS_FEED: "Aktualności"
+ EXTERNAL_URL: "Zewnętrzny URL"
+ SESSION_PATH: "Ścieżka sesji"
+ SESSION_PATH_HELP: "Użyj tej opcji tylko jeśli używasz niestandardowego bazowego URL (zmieniasz domenę / subfolder strony)"
+ CUSTOM_BASE_URL: "Niestandardowy bazowy adres URL"
+ CUSTOM_BASE_URL_HELP: "Użyj tej opcji jeśli chcesz zmienić domenę strony albo użyć innego subfolderu, niż ten używany przez Grav. Przykład: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'Nie można użyć "%s" poza stronami.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Nie można przesłać pliku %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'Nie można przenieść pliku %s do "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'Anuluj przesyłanie'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Czy na pewno chcesz anulować przesyłanie?'
+ DROPZONE_DEFAULT_MESSAGE: 'Upuść pliki tutaj lub kliknij ten przycisk '
+ DROPZONE_FALLBACK_MESSAGE: 'Twoja przeglądarka nie wspiera przesyłania plików metodą przeciągnij i upuść.'
+ DROPZONE_FALLBACK_TEXT: 'Użyj formularza poniżej, aby przesłać pliki tak jak w dawnych czasach.'
+ DROPZONE_FILE_TOO_BIG: 'Przekroczono rozmiar pliku ({{filesize}}MiB). Maksymalny rozmiar pliku: {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "Nie można przesłać plików tego typu."
+ DROPZONE_MAX_FILES_EXCEEDED: "Nie można już przesłać więcej plików."
+ DROPZONE_REMOVE_FILE: "Usuń plik"
+ DROPZONE_RESPONSE_ERROR: "Serwer odpowiedział kodem {{statusCode}}."
+ PREMIUM_PRODUCT: "Premium"
+ ADMIN_CACHING: "Włącz buforowanie panelu admina"
+ ADMIN_CACHING_HELP: "Buforowanie panelu admina można kontrolować niezależnie od reszty strony"
+ DESTINATION_NOT_SPECIFIED: "Cel nie został określony"
+ UPLOAD_ERR_NO_TMP_DIR: "Folder tymczasowy nie został odnaleziony"
+ SESSION_SPLIT: "Podział sesji"
+ SESSION_SPLIT_HELP: "Niezależne, oddzielne sesje między stroną i innymi wtyczkami (takimi jak panel admina)"
+ ERROR_FULL_BACKTRACE: "Pełna informacja o błędzie"
+ ERROR_SIMPLE: "Uproszczona nazwa błędu"
+ ERROR_SYSTEM: "Błąd systemu"
+ IMAGES_AUTO_FIX_ORIENTATION: "Automatycznie popraw orientację"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Automatycznie poprawia orientację obrazu na podstawie danych Exif"
+ REDIS_SOCKET: "Gniazdo Redis"
+ REDIS_SOCKET_HELP: "Gniazdo serwera Redis"
+ NOT_SET: "Nie ustawione"
+ PERMISSIONS: "Uprawnienia"
+ NEVER_CACHE_TWIG: "Nigdy nie buforuj Twiga"
+ NEVER_CACHE_TWIG_HELP: "Buforuj jedynie treść, przetwarzając szablony Twiga przy każdym ich załadowaniu. Ignoruje ustawienie twig_first."
+ ALLOW_WEBSERVER_GZIP: "Gzip na serwerze"
+ ALLOW_WEBSERVER_GZIP_HELP: "Domyślnie wyłączone. Po włączeniu, Gzip/Deflate po stronie serwera będzie działać, ale połączenie HTTP nie będzie zamykane przed wykonaniem komendy onShutDown(), co spowolni ładowanie stron"
+ OFFLINE_WARNING: "Nie można nawiązać połączenia z GPM"
+ CLEAR_IMAGES_BY_DEFAULT: "Wyczyść pamięć podręczną obrazów (opcja domyślna)"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "Domyślnie przetworzone obrazy są usuwane podczas opróżniania pamięci podręcznej, można to wyłączyć"
+ CLI_COMPATIBILITY: "Kompatybilność z CLI"
+ CLI_COMPATIBILITY_HELP: "Gwarantuje użycie jedynie nieulotnych sterowników pamięci podręcznej (file, redis, memcache itp.)"
+ REINSTALL_PLUGIN: "Zreinstaluj wtyczkę"
+ REINSTALL_THEME: "Zreinstaluj motyw"
+ REINSTALL_THE: "Zreinstaluj %s"
+ CONFIRM_REINSTALL: "Czy na pewno chcesz zreinstalować ten %s?"
+ REINSTALLED_SUCCESSFULLY: "pomyślnie zreinstalowano %s"
+ ERROR_REINSTALLING_THE: "Wystąpił błąd podczas reinstalacji %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Pomyślnie zreinstalowano pakiet %s"
+ REINSTALLATION_FAILED: "Reinstalacja nie powiodła się"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "Zainstalowana wersja nie jest najnowszym wydaniem. Klikając \"Kontynuuj\", aby usunąć obecną wersję i zainstalować najnowsze dostępne wydanie"
+ TOOLS: "Narzędzia"
+ DIRECT_INSTALL: "Bezpośrednia instalacja"
+ NO_PACKAGE_NAME: "Nie określono nazwy pakietu"
+ PACKAGE_EXTRACTION_FAILED: "Wypakowywanie pakietu się nie powiodło"
+ NOT_VALID_GRAV_PACKAGE: "Paczka nie jest prawidłowym pakietem Grav"
+ NAME_COULD_NOT_BE_DETERMINED: "Nie można określić nazwy"
+ CANNOT_OVERWRITE_SYMLINKS: "Nie można zastąpić dowiązania symbolicznego"
+ ZIP_PACKAGE_NOT_FOUND: "Nie można odnaleźć pakietu ZIP"
+ GPM_OFFICIAL_ONLY: "Tylko oficjalne GPM"
+ GPM_OFFICIAL_ONLY_HELP: "Zezwalaj na bezpośrednie instalacje jedynie z oficjalnego repozytorium GPM."
+ SORTABLE_PAGES: "Sortowalne strony:"
+ ADMIN_SPECIFIC_OVERRIDES: "Nadpisania panelu admina"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Kolejność wyświetlania dzieci"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "Kolejność, w której dzieci tej strony będą wyświetlane w widoku stron panelu admina"
+ PWD_PLACEHOLDER: "złożony ciąg o długości co najmniej 8 znaków"
+ PWD_REGEX: "Wyrażenie regularne haseł użytkownika"
+ PWD_REGEX_HELP: "Domyślnie: hasło musi zawierać co najmniej jedną cyfrę, jedną wielką i jedną małą literę, a także mieć co najmniej 8 znaków długości"
+ USERNAME_PLACEHOLDER: "tylko małe litery, np. 'admin'"
+ USERNAME_REGEX: "Wyrażenie regularne nazw użytkownika"
+ USERNAME_REGEX_HELP: "Domyślnie: tylko małe litery, cyfry, myślniki i znaki podkreślenia, 3-16 znaków"
+ CONTENT_PADDING_HELP: "Włącz / wyłącz dopełnianie wokół obszaru zawartości, aby zapewnić więcej miejsca"
+ ENABLE_AUTO_METADATA: "Automatyczne metadane z Exif"
+ ENABLE_AUTO_METADATA_HELP: "Automatycznie generuj pliki metadanych dla obrazów z dołączonym Exif"
+ 2FA_TITLE: "Uwierzytelnienie dwuetapowe"
+ 2FA_INSTRUCTIONS: "##### Uwierzytelnienie dwuetapowe\nMasz na tym koncie włączone **2FA**. Użyj aplikacji od **2FA** i wprowadź aktualny **kod 6-cyfrowy**, aby zakończyć proces logowania."
+ 2FA_LABEL: "Dostęp administratora"
+ 2FA_FAILED: "Nieprawidłowy kod uwierzytelnienia dwuetapowego, proszę spróbować ponownie..."
+ 2FA_ENABLED: "2FA włączone"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "Sekret 2FA"
+ 2FA_REGENERATE: "Zregeneruj"
+ FORCE_LOWERCASE_URLS: "Wymuś URL małymi literami"
+ VIEW_SITE_TIP: "Wyświetl stronę"
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Prześlij i zainstaluj"
diff --git a/user/plugins/admin/languages/pt.yaml b/user/plugins/admin/languages/pt.yaml
new file mode 100644
index 0000000..51ca69e
--- /dev/null
+++ b/user/plugins/admin/languages/pt.yaml
@@ -0,0 +1,552 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Esta é uma versão Beta! Use por sua conta em risco..."
+ ADMIN_REPORT_ISSUE: "Um problema foi encontrado? Por favor, reporte no GitHub."
+ EMAIL_FOOTER: "Motorizado por Grav - CMS Flat File Moderno e Simples"
+ LOGIN_BTN: "Entrar"
+ LOGIN_BTN_FORGOT: "Esqueceu"
+ LOGIN_BTN_RESET: "Reset a senha"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Envie as instruções para resetar a senha"
+ LOGIN_BTN_CLEAR: "Limpar Envio"
+ LOGIN_BTN_CREATE_USER: "Criar Usuário"
+ LOGIN_LOGGED_IN: "Conectado com sucesso"
+ LOGIN_FAILED: "Falha ao logar"
+ LOGGED_OUT: "Você foi desconectado"
+ RESET_NEW_PASSWORD: "Por favor insira um novo senha …"
+ RESET_LINK_EXPIRED: "Link de reset expirado, por favor, tente novamente"
+ RESET_PASSWORD_RESET: "Senha foi redefinida"
+ RESET_INVALID_LINK: "Reset de link usado inválido, por favor tente novamente"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Instruções do reset da senha foi enviado por e-mail para %s"
+ FORGOT_FAILED_TO_EMAIL: "As instruções de email falhou ao ser enviada, tente novamente"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "A senha não pode ser resetada para o endereço de email %s"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "O usuário com login %s não existe"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Senha não pode ser modificada. Este site não foi configurado para enviar emails"
+ FORGOT_EMAIL_SUBJECT: "%s Pedido de reset de senha"
+ FORGOT_EMAIL_BODY: "Senha Restabelecida Caro %1$s,
Um pedido foi feito em%4$s pra restabelecer a sua senha.
Click aqui pra restabelecer a sua senha
Alternativamente, copia o seguinte URL na barra de endereços do seu navegador:
%2$s
Atenciosamente, %3$s
"
+ MANAGE_PAGES: "Gerenciar Páginas"
+ CONFIGURATION: "Configuração"
+ PAGES: "Páginas"
+ PLUGINS: "Extensões"
+ PLUGIN: "Extensão"
+ THEMES: "Temas"
+ LOGOUT: "Sair"
+ BACK: "Voltar"
+ NEXT: "Próximo"
+ PREVIOUS: "Anterior"
+ ADD_PAGE: "Adicionar Página"
+ ADD_MODULAR: "Adicionar Módulo"
+ MOVE: "Mover"
+ DELETE: "Apagar"
+ VIEW: "Visualizar"
+ SAVE: "Gravar"
+ NORMAL: "Normais"
+ EXPERT: "Esperto"
+ EXPAND_ALL: "Expandir Tudo"
+ COLLAPSE_ALL: "Recolher Tudo"
+ SECURE_DELETE_DESC: "Digite DELETE e confirme"
+ ERROR: "Erro"
+ CLOSE: "Fechar"
+ CANCEL: "Cancelar"
+ CONTINUE: "Continuar"
+ CONFIRM: "Confirmar"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Confirmação Requerida"
+ MODAL_CHANGED_DETECTED_TITLE: "Alterações Detectadas"
+ MODAL_CHANGED_DETECTED_DESC: "Você tem alterações não salvas. Tem certeza que deseja sair sem salvar?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Confirmação Requerida"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Tem a certeza de que pretende apagar o arquivo? Esta ação não pode ser desfeita."
+ ADD_FILTERS: "Adicionar Filtros"
+ SEARCH_PAGES: "Buscar Páginas"
+ VERSION: "Versão"
+ WAS_MADE_WITH: "Foi criado com"
+ BY: "Por"
+ UPDATE_THEME: "Atualizar Tema"
+ UPDATE_PLUGIN: "Atualizar Extensões"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "de este tema já está disponível"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "de este extensões já está disponível"
+ AUTHOR: "Autor"
+ HOMEPAGE: "Página Inicial"
+ DEMO: "Demo"
+ BUG_TRACKER: "Rastreador de erro"
+ KEYWORDS: "Palavras-chave"
+ LICENSE: "Licença"
+ DESCRIPTION: "Descrição"
+ README: "Leia me"
+ REMOVE_THEME: "Apagar Tema"
+ INSTALL_THEME: "Instalar Tema"
+ THEME: "Tema"
+ BACK_TO_THEMES: "Voltar a Temas"
+ BACK_TO_PLUGINS: "Voltar a Extensões"
+ CHECK_FOR_UPDATES: "Verificar Atualizações"
+ ADD: "Adicionar"
+ CLEAR_CACHE: "Limpar Cache"
+ CLEAR_CACHE_ALL_CACHE: "Todo o Cache"
+ CLEAR_CACHE_ASSETS_ONLY: "Somente Ativos"
+ CLEAR_CACHE_IMAGES_ONLY: "Apenas Imagens"
+ CLEAR_CACHE_CACHE_ONLY: "Apenas o Cache"
+ CLEAR_CACHE_TMP_ONLY: "Tmp somente"
+ DASHBOARD: "Painel"
+ UPDATES_AVAILABLE: "Atualização disponível"
+ DAYS: "Dias"
+ UPDATE: "Atualizar"
+ BACKUP: "Backup"
+ BACKUPS: "Cópias de segurança"
+ BACKUP_NOW: "Realizar Backup Agora"
+ BACKUPS_STATS: "Estatísticas de backup"
+ BACKUPS_HISTORY: "Histórico de backup"
+ BACKUPS_PROFILES: "Perfis de backup"
+ BACKUPS_COUNT: "Número de Cópias de Segurança"
+ BACKUPS_PROFILES_COUNT: "Número de Perfis"
+ BACKUPS_TOTAL_SIZE: "Espaço Usado"
+ BACKUPS_NEWEST: "Cópia de Segurança Mais Recente"
+ BACKUPS_OLDEST: "Cópia de Segurança Mais Antiga"
+ BACKUPS_PURGE: "Limpar"
+ BACKUPS_NOT_GENERATED: "Nenhuma cópia de segurança foi gerada ainda..."
+ BACKUPS_PURGE_NUMBER: "Usando %s de %s espaços de backup"
+ BACKUPS_PURGE_TIME: "%s dias de backups restantes"
+ BACKUPS_PURGE_SPACE: "Usando %s de %s"
+ BACKUP_DELETED: "Cópia de Segurança Apagada Com Sucesso"
+ BACKUP_NOT_FOUND: "Cópia de Segurança Não Encontrada"
+ BACKUP_DATE: "Data da Cópia de Segurança"
+ STATISTICS: "Estatísticas"
+ TODAY: "Hoje"
+ WEEK: "Semana"
+ MONTH: "Mês"
+ LATEST_PAGE_UPDATES: "Atualizações de pagina mais recente"
+ MAINTENANCE: "Manutenção"
+ UPDATED: "Atualizada"
+ MON: "Seg"
+ TUE: "Ter"
+ WED: "Qua"
+ THU: "Qui"
+ FRI: "Sex"
+ SAT: "Sáb"
+ SUN: "Dom"
+ COPY: "Copiar"
+ EDIT: "Editar"
+ CREATE: "Criar"
+ GRAV_ADMIN: "Grav Admin"
+ GRAV_OFFICIAL_PLUGIN: "Extensões Oficial do Grav"
+ GRAV_OFFICIAL_THEME: "Temas Oficial do Grav"
+ PLUGIN_SYMBOLICALLY_LINKED: "Este plugin está ligado simbolicamente. As atualizações não serão detectadas."
+ THEME_SYMBOLICALLY_LINKED: "Este tema está ligado simbolicamente. As atualizações não serão detectadas"
+ REMOVE_PLUGIN: "Apagar Extensões"
+ INSTALL_PLUGIN: "Instalar Extensões"
+ AVAILABLE: "Disponíveis"
+ INSTALLED: "Instalado"
+ INSTALL: "Instalar"
+ ACTIVE_THEME: "Tema Ativo"
+ SWITCHING_TO: "Mudando pra"
+ SWITCHING_TO_DESCRIPTION: "Ao mudar para um tema diferente, não há garantia de que todas as páginas de layout são suportadas, podendo causar erros ao tentar carregar as páginas."
+ SWITCHING_TO_CONFIRMATION: "Gostaria de continuar mudando o tema"
+ CREATE_NEW_USER: "Criar Novo Usuário"
+ REMOVE_USER: "Apagar Usuários"
+ ACCESS_DENIED: "Acesso rejeitado"
+ ACCOUNT_NOT_ADMIN: "sua conta não tem direitos administrativas"
+ PHP_INFO: "Info do PHP"
+ INSTALLER: "Instalador"
+ AVAILABLE_THEMES: "Temas Disponíveis"
+ AVAILABLE_PLUGINS: "Extensões Disponíveis"
+ INSTALLED_THEMES: "Temas Instalados"
+ INSTALLED_PLUGINS: "Extensões Instalado"
+ BROWSE_ERROR_LOGS: "Navegue registros de erro"
+ SITE: "Site"
+ INFO: "Info"
+ SYSTEM: "Sistema"
+ USER: "Usuário"
+ ADD_ACCOUNT: "Adicionar Conta"
+ SWITCH_LANGUAGE: "Mude de linguagem"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Plugin habilitado com sucesso"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Plugin desabilitado com sucesso"
+ SUCCESSFULLY_CHANGED_THEME: "Tema padrão mudado com sucesso"
+ INSTALLATION_FAILED: "Instalação falhou"
+ INSTALLATION_SUCCESSFUL: "Instalação Sucesso"
+ UNINSTALL_FAILED: "Falha ao desinstalar"
+ UNINSTALL_SUCCESSFUL: "Desinstalação sucedida"
+ SUCCESSFULLY_SAVED: "Salvo com sucesso"
+ SUCCESSFULLY_COPIED: "Copia com sucedida"
+ REORDERING_WAS_SUCCESSFUL: "Reordenação foi um sucesso"
+ SUCCESSFULLY_DELETED: "Exclusão Bem-sucedida"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Mudança de linguagem sucedida"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Você não tem as permissões suficientes para cumprir a tarefa"
+ CACHE_CLEARED: "Cache apagado"
+ METHOD: "Método"
+ ERROR_CLEARING_CACHE: "Erro ao limpar cache"
+ AN_ERROR_OCCURRED: "Ocorreu um erro"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Seu backup está pronto para ser abaixado"
+ DOWNLOAD_BACKUP: "Baixar backup"
+ PAGES_FILTERED: "Paginas filtradas"
+ NO_PAGE_FOUND: "Nenhuma página encontrada"
+ INVALID_PARAMETERS: "Parâmetros inválidos"
+ NO_FILES_SENT: "Nenhum arquivo foi enviado"
+ EXCEEDED_FILESIZE_LIMIT: "Excedeu o limite de tamanho de arquivo de configuração do PHP"
+ UNKNOWN_ERRORS: "Erro desconhecido"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Excedeu o limite de tamanho de arquivo de configuração do Grav"
+ UNSUPPORTED_FILE_TYPE: "Este tipo de arquivo não é apoiado"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Falha ao mover o arquivo carregado."
+ FILE_UPLOADED_SUCCESSFULLY: "Arquivo carregado com sucesso"
+ FILE_DELETED: "Arquivo excluído"
+ FILE_COULD_NOT_BE_DELETED: "O arquivo não pôde ser excluído"
+ FILE_NOT_FOUND: "O arquivo não foi encontrado"
+ NO_FILE_FOUND: "Nenhum arquivo encontrado"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav foi atualizado com sucesso para"
+ GRAV_UPDATE_FAILED: "Atualização de Grav falhou"
+ EVERYTHING_UPDATED: "Tudo atualizado"
+ UPDATES_FAILED: "Falha na atualização"
+ AVATAR_BY: "Avatar por"
+ AVATAR_UPLOAD_OWN: "Ou carregue o seu próprio arquivo..."
+ LAST_BACKUP: "Último Backup"
+ FULL_NAME: "Nome Completo"
+ USERNAME: "Nome de Usuário"
+ EMAIL: "Email"
+ USERNAME_EMAIL: "Nome de usuário ou E-mail"
+ PASSWORD: "Senha"
+ PASSWORD_CONFIRM: "Confirme sua senha"
+ TITLE: "Título"
+ LANGUAGE: "Idioma"
+ ACCOUNT: "Conta"
+ EMAIL_VALIDATION_MESSAGE: "Precisa estar um endereço de email válido"
+ PASSWORD_VALIDATION_MESSAGE: "Senha deveria conter pelo menos um número e uma carta maiúscula e minúscula, e pelo menos 8 ou mais caracteres"
+ LANGUAGE_HELP: "Definir o idioma preferido"
+ MEDIA: "Midia"
+ DEFAULTS: "Padrãos"
+ SITE_TITLE: "Título do Site"
+ SITE_TITLE_PLACEHOLDER: "Título do site geral"
+ SITE_TITLE_HELP: "Título padrão para o seu site, muitas vezes usado em temas"
+ SITE_DEFAULT_LANG: "Idioma padrão"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Idioma padrão para ser utilizado pelo tema "
+ SITE_DEFAULT_LANG_HELP: "Idioma padrão para ser utilizado pelo tema "
+ DEFAULT_AUTHOR: "Autor de Padrão"
+ DEFAULT_AUTHOR_HELP: "Um nome de autor padrão, frequentemente usado em temas ou no conteúdo da página"
+ DEFAULT_EMAIL: "Email Padrão"
+ DEFAULT_EMAIL_HELP: "Um email padrão para referência em temas ou páginas"
+ TAXONOMY_TYPES: "Tipos de Taxonomia"
+ TAXONOMY_TYPES_HELP: "Tipos de taxonomia tem que ser definidos aqui se você deseja usar-os em páginas"
+ PAGE_SUMMARY: "Resumo de Página"
+ ENABLED: "Habilitado"
+ ENABLED_HELP: "Habilitar resumo de página (Resumo retorna o mesmo que o conteúdo da página)"
+ 'YES': "Sim"
+ 'NO': "Não"
+ SUMMARY_SIZE: "Tamanho do Resumo"
+ SUMMARY_SIZE_HELP: "A quantidade de caracteres de uma página para usar como um resumo do conteúdo"
+ FORMAT: "Formato"
+ FORMAT_HELP: "resumo = determinado pelo primeiro delimitador utilizado ou tamanho do texto; longo = o delimitador do resumo será ignorado"
+ SHORT: "Curto"
+ LONG: "Longo"
+ DELIMITER: "Delimitador"
+ DELIMITER_HELP: "O delimitador de resumo (padrão ' === ')"
+ METADATA: "Metadado"
+ METADATA_HELP: "Valores de metadados padrão que serão exibidos em cada página com exceção dos que são substituída pela página"
+ NAME: "Nome"
+ CONTENT: "Conteúdo"
+ SIZE: "Tamanho"
+ ACTION: "Ação"
+ REDIRECTS_AND_ROUTES: "Redirecionamentos e Rotas"
+ CUSTOM_REDIRECTS: "Redirecionamentos personalizados"
+ CUSTOM_REDIRECTS_HELP: "routes para redirecionar para outras páginas. Substituição de Regex padrão é válida"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/seu/pseudônimo"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/seu/redirecionamento"
+ CUSTOM_ROUTES: "Routes personalizados"
+ CUSTOM_ROUTES_HELP: "routes para redirecionar para outras páginas. Substituição de Regex padrão é válida"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/seu/pseudônimo"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/seu/route/"
+ FILE_STREAMS: "Fluxos de arquivo"
+ DEFAULT: "Padrão"
+ PAGE_MEDIA: "Mídia de Página"
+ OPTIONS: "Opções"
+ PUBLISHED: "Publicado"
+ PUBLISHED_HELP: "Por padrão, uma página é publicada a menos que você explicitamente defini published: false ou através de um publish_date no futuro ou unpublish_date no passado"
+ DATE: "Data"
+ DATE_HELP: "O variável de Data permite definir especificamente uma data associada a esta página."
+ PUBLISHED_DATE: "Data de publicação"
+ PUBLISHED_DATE_HELP: "Pode prover uma data para acionar a publicação automaticamente."
+ UNPUBLISHED_DATE: "Data que foi despublicado"
+ UNPUBLISHED_DATE_HELP: "Pode prover uma data para acionar a des-publicação automaticamente."
+ ROBOTS: "Robôs"
+ TAXONOMIES: "Taxonomias"
+ TAXONOMY: "Taxonomia"
+ ADVANCED: "Avançado"
+ SETTINGS: "Preferências"
+ FOLDER_NUMERIC_PREFIX: "Prefixo numérico de pasta"
+ FOLDER_NUMERIC_PREFIX_HELP: "Prefixo numérico que fornece ordenação manual e implica visibilidade"
+ FOLDER_NAME: "Nome de Pasta"
+ FOLDER_NAME_HELP: "O nome da pasta que será armazenado no sistema de arquivos para esta página"
+ PARENT: "Mãe"
+ DEFAULT_OPTION_ROOT: "-Raiz-"
+ DEFAULT_OPTION_SELECT: "- Selecionar -"
+ DISPLAY_TEMPLATE: "Modelo de exibição"
+ DISPLAY_TEMPLATE_HELP: "O tipo de página que traduz em qual modelo Twig processa a página"
+ BODY_CLASSES: "Classes do corpo"
+ ORDERING: "Ordenação"
+ PAGE_ORDER: "Ordem da Página"
+ OVERRIDES: "Substituições"
+ MENU: "Menu"
+ MENU_HELP: "A sequência de caracteres para ser usado em um menu. Se não for definido, o título será usado."
+ SLUG: "Curto"
+ SLUG_HELP: "A variável do slug permite você definir especificamente a parte da página da URL"
+ SLUG_VALIDATE_MESSAGE: "Um slug deve cotener apenas cartas minúsculas, números, e traços"
+ PROCESS: "Processo"
+ PROCESS_HELP: "Controle como as páginas são processadas. Pode ser definido por página em vez de globalmente"
+ DEFAULT_CHILD_TYPE: "Tipo de criança padrão"
+ USE_GLOBAL: "Utilizar Globalmente"
+ ROUTABLE: "Roteável"
+ ROUTABLE_HELP: "Se esta página é acessível através de uma URL"
+ CACHING: "Armazenamento em cache"
+ VISIBLE: "Visível"
+ VISIBLE_HELP: "Determina se uma página é visível na navegação."
+ DISABLED: "Indisponível"
+ ITEMS: "Itens"
+ ORDER_BY: "Ordenar por"
+ ORDER: "Ordem"
+ FOLDER: "Pasta"
+ ASCENDING: "Crescente"
+ DESCENDING: "Decrescente"
+ ADD_MODULAR_CONTENT: "Adicionar conteúdo Modular"
+ PAGE_TITLE: "Título da Página"
+ PAGE_TITLE_HELP: "O Título da Página"
+ PAGE: "Página"
+ MODULAR_TEMPLATE: "Template modular"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Nome de Arquivo"
+ PARENT_PAGE: "Página Mãe"
+ HOME_PAGE: "Página Inicial"
+ HOME_PAGE_HELP: "A página que Grav usará como a página inicial padrão"
+ DEFAULT_THEME: "Tema Padrão"
+ DEFAULT_THEME_HELP: "Definir o tema padrão para Grav usar (o padrão é Antimatter)"
+ TIMEZONE: "Fuso Horário"
+ TIMEZONE_HELP: "Substituir o padrão de fuso horário do servidor"
+ SHORT_DATE_FORMAT: "Formato de exposição de data curta"
+ SHORT_DATE_FORMAT_HELP: "Definir o formato de data curta que pode ser usado por temas"
+ LONG_DATE_FORMAT: "Formato de exposição de data longa"
+ LONG_DATE_FORMAT_HELP: "Definir o formato de data longa que pode ser usado por temas"
+ DEFAULT_ORDERING: "Padrão de ordenação"
+ DEFAULT_ORDERING_HELP: "Páginas em uma lista processará usando esta ordem, a menos que ele seja substituída"
+ DEFAULT_ORDERING_DEFAULT: "Padrão - baseado no nome da pasta"
+ DEFAULT_ORDERING_FOLDER: "Pasta - baseado no nome da pasta, menos o prefixo"
+ DEFAULT_ORDERING_TITLE: "Título - baseado no campo de título no cabeçalho"
+ DEFAULT_ORDERING_DATE: "Data - baseado no campo de data no cabeçalho"
+ DEFAULT_ORDER_DIRECTION: "Direção de ordem padrão"
+ DEFAULT_ORDER_DIRECTION_HELP: "A direção de páginas em uma lista"
+ DEFAULT_PAGE_COUNT: "Contagem de página padrão"
+ DEFAULT_PAGE_COUNT_HELP: "Padrão - Máximo numero de páginas em uma lista"
+ DATE_BASED_PUBLISHING: "Publicação baseada na data"
+ DATE_BASED_PUBLISHING_HELP: "Automaticamente publicar ou des-publicar postagens baseado na sua data"
+ EVENTS: "Eventos"
+ EVENTS_HELP: "Habilitar ou desabilitar a eventos específicos. Desabilitar estes eventos pode quebrar plugins"
+ REDIRECT_DEFAULT_ROUTE: "Redirecionar a route padrão"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Automaticamente redirecionar para o route padrão de uma página"
+ LANGUAGES: "Linguagens"
+ SUPPORTED: "Apoiado"
+ SUPPORTED_HELP: "Lista separada por vírgulas de 2 letras para cada código de linguagem (por exemplo ' en, fr, de')"
+ TRANSLATIONS_ENABLED: "Traduções habilitadas"
+ TRANSLATIONS_ENABLED_HELP: "Suporte a traduções em Grav, plugins e extensões"
+ TRANSLATIONS_FALLBACK: "Reverter tradução"
+ TRANSLATIONS_FALLBACK_HELP: "Recuar através de traduções suportadas se idioma ativo não existir"
+ ACTIVE_LANGUAGE_IN_SESSION: "Idioma ativo na sessão"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Armazenar o idioma ativo na sessão"
+ HTTP_HEADERS: "Cabeçalhos HTTP"
+ EXPIRES: "Expira em"
+ EXPIRES_HELP: "Define o cabeçalho de expiração. O valor é em segundos."
+ CACHE_CONTROL: "HTTP Cache-Control"
+ CACHE_CONTROL_HELP: "Defina para um valor de controle de cache válido, como `no-cache, no-store, must-revalidate`"
+ LAST_MODIFIED: "Última Modificação"
+ LAST_MODIFIED_HELP: "Define o último cabeçalho modificado que pode ajudar a otimizar o proxy e cache do navegador"
+ ETAG: "ETag"
+ ETAG_HELP: "Define o cabeçalho etag para ajudar a identificar quando uma página foi modificada"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Define o cabeçalho 'Vary: Accept Encoding' para ajudar com proxy e cache de CDN"
+ MARKDOWN: "Markdown"
+ MARKDOWN_EXTRA: "Markdown extra"
+ MARKDOWN_EXTRA_HELP: "Habilitar o suporte a padrão Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ MARKDOWN_EXTRA_ESCAPE_FENCES: "Escape elementos HTML em vedações extras de markdown"
+ MARKDOWN_EXTRA_ESCAPE_FENCES_HELP: "Escape elementos HTML em vedações extras de markdown"
+ AUTO_LINE_BREAKS: "Quebras de linha automática"
+ AUTO_LINE_BREAKS_HELP: "Habilitar o suporte para quebra de linha automática em markdown"
+ AUTO_URL_LINKS: "Links de URL automatico"
+ AUTO_URL_LINKS_HELP: "Habilitar a conversão automática de URLs em hiperlinks HTML"
+ ESCAPE_MARKUP: "Escapar marcação"
+ ESCAPE_MARKUP_HELP: "Escapar tags de marcação em entidades HTML"
+ CACHING_HELP: "Chave LIGA/DESLIGA global para habilitar/desabilitar cache de Grav"
+ CACHE_CHECK_METHOD: "Método de verificação de cache"
+ CACHE_CHECK_METHOD_HELP: "Selecione o método que Grav usa para verificar se os arquivos de página foram modificados."
+ CACHE_DRIVER: "Driver de Cache"
+ CACHE_DRIVER_HELP: "Escolha qual controlador de cache Grav deve usar. 'Auto detectar' tenta encontrar o melhor para você"
+ CACHE_PREFIX: "Prefixo de cache"
+ CACHE_PREFIX_HELP: "Um identificador para parte da chave Grav. Não mude a menos que você saiba o que faz."
+ CACHE_PREFIX_PLACEHOLDER: "Derivado da URL base (substituir por entrada aleatória)"
+ CACHE_PURGE: "Limpar cache obsoleto"
+ LIFETIME: "Duração"
+ LIFETIME_HELP: "Define o tempo de vida do cache em segundos. 0 = infinito"
+ GZIP_COMPRESSION: "Compactação Gzip"
+ GZIP_COMPRESSION_HELP: "Habilite a compactação GZip da página Grav para aumentar o desempenho."
+ TWIG_TEMPLATING: "Template Twig"
+ TWIG_CACHING: "Cache de Twig"
+ TWIG_CACHING_HELP: "Controla o mecanismo de cache do Twig. Deixe habilitado para melhor desempenho."
+ TWIG_DEBUG: "Depurar Twig"
+ TWIG_DEBUG_HELP: "Permite a opção de não carregar a extensão do depurador Twig"
+ DETECT_CHANGES: "Detectar alterações"
+ DETECT_CHANGES_HELP: "Twig irá recompilar automaticamente o cacheTwig se detectar qualquer alteração no Template Twig"
+ AUTOESCAPE_VARIABLES: "Escapa automática variáveis"
+ AUTOESCAPE_VARIABLES_HELP: "Escapa automática todas as variáveis. Isso provavelmente quebrará seu site"
+ ASSETS: "Recursos"
+ CSS_PIPELINE: "CSS pipeline"
+ CSS_PIPELINE_HELP: "CSS pipeline é a unificação de múltiplos recursos CSS em um único arquivo"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Incluir externos em CSS pipeline"
+ CSS_MINIFY: "Minificar CSS"
+ CSS_REWRITE: "Reescrever CSS"
+ JAVASCRIPT_MINIFY: "Minificar JavaScript"
+ COLLECTIONS: "Coleções"
+ ERROR_HANDLER: "Processamento de Erros"
+ DISPLAY_ERRORS: "Erros de exibição"
+ DISPLAY_ERRORS_HELP: "Exibir página de erro com log completo"
+ LOG_ERRORS: "Log de erros"
+ DEBUGGER: "Depurador"
+ DEBUG_TWIG: "Depurar Twig"
+ DEFAULT_IMAGE_QUALITY: "Qualidade de imagem padrão"
+ CACHE_ALL: "Todas as imagens em cache"
+ UPLOAD_LIMIT: "Limite de upload de arquivo"
+ UPLOAD_LIMIT_HELP: "Definir o tamanho máximo de upload em bytes (0 é ilimitado)"
+ ENABLE_MEDIA_TIMESTAMP: "Habilitar os carimbos de hora na mídia"
+ SESSION: "Sessão"
+ TIMEOUT: "Tempo limite esgotado"
+ TIMEOUT_HELP: "Define o tempo limite da sessão em segundos"
+ ABSOLUTE_URLS: "URL absoluto"
+ PARAMETER_SEPARATOR: "Separador de parâmetro"
+ TASK_COMPLETED: "Tarefas concluídas"
+ EVERYTHING_UP_TO_DATE: "Está tudo atualizado"
+ UPDATES_ARE_AVAILABLE: "atualizações estão disponíveis"
+ IS_AVAILABLE_FOR_UPDATE: "está disponível para atualização"
+ IS_NOW_AVAILABLE: "já está disponível"
+ CURRENT: "Atual"
+ UPDATE_GRAV_NOW: "Atualizar Grav Agora"
+ GRAV_SYMBOLICALLY_LINKED: "Grav é simbolicamente ligada. Atualização não estará disponível"
+ UPDATING_PLEASE_WAIT: "Atualizando... por favor aguarde, abaixando"
+ OF_THIS: "isto"
+ OF_YOUR: "de seu"
+ HAVE_AN_UPDATE_AVAILABLE: "tem uma atualização disponível"
+ SAVE_AS: "Gravar como"
+ AND: "e"
+ UPDATE_AVAILABLE: "Atualização disponível"
+ METADATA_KEY: "Chave (por exemplo, ' Palavra Chave')"
+ METADATA_VALUE: "Valor (e.g. 'Blog, Grav')"
+ USERNAME_HELP: "Nome de usuário deve situar-se entre 3 e 16 caracteres, incluindo letras minúsculas, números, sublinhados, e hífens. Letras maiúsculas, espaços e caracteres especiais, não são permitidos"
+ FULLY_UPDATED: "Totalmente Atualizado"
+ SAVE_LOCATION: "Gravado às"
+ PAGE_FILE: "Template Página"
+ NO_USER_ACCOUNTS: "As contas de usuário não foram encontradas, por favor crie uma primeiro..."
+ REDIRECT_TRAILING_SLASH: "Redirecionar a barra à direita"
+ DEFAULT_DATE_FORMAT: "Formato de data de página"
+ DEFAULT_DATE_FORMAT_HELP: "Formato de data de página usado pelo Grav. Por padrão, Grav tenta de adivinhar a seu formato de data, mas tambem você pode especificar um formato usando a sintaxe de data do PHP (por exemplo: Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Adivinha automaticamente"
+ IGNORE_FILES: "Ignorar arquivos"
+ IGNORE_FILES_HELP: "Arquivos específicos para ignorar quando estiver processando as páginas"
+ IGNORE_FOLDERS: "Ignorar as pastas"
+ IGNORE_FOLDERS_HELP: "Pastas específicas para ignorar quando estiver processando as páginas"
+ HIDE_EMPTY_FOLDERS: "Ocultar pastas vazias"
+ HTTP_ACCEPT_LANGUAGE: "Definir idioma pelo navegador"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Você pode optar para tentar definir o idioma com base em tag de cabeçalho 'http_accept_language' no navegador"
+ OVERRIDE_LOCALE: "Substituir a localidade"
+ OVERRIDE_LOCALE_HELP: "Substituir a configuração de localidade em PHP baseado na linguagem atual"
+ REDIRECT: "Redirecionamento de página"
+ PLUGIN_STATUS: "Estado do Extensões"
+ INCLUDE_DEFAULT_LANG: "Incluir o idioma padrão"
+ INCLUDE_DEFAULT_LANG_HELP: "Isto irá preceder todas os URLs na língua padrão com o idioma padrão. por exemplo, `/en/blog/my-post`"
+ ALLOW_URL_TAXONOMY_FILTERS: "Filtros de taxonomia de URL"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Coleções baseado em página permitem que você filtre através de ` /taxonomia: valor `."
+ REDIRECT_DEFAULT_CODE: "Código de redirecionamento padrão"
+ REDIRECT_DEFAULT_CODE_HELP: "O código de status de HTTP para usar para redirecionamentos"
+ IGNORE_HIDDEN: "Ignorar escondidos"
+ IGNORE_HIDDEN_HELP: "Ignorar todos os arquivos e pastas que começam com um ponto"
+ WRAPPED_SITE: "Site embrulhado"
+ WRAPPED_SITE_HELP: "Para os temas/plugins saber se Grav é embrulhado por outra plataforma"
+ APPEND_URL_EXT: "Acrescentar a extensão de URL"
+ PAGE_MODES: "Modos de página"
+ PAGE_TYPES: "Tipos de Página"
+ ACCESS_LEVELS: "Níveis de acesso"
+ GROUPS: "Grupos"
+ GROUPS_HELP: "Lista de grupos de qual o usuário faz parte"
+ ADMIN_ACCESS: "Acesso de Administrador"
+ SITE_ACCESS: "Acesso ao Site"
+ INVALID_SECURITY_TOKEN: "Token de segurança inválido"
+ ACTIVATE: "Activar"
+ CACHE_PERMS: "Permissões de cache"
+ REMOVE_SUCCESSFUL: "Remover foi sucedido"
+ REMOVE_FAILED: "Remover Falhou"
+ HIDE_HOME_IN_URLS: "Esconder route da página home em URLs"
+ HIDE_HOME_IN_URLS_HELP: "Assegurará que os routes padrão para todas as páginas de baixo de home não referência o route regular da pagina home"
+ TWIG_FIRST: "Processar Twig Primeiro"
+ TWIG_FIRST_HELP: "Se você tiver habilitado o processamento de página twig, você pode configurar twig a processar antes ou depois do markdown"
+ SESSION_SECURE: "Seguro"
+ SESSION_SECURE_HELP: "Se for verdade, indica que a comunicação para este cookie deve ser sobre uma transmissão encriptada. Aviso: Habilitar isso apenas em sites que são executados exclusivamente em HTTPS"
+ SESSION_HTTPONLY: "Apenas HTTP"
+ SESSION_HTTPONLY_HELP: "Se for verdade, indica que os cookies devem ser usados apenas por HTTP, e modificaçoes feito por JavaScript não é permitido"
+ REVERSE_PROXY: "Proxy reverso"
+ REVERSE_PROXY_HELP: "Habilitar isto se você está atrás de um proxy reverso e você está tendo problemas com URLs contendo portas incorretas"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Frontmatter inválido, não foi possível salvar"
+ ADD_FOLDER: "Adicionar pasta"
+ PROXY_URL: "URL do proxy"
+ PROXY_URL_HELP: "Digite o Host do proxy ou IP e porta"
+ NOTHING_TO_SAVE: "Nada a salvar"
+ ADD_ITEM: "Adicionar iten"
+ INSTALLING: "Instalando"
+ LOADING: "Carregando.."
+ INSTALLING_DEPENDENCIES: "Instalando dependências..."
+ INSTALLING_PACKAGES: "Instalando pacote(s).."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Pacote(s) instalado com sucesso."
+ READY_TO_INSTALL_PACKAGES: "Pronto para instalar pacote(s)"
+ PACKAGES_NOT_INSTALLED: "Pacotes não instalados"
+ PACKAGES_NEED_UPDATE: "Pacotes já instalados, mas muito antigos"
+ REMOVE_THE: "Remova %s"
+ CONFIRM_REMOVAL: "Você tem certeza que deseja apagar esse %s?"
+ REMOVED_SUCCESSFULLY: "%s removido com sucesso"
+ ERROR_REMOVING_THE: "Erro ao remover %s"
+ READY_TO_UPDATE_PACKAGES: "Pronto para atualizar pacote(s)"
+ ERROR_UPDATING_PACKAGES: "Erro ao atualizar pacote(s)"
+ UPDATING_PACKAGES: "Atualizando pacote(s).."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Pacote(s) atualizado com sucesso."
+ UPDATING: "Atualizando"
+ AUTO: "Auto"
+ STABLE: "Estável"
+ TESTING: "Testando"
+ INSERT: "Inserir"
+ UNDO: "Desfazer"
+ REDO: "Refazer"
+ HEADERS: "Cabeçalhos"
+ BOLD: "Negrito"
+ ITALIC: "Itálico"
+ IMAGE: "Imagem"
+ BLOCKQUOTE: "Citação de Bloco"
+ UNORDERED_LIST: "Lista Desordenada"
+ ORDERED_LIST: "Lista Ordenada"
+ EDITOR: "Editor"
+ CHARACTERS: "caracteres"
+ PUBLISHING: "Publicando"
+ TYPE: "Tipo"
+ REDIS_PORT: "Porta do Redis"
+ REDIS_PORT_HELP: "A porta do servidor Redis"
+ REDIS_PASSWORD: "Senha do Redis"
+ ALL: "Tudo"
+ FROM: "de"
+ TO: "para"
+ RELEASE_DATE: "Data de Lançamento"
+ SORT_BY: "Ordenar por"
+ RESOURCE_FILTER: "Filtro..."
+ FORCE_SSL: "Forçar SSL"
+ NEWS_FEED: "Feed de Notícias"
+ EXTERNAL_URL: "URL Externa"
+ SESSION_PATH_HELP: "Use somente se você escolher um URL de base personalizada (você reescreve o domínio / subpasta do site)"
+ CUSTOM_BASE_URL: "URL de base personalizada"
+ DROPZONE_CANCEL_UPLOAD: 'Cancelar Upload'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Tem certeza de que deseja cancelar este upload?'
+ DROPZONE_DEFAULT_MESSAGE: 'Solte seus arquivos aqui ou clique nessa área '
+ DROPZONE_FALLBACK_MESSAGE: 'Seu navegador não suporta upload de arquivos via arrastar e soltar.'
+ DROPZONE_FALLBACK_TEXT: 'Use o formulário de retorno abaixo para fazer o upload de seus arquivos como nos dias mais antigos.'
+ DROPZONE_INVALID_FILE_TYPE: "Você não pode fazer o upload de arquivos desse tipo."
+ DROPZONE_MAX_FILES_EXCEEDED: "Você não pode carregar mais arquivos."
+ DROPZONE_REMOVE_FILE: "Remover arquivo"
+ DROPZONE_RESPONSE_ERROR: "Servidor respondeu com o código {{statusCode}}."
+ PREMIUM_PRODUCT: "Premium"
+ ADMIN_CACHING: "Habilitar Cache Admin"
+ SORTABLE_PAGES: "Páginas Ordenáveis:"
+ 2FA_LABEL: "Acesso Admin"
+ 2FA_REGENERATE: "Regerar"
+ BACKUPS_MAX_COUNT_HELP: "0 é ilimitado"
diff --git a/user/plugins/admin/languages/ro.yaml b/user/plugins/admin/languages/ro.yaml
new file mode 100644
index 0000000..ee2b9dd
--- /dev/null
+++ b/user/plugins/admin/languages/ro.yaml
@@ -0,0 +1,819 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Aceasta este o versiune Beta! Folosiți-o pe propiul risc..."
+ ADMIN_REPORT_ISSUE: "Ați găsit o problemă? Raportați-o pe Github."
+ EMAIL_FOOTER: "Construit cu Grav - CMS modern"
+ LOGIN_BTN: "Logare"
+ LOGIN_BTN_FORGOT: "Ai uitat?"
+ LOGIN_BTN_RESET: "Resetarea parolei"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Trimite instrucţiunile de resetare"
+ LOGIN_BTN_CLEAR: "Șterge datele introduse"
+ LOGIN_BTN_CREATE_USER: "Creează utilizator"
+ LOGIN_LOGGED_IN: "Te-ai conectat cu succes"
+ LOGIN_FAILED: "Conectare nereuşită"
+ LOGGED_OUT: "Ai fost deconectat"
+ RESET_NEW_PASSWORD: "Te rugăm să introduci o parolă nouă …"
+ RESET_LINK_EXPIRED: "Link-ul de resetare a expirat, vă rugăm să încercaţi din nou"
+ RESET_PASSWORD_RESET: "Parola a fost resetată"
+ RESET_INVALID_LINK: "Incorecta a reseta link-ul folosit, vă rugăm să încercaţi din nou, vă rugăm încercați din nou"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Instrucţiunile pentru resetarea parolei au fost trimise prin e-mail la %s"
+ FORGOT_FAILED_TO_EMAIL: "Instrucţiunile nu au putut fi trimise pe e-mail, vă rugăm să încercaţi din nou"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Nu se poate reseta parola pentru %s, nu există o adresă de e-mail setată"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Utilizatorul cu numele de %s nu există"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Nu se poate reseta parola. Acest site nu este configurat pentru a trimite email-uri"
+ FORGOT_EMAIL_SUBJECT: "%s cerere de resetare parolă"
+ FORGOT_EMAIL_BODY: "Resetare parolă Dragă %1$s,
Cererea a fost făcută pe data de %4$s pentru a reseta parola.
< br / > Apăsați aici pentru a reseta parola < br / > < br / >
Alternativ, copiaţi adresa URL în bara de adrese a browser-ului dvs:
%2$s
< br / > Cu respect, < br / > < br / >%3$s
"
+ MANAGE_PAGES: "Gestionare pagini"
+ CONFIGURATION: "Configurație"
+ PAGES: "Pagini"
+ PLUGINS: "Module"
+ PLUGIN: "Modul"
+ THEMES: "Teme"
+ LOGOUT: "Deconectare"
+ BACK: "Înapoi"
+ NEXT: "Următorul"
+ PREVIOUS: "Anteriorul"
+ ADD_PAGE: "Adaugă pagină"
+ ADD_MODULAR: "Adaugă modulare"
+ MOVE: "Mută"
+ DELETE: "Sterge"
+ UNSET: "Nefixat"
+ VIEW: "Vizualizaţi"
+ SAVE: "Salvează"
+ NORMAL: "Normal"
+ EXPERT: "Expert"
+ EXPAND_ALL: "Extinde tot"
+ COLLAPSE_ALL: "Restrânge toate"
+ ERROR: "Eroare"
+ CLOSE: "Închide"
+ CANCEL: "Anulează"
+ CONTINUE: "Continuare"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Confirmare necesară"
+ MODAL_CHANGED_DETECTED_TITLE: "Modificări detectate"
+ MODAL_CHANGED_DETECTED_DESC: "Există modificări nesalvate. Sunteţi sigur că doriţi să părăsiți pagina fără salvare?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Confirmare necesară"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Sunteţi sigur că doriţi să ştergeţi acest fişier? Această acţiune nu poate fi modificată ulterior."
+ ADD_FILTERS: "Adaugă filtre"
+ SEARCH_PAGES: "Căutaţi pagini"
+ VERSION: "Versiune"
+ WAS_MADE_WITH: "A fost creat cu"
+ BY: "De"
+ UPDATE_THEME: "Actualizare temă"
+ UPDATE_PLUGIN: "Actualizare Modul"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "a acestei teme este disponibilă"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "a acestui modul este disponibilă"
+ AUTHOR: "Autor"
+ HOMEPAGE: "Pagina de start"
+ DEMO: "Demonstraţie"
+ BUG_TRACKER: "Sistemul de urmărire a defecțiunilor"
+ KEYWORDS: "Cuvinte cheie"
+ LICENSE: "Licenţă"
+ DESCRIPTION: "Descriere"
+ README: "Citește-mă"
+ REMOVE_THEME: "Eliminaţi tema"
+ INSTALL_THEME: "Instalează tema"
+ THEME: "Temă"
+ BACK_TO_THEMES: "Înapoi la teme"
+ BACK_TO_PLUGINS: "Înapoi la module"
+ CHECK_FOR_UPDATES: "Caută Actualizări"
+ ADD: "Adăugaţi"
+ CLEAR_CACHE: "Golește memoria cache"
+ CLEAR_CACHE_ALL_CACHE: "Toate elementele din memoria cache"
+ CLEAR_CACHE_ASSETS_ONLY: "Doar activele"
+ CLEAR_CACHE_IMAGES_ONLY: "Doar imaginile"
+ CLEAR_CACHE_CACHE_ONLY: "Doar memoria cache"
+ CLEAR_CACHE_TMP_ONLY: "Doar Tmp"
+ DASHBOARD: "Panou de control"
+ UPDATES_AVAILABLE: "Actualizări disponibile"
+ DAYS: "Zile"
+ UPDATE: "Actualizează"
+ BACKUP: "Copie de siguranță"
+ BACKUPS: "Copii de rezervă"
+ BACKUP_NOW: "Crează o copie de rezervă acum"
+ BACKUPS_STATS: "Statistici pentru copiile de rezervă"
+ BACKUPS_HISTORY: "Istoric pentru copiile de rezervă"
+ BACKUPS_PURGE_CONFIG: "Șterge copiile de rezervă"
+ BACKUPS_PROFILES: "Profiluri de rezervă"
+ BACKUPS_COUNT: "Numărul de copii de rezervă"
+ BACKUPS_PROFILES_COUNT: "Numărul de profiluri"
+ BACKUPS_TOTAL_SIZE: "Spaţiul utilizat"
+ BACKUPS_NEWEST: "Cea mai nouă copipe de rezervă"
+ BACKUPS_OLDEST: "Cea mai veche copie de rezervă"
+ BACKUPS_PURGE: "Șterge"
+ BACKUPS_NOT_GENERATED: "Nu au fost generate copii de rezervă..."
+ BACKUPS_PURGE_NUMBER: "Folosind %s din %s sloturi de rezervă"
+ BACKUPS_PURGE_TIME: "%s zile rămase pentru copii de rezervă"
+ BACKUPS_PURGE_SPACE: "Folosind %s din %s"
+ BACKUP_DELETED: "Copia de rezervă ştearsă cu succes"
+ BACKUP_NOT_FOUND: "Copia de rezervă nu a fost găsită"
+ BACKUP_DATE: "Data copiei de rezervă"
+ STATISTICS: "Statistici"
+ TODAY: "Azi"
+ WEEK: "Săptămână"
+ MONTH: "Lună"
+ LATEST_PAGE_UPDATES: "Cele mai recente actualizări ale paginilor"
+ MAINTENANCE: "Mentenanță"
+ UPDATED: "Actualizat"
+ MON: "Luni"
+ TUE: "Marţi"
+ WED: "Miercuri"
+ THU: "Joi"
+ FRI: "Vineri"
+ SAT: "Sâmbătă"
+ SUN: "Duminică"
+ COPY: "Copiază"
+ EDIT: "Editează"
+ CREATE: "Creează"
+ GRAV_ADMIN: "Grav Admin"
+ GRAV_OFFICIAL_PLUGIN: "Modul oficial al Grav"
+ GRAV_OFFICIAL_THEME: "Tema oficială a Grav"
+ PLUGIN_SYMBOLICALLY_LINKED: "Acest modul este legat simbolic. Actualizările nu vor fi detectate."
+ THEME_SYMBOLICALLY_LINKED: "Această temă este legată simbolic. Actualizările nu vor fi detectate"
+ REMOVE_PLUGIN: "Elimina modul"
+ INSTALL_PLUGIN: "Instalează modul"
+ AVAILABLE: "Disponibile"
+ INSTALLED: "Instalate"
+ INSTALL: "Instalează"
+ ACTIVE_THEME: "Temă activă"
+ SWITCHING_TO: "Schimbă"
+ SWITCHING_TO_DESCRIPTION: "Prin schimbarea temei nu există nici o garanție că toate aspectele paginilor sunt acceptate și pot apărea erori la încărcarea paginilor respective."
+ SWITCHING_TO_CONFIRMATION: "Vreți să continuați cu schimbarea temei"
+ CREATE_NEW_USER: "Creaţi utilizator nou"
+ REMOVE_USER: "Eliminați utilizatorul"
+ ACCESS_DENIED: "Acces refuzat"
+ ACCOUNT_NOT_ADMIN: "contul nu are permisiuni de administrator"
+ PHP_INFO: "Informaţii PHP"
+ INSTALLER: "Programul de instalare"
+ AVAILABLE_THEMES: "Teme disponibile"
+ AVAILABLE_PLUGINS: "Module disponibile"
+ INSTALLED_THEMES: "Teme instalate"
+ INSTALLED_PLUGINS: "Module instalate"
+ BROWSE_ERROR_LOGS: "Răsfoiţi jurnalele de erori"
+ SITE: "Site"
+ INFO: "Informații"
+ SYSTEM: "Sistem"
+ USER: "Utilizator"
+ ADD_ACCOUNT: "Adaugă cont"
+ SWITCH_LANGUAGE: "Schimbare limbă"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Modul activat"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Modul dezacticat"
+ SUCCESSFULLY_CHANGED_THEME: "Tema implicită a fost schimbată"
+ INSTALLATION_FAILED: "Instalare nereușită"
+ INSTALLATION_SUCCESSFUL: "Instalare reușită"
+ UNINSTALL_FAILED: "Dezinstalare nereușită"
+ UNINSTALL_SUCCESSFUL: "Dezinstalare reușită"
+ SUCCESSFULLY_SAVED: "Salvare reuşită"
+ SUCCESSFULLY_COPIED: "Copiere reușită"
+ REORDERING_WAS_SUCCESSFUL: "Reordonare a fost reușită"
+ SUCCESSFULLY_DELETED: "Ștergere reușită"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Schimbarea limbii reușită"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Nu aveți permisiuni suficiente pentru sarcină"
+ CACHE_CLEARED: "Memoria cache golită"
+ METHOD: "Metodă"
+ ERROR_CLEARING_CACHE: "Eroare golire memoria cache"
+ AN_ERROR_OCCURRED: "A apărut o eroare"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Copia de rezervă este gata pentru descărcare"
+ DOWNLOAD_BACKUP: "Descarcă copie de rezervă"
+ PAGES_FILTERED: "Pagini filtrate"
+ NO_PAGE_FOUND: "Pagina nu a fost găsită"
+ INVALID_PARAMETERS: "Parametri incorecţi"
+ NO_FILES_SENT: "Nici un fișier trimis"
+ EXCEEDED_FILESIZE_LIMIT: "Limita fișierului din configurarea PHP a fost depășită"
+ EXCEEDED_POSTMAX_LIMIT: "A depăşit configurarea PHP pentru post_max_size"
+ UNKNOWN_ERRORS: "Eroare necunoscută"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Limita fișierului de configurare Grav a fost depășită"
+ UNSUPPORTED_FILE_TYPE: "Tipul de fișier nu este suportat"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Nu a reuşit mutarea fişierului încărcat."
+ FILE_UPLOADED_SUCCESSFULLY: "Fişier încărcat cu succes"
+ FILE_DELETED: "Fișier sters"
+ FILE_COULD_NOT_BE_DELETED: "Fișierul nu poate fi șters"
+ FILE_NOT_FOUND: "Fișierul nu a fost găsit"
+ NO_FILE_FOUND: "Nici un fişier găsit"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav a fost actualizat cu succes la"
+ GRAV_UPDATE_FAILED: "Actualizarea Grav a eşuat"
+ EVERYTHING_UPDATED: "Totul a fost actualizat"
+ UPDATES_FAILED: "Actualizări nereușite"
+ AVATAR_BY: "Avatar de"
+ AVATAR_UPLOAD_OWN: "Sau încărcaţi-vă propriul..."
+ LAST_BACKUP: "Ultima copie de rezervă"
+ FULL_NAME: "Nume si prenume"
+ USERNAME: "Nume de utilizator"
+ EMAIL: "Adresa de e-mail"
+ USERNAME_EMAIL: "Numele de utilizator sau E-mail"
+ PASSWORD: "Parolă"
+ PASSWORD_CONFIRM: "Confirmați parola"
+ TITLE: "Titlu"
+ LANGUAGE: "Limbă"
+ ACCOUNT: "Cont"
+ EMAIL_VALIDATION_MESSAGE: "Trebuie să fie o adresă de e-mail validă"
+ PASSWORD_VALIDATION_MESSAGE: "Parola trebuie să conţină cel puţin un număr şi o majusculă, litere mici şi cel puţin 8 sau mai multe caractere"
+ LANGUAGE_HELP: "Setează limba preferată"
+ MEDIA: "Media"
+ DEFAULTS: "Implicite"
+ SITE_TITLE: "Titlul Site-ului"
+ SITE_TITLE_PLACEHOLDER: "Titlul Site-ului"
+ SITE_TITLE_HELP: "Titlul Site-ului dvs., adesea utilizat în teme"
+ SITE_DEFAULT_LANG: "Limba de bază"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Limba de bază folosită de eticheta a temei"
+ SITE_DEFAULT_LANG_HELP: "Limba de bază folosită de eticheta a temei"
+ DEFAULT_AUTHOR: "Autor implicit"
+ DEFAULT_AUTHOR_HELP: "Un nume de autor implicit, adesea utilizat în teme sau conţinutul paginii"
+ DEFAULT_EMAIL: "Adresă e-mail implicită"
+ DEFAULT_EMAIL_HELP: "O adresă de e-mail implicită folosită în teme sau pagini"
+ TAXONOMY_TYPES: "Tipuri de taxonomie"
+ TAXONOMY_TYPES_HELP: "Tipurile de taxonomie trebuie să fie definite aici dacă doriţi să le utilizaţi în pagini"
+ PAGE_SUMMARY: "Rezumatul paginii"
+ ENABLED: "Activat"
+ ENABLED_HELP: "Permite Rezumatul paginii (Rezumat este la fel cu conţinutul paginii)"
+ 'YES': "Da"
+ 'NO': "Nu"
+ SUMMARY_SIZE: "Dimensiunea Rezumatului"
+ SUMMARY_SIZE_HELP: "Cantitatea de caractere pe care o pagină le folosește pentru Rezumat"
+ FORMAT: "Formatul"
+ FORMAT_HELP: "scurt = utilizează prima apariţie a delimitatorului sau mărime; lung = delimitatorul pentru rezumat va fi ignorat"
+ SHORT: "Scurt"
+ LONG: "Lung"
+ DELIMITER: "Delimitator"
+ DELIMITER_HELP: "Delimitatorul rezumatului (implicit ' ===')"
+ METADATA: "Metadate"
+ METADATA_HELP: "Valorile implicite ale metadatelor care vor fi afişate pe fiecare pagină, cu excepţia cazului în care sunt suprascrise de pagini"
+ NAME: "Nume"
+ CONTENT: "Conținut"
+ SIZE: "Dimensiune"
+ ACTION: "Acţiune"
+ REDIRECTS_AND_ROUTES: "Redirecţionări & rute"
+ CUSTOM_REDIRECTS: "Redirecţionări personalizate"
+ CUSTOM_REDIRECTS_HELP: "rute pentru a redirecţiona către alte pagini. Înlocuirea Standard Regex este valabilă"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/pseudonimul/tau"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/redirecţionarea/ta"
+ CUSTOM_ROUTES: "Rute personalizate"
+ CUSTOM_ROUTES_HELP: "rute pentru a redirecţiona alias-ul către alte pagini. Înlocuirea Standard Regex este valabilă"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/pseudonimul/tau"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/ruta/ta"
+ FILE_STREAMS: "Fluxuri de fișiere"
+ DEFAULT: "Implicit"
+ PAGE_MEDIA: "Media din pagină"
+ OPTIONS: "Opţiuni"
+ PUBLISHED: "Publicat"
+ PUBLISHED_HELP: "În mod implicit, o pagină este publicată cu excepţia cazului în care setaţi explicit publicarea: false sau prin intermediul unui publish_date în viitor, sau unpublish_date în trecut"
+ DATE: "Dată"
+ DATE_HELP: "Variabila dată permite setarea unei date specifice asociată cu această pagină."
+ PUBLISHED_DATE: "Data publicării"
+ PUBLISHED_DATE_HELP: "Poți oferi o dată pentru a declanşa în mod automat publicarea."
+ UNPUBLISHED_DATE: "Data nepublicare"
+ UNPUBLISHED_DATE_HELP: "Poți oferi o dată pentru a declanşa în mod automat nepublicarea."
+ ROBOTS: "Roboți"
+ TAXONOMIES: "Taxonomii"
+ TAXONOMY: "Taxonomie"
+ ADVANCED: "Setări Avansate"
+ SETTINGS: "Configurări"
+ FOLDER_NUMERIC_PREFIX: "Prefix numeric pentru dosare"
+ FOLDER_NUMERIC_PREFIX_HELP: "Prefix numeric care asigură ordonarea manuală şi presupune vizibilitate"
+ FOLDER_NAME: "Numele Dosarului"
+ FOLDER_NAME_HELP: "Numele dosarului care va fi stocat în sistem pentru această pagină"
+ PARENT: "Părinte"
+ DEFAULT_OPTION_ROOT: "-Rădăcină-"
+ DEFAULT_OPTION_SELECT: "- Selectează -"
+ DISPLAY_TEMPLATE: "Afișează șablon"
+ DISPLAY_TEMPLATE_HELP: "Tipuri de pagini care implică șablonurie Twig ce vor reda pagina"
+ BODY_CLASSES: "Clase pentru „Body”"
+ ORDERING: "Ordonare"
+ PAGE_ORDER: "Ordinea paginilor"
+ OVERRIDES: "Suprascrie"
+ MENU: "Meniu"
+ MENU_HELP: "Numele utilizat în meniu. Dacă nu există, Titlul va fi folosit."
+ SLUG: "Slug"
+ SLUG_HELP: "Variabila slug permite setarea părții URL-ului paginii"
+ SLUG_VALIDATE_MESSAGE: "Variabila slug trebuie să conţină numai caractere alfanumerice, litere mici și cratime"
+ PROCESS: "Prelucrare"
+ PROCESS_HELP: "Controlează modul în care paginile sunt prelucrate. Poate fi setat mai degrabă pe pagină decât la nivel global"
+ DEFAULT_CHILD_TYPE: "Tipul implicit de copil"
+ USE_GLOBAL: "Utilizează global"
+ ROUTABLE: "Rutabil"
+ ROUTABLE_HELP: "Dacă această pagină este accesibilă cu un URL"
+ CACHING: "Cache-ul"
+ VISIBLE: "Vizibil"
+ VISIBLE_HELP: "Determină dacă o pagină este vizibilă în navigaţie."
+ DISABLED: "Dezactivat"
+ ITEMS: "Articole"
+ ORDER_BY: "Ordonare după"
+ ORDER: "Ordonare"
+ FOLDER: "Dosare"
+ ASCENDING: "Crescător"
+ DESCENDING: "Descrescător"
+ ADD_MODULAR_CONTENT: "Adăugaţi conţinut modular"
+ PAGE_TITLE: "Titlul paginii"
+ PAGE_TITLE_HELP: "Titlul paginii"
+ PAGE: "Pagină"
+ MODULAR_TEMPLATE: "Şablon modular"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Nume fișier"
+ PARENT_PAGE: "Pagina părinte"
+ HOME_PAGE: "Pagina principală"
+ HOME_PAGE_HELP: "Pagina pe care Grav o va folosi ca pagină de destinaţie implicită"
+ DEFAULT_THEME: "Tema implicită"
+ DEFAULT_THEME_HELP: "Setează tema implicită pentru Grav (implicit este Antimatter)"
+ TIMEZONE: "Fus orar"
+ TIMEZONE_HELP: "Suprascrie implicit server-ul de fus orar"
+ SHORT_DATE_FORMAT: "Format de afişare pentru dată scurtă"
+ SHORT_DATE_FORMAT_HELP: "Setaţi formatul dată scurtă care poate fi utilizat de teme"
+ LONG_DATE_FORMAT: "Format de afişare pentru dată lungă"
+ LONG_DATE_FORMAT_HELP: "Setaţi formatul dată lungă care poate fi utilizat de teme"
+ DEFAULT_ORDERING: "Ordine de sortare implicită"
+ DEFAULT_ORDERING_HELP: "Paginile în listă vor fi afișate utilizând această ordonare doar dacă nu este suprascrisă"
+ DEFAULT_ORDERING_DEFAULT: "Implicit - bazat pe nume de dosar"
+ DEFAULT_ORDERING_FOLDER: "Dosar - bazat pe nume de dosar fără prefix"
+ DEFAULT_ORDERING_TITLE: "Titlu - bazat pe câmpul titlu din Header"
+ DEFAULT_ORDERING_DATE: "Dată - bazat pe câmpul dată din Header"
+ DEFAULT_ORDER_DIRECTION: "Metodă implicită de ordonare"
+ DEFAULT_ORDER_DIRECTION_HELP: "Direcţia paginilor într-o listă"
+ DEFAULT_PAGE_COUNT: "Contor de pagină implicit"
+ DEFAULT_PAGE_COUNT_HELP: "Numărul maxim de cotor de pagini într-o listă"
+ DATE_BASED_PUBLISHING: "Publicare bazată pe dată"
+ DATE_BASED_PUBLISHING_HELP: "(ne)publică în mod automat în funcție de dată"
+ EVENTS: "Evenimente"
+ EVENTS_HELP: "Activează sau dezactivează evenimente specifice. Dezactivarea acestora poate strica funcționarea modulelor"
+ REDIRECT_DEFAULT_ROUTE: "Redirecţionare Rută implicită"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Redirecționarea automată către ruta implicită a unei pagini"
+ LANGUAGES: "Limbi"
+ SUPPORTED: "Acceptate"
+ SUPPORTED_HELP: "Separate prin virgulă, lista codurilor de limbă din 2 litere (de exemplu \"en, fr, de\")"
+ TRANSLATIONS_ENABLED: "Traduceri activate"
+ TRANSLATIONS_ENABLED_HELP: "Suportă traduceri în Grav, module și extensii"
+ TRANSLATIONS_FALLBACK: "Traduceri de rezervă"
+ TRANSLATIONS_FALLBACK_HELP: "Selectează automat traduceri suportate dacă limba activă nu există"
+ ACTIVE_LANGUAGE_IN_SESSION: "Limba activă în sesiune"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Stochează limba activă în sesiune"
+ HTTP_HEADERS: "Antetele HTTP"
+ EXPIRES: "Expiră la"
+ EXPIRES_HELP: "Setează expirare antet. Valoarea este în secunde."
+ CACHE_CONTROL: "HTTP Cache-Control"
+ CACHE_CONTROL_HELP: "Setat la o valoare validă pentru controlul cache precum `no-cache, no-store, must-revalidate`"
+ LAST_MODIFIED: "Ultima modificare"
+ LAST_MODIFIED_HELP: "Setează ultimul antet modificat, care poate ajuta la optimizarea proxy-ului şi cache-ului în browser"
+ ETAG: "ETag"
+ ETAG_HELP: "Setează antetul etag pentru a ajuta la identificare în cazul în care o pagină a fost modificată"
+ VARY_ACCEPT_ENCODING: "Variază codificarea acceptată"
+ VARY_ACCEPT_ENCODING_HELP: "Setează antetul ' Vary: acceptă codificarea ' pentru a ajuta cu proxy şi CDN cache"
+ MARKDOWN: "Markdown"
+ MARKDOWN_EXTRA: "Markdown extra"
+ MARKDOWN_EXTRA_HELP: "Activaţi suportul implicit pentru Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ MARKDOWN_EXTRA_ESCAPE_FENCES: "Ignoră elementele HTML în interiorul textelor Markdown Extra"
+ MARKDOWN_EXTRA_ESCAPE_FENCES_HELP: "Ignoră elementele HTML în interiorul textelor Markdown Extra"
+ AUTO_LINE_BREAKS: "Terminări de linii automate"
+ AUTO_LINE_BREAKS_HELP: "Activarea suportului pentru linie automata în markdown"
+ AUTO_URL_LINKS: "Link-uri URL auto"
+ AUTO_URL_LINKS_HELP: "Activează conversia automata de URL-uri în hyperlink-uri HTML"
+ ESCAPE_MARKUP: "Ieșire din markup"
+ ESCAPE_MARKUP_HELP: "Modifică automat etichetele markup în HTML"
+ CACHING_HELP: "Comutator global on/off pentru a activa/dezactiva cache Grav"
+ CACHE_CHECK_METHOD: "Metoda de verificare cache"
+ CACHE_CHECK_METHOD_HELP: "Selectaţi metoda pe care Grav o utilizează pentru a verifica dacă fişierele au fost modificate."
+ CACHE_DRIVER: "Driver-ul pentru Cache"
+ CACHE_DRIVER_HELP: "Alege ce driver de cache ar trebui să utilizeze Grav. 'Detectare automată' încearcă să găsească cel mai bun în cazul de față"
+ CACHE_PREFIX: "Prefixul Cache-ului"
+ CACHE_PREFIX_HELP: "Un identificator pentru o parte a cheii Grav. Nu modificați decât dacă ştiți ce faceți."
+ CACHE_PREFIX_PLACEHOLDER: "Derivat din URL de bază (suprascrie introducând şir aleator)"
+ CACHE_PURGE_JOB: "Rulează acțiunea prestabilită de ștergere"
+ CACHE_PURGE_JOB_HELP: "Cu ajutorul acestei acțiuni puteți curăța la intervale prestabilite fișierele vechi ale Doctrine"
+ CACHE_CLEAR_JOB: "Rulează acțiunea prestabilită de ștergere"
+ CACHE_CLEAR_JOB_HELP: "Cu orarul periodic puteţi şterge memoria Grav Cache"
+ CACHE_JOB_TYPE: "Tip de memorie cache"
+ CACHE_JOB_TYPE_HELP: "Șterge memoria cache ori cu opțiunea pe dosare 'standard' ori cu 'toate' dosarele"
+ CACHE_PURGE: "Șterge Cache-ul vechi"
+ LIFETIME: "Durata de viaţă"
+ LIFETIME_HELP: "Stabileşte durata memoriei cache în secunde. 0 = infinit"
+ GZIP_COMPRESSION: "Compresie gzip"
+ GZIP_COMPRESSION_HELP: "Activați compresia GZip a Grav pentru performanţe sporite."
+ TWIG_TEMPLATING: "Creare șabloane cu Twig"
+ TWIG_CACHING: "Memoria Cache pentru Twig"
+ TWIG_CACHING_HELP: "Controlează mecanismul pentru memoria cache a Twig. Lăsați această opțiune activată pentru cea mai bună performanță."
+ TWIG_DEBUG: "Twig debug"
+ TWIG_DEBUG_HELP: "Permite opțiunea de a nu încărca extensia Twig debugger"
+ DETECT_CHANGES: "Detectează schimbări"
+ DETECT_CHANGES_HELP: "Twig va recompila automat cache-ul Twig în cazul în care detectează orice modificări în şabloanele Twig"
+ AUTOESCAPE_VARIABLES: "Variabile automate de ieșire"
+ AUTOESCAPE_VARIABLES_HELP: "Ieșire pentru toate variabilele. Acest lucru cel mai probabil va strica site-ul"
+ ASSETS: "Active"
+ CSS_PIPELINE: "CSS principal"
+ CSS_PIPELINE_HELP: "CSS principal este unificarea resurselor CSS într-un singur fișier"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Include externe în CSS principal"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Uneori, URL-urile externe au referințe fișiere relative și nu trebuiesc incluse în CSS principal"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "Randarea întâi a CSS principal"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Randare CSS principal înaintea oricăror alte referințe CSS care nu sunt incluse"
+ CSS_MINIFY: "Micșorare CSS"
+ CSS_MINIFY_HELP: "Micșorează CSS în timpul creării CSS-ului principal"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "Suprascrie micșorarea CSS pentru Windows"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Suprascrie micșorarea pentru platformele Windows. False în mod implicit datorită ThreadStackSize"
+ CSS_REWRITE: "Rescrie CSS"
+ CSS_REWRITE_HELP: "Rescrie URL-uri relative în timpul creării CSS principal"
+ JAVASCRIPT_PIPELINE: "JavaScript Principal"
+ JAVASCRIPT_PIPELINE_HELP: "JS principal este unificarea resurselor JS într-un singur fișier"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Include externe în JS principal"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Uneori, URL-urile externe au referințe fișiere relative și nu trebuiesc incluse în JS principal"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Randarea întâi a JS principal"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Randarea JS principal înaintea oricăror referințe JS care nu sunt incluse"
+ JAVASCRIPT_MINIFY: "Micșorarea JavaScript"
+ JAVASCRIPT_MINIFY_HELP: "Micșorează JS în timpul creării JS-ului principal"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Activează ștampile de dată pentru active"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Activează ștampilele de dată ale activelor"
+ COLLECTIONS: "Colecții"
+ ERROR_HANDLER: "Manipulant pentru erori"
+ DISPLAY_ERRORS: "Afișează erorile"
+ DISPLAY_ERRORS_HELP: "Afișează în stil complet pagina cu erori"
+ LOG_ERRORS: "Salvează erorile"
+ LOG_ERRORS_HELP: "Salvează erorile în /fișiere pentru erori"
+ LOG_HANDLER: "Manipulant pentru registru"
+ LOG_HANDLER_HELP: "Unde for fi stocate registrele"
+ SYSLOG_FACILITY: "Facilitatea Syslog"
+ SYSLOG_FACILITY_HELP: "Facilitatea Syslog pentru rezultate"
+ DEBUGGER: "Debugger"
+ DEBUGGER_HELP: "Activează debugger pentru Grav și următoarele setări"
+ DEBUG_TWIG: "Debug Twig"
+ DEBUG_TWIG_HELP: "Activează debugging pentru șabloane Twig"
+ SHUTDOWN_CLOSE_CONNECTION: "Blochează conexiunea închisă"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Închide conexiunea înainte de onShutdown(). false pentru debug"
+ DEFAULT_IMAGE_QUALITY: "Calitatea implicită a imaginilor"
+ DEFAULT_IMAGE_QUALITY_HELP: "Calitatea implicită a imaginilor pentru crearea de cache sau re-rendare (85%)"
+ CACHE_ALL: "Include în memoria cache toate imaginile"
+ CACHE_ALL_HELP: "Rulează toate imaginile prin intermediul sistemului de cache-ul al Grav, chiar dacă au manipulări de media"
+ IMAGES_DEBUG: "Inscripționare imagini pentru Debug"
+ IMAGES_DEBUG_HELP: "Arată o suprapunere peste imagini indicând adâncimea de pixeli a imaginii atunci când se lucrează cu retina de exemplu"
+ IMAGES_SEOFRIENDLY: "Nume de imagini bune pentru SEO"
+ IMAGES_SEOFRIENDLY_HELP: "Când este activată, numele imaginii este afişat primul, apoi un hash mai mic pentru a reflecta operaţiunile procesate"
+ UPLOAD_LIMIT: "Valoare limită încărcare fișier"
+ UPLOAD_LIMIT_HELP: "Setaţi dimensiunea maximă de încărcare în octeţi (0 este nelimitat)"
+ ENABLE_MEDIA_TIMESTAMP: "Activează ștampile de dată pentru fișiere media"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Adaugă ștamplilă dată bazată pe ultima modificare pentru fiecare element de media"
+ SESSION: "Sesiune"
+ SESSION_ENABLED_HELP: "Permite ajutor de sesiune în cadrul Grav"
+ TIMEOUT: "Timeout"
+ TIMEOUT_HELP: "Setează timeout pentru sesiune în secunde"
+ SESSION_NAME_HELP: "Un identificator utilizat pentru a forma denumirea modulul cookie de sesiune"
+ SESSION_UNIQUENESS: "Șiruri de caractere unice"
+ SESSION_UNIQUENESS_HELP: "MD5 hash pentru calea de acces principală a Grav, de exemplu 'GRAV_ROOT' (calea implicită) sau bazată pe o înșiruire aleatorie de caractere 'security.salt'."
+ ABSOLUTE_URLS: "URL-urile absolute"
+ ABSOLUTE_URLS_HELP: "URL-uri absolute sau relative pentru 'base_url'"
+ PARAMETER_SEPARATOR: "Separator de parametri"
+ PARAMETER_SEPARATOR_HELP: "Separator pentru parametrii trecuți, care pot fi schimbați pentru Apache pe platforme Windows"
+ TASK_COMPLETED: "Sarcină finalizată"
+ EVERYTHING_UP_TO_DATE: "Totul este la zi"
+ UPDATES_ARE_AVAILABLE: "sunt disponibile noi actualizări"
+ IS_AVAILABLE_FOR_UPDATE: "este disponibil pentru actualizare"
+ IS_NOW_AVAILABLE: "este acum disponibil"
+ CURRENT: "Curent"
+ UPDATE_GRAV_NOW: "Actualizează Grav acum"
+ GRAV_SYMBOLICALLY_LINKED: "Grav este legat simbolic. Nu va fi disponibilă actualizarea"
+ UPDATING_PLEASE_WAIT: "Actualizare... vă rugăm aşteptaţi, descărcare"
+ OF_THIS: "al acestui"
+ OF_YOUR: "al tău"
+ HAVE_AN_UPDATE_AVAILABLE: "au o actualizare disponibilă"
+ SAVE_AS: "Salvează ca"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Sunteţi sigur că doriţi să ştergeţi această pagină şi toți copiii ei? În cazul în care pagina este tradusă în alte limbi, aceste traduceri vor fi păstrate şi trebuie să șterse separat. În caz contrar dosarul paginii va fi şters împreună cu subpaginile sale. Această acţiune nu poate fi modificată ulterior."
+ AND: "şi"
+ UPDATE_AVAILABLE: "Actualizare disponibilă"
+ METADATA_KEY: "Cheie (ex: ' cuvânt-cheie')"
+ METADATA_VALUE: "Valoare (ex. ' Blog, Grav')"
+ USERNAME_HELP: "Numele de utilizator trebuie să fie între 3 şi 16 caractere ți să includă litere mici, numere, linii de subliniere şi cratime. Literele mari, spaţile şi caracterele speciale nu sunt permise"
+ FULLY_UPDATED: "Complet actualizat"
+ SAVE_LOCATION: "Salvați locația"
+ PAGE_FILE: "Şablon de pagină"
+ PAGE_FILE_HELP: "Numele șablonului de pagină și implicit șablonul afișat pentru această pagină"
+ NO_USER_ACCOUNTS: "Nu au fost găsiți conturi de utilizatori, vă rugăm să creați unul..."
+ NO_USER_EXISTS: "Nu există utilizator local pentru acest cont, nu se poate efectua salvarea..."
+ REDIRECT_TRAILING_SLASH: "/ atașat după redirecționare"
+ REDIRECT_TRAILING_SLASH_HELP: "Efectuează o redirecționare 301 decât folosirea / atașat după redirecționare."
+ DEFAULT_DATE_FORMAT: "Formatarea datei paginii"
+ DEFAULT_DATE_FORMAT_HELP: "Formatarea datei folosită de Grav. Implicit, Grav încearcă să ghicească formatul datei, dar poți specifica un format folosind sintaxa PHP (ex: Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Ghicește automat"
+ IGNORE_FILES: "Ignoră fișierele"
+ IGNORE_FILES_HELP: "Fişiere specifice ignorate la prelucrarea paginii"
+ IGNORE_FOLDERS: "Ignoră dosare"
+ IGNORE_FOLDERS_HELP: "Dosare specifice ignorate la prelucrarea paginii"
+ HIDE_EMPTY_FOLDERS: "Ascunde dosare goale"
+ HIDE_EMPTY_FOLDERS_HELP: "În cazul în care dosarul nu are nici o filă .md, ar trebui să fie ascuns în navigare și nerutabil"
+ HTTP_ACCEPT_LANGUAGE: "Configurează limba din browser"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Puteţi opta pentru a încerca să configuraţi limba în browser bazat pe tag-ul de antet 'http_accept_language'"
+ OVERRIDE_LOCALE: "Suprascrie setările locale"
+ OVERRIDE_LOCALE_HELP: "Suprascrie setarările locale în PHP bazate pe Limba curentă"
+ REDIRECT: "Redirecţionare de pagină"
+ REDIRECT_HELP: "Introduceţi o de rută de pagină sau URL-ul extern pentru această pagină pentru a redirecţiona. ex. '/ traseu/anume' sau 'http://siteulmeu.com'"
+ PLUGIN_STATUS: "Statutul modulului"
+ INCLUDE_DEFAULT_LANG: "Include limba implicită"
+ INCLUDE_DEFAULT_LANG_HELP: "Acest lucru va ataşa pentru toate URL-urile în limba implicită cu limba implicită. ex. \"/ ro/blog/articol\""
+ PAGES_FALLBACK_ONLY: "Doar pagini de rezervă"
+ PAGES_FALLBACK_ONLY_HELP: "Doar \"rezervă\" pentru a găsi conţinutul paginii prin limbajele suportate, comportamentul implicit este de a afişa orice limbă găsită dacă lipseşte limba activă"
+ ALLOW_URL_TAXONOMY_FILTERS: "Filtre de taxonomie URL"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Pagini bazate pe colecții permite filtrarea prin ' / taxonomy: valoare '."
+ REDIRECT_DEFAULT_CODE: "Codul implicit de redirecţionare"
+ REDIRECT_DEFAULT_CODE_HELP: "Codul de stare HTTP pentru redirecționare"
+ IGNORE_HIDDEN: "Ignoră ascunse"
+ IGNORE_HIDDEN_HELP: "Ignoră toate fişierele şi dosarele care încep cu un punct „.”"
+ WRAPPED_SITE: "Site „învelit”"
+ WRAPPED_SITE_HELP: "Pentru ca temele/modulele să ştie dacă Grav este înfăşurat de o altă platformă"
+ FALLBACK_TYPES: "Tipuri permise de rezervă"
+ FALLBACK_TYPES_HELP: "Tipuri de fișiere permise care pot fi găsite la accesarea rutei. Implicite sunt toate fișierele media suportate."
+ INLINE_TYPES: "Tipuri permise de rezervă „inline”"
+ INLINE_TYPES_HELP: "O listă cu tipuri de fişiere care ar trebui să fie afişate mai degrabă „inline” decât descarcate"
+ APPEND_URL_EXT: "Adăugaţi extensia URL-ului"
+ APPEND_URL_EXT_HELP: "Va adăuga o extensie personalizat URL-ul paginii. Reţineţi, acest lucru va însemna că Grav va căuta șablonul `. . twig`"
+ PAGE_MODES: "Moduri de pagini"
+ PAGE_TYPES: "Tipuri de pagină"
+ PAGE_TYPES_HELP: "Determină tipul de pagină care suporta Grav şi ordinea determină ce tip să fie folosit în cazul în care cererea este ambiguă"
+ ACCESS_LEVELS: "Nivele de acces"
+ GROUPS: "Grupuri"
+ GROUPS_HELP: "Lista de grupuri din care utilizatorul face parte"
+ ADMIN_ACCESS: "Acces Administrator"
+ SITE_ACCESS: "Acces Site"
+ INVALID_SECURITY_TOKEN: "Token de securitate incorect"
+ ACTIVATE: "Activează"
+ TWIG_UMASK_FIX: "Umask Fix"
+ TWIG_UMASK_FIX_HELP: "Implicit Twig creează fişiere cache cu permisiunea 0755, repararea comută la 0775"
+ CACHE_PERMS: "Permisiunile cache-ullui"
+ CACHE_PERMS_HELP: "Permisiunile dosarului de cache-ul. De obicei 0755 sau 0775 în funcţie de configurare"
+ REMOVE_SUCCESSFUL: "Eliminare reușită"
+ REMOVE_FAILED: "Eliminarea nu a reușit"
+ HIDE_HOME_IN_URLS: "Ascunde ruta principală în URL-uri"
+ HIDE_HOME_IN_URLS_HELP: "Va asigura rutele implicite pentru orice pagini copil ale paginii principale"
+ TWIG_FIRST: "Procesează Twig primul"
+ TWIG_FIRST_HELP: "Dacă aţi activat prelucrarea Twig, puteţi configura Twig să fie prelucrat înainte sau după Markdown"
+ SESSION_SECURE: "Securizat"
+ SESSION_SECURE_HELP: "Dacă este adevărat indică faptul că, comunicarea pentru aceste cookie trebuie să fie prin transmisie criptată. Avertisment: Activeză numai pe site-uri care rulează exclusiv pe HTTPS (server-ul este setat cu HTTPS)"
+ SESSION_HTTPONLY: "numai HTTP"
+ SESSION_HTTPONLY_HELP: "Dacă este adevărat, indică faptul că se folosesc cookie-uri doar prin HTTP şi modificarea prin JavaScript nu este permisă"
+ REVERSE_PROXY: "Inversare proxy"
+ REVERSE_PROXY_HELP: "Activeză această opțiune în cazul în care sunteţi în spatele un proxy şi aveţi probleme cu URL-uri care conţin porturi incorecte"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Frontmatter nu este valid, salvare nereușită"
+ ADD_FOLDER: "Adaugă dosar"
+ PROXY_URL: "Proxy URL"
+ PROXY_URL_HELP: "Introduceţi proxy HOST sau IP şi portul"
+ NOTHING_TO_SAVE: "Nimic de salvat"
+ FILE_ERROR_ADD: "S-a produs o eroare în timpul adăugării fișierului"
+ FILE_ERROR_UPLOAD: "O eroare s-a produs în timpul încărcării fișierului"
+ FILE_UNSUPPORTED: "Tipul de fișier nu este suportat"
+ ADD_ITEM: "Adauga articol"
+ FILE_TOO_LARGE: "Fişierul este prea mare pentru a fi încărcat, maximul permis este %s, conform setărilor PHP. Mărește setarea PHP 'post_max_size'"
+ INSTALLING: "Instalare"
+ LOADING: "Încărcare.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "Următoarele dependenţe trebuie să fie îndeplinite înainte:"
+ ERROR_INSTALLING_PACKAGES: "Eroare în timpul instalării pachetului(lor)"
+ INSTALLING_DEPENDENCIES: "Instalare dependenţe..."
+ INSTALLING_PACKAGES: "Instalare pachet(e).."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Pachet(e) instalat(e) cu succes."
+ READY_TO_INSTALL_PACKAGES: "Gata pentru a instala pachete"
+ PACKAGES_NOT_INSTALLED: "Pachetele nu au fost instalate"
+ PACKAGES_NEED_UPDATE: "Pachete deja instalate, dar prea vechi"
+ PACKAGES_SUGGESTED_UPDATE: "Pachete deja instalate, şi versiunea este ok, dar vor fi actualizate pentru a vă ţine la curent"
+ REMOVE_THE: "Eliminare %s"
+ CONFIRM_REMOVAL: "Eşti sigur că dorești să ștergi %s?"
+ REMOVED_SUCCESSFULLY: "%s eliminat cu succes"
+ ERROR_REMOVING_THE: "Eroare la ștergerea %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "%s necesită următoarele dependenţe, care nu sunt cerute de alte pachete instalate. Dacă nu le utilizaţi, eliminaţi-le direct de aici."
+ READY_TO_UPDATE_PACKAGES: "Gata pentru a actualiza pachete"
+ ERROR_UPDATING_PACKAGES: "Eroare în timpul actualizării pachetelor"
+ UPDATING_PACKAGES: "Actualizarea pachete.."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Pachet(e) actualizat(e) cu succes."
+ UPDATING: "Actualizare"
+ GPM_RELEASES: "Lansări GPM"
+ GPM_RELEASES_HELP: "Alege 'Testare' pentru a instala versiunea beta sau versiuni de testare"
+ GPM_METHOD: "Metoda de descărcare de la distanță"
+ GPM_METHOD_HELP: "Când este setată Auto, Grav va determina dacă fopen este disponibil și îl va utiliza, altfel va merge pe varianta cURL. Pentru a forța utilizarea uneia dintre opțiuni modificați setarea."
+ GPM_VERIFY_PEER: "Verificarea de la distanță a emitentului (SSL)"
+ GPM_VERIFY_PEER_HELP: "Unii furnizori nu reușesc să verifice certificatul SSL al getgrav.org și din această cauză GPM nu funcționează. Dacă vă aflați în această situație, oprirea acestei setări vă poate ajuta"
+ AUTO: "Automat"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stabil"
+ TESTING: "Testare"
+ FRONTMATTER_PROCESS_TWIG: "Procesează Twig din frontmatter"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Când este activat, poți folosi variabile de configurare Twig în paginație"
+ FRONTMATTER_IGNORE_FIELDS: "Ignoră câmpurile frontmatter"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Anumite câmpuri din frontmatter pot conține Twig dar nu are trebui procesate, ex: formulare"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Pachetul %s instalat cu succes"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Părintele setează ordinea, ordinea dezactivată"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Pagina nu este vizibilă, ordonarea este dezactovată"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "Ordonarea via Admin nu este acceptată deoarece există mai mult de 200 de „rude”"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Aranjarea paginilor este dezactivată pentru această pagină pentru că Numărarea Numerică a Dosarelor este dezactivată"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "Notă: Nu puteți adăuga fișiere media decât după salvarea paginii. Dați clic pe \"Salvează\" în dreapta-sus"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "Notă: Pagina trebuie salvată înainte de a putea încărca fișiere."
+ DROP_FILES_HERE_TO_UPLOAD: "Adăugați fișierele aici sau Dați clic pe zona aceasta "
+ INSERT: "Inserează"
+ UNDO: "Revenire"
+ REDO: "Refă"
+ HEADERS: "Anteturi"
+ BOLD: "Îngroşat"
+ ITALIC: "Cursiv"
+ STRIKETHROUGH: "Tăiat cu o linie"
+ SUMMARY_DELIMITER: "Delimitator pentru rezumat"
+ LINK: "Link"
+ IMAGE: "Imagine"
+ BLOCKQUOTE: "Citat"
+ UNORDERED_LIST: "Listă neordonată"
+ ORDERED_LIST: "Listă ordonată"
+ EDITOR: "Editor"
+ PREVIEW: "Previzualizare"
+ FULLSCREEN: "Pe tot ecranul"
+ MODULAR: "Modulare"
+ MODULAR_SETUP: "Configurare modulară"
+ NON_ROUTABLE: "Nerutabil"
+ NON_MODULAR: "Non-modular"
+ NON_VISIBLE: "Invizibil"
+ NON_PUBLISHED: "Nepublicat"
+ CHARACTERS: "caractere"
+ PUBLISHING: "Pentru publicare"
+ NOTIFICATIONS: "Notificări"
+ MEDIA_TYPES: "Tipuri de media"
+ IMAGE_OPTIONS: "Opţiuni Imagine"
+ MIME_TYPE: "Tipuri Mime"
+ THUMB: "Icoană"
+ TYPE: "Tip"
+ FILE_EXTENSION: "Extensie de fișier"
+ LEGEND: "Legenda Paginii"
+ MEMCACHE_SERVER: "Server pentru Memcache"
+ MEMCACHE_SERVER_HELP: "Adresa serverului Memcache"
+ MEMCACHE_PORT: "Portul pentru Memcache"
+ MEMCACHE_PORT_HELP: "Portul serverului pentru Memcahce"
+ MEMCACHED_SERVER: "Server Memcached"
+ MEMCACHED_SERVER_HELP: "Adresa serverului Memcached"
+ MEMCACHED_PORT: "Portul pentru Memcached"
+ MEMCACHED_PORT_HELP: "Portul serverului pentru Memcahced"
+ REDIS_SERVER: "Server Redis"
+ REDIS_SERVER_HELP: "Adresa serverului Redis"
+ REDIS_PORT: "Portul pentru Redis"
+ REDIS_PORT_HELP: "Portul serverului pentru Redis"
+ REDIS_PASSWORD: "Parolă / Secret Redis"
+ ALL: "Toate"
+ FROM: "de la"
+ TO: "către"
+ RELEASE_DATE: "Data de lansare"
+ SORT_BY: "Sortează după"
+ RESOURCE_FILTER: "Filtrează ..."
+ FORCE_SSL: "Forțează SSL"
+ FORCE_SSL_HELP: "Forțează SSL la nivel global, dacă este activată în timp ce site-ul este accesibil prin HTTP, Grav trimite o redirecționare către pagina HTTPS"
+ NEWS_FEED: "Flux de ştiri"
+ EXTERNAL_URL: "URL Extern"
+ SESSION_PATH: "Calea sesiunii"
+ SESSION_PATH_HELP: "Folosiți doar în cazul în care alegeți un URL personalizat (rescrieți dosarul / domeniul site-ului)"
+ CUSTOM_BASE_URL: "URL de bază personalizat"
+ CUSTOM_BASE_URL_HELP: "Utilizați doar în cazul în care rescrieți domeniul site-ului sau folosiți alt dosar față de cel utilizat în mod implicit de Grav. De eg: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: '"%s" nu pot fi utilizate în afara paginilor.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Fișierul %s: %s nu a putut fi încărcat'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'Fișierul %s nu a putut fi mutat în "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'Anulează încărcarea'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Sunteţi sigur că doriţi să anulaţi această încărcare?'
+ DROPZONE_DEFAULT_MESSAGE: 'Adăugați fișierele aici sau Dați clic pe zona aceasta '
+ DROPZONE_FALLBACK_MESSAGE: 'Browser-ul Dvs. nu acceptă încărcarea prin tragere și eliberare.'
+ DROPZONE_FALLBACK_TEXT: 'Vă rugăm utilizați formularul de rezervă de mai jos pentru a încărca fișierele Dvs. precum în zilele de demult.'
+ DROPZONE_FILE_TOO_BIG: 'Fișierul este prea mare ({{filesize}}MiB). Mărimea maximă admisă este {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "Nu este permisă încărcarea acestui tip de fișier."
+ DROPZONE_MAX_FILES_EXCEEDED: "Nu mai puteţi încărca alte fişiere."
+ DROPZONE_REMOVE_FILE: "Ștergeți fișier"
+ DROPZONE_RESPONSE_ERROR: "Serverul a răspuns cu codul de {{statusCode}}."
+ PREMIUM_PRODUCT: "Versiunea Premium"
+ ADMIN_CACHING: "Permite adăugarea în memoria cache a Admin"
+ ADMIN_CACHING_HELP: "Memoria de cache în Admin poate fi controlată independent de memoria cache a site-ului"
+ DESTINATION_NOT_SPECIFIED: "Destinația nu a fost specificată"
+ UPLOAD_ERR_NO_TMP_DIR: "Lipsește un dosar temporar"
+ SESSION_SPLIT: "Împărțire sesiune"
+ SESSION_SPLIT_HELP: "Sesiuni independente și separate între site și alte module instalate (de eg: Admin)"
+ ERROR_FULL_BACKTRACE: "Eroare cu calea completă"
+ ERROR_SIMPLE: "Eroare simplă"
+ ERROR_SYSTEM: "Eroare de sistem"
+ IMAGES_AUTO_FIX_ORIENTATION: "Fixează orientarea automat"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Decide automat orientarea imaginii în funcție de detaliile Exif"
+ REDIS_SOCKET: "Port pentru Redis"
+ REDIS_SOCKET_HELP: "Portul pentru Redis"
+ NOT_SET: "Nu este setată"
+ PERMISSIONS: "Permisiuni"
+ NEVER_CACHE_TWIG: "Nu utiliza Cache-ul Twig niciodată"
+ NEVER_CACHE_TWIG_HELP: "Stochează în cache doar conținutul șoc procesează Twig de fiecare dată pentru pagini. Ignoră setările twig_first."
+ ALLOW_WEBSERVER_GZIP: "Permite WebServer Gzip"
+ ALLOW_WEBSERVER_GZIP_HELP: "Dezactivată în mod implicit. Dacă este activată, compresia Gzip/Defflate configurată pe WebServer va funcționa dar conexiunile Http nu vor fi închise cu evenimentul onShutDown() astfel paginile se vor încarca mai greu"
+ OFFLINE_WARNING: "Conexiunea la GPM nu poate fi stabilită"
+ CLEAR_IMAGES_BY_DEFAULT: "Eliberați impicit cache-ul pentru imagini"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "În mod implicit imaginile procesate sunt eliminate din cache pentru toate setările de eliberarea memoriei cache, însă acest lucru poate fi dezactivat"
+ CLI_COMPATIBILITY: "Compatibilitate CLI"
+ CLI_COMPATIBILITY_HELP: "Asigură folosirea doar a driverelor de Cache ne-volatile (fișiere, redis, memcache, etc...)"
+ REINSTALL_PLUGIN: "Reinstalati Modulul"
+ REINSTALL_THEME: "Reinstalaţi Tema"
+ REINSTALL_THE: "Reinstalaţi %s"
+ CONFIRM_REINSTALL: "Sunteţi sigur că doriţi să reinstalaţi această %s?"
+ REINSTALLED_SUCCESSFULLY: "%s reinstalat cu succes"
+ ERROR_REINSTALLING_THE: "Eroare la reinstalarea %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Pachet %s reinstalat cu succes"
+ REINSTALLATION_FAILED: "Reinstalarea nu a reușit"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "Versiunea instalată nu este cea mai recentă versiune. Făcând clic pe continue (continuare), veţi elimina versiunea curentă şi instala versiunea mai recent disponibilă"
+ TOOLS: "Unelte"
+ DIRECT_INSTALL: "Instalare directă"
+ NO_PACKAGE_NAME: "Numele pachetului nu este specificat"
+ PACKAGE_EXTRACTION_FAILED: "Extragerea pachetului eșuată"
+ NOT_VALID_GRAV_PACKAGE: "Nu este un pachet valid pentru Grav"
+ NAME_COULD_NOT_BE_DETERMINED: "Numele nu a putut fi determinat"
+ CANNOT_OVERWRITE_SYMLINKS: "Nu este posibilă suprascrierea legăturilor simbolice"
+ ZIP_PACKAGE_NOT_FOUND: "Pachetul ZIP nu a putut fi localizat"
+ GPM_OFFICIAL_ONLY: "Doar canalul oficial GPM"
+ GPM_OFFICIAL_ONLY_HELP: "Permite doar instalarea directă din canalul oficial GPM."
+ NO_CHILD_TYPE: "Nu există nici un tip de copil pentru această rutare rawroute"
+ SORTABLE_PAGES: "Pagini sortabile:"
+ UNSORTABLE_PAGES: "Pagini nesortabile"
+ ADMIN_SPECIFIC_OVERRIDES: "Suprascrieri spcifice Administratorului"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Ordinea de afișare a copiilor"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "Ordinea în care copiii acestei pagini ar trebui să fie afișați în modul „view” al modulului de Admin"
+ PWD_PLACEHOLDER: "şir complex cel puțin 8 caractere lungime"
+ PWD_REGEX: "Parolă Regex"
+ PWD_REGEX_HELP: "În mod implicit: Parola trebuie să conţină cel puţin un număr şi o majusculă, litere mici şi cel puţin 8 sau mai multe caractere"
+ USERNAME_PLACEHOLDER: "doar caractere mici, de ex: „admin”"
+ USERNAME_REGEX: "Numele de utilizator Regex"
+ USERNAME_REGEX_HELP: "În mod implicit: Doar caractere mici, cifre, liniuţe şi linii de subliniere. 3 - 16 caractere"
+ CONTENT_PADDING: "Căptușeală pentru conținut"
+ CONTENT_PADDING_HELP: "Activează/dezactivează conţinut de căptușeală în jurul conţinutului pentru a oferi mai mult spaţiu"
+ ENABLE_AUTO_METADATA: "Auto metadate din Exif"
+ ENABLE_AUTO_METADATA_HELP: "Generează automat metadatele fişierelor de imagini cu informaţii exif"
+ 2FA_TITLE: "Autentificare dublu factor"
+ 2FA_INSTRUCTIONS: "##### Autentificare dublu factor\nAveți **2FA** activată pentru acest cont. Vă rugăm folosiți aplicația pentru **2FA** pentru a introduce **codul de 6 cifre** pentru a completa procesul de logare."
+ 2FA_REGEN_HINT: "Regenerarea acestui secret necesită actualizarea aplicației de autentificare"
+ 2FA_LABEL: "Acces Administrator"
+ 2FA_FAILED: "Codul pentru Autentificarea dublu factor este invalid, vă rugăm încercați din nou..."
+ 2FA_ENABLED: "2FA activat"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "2FA Secret"
+ 2FA_SECRET_HELP: "Scanați acest cod QR cu [aplicația de autentificare](https://learn.getgrav.org/admin-panel/2fa#apps). De asemenea este o idee bună să salvați secretul într-o locație sigură, în cazul in care aveți nevoie să reinstalați aplicația de autentificare. Verificați [documentația Grav](https://learn.getgrav.org/admin-panel/2fa) pentru mai multe informații "
+ 2FA_REGENERATE: "Regenerează"
+ FORCE_LOWERCASE_URLS: "Forțează litere mici pentru URL-uri"
+ FORCE_LOWERCASE_URLS_HELP: "În mod implicit Grav setează toate Url-urile cu litere mici. Dacă opțiunea este falsă, se pot folosi Majuscule pentru Url-uri"
+ INTL_ENABLED: "Integrarea modulului Intl"
+ INTL_ENABLED_HELP: "Utilizaţi modulul Intl PHP şi adună pentru a sorta colecțiile bazate pe UTF8"
+ VIEW_SITE_TIP: "Vizualizează site"
+ TOOLS_DIRECT_INSTALL_TITLE: "Instalare directă pentru pachete Grav"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "Instalaţi pachetul prin Încărcare Directă de fișiere în format ZIP"
+ TOOLS_DIRECT_INSTALL_UPLOAD_DESC: "Puteţi instala cu uşurinţă o temă , un modul sau chiar actualizări Grav în format Zip prin această metodă. Acest pachet nu trebuie să fie înregistrat prin intermediul GPM şi vă permite să reveniți uşor la o versiune anterioară sau instalare pentru testare."
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "Instalaţi pachet folosind Remote URL"
+ TOOLS_DIRECT_INSTALL_URL_DESC: "Alternativ, puteţi introduce un URL către fişierul ZIP şi instala acest fișier."
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Încărcare şi instalare"
+ ROUTE_OVERRIDES: "Suprascriere Rute"
+ ROUTE_DEFAULT: "Ruta implicită"
+ ROUTE_CANONICAL: "Ruta canonică"
+ ROUTE_ALIASES: "Pseudonime pentru Rută"
+ OPEN_NEW_TAB: "Deschide în Tab Nou"
+ PAGEMEDIA_RESIZER: "> Următoarele setări se aplică imaginilor încărcate folosind 'page media'. Redimensionarea lăţime / înălţime va redimensiona automat şi proporţional o imagine dacă depăşeşte limitele stabilite în setări. Valorile min şi max pentru rezoluţie definesc gamele de dimensiune pentru imaginile încărcate. Setaţi câmpurile la 0 pentru a preveni orice manipulare."
+ SESSION_INITIALIZE: "Iniţializează sesiune"
+ SESSION_INITIALIZE_HELP: "Grav pornește o sesiune de lucru. Această caracteristică este necesară pentru a face funcțională orice interacţiune a utilizatorul de a lucra, cum ar fi autentificare, formulare etc... Modulul Admin nu este afectat de această setare."
+ STRICT_YAML_COMPAT: "Compatibilitate YAML"
+ STRICT_YAML_COMPAT_HELP: "Revine la programul de analiză Symfony 2.4 YAML în cazul în care programul de analiză Nativ sau versiunea 3.4 eșuează"
+ STRICT_TWIG_COMPAT: "Compatibilitate Twig"
+ STRICT_TWIG_COMPAT_HELP: "Permite setarea depreciată pentru a ignore Twig. Când este dezactivată, este necesar filtrul |raw pentru a transcrie codul în HTML, deoarece Twig va ignora automat"
+ SCHEDULER: "Programator"
+ SCHEDULER_INSTALL_INSTRUCTIONS: "Instrucțiuni de instalare"
+ SCHEDULER_INSTALLED_READY: "Instalat şi gata de acțiune"
+ SCHEDULER_CRON_NA: "Cron nu este disponibil"
+ SCHEDULER_NOT_ENABLED: "Nu este activat"
+ SCHEDULER_SETUP: "Setarea planificatorului"
+ SCHEDULER_INSTRUCTIONS: "Grav Scheduler vă permite să creaţi şi personalizați acțiuni. Acesta prevede o metodă pentru modulele Grav de a integra programatic şi adăuga dinamic acșiuni pentru a fi rulate periodic."
+ SCHEDULER_POST_INSTRUCTIONS: "Pentru a activa funcţionalitatea Scheduler, trebuie să adăugaţi Grav Scheduler la fişierul de crontab din sistemul dvs. Executaţi comanda de mai sus în terminal pentru a o adăuga automat. Odată salvat, reîmprospătați această pagină pentru a vedea statutul Ready."
+ SCHEDULER_JOBS: "Program personalizat de acțiuni"
+ SCHEDULER_STATUS: "Statutul planificatorului"
+ SCHEDULER_RUNAT: "Rulează la"
+ SCHEDULER_RUNAT_HELP: "Cron formatat 'la' sintaxa"
+ SCHEDULER_OUTPUT: "Fişier final"
+ SCHEDULER_OUTPUT_HELP: "Calea/numele fișierului final (începând cu rădăcina instalării Grav)"
+ SCHEDULER_OUTPUT_TYPE: "Tipul de ieşire"
+ SCHEDULER_OUTPUT_TYPE_HELP: "Adăugare la acelaşi fişier de fiecare dată când rulează sau suprascrie fişierul cu fiecare rulare"
+ SCHEDULER_EMAIL: "Adresa de e-mail"
+ SCHEDULER_EMAIL_HELP: "E-mail pentru a trimite fișierul final. Notă: necesită prestabilirea fişierului de ieşire"
+ SECURITY: "Securitate"
+ XSS_SECURITY: "XSS securitate pentru conţinut"
+ XSS_WHITELIST_PERMISSIONS: "Lista albă de permisiuni"
+ XSS_WHITELIST_PERMISSIONS_HELP: "Utilizatorii cu aceste permisiuni vor trece peste regulile XSS la salvarea conţinutului"
+ XSS_ON_EVENTS: "Filtru evenimente"
+ XSS_INVALID_PROTOCOLS: "Filtru protocoale nevalidate"
+ XSS_INVALID_PROTOCOLS_LIST: "Listă protocoale nevalidate"
+ XSS_MOZ_BINDINGS: "Legături Filter Moz"
+ XSS_HTML_INLINE_STYLES: "Filtează stiluri în HTML"
+ XSS_DANGEROUS_TAGS: "Filtrează Etichete periculoase HTML"
+ XSS_DANGEROUS_TAGS_LIST: "Listă de Etichete HTML periculoase"
+ XSS_ONSAVE_ISSUE: "Salvare nereușită: problemă XSS detectată..."
+ XSS_ISSUE: "NOTĂ: Grav a găsit probleme potenţiale de XSS în %s "
+ UPLOADS_SECURITY: "Încărcări de securitate"
+ UPLOADS_DANGEROUS_EXTENSIONS: "Extensii periculoase"
+ UPLOADS_DANGEROUS_EXTENSIONS_HELP: "Blochează aceste extensii la încărcare indiferent de tipurile MIME acceptate"
+ REPORTS: "Rapoarte"
+ LOGS: "Log-uri"
+ LOG_VIEWER_FILES: "Vizualizator de fişiere log"
+ LOG_VIEWER_FILES_HELP: "Fişiere în /logs/ care vor fi disponibile pentru vizualizare în Instrumente - log-uri. ex. \"grav' = /logs/grav.log"
+ BACKUPS_STORAGE_PURGE_TRIGGER: "Declanșatorul de ștergere definitivă ale copiilor de rezervă"
+ BACKUPS_MAX_COUNT: "Numărul maxim de copii de rezervă"
+ BACKUPS_MAX_COUNT_HELP: "0 este nelimitat"
+ BACKUPS_MAX_SPACE: "Spaţiul maxim de backup"
+ BACKUPS_MAX_RETENTION_TIME: "Perioada maximă de retenție"
+ BACKUPS_MAX_RETENTION_TIME_APPEND: "în zile"
+ BACKUPS_PROFILE_NAME: "Numele copiei de rezervă"
+ BACKUPS_PROFILE_ROOT_FOLDER: "Dosarul rădăcină"
+ BACKUPS_PROFILE_ROOT_FOLDER_HELP: "Poate fi o cale absolută sau un 'stream'"
+ BACKUPS_PROFILE_EXCLUDE_PATHS: "Exclude căile"
+ BACKUPS_PROFILE_EXCLUDE_PATHS_HELP: "Căi absolute excluse, câte una pe linie"
+ BACKUPS_PROFILE_EXCLUDE_FILES: "Excludeţi fişiere"
+ BACKUPS_PROFILE_EXCLUDE_FILES_HELP: "Specifice fişiere sau Dosare excluse, câte unul pe linie"
+ BACKUPS_PROFILE_SCHEDULE: "Activează acțiunea prestabilită planificată"
+ BACKUPS_PROFILE_SCHEDULE_AT: "Rulează acțiunea prestabilită"
+ COMMAND: "Comandă"
+ EXTRA_ARGUMENTS: "Argumente extra"
diff --git a/user/plugins/admin/languages/ru.yaml b/user/plugins/admin/languages/ru.yaml
new file mode 100644
index 0000000..d3c02e2
--- /dev/null
+++ b/user/plugins/admin/languages/ru.yaml
@@ -0,0 +1,812 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Это бета-релиз! Используете его в продакшене на свой страх и риск..."
+ ADMIN_REPORT_ISSUE: "Нашли ошибку? Пожалуйста, сообщите об этом на GitHub."
+ EMAIL_FOOTER: "Работает на Grav - Современная CMS на файлах"
+ LOGIN_BTN: "Логин"
+ LOGIN_BTN_FORGOT: "Забыл"
+ LOGIN_BTN_RESET: "Сбросить пароль"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Отправить инструкции по сбросу"
+ LOGIN_BTN_CLEAR: "Очистить форму"
+ LOGIN_BTN_CREATE_USER: "Создать пользователя"
+ LOGIN_LOGGED_IN: "Вы успешно вошли в систему"
+ LOGIN_FAILED: "Не удалось войти"
+ LOGGED_OUT: "Вы вышли из системы"
+ RESET_NEW_PASSWORD: "Пожалуйста, введите новый пароль …"
+ RESET_LINK_EXPIRED: "Время ссылки для сброса истекло, пожалуйста, повторите попытку"
+ RESET_PASSWORD_RESET: "Пароль был сброшен"
+ RESET_INVALID_LINK: "Неверная ссылка сброса пароля, пожалуйста, повторите попытку"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Инструкции для сброса пароля были высланы на ваш адрес электронной почты"
+ FORGOT_FAILED_TO_EMAIL: "Не удалось отправить инструкции по электронной почте, пожалуйста, повторите попытку позже"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Невозможно сбросить пароль для %s, не задан адрес электронной почты"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Пользователь с логином %s не существует"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Не удаётся сбросить пароль. Этот сайт не настроен для отправки электронной почты"
+ FORGOT_EMAIL_SUBJECT: "%s Запрос на восстановление пароля"
+ FORGOT_EMAIL_BODY: "Сброс пароля Уважемый %1$s,
Был отправлен запрос на %4$s для сброса пароля.
Нажмите эту кнопку, чтобы сбросить свой пароль
Или скопируйте следующий URL в адресную строку вашего браузера:
%2$s
С уважением, %3$s
"
+ MANAGE_PAGES: "Управление страницами"
+ CONFIGURATION: "Настройка"
+ PAGES: "Страницы"
+ PLUGINS: "Плагины"
+ PLUGIN: "Плагин"
+ THEMES: "Темы"
+ LOGOUT: "Выход"
+ BACK: "Назад"
+ NEXT: "Далее"
+ PREVIOUS: "Ранее"
+ ADD_PAGE: "Добавить страницу"
+ ADD_MODULAR: "Добавить модульный блок"
+ MOVE: "Перенести"
+ DELETE: "Удалить"
+ UNSET: "Сброс"
+ VIEW: "Просмотр"
+ SAVE: "Сохранить"
+ NORMAL: "Обычный"
+ EXPERT: "Экспертный"
+ EXPAND_ALL: "Развернуть все"
+ COLLAPSE_ALL: "Свернуть все"
+ ERROR: "Ошибка"
+ CLOSE: "Закрыть"
+ CANCEL: "Отменить"
+ CONTINUE: "Продолжить"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Требуется подтверждение"
+ MODAL_CHANGED_DETECTED_TITLE: "Обнаружены изменения"
+ MODAL_CHANGED_DETECTED_DESC: "У вас есть несохраненные изменения. Вы уверены, что хотите выйти без сохранения?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Требуется подтверждение"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Вы уверены, что хотите удалить этот файл? Это действие не может быть отменено."
+ ADD_FILTERS: "Добавить фильтры"
+ SEARCH_PAGES: "Поиск страниц"
+ VERSION: "Версия"
+ WAS_MADE_WITH: "сделано с"
+ BY: "От"
+ UPDATE_THEME: "Обновить тему"
+ UPDATE_PLUGIN: "Обновить плагин"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "этой темы теперь доступно"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "этого плагина теперь доступно"
+ AUTHOR: "Автор"
+ HOMEPAGE: "Домашняя страница"
+ DEMO: "Демо"
+ BUG_TRACKER: "Баг-трекер"
+ KEYWORDS: "Ключевые слова"
+ LICENSE: "Лицензия"
+ DESCRIPTION: "Описание"
+ README: "Инструкция"
+ REMOVE_THEME: "Удалить тему"
+ INSTALL_THEME: "Установить тему"
+ THEME: "Тема"
+ BACK_TO_THEMES: "Назад к темам"
+ BACK_TO_PLUGINS: "Назад к плагинам"
+ CHECK_FOR_UPDATES: "Проверить обновления"
+ ADD: "Добавить"
+ CLEAR_CACHE: "Очистить кэш"
+ CLEAR_CACHE_ALL_CACHE: "Весь кэш"
+ CLEAR_CACHE_ASSETS_ONLY: "Только ресурсы"
+ CLEAR_CACHE_IMAGES_ONLY: "Только изображения"
+ CLEAR_CACHE_CACHE_ONLY: "Только кэш"
+ CLEAR_CACHE_TMP_ONLY: "Только временные файлы"
+ DASHBOARD: "Панель управления"
+ UPDATES_AVAILABLE: "Доступны обновления"
+ DAYS: "Дни"
+ UPDATE: "Обновление"
+ BACKUP: "Резервная копия"
+ BACKUPS: "Резервные копии"
+ BACKUP_NOW: "Создать резервную копию"
+ BACKUPS_STATS: "Статистика резервного копирования"
+ BACKUPS_HISTORY: "История резервного копирования"
+ BACKUPS_PURGE_CONFIG: "Очистить конфигурацию резервного копирования"
+ BACKUPS_PROFILES: "Профили резервного копирования"
+ BACKUPS_COUNT: "Количество резервных копий"
+ BACKUPS_PROFILES_COUNT: "Количество профилей"
+ BACKUPS_TOTAL_SIZE: "Занято пространства"
+ BACKUPS_NEWEST: "Самая новая резервная копия"
+ BACKUPS_OLDEST: "Самая старая резервная копия"
+ BACKUPS_PURGE: "Очистить"
+ BACKUPS_NOT_GENERATED: "Резервные копии еще не созданы..."
+ BACKUPS_PURGE_NUMBER: "Использовано %s из %s слотов резервных копий"
+ BACKUPS_PURGE_TIME: "Резервной копии осталось %s дней"
+ BACKUPS_PURGE_SPACE: "Используется %s из %s"
+ BACKUP_DELETED: "Резервная копия успешно удалена"
+ BACKUP_NOT_FOUND: "Резервная копия не найдена"
+ BACKUP_DATE: "Дата резервной копии"
+ STATISTICS: "Статистика"
+ TODAY: "Сегодня"
+ WEEK: "Неделя"
+ MONTH: "Месяц"
+ LATEST_PAGE_UPDATES: "Последние обновлённые страницы"
+ MAINTENANCE: "Техническое обслуживание"
+ UPDATED: "Обновлено"
+ MON: "Пн"
+ TUE: "Вт"
+ WED: "Ср"
+ THU: "Чт"
+ FRI: "Пт"
+ SAT: "Сб"
+ SUN: "Вс"
+ COPY: "Копировать"
+ EDIT: "Редактировать"
+ CREATE: "Создать"
+ GRAV_ADMIN: "Админ панель Grav"
+ GRAV_OFFICIAL_PLUGIN: "Официальный плагин Grav"
+ GRAV_OFFICIAL_THEME: "Официальная тема Grav"
+ PLUGIN_SYMBOLICALLY_LINKED: "Этот плагин символически связан. Обновления не будут найдены."
+ THEME_SYMBOLICALLY_LINKED: "Эта тема символически связана. Обновления не будут найдены."
+ REMOVE_PLUGIN: "Удалить плагин"
+ INSTALL_PLUGIN: "Установить плагин"
+ AVAILABLE: "Доступен"
+ INSTALLED: "Установлен"
+ INSTALL: "Установить"
+ ACTIVE_THEME: "Активная тема"
+ SWITCHING_TO: "Переключить"
+ SWITCHING_TO_DESCRIPTION: "При переключении на другую тему нет никакой гарантии того, что поддерживаются все макеты страниц. Возможно, это приведет к ошибкам при попытке загрузки указанных страниц."
+ SWITCHING_TO_CONFIRMATION: "Хотите продолжить и переключится на тему"
+ CREATE_NEW_USER: "Создать нового пользователя"
+ REMOVE_USER: "Удалить пользователя"
+ ACCESS_DENIED: "Доступ запрещен"
+ ACCOUNT_NOT_ADMIN: "ваша учетная запись не имеет прав администратора"
+ PHP_INFO: "Информация о PHP"
+ INSTALLER: "Установщик"
+ AVAILABLE_THEMES: "Доступные темы"
+ AVAILABLE_PLUGINS: "Доступные плагины"
+ INSTALLED_THEMES: "Установленные темы"
+ INSTALLED_PLUGINS: "Установленные плагины"
+ BROWSE_ERROR_LOGS: "Просмотр журналов ошибок"
+ SITE: "Сайт"
+ INFO: "Информация"
+ SYSTEM: "Система"
+ USER: "Пользователь"
+ ADD_ACCOUNT: "Добавить учетную запись"
+ SWITCH_LANGUAGE: "Переключить язык"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Плагин успешно включён"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Плагин успешно отключён"
+ SUCCESSFULLY_CHANGED_THEME: "Успешно изменена тема по умолчанию"
+ INSTALLATION_FAILED: "Ошибка установки"
+ INSTALLATION_SUCCESSFUL: "Установка завершена успешно"
+ UNINSTALL_FAILED: "Удаление не удалось"
+ UNINSTALL_SUCCESSFUL: "Удаление успешно"
+ SUCCESSFULLY_SAVED: "Сохранение успешно"
+ SUCCESSFULLY_COPIED: "Копирование успешно"
+ REORDERING_WAS_SUCCESSFUL: "Изменение порядка прошло успешно"
+ SUCCESSFULLY_DELETED: "Успешно удалено"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Язык подключён успешно"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "У вас недостаточно прав доступа для задачи"
+ CACHE_CLEARED: "Кэш очищен"
+ METHOD: "Метод"
+ ERROR_CLEARING_CACHE: "Ошибка очистки кэша"
+ AN_ERROR_OCCURRED: "Произошла ошибка"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Резервная копия готова для загрузки"
+ DOWNLOAD_BACKUP: "Скачать"
+ PAGES_FILTERED: "Фильтр страниц"
+ NO_PAGE_FOUND: "Страница не найдена"
+ INVALID_PARAMETERS: "Неверные параметры"
+ NO_FILES_SENT: "Файлы не отправлены"
+ EXCEEDED_FILESIZE_LIMIT: "Превышен заданный в конфигурации PHP предельный размер файла"
+ EXCEEDED_POSTMAX_LIMIT: "Превышено значение post_max_size"
+ UNKNOWN_ERRORS: "Неизвестная ошибка"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Превышен заданный в конфигурации Grav предельный размер файла"
+ UNSUPPORTED_FILE_TYPE: "Неподдерживаемый тип файла"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Не удалось переместить загруженный файл."
+ FILE_UPLOADED_SUCCESSFULLY: "Файл успешно загружен"
+ FILE_DELETED: "Файл удалён"
+ FILE_COULD_NOT_BE_DELETED: "Файл не может быть удалён"
+ FILE_NOT_FOUND: "Файл не найден"
+ NO_FILE_FOUND: "Файлы не найдены"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav успешно обновлён на"
+ GRAV_UPDATE_FAILED: "Ошибка обновления Grav"
+ EVERYTHING_UPDATED: "Все обновлено"
+ UPDATES_FAILED: "Обновление не удалось"
+ AVATAR_BY: "Аватар"
+ AVATAR_UPLOAD_OWN: "Или загрузите свой..."
+ LAST_BACKUP: "Последняя резервная копия"
+ FULL_NAME: "Полное имя"
+ USERNAME: "Имя пользователя"
+ EMAIL: "Email"
+ USERNAME_EMAIL: "Логин или Email"
+ PASSWORD: "Пароль"
+ PASSWORD_CONFIRM: "Подтвердить пароль"
+ TITLE: "Заголовок"
+ LANGUAGE: "Язык"
+ ACCOUNT: "Учетная запись"
+ EMAIL_VALIDATION_MESSAGE: "Адрес электронной почты должен быть действительным"
+ PASSWORD_VALIDATION_MESSAGE: "Пароль должен содержать хотя бы одну цифру, одну заглавную и строчную букву, и по крайней мере 8 или более символов"
+ LANGUAGE_HELP: "Выберите предпочитаемый язык"
+ MEDIA: "Медиа"
+ DEFAULTS: "По умолчанию"
+ SITE_TITLE: "Заголовок сайта"
+ SITE_TITLE_PLACEHOLDER: "Название сайта"
+ SITE_TITLE_HELP: "Название сайта по умолчанию, часто используется в темах оформления."
+ SITE_DEFAULT_LANG: "Язык по умолчанию"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Язык, используемый по умолчанию в теге в шаблонах темы."
+ SITE_DEFAULT_LANG_HELP: "Язык, используемый по умолчанию в теге в шаблонах темы."
+ DEFAULT_AUTHOR: "Автор по умолчанию"
+ DEFAULT_AUTHOR_HELP: "Имя автора по умолчанию, часто используется в темах оформления или содержимом страниц."
+ DEFAULT_EMAIL: "Email по умолчанию"
+ DEFAULT_EMAIL_HELP: "Адрес электронной почты по умолчанию, используется для создания ссылки на почту в темах или на страницах."
+ TAXONOMY_TYPES: "Типы таксономии"
+ TAXONOMY_TYPES_HELP: "Типы таксономии должны быть определены здесь, если хотите использовать их на страницах сайта"
+ PAGE_SUMMARY: "Анонс"
+ ENABLED: "Включено"
+ ENABLED_HELP: "Показывать анонс (содержание страницы до разделителя)."
+ 'YES': "Да"
+ 'NO': "Нет"
+ SUMMARY_SIZE: "Размер анонса"
+ SUMMARY_SIZE_HELP: "Количество символов страницы, выводимых в анонсе"
+ FORMAT: "Формат"
+ FORMAT_HELP: "Короткий = использовать первое вхождение разделителя или размер; Длинный = разделитель игнорируется."
+ SHORT: "Короткий"
+ LONG: "Длинный"
+ DELIMITER: "Разделитель"
+ DELIMITER_HELP: "Разделитель для функции «Читать далее...» (по умолчанию '===')"
+ METADATA: "Метаданные"
+ METADATA_HELP: "По умолчанию значения метаданных, которые будут отображаться на каждой странице, если это не определено у конкретной страницы."
+ NAME: "Имя"
+ CONTENT: "Содержание"
+ SIZE: "Размер"
+ ACTION: "Действие"
+ REDIRECTS_AND_ROUTES: "Перенаправления и маршруты"
+ CUSTOM_REDIRECTS: "Пользовательские перенаправления"
+ CUSTOM_REDIRECTS_HELP: "Маршрутизация для перенаправления на другие страницы. Действует замена стандартных регулярных выражений"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/your/redirect"
+ CUSTOM_ROUTES: "Нестандартная маршрутизация"
+ CUSTOM_ROUTES_HELP: "маршруты к псевдониму на другие страницы. Доступна замена стандартными регулярными выражениями"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/your/route"
+ FILE_STREAMS: "Файловые потоки"
+ DEFAULT: "По умолчанию"
+ PAGE_MEDIA: "Вложения"
+ OPTIONS: "Опции"
+ PUBLISHED: "Опубликовано"
+ PUBLISHED_HELP: "По умолчанию страница опубликована, если явно не указано published: false или publish_date в будущем, unpublish_date в прошлом"
+ DATE: "Дата"
+ DATE_HELP: "Переменная даты позволяет специально установить дату, связанную с этой страницей."
+ PUBLISHED_DATE: "Дата публикации"
+ PUBLISHED_DATE_HELP: "Можете указать дату автоматической публикации страницы."
+ UNPUBLISHED_DATE: "Дата снятия с публикации"
+ UNPUBLISHED_DATE_HELP: "Можете указать дату автоматического снятия с публикации."
+ ROBOTS: "Роботы"
+ TAXONOMIES: "Таксономии"
+ TAXONOMY: "Таксономия"
+ ADVANCED: "Дополнительно"
+ SETTINGS: "Настройки"
+ FOLDER_NUMERIC_PREFIX: "Числовой префикс папки"
+ FOLDER_NUMERIC_PREFIX_HELP: "Числовой префикс, позволяет упорядочить страницы вручную, и подразумевает видимость страницы"
+ FOLDER_NAME: "Имя папки"
+ FOLDER_NAME_HELP: "Имя папки, которое будут храниться в файловой системе для этой страницы."
+ PARENT: "Родитель"
+ DEFAULT_OPTION_ROOT: "- Корень -"
+ DEFAULT_OPTION_SELECT: "- Выбрать -"
+ DISPLAY_TEMPLATE: "Шаблон отображения"
+ DISPLAY_TEMPLATE_HELP: "Тип страницы, который определяет, какая ветка шаблона отображает страницу."
+ BODY_CLASSES: "Класс страницы"
+ ORDERING: "Порядок"
+ PAGE_ORDER: "Порядок страниц"
+ OVERRIDES: "Переопределения"
+ MENU: "Меню"
+ MENU_HELP: "Строка, которая будет использоваться в меню. Если не задана, будет использоваться заголовок."
+ SLUG: "Псевдоним"
+ SLUG_HELP: "Эта переменная позволяет установить псевдоним, часть URL-адреса страницы"
+ SLUG_VALIDATE_MESSAGE: "Псевдоним должен состоять только из строчных алфавитно-цифровых символов и дефисов"
+ PROCESS: "Процесс"
+ PROCESS_HELP: "Контролирует как обрабатываются страницы. Может быть установлен на странице, а не глобально"
+ DEFAULT_CHILD_TYPE: "Тип вложенного элемента"
+ USE_GLOBAL: "Использовать глобальный"
+ ROUTABLE: "Маршрутизируемая"
+ ROUTABLE_HELP: "Если страница доступна из URL"
+ CACHING: "Кэширование"
+ VISIBLE: "Видимая"
+ VISIBLE_HELP: "Определяет, отображается ли страница в навигации."
+ DISABLED: "Отключено"
+ ITEMS: "Элементы"
+ ORDER_BY: "Сортировать по"
+ ORDER: "Порядок"
+ FOLDER: "Папка"
+ ASCENDING: "По возрастанию"
+ DESCENDING: "По убыванию"
+ ADD_MODULAR_CONTENT: "Добавить модульное содержимое"
+ PAGE_TITLE: "Заголовок страницы"
+ PAGE_TITLE_HELP: "Заголовок текущей страницы"
+ PAGE: "Страница"
+ MODULAR_TEMPLATE: "Модульный шаблон"
+ FRONTMATTER: "Вступление"
+ FILENAME: "Имя файла"
+ PARENT_PAGE: "Родительская страница"
+ HOME_PAGE: "Главная страница"
+ HOME_PAGE_HELP: "Страница, которую Grav будет использовать по умолчанию в качестве главной."
+ DEFAULT_THEME: "Тема по умолчанию"
+ DEFAULT_THEME_HELP: "Шаблон оформления, используемый по умолчанию."
+ TIMEZONE: "Часовой пояс"
+ TIMEZONE_HELP: "Изменить значение часового пояса, используемого сервером по умолчанию."
+ SHORT_DATE_FORMAT: "Краткий формат даты"
+ SHORT_DATE_FORMAT_HELP: "Укажите короткий формат даты, который может использоваться в темах оформления."
+ LONG_DATE_FORMAT: "Длинный формат даты"
+ LONG_DATE_FORMAT_HELP: "Укажите длинный формат даты, который может использоваться в темах оформления."
+ DEFAULT_ORDERING: "Порядок по умолчанию"
+ DEFAULT_ORDERING_HELP: "Страницы в списке будут выводиться с учетом этого порядка, если он не переопределен."
+ DEFAULT_ORDERING_DEFAULT: "По умолчанию - в зависимости от имени папки"
+ DEFAULT_ORDERING_FOLDER: "Папка - в зависимости от приставки имени папки"
+ DEFAULT_ORDERING_TITLE: "Заголовок - в зависимости от названия поля заголовка"
+ DEFAULT_ORDERING_DATE: "Дата - в зависимости от поля даты"
+ DEFAULT_ORDER_DIRECTION: "Порядок сортировки по умолчанию"
+ DEFAULT_ORDER_DIRECTION_HELP: "Направление сортировки страниц в списке материалов."
+ DEFAULT_PAGE_COUNT: "Количество страниц по умолчанию"
+ DEFAULT_PAGE_COUNT_HELP: "Максимальное количество страниц в списке по умолчанию."
+ DATE_BASED_PUBLISHING: "Публикация на основе даты"
+ DATE_BASED_PUBLISHING_HELP: "Автоматически (не)публикует посты на основе их даты."
+ EVENTS: "События"
+ EVENTS_HELP: "Включить или отключить специфические события. Отключение этих событий может привести к ошибкам в работе плагинов."
+ REDIRECT_DEFAULT_ROUTE: "Перенаправить маршрут по умолчанию"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Автоматическое перенаправление страниц согласно маршруту"
+ LANGUAGES: "Языки"
+ SUPPORTED: "Поддерживаемые языки"
+ SUPPORTED_HELP: "Укажите необходимые вам языки через запятую (например 'en,ru,uk')"
+ TRANSLATIONS_ENABLED: "Поддержка перевода"
+ TRANSLATIONS_ENABLED_HELP: "Поддержка перевода в Grav, плагинах и расширениях"
+ TRANSLATIONS_FALLBACK: "Резервный перевод"
+ TRANSLATIONS_FALLBACK_HELP: "Использовать резервный вариант перевода, если активного языка не существует"
+ ACTIVE_LANGUAGE_IN_SESSION: "Активный язык в сессии"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Хранить активный язык в сессии пользователя"
+ HTTP_HEADERS: "HTTP заголовки"
+ EXPIRES: "Срок действия"
+ EXPIRES_HELP: "Устанавливает заголовок Expires. Значение в секундах."
+ CACHE_CONTROL: "HTTP кэш-контроль"
+ CACHE_CONTROL_HELP: "Установите допустимое значение переменной кэш-контроля сервера из списка: `no-cache, no-store, must-revalidate`."
+ LAST_MODIFIED: "Дата последнего изменения"
+ LAST_MODIFIED_HELP: "Устанавливает заголовок последнего изменения, который может помочь оптимизировать кэширование прокси и браузера"
+ ETAG: "ETag"
+ ETAG_HELP: "Устанавливает заголовок ETag, чтобы помочь определить, когда страница была изменена."
+ VARY_ACCEPT_ENCODING: "Варьировать допустимую кодировку"
+ VARY_ACCEPT_ENCODING_HELP: "Установите заголовок `Vary: Accept Encoding`, чтобы избежать проблем при работе с общедоступными прокси-серверами и CDN, которые не распознают наличие заголовка Content-Encoding."
+ MARKDOWN: "Markdown"
+ MARKDOWN_EXTRA: "Расширенный Markdown"
+ MARKDOWN_EXTRA_HELP: "Включение поддержки Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ MARKDOWN_EXTRA_ESCAPE_FENCES: "Экранирование HTML-элементов (синтаксис fences) в Markdown Extra"
+ MARKDOWN_EXTRA_ESCAPE_FENCES_HELP: "Избегать HTML-элементов в Markdown Extra для синтаксиса fences"
+ AUTO_LINE_BREAKS: "Авто разрывы строк"
+ AUTO_LINE_BREAKS_HELP: "Включить поддержку автоматических разрывов строк в markdown"
+ AUTO_URL_LINKS: "Авто URL ссылки"
+ AUTO_URL_LINKS_HELP: "Включить автоматическое преобразование URL в HTML-код гиперссылки."
+ ESCAPE_MARKUP: "Экранирование разметки"
+ ESCAPE_MARKUP_HELP: "Избегать тегов разметки в HTML объектах"
+ CACHING_HELP: "Глобальный выключатель для включения/отключения кэширования Grav"
+ CACHE_CHECK_METHOD: "Метод проверки кэша"
+ CACHE_CHECK_METHOD_HELP: "Выберите метод, который Grav использует для проверки, были ли файлы страниц изменены."
+ CACHE_DRIVER: "Драйвер кэширования"
+ CACHE_DRIVER_HELP: "Выберите метод кэширования, который должен использоваться Grav. 'Auto Detect' - попытается определить лучший метод самостоятельно"
+ CACHE_PREFIX: "Префикс кэша"
+ CACHE_PREFIX_HELP: "Идентификатор для части ключа Grav. Не изменяйте, если не знаете, к чему это приведет."
+ CACHE_PREFIX_PLACEHOLDER: "Производный от базового URL (переопределите, введя произвольную строку)"
+ CACHE_PURGE_JOB: "Запустить запланированную очистку"
+ CACHE_PURGE_JOB_HELP: "Посредством планировщика можно периодически очищать старые папки кэша файлов Doctrine"
+ CACHE_CLEAR_JOB: "Запустить запланированную очистку"
+ CACHE_CLEAR_JOB_HELP: "С помощью планировщика можно периодически очищать кэш Grav"
+ CACHE_JOB_TYPE: "Тип работы кэша"
+ CACHE_JOB_TYPE_HELP: "Очистить либо с помощью 'стандартной' очистки кэша папок, либо 'все' папки"
+ CACHE_PURGE: "Очистить старый кэш"
+ LIFETIME: "Время жизни"
+ LIFETIME_HELP: "Задает время жизни кэша в секундах. 0 = бесконечно."
+ GZIP_COMPRESSION: "Сжатие Gzip"
+ GZIP_COMPRESSION_HELP: "Включить сжатие Gzip в Grav для увеличения скорости загрузки страниц."
+ TWIG_TEMPLATING: "Шаблонизация Twig"
+ TWIG_CACHING: "Кэширование Twig"
+ TWIG_CACHING_HELP: "Управление механизмом кэширования Twig. Оставьте его включённым для обеспечения лучшей производительности."
+ TWIG_DEBUG: "Отладка Twig"
+ TWIG_DEBUG_HELP: "Позволяет не загружать расширение Twig Debugger."
+ DETECT_CHANGES: "Обнаружение изменений"
+ DETECT_CHANGES_HELP: "Twig автоматически перекомпилирует кэш Twig, если обнаружит какие-либо изменения в шаблонах Twig"
+ AUTOESCAPE_VARIABLES: "Экранирование переменных"
+ AUTOESCAPE_VARIABLES_HELP: "Автоматическое экранирование всех используемых переменных. Это скорей всего нарушит работу сайта."
+ ASSETS: "Ресурсы"
+ CSS_PIPELINE: "CSS конвейер"
+ CSS_PIPELINE_HELP: "Позволяет объединить несколько CSS файлов в один"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Включать внешние ресурсы в CSS конвейер"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Внешние URL-адреса иногда имеют связанные ссылки на файлы и не могут быть в конвейере"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "CSS конвейер рендерит первым"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Рендер CSS конвейера происходит до остальных CSS связей, которые не включены"
+ CSS_MINIFY: "Минимизировать CSS"
+ CSS_MINIFY_HELP: "Минимизировать CSS файлы во время объединения"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "Минимизировать CSS (переопределение для Windows)"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Минимизировать переопределение для платформ Windows. Отключено по умолчанию из-за ThreadStackSize."
+ CSS_REWRITE: "Переписать CSS"
+ CSS_REWRITE_HELP: "Переписать любые CSS относительные URL-адреса во время объединения"
+ JAVASCRIPT_PIPELINE: "Объединение JavaScript"
+ JAVASCRIPT_PIPELINE_HELP: "Объединение нескольких JS файлов в один"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Включать внешние ресурсы в JS пайплайн"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Внешние URL-адреса иногда имеют относительные ссылки на файлы и не могут быть в пайплайне"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Рендер JS пайплайна сначала"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Рендер JS пайплайна до остальных JS связей, которые не включены"
+ JAVASCRIPT_MINIFY: "Минимизировать JavaScript"
+ JAVASCRIPT_MINIFY_HELP: "Минимизировать в JS во время объединения"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Включить временные метки по активам"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Включить временные метки активов"
+ COLLECTIONS: "Коллекции"
+ ERROR_HANDLER: "Обработчик ошибок"
+ DISPLAY_ERRORS: "Выводить ошибки"
+ DISPLAY_ERRORS_HELP: "Отобразить полную цепочку стилей при ошибке на странице"
+ LOG_ERRORS: "Логи ошибок"
+ LOG_ERRORS_HELP: "Логи ошибок находятся в папке /logs"
+ LOG_HANDLER: "Обработчик логов"
+ LOG_HANDLER_HELP: "Где хранить логи"
+ SYSLOG_FACILITY: "Категория syslog"
+ SYSLOG_FACILITY_HELP: "Категория syslog для вывода"
+ DEBUGGER: "Отладчик"
+ DEBUGGER_HELP: "Включите отладчик Grav и после параметров настройки"
+ DEBUG_TWIG: "Отладчик Twig"
+ DEBUG_TWIG_HELP: "Включите отладчик шаблонов Twig"
+ SHUTDOWN_CLOSE_CONNECTION: "Завершение открытых соединений"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Закройте соединение до вызова onShutdown(). ложно для отладки"
+ DEFAULT_IMAGE_QUALITY: "Качество изображений по умолчанию"
+ DEFAULT_IMAGE_QUALITY_HELP: "Качество изображения по умолчанию для использования при повторной выборки или кэширования изображений (85%)"
+ CACHE_ALL: "Кэшировать все картинки"
+ CACHE_ALL_HELP: "Выполните все изображения через систему кэша Grav, даже если у них нет взаимодействия диском"
+ IMAGES_DEBUG: "Отладка водяного знака изображения"
+ IMAGES_DEBUG_HELP: "Показать накладку над изображениями с указанием глубины пикселя изображения при работе с сеткой"
+ IMAGES_SEOFRIENDLY: "ЧПУ имена изображений"
+ IMAGES_SEOFRIENDLY_HELP: "Если включено, сначала отображается имя изображения, а потом небольшой хэш для отражения обрабатываемых операций"
+ UPLOAD_LIMIT: "Лимит загрузки файла"
+ UPLOAD_LIMIT_HELP: "Установите максимальный размер в байтах для загрузки (0 безлимита)"
+ ENABLE_MEDIA_TIMESTAMP: "Включить временные метки на медиа"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Добавляет метку на основе даты последнего изменения каждого элемента в медиа"
+ SESSION: "Сессия"
+ SESSION_ENABLED_HELP: "Включить поддержку сессий в рамках Grav"
+ TIMEOUT: "Время ожидания"
+ TIMEOUT_HELP: "Устанавливает тайм-аут сеанса в секундах"
+ SESSION_NAME_HELP: "Идентификатор используется для формирования имени сессионной куки"
+ SESSION_UNIQUENESS: "Уникальная строка"
+ SESSION_UNIQUENESS_HELP: "MD5 хэш корневого пути Grav, например `GRAV_ROOT` (по умолчанию) или на основе случайной строки `security.salt`."
+ ABSOLUTE_URLS: "Абсолютный путь"
+ ABSOLUTE_URLS_HELP: "Использование абсолютных или относительных URL-адресов для переменной `base_url`."
+ PARAMETER_SEPARATOR: "Разделитель параметров"
+ PARAMETER_SEPARATOR_HELP: "Разделитель для передаваемых параметров, которые могут быть изменены для Apache на Windows."
+ TASK_COMPLETED: "Задача завершена"
+ EVERYTHING_UP_TO_DATE: "Всё актуально"
+ UPDATES_ARE_AVAILABLE: "обновления доступны"
+ IS_AVAILABLE_FOR_UPDATE: "доступно для обновления"
+ IS_NOW_AVAILABLE: "теперь доступно"
+ CURRENT: "Текущий"
+ UPDATE_GRAV_NOW: "Обновить Grav сейчас"
+ GRAV_SYMBOLICALLY_LINKED: "Grav символично связаны. Обновление не будет доступно"
+ UPDATING_PLEASE_WAIT: "Обновление... Пожалуйста, подождите, загрузка"
+ OF_THIS: "этого"
+ OF_YOUR: "из ваших"
+ HAVE_AN_UPDATE_AVAILABLE: "есть доступное обновление"
+ SAVE_AS: "Сохранить как"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Хотите удалить эту страницу и все вложенные? Если страница переведена на другие языки, эти переводы будут сохранены и должны быть удалены отдельно. В противном случае папка страницы будет удалена вместе со своими подстраницами. Это действие не может быть отменено."
+ AND: "и"
+ UPDATE_AVAILABLE: "Доступно обновление"
+ METADATA_KEY: "Ключ (например: 'Ключевые слова')"
+ METADATA_VALUE: "Значение (например: 'Blog, Grav')"
+ USERNAME_HELP: "Имя пользователя должно быть от 3 до 16 символов, в том числе строчных букв, цифр, подчеркивания и дефиса. Прописные буквы, пробелы и специальные символы не допускаются"
+ FULLY_UPDATED: "Полностью обновлено"
+ SAVE_LOCATION: "Расположение"
+ PAGE_FILE: "Шаблон страницы"
+ PAGE_FILE_HELP: "Название файла шаблона страницы, используемого для отображения содержимого"
+ NO_USER_ACCOUNTS: "Учетные записи не найдены, пожалуйста, создайте первую запись..."
+ NO_USER_EXISTS: "Локальный пользователь для этой учетной записи не существует, невозможно сохранить..."
+ REDIRECT_TRAILING_SLASH: "Перенаправление замыкающей слэш"
+ REDIRECT_TRAILING_SLASH_HELP: "Выполните 301 редирект на замыкающий слэш."
+ DEFAULT_DATE_FORMAT: "Формат даты"
+ DEFAULT_DATE_FORMAT_HELP: "Формат даты страницы, используемый в Grav. По умолчанию Grav пытается самостоятельно определить правильный формат даты, однако можно задать нужный формат вручную с помощью соответствующего синтаксиса (например: Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Определить автоматически"
+ IGNORE_FILES: "Игнорировать файлы"
+ IGNORE_FILES_HELP: "Файлы, которые будут проигнорированы при обработке страниц."
+ IGNORE_FOLDERS: "Игнорировать папки"
+ IGNORE_FOLDERS_HELP: "Папки, которые будут проигнорированы при обработке страниц."
+ HIDE_EMPTY_FOLDERS: "Скрыть пустые папки"
+ HIDE_EMPTY_FOLDERS_HELP: "Если в папке нет .md файла, она должна быть скрыта в навигации, а также недоступна для чтения"
+ HTTP_ACCEPT_LANGUAGE: "Определять язык браузера"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Попробуйте разрешить установку языка на основе 'http_accept_language' тега заголовка в браузере пользователя."
+ OVERRIDE_LOCALE: "Переопределение локали"
+ OVERRIDE_LOCALE_HELP: "Переопределение языковой локали в PHP на основе текущего языка."
+ REDIRECT: "Страница редиректа"
+ REDIRECT_HELP: "Введите адрес страницы или внешний URL для страницы сайта, чтобы перенаправить, например, '/some/route' или 'http://somesite.com'"
+ PLUGIN_STATUS: "Статус плагина"
+ INCLUDE_DEFAULT_LANG: "Включить язык по умолчанию"
+ INCLUDE_DEFAULT_LANG_HELP: "Все URL-адреса будут предваряться приставкой языка. Например, `/en/blog/my-post`."
+ PAGES_FALLBACK_ONLY: "Только резервные страницы"
+ PAGES_FALLBACK_ONLY_HELP: "Только 'запасной вариант' для поиска содержимого страницы на поддерживаемых языках. По умолчанию используется любой найденный язык, если отсутствует активный язык"
+ ALLOW_URL_TAXONOMY_FILTERS: "URL для фильтров таксономии"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Базовая страница для фильтров`/taxonomy:value`."
+ REDIRECT_DEFAULT_CODE: "Перенаправление по умолчанию"
+ REDIRECT_DEFAULT_CODE_HELP: "Статус заголовка HTTP при перенаправлении URL."
+ IGNORE_HIDDEN: "Игнорировать скрытые"
+ IGNORE_HIDDEN_HELP: "Игнорировать все скрытые файлы и папки"
+ WRAPPED_SITE: "Завернутый сайт"
+ WRAPPED_SITE_HELP: "Сообщает плагину или теме оформления, что Grav обернут в другую платформу."
+ FALLBACK_TYPES: "Разрешенные резервные типы"
+ FALLBACK_TYPES_HELP: "Разрешенные типы файлов, которые могут быть найдены, если обратиться через маршрутизатор страниц"
+ INLINE_TYPES: "Встроенные типы запасных вариантов"
+ INLINE_TYPES_HELP: "Список типов файлов, которые должны отображаться, как строенные, а не загружаться"
+ APPEND_URL_EXT: "Окончание URL-адресов"
+ APPEND_URL_EXT_HELP: "Будет добавлено пользовательское расширение URL страницы. Обратите внимание, что Grav будет искать шаблон в формате `..twig`"
+ PAGE_MODES: "Режимы страницы"
+ PAGE_TYPES: "Типы страницы"
+ PAGE_TYPES_HELP: "Определяет типы страниц, которые Grav поддерживает, а также порядок, какой тип значения должен вернуться назад, если запрос является двусмысленным"
+ ACCESS_LEVELS: "Уровни доступа"
+ GROUPS: "Группы"
+ GROUPS_HELP: "Список групп пользователей, входящих в"
+ ADMIN_ACCESS: "Доступ администратора"
+ SITE_ACCESS: "Доступ сайта"
+ INVALID_SECURITY_TOKEN: "Неверный маркер безопасности"
+ ACTIVATE: "Активировать"
+ TWIG_UMASK_FIX: "Исправление Umask (0755 -> 0775)"
+ TWIG_UMASK_FIX_HELP: "По умолчанию Twig создает кэшированные файлы с правами 0755, исправить переключатели на 0775"
+ CACHE_PERMS: "Права кэша"
+ CACHE_PERMS_HELP: "По умолчанию права папки кэша. Обычно 0755 или 0775 в зависимости от настроек сервера"
+ REMOVE_SUCCESSFUL: "Успешно удалено"
+ REMOVE_FAILED: "Не удалось удалить"
+ HIDE_HOME_IN_URLS: "Убрать путь к главной странице из адресов дочерних"
+ HIDE_HOME_IN_URLS_HELP: "Все дочерние страницы внутри главной не будут содержать в адресах маршрутизатор главной (например, вместо /blog/mypage будет отображаться просто /mypage)"
+ TWIG_FIRST: "Обрабатывать Twig в первую очередь"
+ TWIG_FIRST_HELP: "Если включали обработку страницы Twig, то можно сконфигурировать Twig, чтобы обработать прежде или после markdown"
+ SESSION_SECURE: "Безопасный"
+ SESSION_SECURE_HELP: "Если включено, указывает, что соединение для этого cookie должна быть по зашифрованной передаче. ПРЕДУПРЕЖДЕНИЕ: Включите это только на сайтах, которые работают исключительно на HTTPS"
+ SESSION_HTTPONLY: "Только HTTP"
+ SESSION_HTTPONLY_HELP: "Если Да, то куки должны быть использованы только по протоколу HTTP, и модификация JavaScript не допускается"
+ REVERSE_PROXY: "Обратный прокси-сервер"
+ REVERSE_PROXY_HELP: "Включить, если находитесь за обратным прокси и испытываете проблемы с URL-адресов, содержащих некорректные порты"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Неверный формат вступления, не удалось сохранить"
+ ADD_FOLDER: "Добавить папку"
+ PROXY_URL: "Адрес прокси"
+ PROXY_URL_HELP: "Введите адрес прокси или IP-адрес и номер порта"
+ NOTHING_TO_SAVE: "Нечего сохранять"
+ FILE_ERROR_ADD: "Произошла ошибка при попытке добавить файл"
+ FILE_ERROR_UPLOAD: "Произошла ошибка при попытке загрузить файл"
+ FILE_UNSUPPORTED: "Неподдерживаемый тип файла"
+ ADD_ITEM: "Добавить элемент"
+ FILE_TOO_LARGE: "Файл слишком большой, чтобы быть загруженным, максимально допустимый размер %s согласно настройке PHP. Увеличте значение параметра настройки PHP `post_max_size`"
+ INSTALLING: "Установка"
+ LOADING: "Загрузка.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "Для начала должны быть удовлетворены следующие зависимости:"
+ ERROR_INSTALLING_PACKAGES: "Ошибка при установке пакетов"
+ INSTALLING_DEPENDENCIES: "Установка зависимостей..."
+ INSTALLING_PACKAGES: "Установка пакетов.."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Пакеты успешно установлены."
+ READY_TO_INSTALL_PACKAGES: "Всё готово для установки пакетов"
+ PACKAGES_NOT_INSTALLED: "Пакеты не установлены"
+ PACKAGES_NEED_UPDATE: "Пакеты уже установлены, но их версии слишком старые"
+ PACKAGES_SUGGESTED_UPDATE: "Пакеты уже установлены и их версии в порядке, но они будут обновлены, чтобы использовать последние версии"
+ REMOVE_THE: "Удалить %s"
+ CONFIRM_REMOVAL: "Хотите удалить %s?"
+ REMOVED_SUCCESSFULLY: "%s успешно удалён"
+ ERROR_REMOVING_THE: "Ошибка удаления %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "%s требует следующие зависимости, которые не нужны в других установленных пакетах. Если не используете их, можно удалить их прямо отсюда."
+ READY_TO_UPDATE_PACKAGES: "Всё готово для обновления пакетов"
+ ERROR_UPDATING_PACKAGES: "Ошибка при обновлении пакетов"
+ UPDATING_PACKAGES: "Обновление пакетов.."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Пакеты успешно обновлены."
+ UPDATING: "Обновление"
+ GPM_RELEASES: "Релизы GPM"
+ GPM_RELEASES_HELP: "Выберите вариант «Testing» для установки бета-релизов или тестовых версий"
+ GPM_METHOD: "Метод удаленного обновления"
+ GPM_METHOD_HELP: "При использовании значения «Авто» Grav определит, доступна ли функция fopen и использует её, в противном случае будет использована cURL. Чтобы принудительно использовать один из вариантов, укажите его в настройках."
+ GPM_VERIFY_PEER: "Удаленная проверка пиров (SSL)"
+ GPM_VERIFY_PEER_HELP: "Некоторые провайдеры не могут проверить подлинность удалённого узла getgrav.org. Если это так, отключение этой настройки может помочь."
+ AUTO: "Авто"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Стабильная версия"
+ TESTING: "Тестирование"
+ FRONTMATTER_PROCESS_TWIG: "Обрабатывать Twig в обобщенных страницах"
+ FRONTMATTER_PROCESS_TWIG_HELP: "При включении вы можете использовать конфигурационные переменные Twig в обобщенных страницах"
+ FRONTMATTER_IGNORE_FIELDS: "Игнорировать обобщенные поля"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Некоторые обобщенные поля могут содержать Twig, но не должны быть обработаны как «формы»"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Пакет %s успешно установлен"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Установка сортировки родителя, изменение порядка отключено"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Страница не отображаема, изменение порядка отключено"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "Сортировка через админ-панель не поддерживается, поскольку используется более 200 потомков"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "На этой странице отключен порядок, поскольку Числовой префикс папки не включен"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "Примечание: добавление файлов невозможно, пока вы не сохраните страницу. Кликните «Сохранить» в верхней части админки."
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "Примечание: страница должна быть сохранена перед загрузкой файлов."
+ DROP_FILES_HERE_TO_UPLOAD: "Переместите файлы сюда или нажмите на эту область "
+ INSERT: "Вставить"
+ UNDO: "Отменить"
+ REDO: "Повторить"
+ HEADERS: "Заголовки"
+ BOLD: "Жирный"
+ ITALIC: "Курсив"
+ STRIKETHROUGH: "Перечеркнутый"
+ SUMMARY_DELIMITER: "Разделитель"
+ LINK: "Ссылка"
+ IMAGE: "Изображение"
+ BLOCKQUOTE: "Цитата"
+ UNORDERED_LIST: "Маркированный список"
+ ORDERED_LIST: "Нумерованный список"
+ EDITOR: "Редактор"
+ PREVIEW: "Просмотреть"
+ FULLSCREEN: "Полноэкранный режим"
+ MODULAR: "Модульная"
+ MODULAR_SETUP: "Модульная настройка"
+ NON_ROUTABLE: "Немаршрутизируемая"
+ NON_MODULAR: "Немодульная"
+ NON_VISIBLE: "Невидимая"
+ NON_PUBLISHED: "Неопубликованная"
+ CHARACTERS: "символов"
+ PUBLISHING: "Публикация"
+ NOTIFICATIONS: "Уведомления"
+ MEDIA_TYPES: "Типы вложений"
+ IMAGE_OPTIONS: "Параметры изображения"
+ MIME_TYPE: "Mime-тип"
+ THUMB: "Миниатюра"
+ TYPE: "Тип"
+ FILE_EXTENSION: "Расширение файла"
+ LEGEND: "Обозначения"
+ MEMCACHE_SERVER: "Сервер Memcache"
+ MEMCACHE_SERVER_HELP: "Адрес сервера Memcache"
+ MEMCACHE_PORT: "Порт Memcache"
+ MEMCACHE_PORT_HELP: "Порт сервера Memcache"
+ MEMCACHED_SERVER: "Сервер Memcached"
+ MEMCACHED_SERVER_HELP: "Адрес сервера Memcached"
+ MEMCACHED_PORT: "Порт Memcached"
+ MEMCACHED_PORT_HELP: "Порт сервера Memcached"
+ REDIS_SERVER: "Сервер Redis"
+ REDIS_SERVER_HELP: "Адрес сервера Redis"
+ REDIS_PORT: "Порт Redis"
+ REDIS_PORT_HELP: "Порт сервера Redis"
+ REDIS_PASSWORD: "Redis пароль/секрет"
+ ALL: "все"
+ FROM: "от"
+ TO: "к"
+ RELEASE_DATE: "Дата выпуска"
+ SORT_BY: "Сортировать по"
+ RESOURCE_FILTER: "Фильтр..."
+ FORCE_SSL: "Принудительный SSL"
+ FORCE_SSL_HELP: "Принудительно включить SSL по всему сайту. Если включено, при открытии сайта по HTTP Grav перенаправит на HTTPS"
+ NEWS_FEED: "Лента новостей"
+ EXTERNAL_URL: "Внешний URL"
+ SESSION_PATH: "Путь сессии"
+ SESSION_PATH_HELP: "Используйте, только если выбран пользовательский базовый URL (вы изменяли домен сайта или подпапку)"
+ CUSTOM_BASE_URL: "Пользовательский базовый URL"
+ CUSTOM_BASE_URL_HELP: "Используйте, если хотите переписать домен сайта или использовать подпапку, отличную от той, что использует Grav. Пример: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'Нельзя использовать "%s" за пределами страницы.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Не удается загрузить файл %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'Не удается переместить файл %s в "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'Отменить загрузку'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Хотите отменить эту загрузку?'
+ DROPZONE_DEFAULT_MESSAGE: 'Перетащите файлы сюда или нажмите на эту область '
+ DROPZONE_FALLBACK_MESSAGE: 'Ваш браузер не поддерживает жест перетаскивания для загрузки файлов.'
+ DROPZONE_FALLBACK_TEXT: 'Пожалуйста, используйте резервную форму ниже, чтобы загрузить файлы как раньше.'
+ DROPZONE_FILE_TOO_BIG: 'Файл слишком велик ({{filesize}}MiB). Максимальный допустимый размер: {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "Вы не можете загружать файлы этого типа."
+ DROPZONE_MAX_FILES_EXCEEDED: "Вы не можете загрузить больше файлов."
+ DROPZONE_REMOVE_FILE: "Удалить файл"
+ DROPZONE_RESPONSE_ERROR: "Сервер ответил с кодом {{statusCode}}."
+ PREMIUM_PRODUCT: "Премиум"
+ ADMIN_CACHING: "Включить кэширование админки"
+ ADMIN_CACHING_HELP: "Кэшированием в админке можно управлять независимо от основного сайта"
+ DESTINATION_NOT_SPECIFIED: "Пункт назначения не указан"
+ UPLOAD_ERR_NO_TMP_DIR: "Отсутствует временная директория"
+ SESSION_SPLIT: "Раздельные сессии"
+ SESSION_SPLIT_HELP: "Отдельные сессии для сайта и других плагинов (такими как Admin)"
+ ERROR_FULL_BACKTRACE: "Полная трассировка ошибки"
+ ERROR_SIMPLE: "Простая ошибка"
+ ERROR_SYSTEM: "Системная ошибка"
+ IMAGES_AUTO_FIX_ORIENTATION: "Автоориентация изображений"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Автоматическое исправление ориентации изображений на основе данных Exif"
+ REDIS_SOCKET: "Сокет Redis"
+ REDIS_SOCKET_HELP: "Сокет Redis"
+ NOT_SET: "Не задано"
+ PERMISSIONS: "Разрешения"
+ NEVER_CACHE_TWIG: "Никогда не кэшировать Twig"
+ NEVER_CACHE_TWIG_HELP: "Кэшировать только содержание и обрабатывать Twig каждый раз при загрузке страниц. Игнорирует параметр twig_first."
+ ALLOW_WEBSERVER_GZIP: "Разрешить сжатие Gzip на сервере"
+ ALLOW_WEBSERVER_GZIP_HELP: "Выключено по умолчанию. При включении будет работать сконфигурированное на сервере сжатие Gzip/Deflate, но соединение http не будет закрываться перед событием onShutDown(), приводящим к замедлению загрузки страницы"
+ OFFLINE_WARNING: "Невозможно установить подключение к GPM"
+ CLEAR_IMAGES_BY_DEFAULT: "Очищать кэш изображений"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "По умолчанию обработанные изображения очищаются при любых чистках кэша, это можно отключить"
+ CLI_COMPATIBILITY: "Совместимость с интерфейсом командной строки (CLI)"
+ CLI_COMPATIBILITY_HELP: "Гарантирует использование долговременных драйверов кэширования (file, redis, memcache и т. д.)"
+ REINSTALL_PLUGIN: "Переустановить плагин"
+ REINSTALL_THEME: "Переустановить тему"
+ REINSTALL_THE: "Переустановить %s"
+ CONFIRM_REINSTALL: "Хотите переустановить %s?"
+ REINSTALLED_SUCCESSFULLY: "%s успешно переустановлен(а)"
+ ERROR_REINSTALLING_THE: "Ошибка переустановки %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Пакет %s успешно переустановлен"
+ REINSTALLATION_FAILED: "Переустановка не удалась"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "Установленная версия не является последней. Нажмите на кнопку «Продолжить», чтобы удалить текущую версию и установить самую свежую"
+ TOOLS: "Сервис"
+ DIRECT_INSTALL: "Прямая установка"
+ NO_PACKAGE_NAME: "Имя пакета не указано"
+ PACKAGE_EXTRACTION_FAILED: "Не удалось извлечь пакет"
+ NOT_VALID_GRAV_PACKAGE: "Недействительный пакет Grav"
+ NAME_COULD_NOT_BE_DETERMINED: "Имя не может быть определено"
+ CANNOT_OVERWRITE_SYMLINKS: "Невозможно перезаписать символические ссылки"
+ ZIP_PACKAGE_NOT_FOUND: "ZIP-пакет не найден"
+ GPM_OFFICIAL_ONLY: "Только официальный GPM"
+ GPM_OFFICIAL_ONLY_HELP: "Разрешать прямые установки только из официального хранилища GPM."
+ NO_CHILD_TYPE: "Нет дочернего типа для этого маршрута"
+ SORTABLE_PAGES: "Сортируемые страницы:"
+ UNSORTABLE_PAGES: "Несортируемые страницы"
+ ADMIN_SPECIFIC_OVERRIDES: "Специальные переопределения"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Порядок отображения дочерних страниц"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "Порядок отображения дочерних элементов этой страницы в окне «Страницы» плагина Admin"
+ PWD_PLACEHOLDER: "Сложная строка длиной не менее 8 символов"
+ PWD_REGEX: "Пароль, регулярка"
+ PWD_REGEX_HELP: "По умолчанию: Пароль должен содержать как минимум одно число, одну прописную и строчную буквы, и как минимум 8 символов."
+ USERNAME_PLACEHOLDER: "только строчные символы, н. п. 'admin'"
+ USERNAME_REGEX: "Имя пользователя, регулярка"
+ USERNAME_REGEX_HELP: "По умолчанию: Только строчные символы, цифры, дефисы и подчеркивания. 3-16 символов."
+ CONTENT_PADDING: "Отступы для содержимого"
+ CONTENT_PADDING_HELP: "Включить/отключить добавление контента вокруг области содержимого, чтобы обеспечить больше места."
+ ENABLE_AUTO_METADATA: "Авто метаданные из Exif"
+ ENABLE_AUTO_METADATA_HELP: "Автоматическое создание файлов метаданных для изображений с помощью информации из Exif."
+ 2FA_TITLE: "Двухфакторная аутентификация"
+ 2FA_INSTRUCTIONS: "##### 2-факторная аутентификация\nУ вас включена **2FA**. Пожалуйста, используйте ваше приложение **2FA**, чтобы ввести текущий **6-значный код**, для завершения процесса входа в систему."
+ 2FA_REGEN_HINT: "Чтобы восстановить секрет, вам потребуется обновить приложение для аутентификации"
+ 2FA_LABEL: "Доступ администратора"
+ 2FA_FAILED: "Недействительный код 2FA, пожалуйста попробуйте еще раз..."
+ 2FA_ENABLED: "2FA включен"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "Секрет Api"
+ 2FA_SECRET_HELP: "Сканируйте этот QR-код в свое [приложение аутентификации] (https://learn.getgrav.org/admin-panel/2fa#apps). Также рекомендуется резервировать секрет в безопасном месте, если вам нужно переустановить приложение. Для получения дополнительной информации просмотрите документы [Grav docs] (https://learn.getgrav.org/admin-panel/2fa) "
+ 2FA_REGENERATE: "Сгенерировать новый"
+ FORCE_LOWERCASE_URLS: "Принудительное приведение URLs к строчному виду"
+ FORCE_LOWERCASE_URLS_HELP: "По умолчанию Grav будет устанавливать все заглушки и маршруты в нижний регистр. Если этот параметр установлен в false, можно использовать заглушки и маршруты в верхнем регистре"
+ INTL_ENABLED: "Интеграция модуля Intl"
+ INTL_ENABLED_HELP: "Использовать PHP модуль Intl и collate для сортировки коллекций, хранящихся в UTF-8"
+ VIEW_SITE_TIP: "Открыть сайт"
+ TOOLS_DIRECT_INSTALL_TITLE: "Прямая установка Grav пакетов"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "Установка пакета через прямую загрузку ZIP-файла"
+ TOOLS_DIRECT_INSTALL_UPLOAD_DESC: "Вы с легкостью можете обновить Grav или установить подходящую тему , плагин для него. Этот пакет не нужно регистрировать с помощью GPM и он позволяет легко откатиться к предыдущей версии или установить тестовую."
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "Установить пакет через удаленный URL-адрес"
+ TOOLS_DIRECT_INSTALL_URL_DESC: "Кроме того, вы также можете указать полный URL-адрес к ZIP-файлу пакета и установить его через этот удаленный URL-адрес."
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Загрузить и установить"
+ ROUTE_OVERRIDES: "Переадресация маршрута"
+ ROUTE_DEFAULT: "Маршрут по умолчанию"
+ ROUTE_CANONICAL: "Канонический маршрут"
+ ROUTE_ALIASES: "Псевдонимы маршрута"
+ OPEN_NEW_TAB: "Открыть в новой вкладке"
+ PAGEMEDIA_RESIZER: "> Следующие настройки применяются к изображениям, загруженным через страницу мультимедиа. Изменение размера по ширине / высоте автоматически изменит размеры и пропорционально уменьшит изображение, если оно превысит установленные пределы. Минимальное и максимальное значения разрешения определяют диапазоны размеров для загружаемых изображений. Установите поля в 0, чтобы предотвратить любые манипуляции."
+ SESSION_INITIALIZE: "Инициализировать сессию"
+ SESSION_INITIALIZE_HELP: "Просит Grav начать сеанс. Эта функция необходима для работы любых взаимодействий с пользователем, таких как вход в систему, формы и т. д. На плагин администратора этот параметр не влияет."
+ STRICT_YAML_COMPAT: "YAML совместимость"
+ STRICT_YAML_COMPAT_HELP: "Откат к YAML парсеру Symfony 2.4, если нативный парсер 3.4 не работает"
+ STRICT_TWIG_COMPAT: "Twig совместимость"
+ STRICT_TWIG_COMPAT_HELP: "Включает устаревшую настройку экранирования Twig. Когда этот параметр отключен, для вывода HTML требуется фильтр |raw, поскольку Twig будет автоматически экранировать вывод"
+ SCHEDULER: "Планировщик"
+ SCHEDULER_INSTALL_INSTRUCTIONS: "Инструкции по установке"
+ SCHEDULER_INSTALLED_READY: "Установлено и готово"
+ SCHEDULER_CRON_NA: "Cron не доступен"
+ SCHEDULER_NOT_ENABLED: "Не включен"
+ SCHEDULER_SETUP: "Настройка планировщика"
+ SCHEDULER_INSTRUCTIONS: "Планировщик Grav позволяет создавать и планировать пользовательские задания. Он также предоставляет Grav плагинам возможность интегрировать программно и динамически добавлять задания для периодического запуска."
+ SCHEDULER_POST_INSTRUCTIONS: "Чтобы включить функции планировщика, вы должны добавить планировщик Grav в файл crontab вашей системы. Запустите указанную выше команду из терминала, чтобы добавить ее автоматически. После сохранения обновите эту страницу, чтобы увидеть статус 'Готов'."
+ SCHEDULER_JOBS: "Пользовательские задания планировщика"
+ SCHEDULER_STATUS: "Статус планировщика"
+ SCHEDULER_RUNAT: "Запустить в"
+ SCHEDULER_OUTPUT: "Выходной файл"
+ SCHEDULER_OUTPUT_HELP: "Путь/имя файла вывода (из корня установки Grav)"
+ SCHEDULER_OUTPUT_TYPE: "Тип вывода"
+ SCHEDULER_EMAIL: "Email"
+ SCHEDULER_EMAIL_HELP: "E-mail для отправки вывода. ПРИМЕЧАНИЕ: требуется указать выходной файл"
+ SECURITY: "Безопасность"
+ XSS_SECURITY: "XSS безопасность контента"
+ XSS_WHITELIST_PERMISSIONS: "Белый список разрешений"
+ XSS_WHITELIST_PERMISSIONS_HELP: "Пользователи с этими разрешениями пропустят правила XSS при сохранении контента"
+ XSS_ON_EVENTS: "Фильтр событий"
+ XSS_INVALID_PROTOCOLS: "Фильтр неверных протоколов"
+ XSS_INVALID_PROTOCOLS_LIST: "Список неверных протоколов"
+ XSS_MOZ_BINDINGS: "Фильтр Moz привязок"
+ XSS_HTML_INLINE_STYLES: "Фильтровать встроенные стили HTML"
+ XSS_DANGEROUS_TAGS: "Фильтровать опасные HTML-теги"
+ XSS_DANGEROUS_TAGS_LIST: "Список опасных HTML-тегов"
+ XSS_ONSAVE_ISSUE: "Не удалось сохранить: обнаружена проблема с XSS..."
+ XSS_ISSUE: "ВНИМАНИЕ: Grav обнаружил возможные проблемы с XSS в %s "
+ UPLOADS_SECURITY: "Безопасность загрузок"
+ UPLOADS_DANGEROUS_EXTENSIONS: "Опасные Расширения"
+ UPLOADS_DANGEROUS_EXTENSIONS_HELP: "Не разрешать загрузку этих расширений в независимости от разрешённых MIME-типов"
+ REPORTS: "Отчёты"
+ LOGS: "Логи"
+ LOG_VIEWER_FILES: "Файлы логов"
+ LOG_VIEWER_FILES_HELP: "Файлы в /logs/ будут доступны для просмотра в Инструменты - Логи. Например, grav' = /logs/grav.log"
+ BACKUPS_MAX_COUNT: "Максимальное количество резервных копий"
+ BACKUPS_MAX_COUNT_HELP: "0 — без ограничений"
+ BACKUPS_MAX_RETENTION_TIME_APPEND: "в днях"
+ BACKUPS_PROFILE_NAME: "Имя резервной копии"
+ BACKUPS_PROFILE_ROOT_FOLDER: "Корневой каталог"
+ BACKUPS_PROFILE_EXCLUDE_PATHS: "Исключенные пути"
+ BACKUPS_PROFILE_EXCLUDE_PATHS_HELP: "Абсолютные пути для исключения, по одному в строке"
+ BACKUPS_PROFILE_EXCLUDE_FILES: "Исключенные файлы"
+ BACKUPS_PROFILE_EXCLUDE_FILES_HELP: "Конкретные файлы или папки для исключения, по одному в строке"
+ BACKUPS_PROFILE_SCHEDULE: "Включить планировщик"
+ COMMAND: "Команда"
+ EXTRA_ARGUMENTS: "Дополнительные аргументы"
diff --git a/user/plugins/admin/languages/sk.yaml b/user/plugins/admin/languages/sk.yaml
new file mode 100644
index 0000000..3f4ad93
--- /dev/null
+++ b/user/plugins/admin/languages/sk.yaml
@@ -0,0 +1,555 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Toto je Beta verzia! Používajte v ostrej prevádzke na vlastné riziko..."
+ ADMIN_REPORT_ISSUE: "Vyskytla sa chyba? Prosím, nahláste to na GitHub."
+ EMAIL_FOOTER: "Poháňaný Grav-om - moderný flat-file redakčný systém"
+ LOGIN_BTN: "Prihlásiť sa"
+ LOGIN_BTN_FORGOT: "Zabudli ste meno/heslo"
+ LOGIN_BTN_RESET: "Obnoviť heslo"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Poslať pokyny na obnovenie"
+ LOGIN_BTN_CLEAR: "Vyčistiť formulár"
+ LOGIN_BTN_CREATE_USER: "Vytvoriť používateľa"
+ LOGIN_LOGGED_IN: "Boli ste úspešne prihlásený"
+ LOGIN_FAILED: "Prihásenie neúspešné"
+ LOGGED_OUT: "Boli ste odhlásení"
+ RESET_NEW_PASSWORD: "Zadajte nové heslo …"
+ RESET_LINK_EXPIRED: "Platnosť odkazu pre obnovenie hesla vypršala, prosím skúste to znovu"
+ RESET_PASSWORD_RESET: "Heslo bolo obnovené"
+ RESET_INVALID_LINK: "Neplatný odkaz pre obnovenie hesla. Prosím, skúste to znovu"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Pokyny na obnovenie hesla boli odoslané prostredníctvom e-mailu pre %s"
+ FORGOT_FAILED_TO_EMAIL: "Nepodarilo sa odoslanie emailu s pokynmi, skúste to prosím neskôr"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Nemožno obnoviť heslo pre %s, nie je nastavená žiadna e-mailová adresa"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Používateľ s prihlasovacím menom %s neexistuje"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Nemožno vytvoriť nové heslo. Táto stránka nie je nakonfigurovaná na odosielanie e-mailov"
+ FORGOT_EMAIL_SUBJECT: "%s žiadosť obnoviť heslo"
+ FORGOT_EMAIL_BODY: "Obnovení hesla Vážený %1$s,
požadavek na obnovu hesla byl zadán na %4$s .
Klikněte zde pro nastavení nového hesla
Případně, zkopírujte následující adresu URL do adresního řádku vašeho prohlížeče:
%2$s
Děkujeme, %3$s
"
+ MANAGE_PAGES: "Spravovať stránky"
+ CONFIGURATION: "Nastavenia"
+ PAGES: "Stránky"
+ PLUGINS: "Rozšírenia"
+ PLUGIN: "Rozšírenie"
+ THEMES: "Šablóny"
+ LOGOUT: "Odhlásiť sa"
+ BACK: "Späť"
+ NEXT: "Ďalej"
+ PREVIOUS: "Späť"
+ ADD_PAGE: "Pridať stránku"
+ ADD_MODULAR: "Pridať modul"
+ MOVE: "Presunúť"
+ DELETE: "Odstrániť"
+ UNSET: "Zrušiť nastavenie"
+ VIEW: "Náhľad"
+ SAVE: "Uložiť"
+ NORMAL: "Normálna"
+ EXPERT: "Expert"
+ EXPAND_ALL: "Rozbaliť všetko"
+ COLLAPSE_ALL: "Zbaliť všetko"
+ ERROR: "Chyba"
+ CLOSE: "Zavrieť"
+ CANCEL: "Zrušiť"
+ CONTINUE: "Pokračovať"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Vyžadované potvrdenie"
+ MODAL_CHANGED_DETECTED_TITLE: "Zistené zmeny"
+ MODAL_CHANGED_DETECTED_DESC: "Neuložili ste zmeny. Ste si istí, že chcete odísť bez uloženia?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Vyžadované potvrdenie"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Naozaj chcete odstrániť tento súbor? Túto akciu nie je možné vrátiť späť."
+ ADD_FILTERS: "Pridať filtre"
+ SEARCH_PAGES: "Hľadať stránky"
+ VERSION: "Verzia"
+ WAS_MADE_WITH: "Bola vytvorená s"
+ BY: "Od"
+ UPDATE_THEME: "Aktualizovať tému"
+ UPDATE_PLUGIN: "Aktualizovať rozšírenie"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "tejto šablóny je k dispozícíi"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "tohto rozšírenia je k dispozícíi"
+ AUTHOR: "Autor"
+ HOMEPAGE: "Domovská stránka"
+ DEMO: "Demo"
+ BUG_TRACKER: "Hlásenie chýb"
+ KEYWORDS: "Kľúčové slová"
+ LICENSE: "Licencia"
+ DESCRIPTION: "Popis"
+ README: "Súbor Readme"
+ REMOVE_THEME: "Odstrániť šablónu"
+ INSTALL_THEME: "Nainštalovať šablónu"
+ THEME: "Šablóna"
+ BACK_TO_THEMES: "Späť na šablóny"
+ BACK_TO_PLUGINS: "Späť na rozšírenia"
+ CHECK_FOR_UPDATES: "Skontrolovať aktualizácie"
+ ADD: "Pridať"
+ CLEAR_CACHE: "Vyprázdniť vyrovnávaciu pamäť"
+ CLEAR_CACHE_ALL_CACHE: "Všetky Cache"
+ CLEAR_CACHE_ASSETS_ONLY: "Iba zdroje"
+ CLEAR_CACHE_IMAGES_ONLY: "Iba obrázky"
+ CLEAR_CACHE_CACHE_ONLY: "Iba cache"
+ CLEAR_CACHE_TMP_ONLY: "Iba dočasné súbory"
+ DASHBOARD: "Panel"
+ UPDATES_AVAILABLE: "K dispozícii je nová aktualizácia"
+ DAYS: "Dní"
+ UPDATE: "Aktualizovať"
+ BACKUP: "Zálohovanie"
+ BACKUPS: "Zálohovanie"
+ BACKUP_NOW: "Zálohovať teraz"
+ BACKUPS_STATS: "Štatistiky zálohovania"
+ BACKUPS_HISTORY: "História zálohovania"
+ BACKUPS_PURGE_CONFIG: "Konfigurácia vyčistenia záloh"
+ BACKUPS_PROFILES: "Profily zálohovania"
+ BACKUPS_COUNT: "Počet záloh"
+ BACKUPS_PROFILES_COUNT: "Počet profilov"
+ BACKUPS_TOTAL_SIZE: "Využité miesto"
+ BACKUPS_NEWEST: "Najnovšia záloha"
+ BACKUPS_OLDEST: "Najstaršia záloha"
+ BACKUPS_PURGE: "Vyčistiť"
+ BACKUPS_NOT_GENERATED: "Zatiaľ neboli vytvorené žiadne zálohy..."
+ BACKUP_DATE: "Dátum zálohy"
+ STATISTICS: "Štatistiky"
+ TODAY: "Dnes"
+ WEEK: "Týždeň"
+ MONTH: "Mesiac"
+ LATEST_PAGE_UPDATES: "Posledná aktualizácia stránky"
+ MAINTENANCE: "Údržba"
+ UPDATED: "Aktualizované"
+ MON: "Po"
+ TUE: "Ut"
+ WED: "St"
+ THU: "Št"
+ FRI: "Pi"
+ SAT: "So"
+ SUN: "Ne"
+ COPY: "Kopírovať"
+ EDIT: "Upraviť"
+ CREATE: "Vytvoriť"
+ GRAV_ADMIN: "Grav administrácia"
+ GRAV_OFFICIAL_PLUGIN: "Oficiálne rozšírenie Grav"
+ GRAV_OFFICIAL_THEME: "Oficiálna šablóna Grav"
+ REMOVE_PLUGIN: "Odstrániť rozšírenie"
+ INSTALL_PLUGIN: "Nainštalovať rozšírenie"
+ AVAILABLE: "Dostupné"
+ INSTALLED: "Inštalované"
+ INSTALL: "Inštalácia"
+ ACTIVE_THEME: "Aktívna Šablóna"
+ SWITCHING_TO: "Prepnúť na"
+ SWITCHING_TO_DESCRIPTION: "Prepnutím na inú šablónu nejde zaručiť, že budú podporované všetky rozloženia stránok, čo môže spôsobiť ich nedostupnosť."
+ SWITCHING_TO_CONFIRMATION: "Chcete pokračovať a prepnúť na inú šablónu"
+ CREATE_NEW_USER: "Vytvoriť nového používateľa"
+ REMOVE_USER: "Odstrániť používateľa"
+ ACCESS_DENIED: "Prístup odmietnutý"
+ ACCOUNT_NOT_ADMIN: "vaše konto nemá oprávnenia správcu"
+ PHP_INFO: "PHP Info"
+ INSTALLER: "Inštalátor"
+ AVAILABLE_THEMES: "Dostupné šablóny"
+ AVAILABLE_PLUGINS: "Dostupné rozšírenia"
+ INSTALLED_THEMES: "Inštalované šablóny"
+ INSTALLED_PLUGINS: "Nainštalované rozšírenia"
+ BROWSE_ERROR_LOGS: "Prehliadať chybové hlášky"
+ SITE: "Stránka"
+ INFO: "Info"
+ SYSTEM: "Systém"
+ USER: "Používateľ"
+ ADD_ACCOUNT: "Pridať účet"
+ SWITCH_LANGUAGE: "Prepnúť jazyk"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Úspešne povolené rozšírenie"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Úspešne zakázané rozšírenie"
+ SUCCESSFULLY_CHANGED_THEME: "Úspešne zmenená predvolená šablóna"
+ INSTALLATION_FAILED: "Inštalácia zlyhala"
+ INSTALLATION_SUCCESSFUL: "Inštalácia prebehla úspešne"
+ UNINSTALL_FAILED: "Odinštalovanie zlyhalo"
+ UNINSTALL_SUCCESSFUL: "Odinštalovanie bolo úspešné"
+ SUCCESSFULLY_SAVED: "Úspešne uložené"
+ SUCCESSFULLY_COPIED: "Úspešne skopírované"
+ REORDERING_WAS_SUCCESSFUL: "Zmena poradia bola úspešná"
+ SUCCESSFULLY_DELETED: "Úspešne odstránené"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Úspešne zmenený jazyk"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Nemáte dostatočné povolenia na vykonanie úlohy"
+ CACHE_CLEARED: "Vyrovnávacia pamäť bola vymazaná"
+ METHOD: "Metóda"
+ ERROR_CLEARING_CACHE: "Chyba pri vymazávaní vyrovnávacej pamäte"
+ AN_ERROR_OCCURRED: "Vyskytla sa chyba"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Záloha je pripravená na stiahnutie"
+ DOWNLOAD_BACKUP: "Stiahni zálohu"
+ PAGES_FILTERED: "Stránky vyfiltrované"
+ NO_PAGE_FOUND: "Žiadna stránka nenájdená"
+ INVALID_PARAMETERS: "Neplatné parametre"
+ NO_FILES_SENT: "Žiadne súbory neboli odoslané"
+ UNKNOWN_ERRORS: "Neznáme chyby"
+ UNSUPPORTED_FILE_TYPE: "Nepodporovaný typ súboru"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Nepodarilo sa presunúť nahraný súbor."
+ FILE_UPLOADED_SUCCESSFULLY: "Súbor bol úspešne nahraný"
+ FILE_DELETED: "Súbor bol vymazaný"
+ FILE_COULD_NOT_BE_DELETED: "Súbor sa nedá odstrániť"
+ FILE_NOT_FOUND: "Súbor sa nenašiel"
+ NO_FILE_FOUND: "Nenašli sa žiadne súbory"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav bol úspešne aktualizovaný na"
+ GRAV_UPDATE_FAILED: "Aktualizácia Grav zlyhala"
+ EVERYTHING_UPDATED: "Všetko aktualizované"
+ UPDATES_FAILED: "Aktualizácia zlyhala"
+ AVATAR_BY: "Avatar od"
+ LAST_BACKUP: "Posledná záloha"
+ FULL_NAME: "Celé meno"
+ USERNAME: "Meno používateľa"
+ EMAIL: "E-mail"
+ USERNAME_EMAIL: "Používateľské meno alebo email"
+ PASSWORD: "Heslo"
+ PASSWORD_CONFIRM: "Potvrdenie hesla"
+ TITLE: "Názov"
+ LANGUAGE: "Jazyk"
+ ACCOUNT: "Účet"
+ EMAIL_VALIDATION_MESSAGE: "Musí byť platná e-mailová adresa"
+ PASSWORD_VALIDATION_MESSAGE: "Heslo musí obsahovať najmenej jedno číslo a jedno veľké písmeno a malé písmeno a aspoň 8 znakov"
+ LANGUAGE_HELP: "Obľúbený jazyk"
+ MEDIA: "Médiá"
+ DEFAULTS: "Predvolené nastavenia"
+ SITE_TITLE: "Nadpis stránky"
+ SITE_TITLE_PLACEHOLDER: "Celý názov stránky"
+ SITE_TITLE_HELP: "Predvolený názov pre vaše stránky, často používaný v šablónach"
+ SITE_DEFAULT_LANG: "Predvolený jazyk"
+ DEFAULT_AUTHOR: "Predvolený autor"
+ DEFAULT_AUTHOR_HELP: "Predvolené meno autora, často používajú šablóny alebo obsah stránky"
+ DEFAULT_EMAIL: "Predvolený e-mail"
+ DEFAULT_EMAIL_HELP: "Predvolený e-mail pre odkazovanie v šablónach alebo stránkach"
+ TAXONOMY_TYPES: "Typy taxonómie"
+ TAXONOMY_TYPES_HELP: "Typy taxonómií musia byť definované tu, ak ich chcete používať stránky"
+ PAGE_SUMMARY: "Sumár stránky"
+ ENABLED: "Povolené"
+ ENABLED_HELP: "Umožňujú zhrnutie stránky (súhrn vráti rovnaký obsah ako obsah stránky)"
+ 'YES': "Áno"
+ 'NO': "Nie"
+ SUMMARY_SIZE: "Veľkosť sumáru"
+ SUMMARY_SIZE_HELP: "Počet znakov, ktoré slúžia ako sumár stránky"
+ FORMAT: "Formát"
+ FORMAT_HELP: "krátky = použitie prvého výskytu oddeľovača alebo veľkosť; dlhý = ignorovať oddeľovač"
+ SHORT: "Krátky"
+ LONG: "Dlhý"
+ DELIMITER: "Oddeľovač"
+ DELIMITER_HELP: "Oddeľovač sumáru (predvolené \"===\")"
+ METADATA: "Metadáta"
+ METADATA_HELP: "Predvolené hodnoty metaúdajov, ktoré sa zobrazia na každej stránke, pokiaľ nie sú prepísané v stránke"
+ NAME: "Meno"
+ CONTENT: "Obsah"
+ SIZE: "Veľkosť"
+ ACTION: "Akcia"
+ REDIRECTS_AND_ROUTES: "Presmerovanie & trasy"
+ CUSTOM_REDIRECTS: "Vlastné presmerovania"
+ CUSTOM_REDIRECTS_HELP: "linky na presmerovanie na iné stránky. Platí štandardné Regex nahradenie"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/váš/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/vaše/presmerovanie"
+ CUSTOM_ROUTES: "Vlastné cesty"
+ CUSTOM_ROUTES_HELP: "linky ako aliasy iných stránok. Platí štandardné Regex nahradenie"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/váš/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/vaša/cesta"
+ FILE_STREAMS: "Súbory"
+ DEFAULT: "Pôvodné"
+ PAGE_MEDIA: "Médiá stránky"
+ OPTIONS: "Možnosti"
+ PUBLISHED: "Uverejnené"
+ PUBLISHED_HELP: "Štandardne je stránka zverejnená pokiaľ nenastavíte 'published: false', 'publish_date' do budúcnosti alebo 'unpublish_date' do minulosti"
+ DATE: "Dátum"
+ DATE_HELP: "Ukazovateľ dátumu umožňuje nastaviť konkrétny dátum priradený k tejto stránke."
+ PUBLISHED_DATE: "Dátum publikovania"
+ PUBLISHED_DATE_HELP: "Môže poskytnúť dátum pre automatické uverejnenie."
+ UNPUBLISHED_DATE: "Dátum skrytia"
+ UNPUBLISHED_DATE_HELP: "Môže poskytnúť dátum pre automatické skrytie článku."
+ ROBOTS: "Roboty"
+ TAXONOMIES: "Taxonómie"
+ TAXONOMY: "Taxonómia"
+ ADVANCED: "Pokročilé"
+ SETTINGS: "Nastavenia"
+ FOLDER_NUMERIC_PREFIX: "Číselná predpona priečinku"
+ FOLDER_NUMERIC_PREFIX_HELP: "Číselná predpona umožňuje manuálne zoradenie"
+ FOLDER_NAME: "Názov priečinku"
+ FOLDER_NAME_HELP: "Názov, ktorý bude použitý ako názov priečinku v súborovom systéme"
+ PARENT: "Rodič"
+ DEFAULT_OPTION_ROOT: "-Koreň-"
+ DEFAULT_OPTION_SELECT: "-Vyberte-"
+ DISPLAY_TEMPLATE: "Šablóna zobrazenia"
+ DISPLAY_TEMPLATE_HELP: "Názov šablóny, ktorá bude použitá pre zobrazenie tejto stránky"
+ BODY_CLASSES: "CSS triedy pre telo"
+ ORDERING: "Zoradenie"
+ PAGE_ORDER: "Poradie stránok"
+ OVERRIDES: "Nahradenia"
+ MENU: "Menu"
+ MENU_HELP: "Reťazec, ktorý bude zobrazený v menu. Ak nie je nastavený, použije sa titulok."
+ SLUG: "Slug"
+ SLUG_HELP: "Slug umožňuje nastaviť časť URL pre túto stránku"
+ SLUG_VALIDATE_MESSAGE: "Slug môže obsahovať iba znaky malej abecedy, čísla a pomlčky"
+ PROCESS: "Zpracovanie stránky"
+ PROCESS_HELP: "Nastavte typ spracovanie stránky. Je možné nastaviť typ jednotlivo aj pre stránky"
+ DEFAULT_CHILD_TYPE: "Predvolený typ podstránky"
+ USE_GLOBAL: "Použi globálne"
+ ROUTABLE: "Má vlastnú URL"
+ ROUTABLE_HELP: "Určuje, či je táto stránka dostupná vlastnou URL"
+ CACHING: "Použiť cache"
+ VISIBLE: "Viditeľné"
+ VISIBLE_HELP: "Určuje, či stránka je viditeľná v navigácii."
+ DISABLED: "Vypnuté"
+ ITEMS: "Položky"
+ ORDER_BY: "Zoradiť podľa"
+ ORDER: "Poradie"
+ FOLDER: "Priečinok"
+ ASCENDING: "Vzostupne"
+ DESCENDING: "Zostupne"
+ ADD_MODULAR_CONTENT: "Pridať modulárny obsah"
+ PAGE_TITLE: "Nadpis stránky"
+ PAGE_TITLE_HELP: "Názov stránky"
+ PAGE: "Stránka"
+ MODULAR_TEMPLATE: "Modulárna šablóna"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Názov súboru"
+ PARENT_PAGE: "Nadradená stránka"
+ HOME_PAGE: "Domovská stránka"
+ HOME_PAGE_HELP: "Stránka, ktorú Grav použije ako predvolenú"
+ DEFAULT_THEME: "Predvolená šablóna"
+ DEFAULT_THEME_HELP: "Nastaviť predvolenú šablónu pre Grav (predvolená je Antimatter)"
+ TIMEZONE: "Časové pásmo"
+ TIMEZONE_HELP: "Prepísať predvolené časové pásmo servera"
+ SHORT_DATE_FORMAT: "Krátke zobrazenie formátu dátumu"
+ SHORT_DATE_FORMAT_HELP: "Nastaviť formát krátkeho dátumu, ktorý môže byť použitý v šablónach"
+ LONG_DATE_FORMAT: "Zobrazovať dlhý formát dátumu"
+ LONG_DATE_FORMAT_HELP: "Nastaviť formát dlhého dátumu, ktorý môžu byť použitý v šablónach"
+ DEFAULT_ORDERING: "Predvolené zoradenie"
+ DEFAULT_ORDERING_HELP: "Stránky budú v zozname zobrazené v tomto poradí, ak nie je poradie nahradené v rámci jednotlivých stránok"
+ DEFAULT_ORDERING_DEFAULT: "Predvolené - podľa názvu priečinka"
+ DEFAULT_ORDERING_TITLE: "Titulok - podľa titulku nastaveného v hlavičke"
+ DEFAULT_ORDERING_DATE: "Dátum - dátum poľa hlavičky"
+ DEFAULT_ORDER_DIRECTION: "Predvolený smer zoradenia"
+ DEFAULT_ORDER_DIRECTION_HELP: "Smer zoradenia v zozname stránok"
+ DEFAULT_PAGE_COUNT: "Predvolený počet stránok"
+ DEFAULT_PAGE_COUNT_HELP: "Predvolený počet zobrazených stránok v zozname"
+ DATE_BASED_PUBLISHING: "Zverejnenie podľa dátumu"
+ EVENTS: "Udalosti"
+ EVENTS_HELP: "Povoliť alebo zakázať konkrétne akcie. Vypnutie môže poškodiť funkčnosť rozšírení"
+ REDIRECT_DEFAULT_ROUTE: "Presmerovanie predvolenej cesty"
+ LANGUAGES: "Jazyky"
+ SUPPORTED: "Podporované"
+ SUPPORTED_HELP: "Čiarkou oddelený zoznam dvojznakových jazykových kódov (napríklad \"en, fr, de\")"
+ TRANSLATIONS_ENABLED: "Preklady povolené"
+ TRANSLATIONS_ENABLED_HELP: "Podpora prekladov Grav, modulov a rozšírení"
+ TRANSLATIONS_FALLBACK: "Hľadať v ostatných jazykoch"
+ TRANSLATIONS_FALLBACK_HELP: "Ak neexistuje aktívny jazyk, prejsť preklady v ostatných jazykoch"
+ ACTIVE_LANGUAGE_IN_SESSION: "Aktívny jazyk relácie"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Uložiť aktívny jazyk v relácii"
+ HTTP_HEADERS: "HTTP hlavičky"
+ EXPIRES: "Vyprší"
+ EXPIRES_HELP: "Nastaví čas vypršania v hlavičke. Hodnota je v sekundách."
+ LAST_MODIFIED: "Naposledy upravené"
+ LAST_MODIFIED_HELP: "Určuje čas poslednej úpravy v hlavičke, ktorý môže pomôcť pri optimalizácii kešovania proxy servera a prehliadača"
+ ETAG: "ETag"
+ ETAG_HELP: "Nastaví 'ETag' záznam v HTTP hlavičke, ktorý pomáha rozoznať kedy bola stránka naposledy upravená"
+ VARY_ACCEPT_ENCODING: "Vary: Accept Encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Nastaví 'Vary: Accept Encoding' záznam v HTTP hlavičke, ktorý pomáha s kešovaním na strane proxy a CDN"
+ MARKDOWN: "Markdown"
+ MARKDOWN_EXTRA: "Markdown extra"
+ MARKDOWN_EXTRA_HELP: "Povoliť štandardné nastavenie syntaxe Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ AUTO_LINE_BREAKS: "Automatické zalamovanie riadkov"
+ AUTO_LINE_BREAKS_HELP: "Zapnutie podpory pre automatické zalamovanie v markdown"
+ AUTO_URL_LINKS: "Automatické URL prepojenia"
+ AUTO_URL_LINKS_HELP: "Povoliť automatickú konverziu adries URL do HTML hypertextových prepojení"
+ ESCAPE_MARKUP: "Escapovat HTML špecifické znaky"
+ ESCAPE_MARKUP_HELP: "Automaticky escapovat tagy na ich HTML ekvivalenty"
+ CACHING_HELP: "Globálne vypnutie/zapnutie kešovania v Grav"
+ CACHE_CHECK_METHOD: "Typ kontroly kešovania"
+ CACHE_CHECK_METHOD_HELP: "Vyberte metódu, pomocou ktorej Grav môže kontrolovať, či boli súbory stránky upravené."
+ CACHE_DRIVER: "Cache driver"
+ CACHE_DRIVER_HELP: "Vyberte spôsob kešovania. 'Auto Detect' sa pokúsi nájsť optimálne nastavenie"
+ CACHE_PREFIX: "Prefix cache"
+ CACHE_PREFIX_HELP: "Identifikátor časti Grav kľúča. Neupravuje, ak si nie ste istý, čo robíte."
+ CACHE_PREFIX_PLACEHOLDER: "Odvodený od základnej adresy URL (prepíšte náhodným reťazcom)"
+ CACHE_PURGE: "Vyčistiť starú medzipamäť"
+ LIFETIME: "Životnosť"
+ LIFETIME_HELP: "Dĺžka platnosti záznamov v cache. 0 = bez obmedzenia"
+ GZIP_COMPRESSION: "Gzip kompresia"
+ GZIP_COMPRESSION_HELP: "Zapnúť GZip kompresiu stránok pre rýchlejšie načítanie."
+ TWIG_TEMPLATING: "Twig šablóny"
+ TWIG_CACHING: "Twig kešovanie"
+ TWIG_CACHING_HELP: "Nastavuje kešovanie Twig šablón. Nechajte zapnuté pre najlepší výkon."
+ TWIG_DEBUG: "Twig debug mód"
+ TWIG_DEBUG_HELP: "Umožňuje vypnúť načítanie rozšírenia Twig Debugger"
+ DETECT_CHANGES: "Detekovať zmeny"
+ DETECT_CHANGES_HELP: "Twig bude automaticky aktualizovať cache, ak nájde zmenu v šablóne Twig"
+ AUTOESCAPE_VARIABLES: "Automaticky escapovať premenné"
+ AUTOESCAPE_VARIABLES_HELP: "Autoescape všetkých premenných. Pravdepodobne dôjde k poškodeniu webu"
+ CSS_PIPELINE: "Zlúčiť CSS súbory"
+ CSS_PIPELINE_HELP: "Spojí všetky CSS súbory do jedného"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Zahrnúť externé CSS do zlúčeného CSS"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Externé URL adresy niekedy obsahujú relatívne odkazy na súbory a preto by nemali byť zlúčené"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "Ako prvé vykresliť zlúčené CSS"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Vykresliť zlúčené CSS pred akékoľvek iné, nezahrnuté CSS odkazy"
+ CSS_MINIFY: "Minifikovat CSS"
+ CSS_MINIFY_HELP: "Minifikuje CSS súbory behom zlúčenia"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "Minifikovať CSS na Windows"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Prepísať nastavenie minifikácie Css pre Windows. Štandardne vypnuté kvôli ThreadStackSize"
+ CSS_REWRITE: "Prepísať URL v rámci CSS"
+ JAVASCRIPT_PIPELINE: "Zlúčenie súborov JavaScriptu"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Zahrnúť externé JS do zlúčeného JS"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Externé URL adresy niekedy obsahujú relatívne odkazy na súbory a preto by nemali byť zlúčené"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Ako prvé vykresliť zlúčené CSS"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Vykresliť zlúčené CSS pred akékoľvek iné, nezahrnuté CSS odkazy"
+ JAVASCRIPT_MINIFY: "Minifikovať JavaScript"
+ JAVASCRIPT_MINIFY_HELP: "Minifikuje JavaScriptové súbory behom zlučovania"
+ COLLECTIONS: "Kolekcie"
+ ERROR_HANDLER: "Hlásenie chýb"
+ DISPLAY_ERRORS: "Zobrazenie chýb"
+ DISPLAY_ERRORS_HELP: "Zobraziť úplnú chybovú stránku štýlom backtrace"
+ LOG_ERRORS: "Denník chýb"
+ LOG_ERRORS_HELP: "Zaznamenať chyby do /logs priečinka"
+ DEBUGGER: "Ladiaci nástroj"
+ DEBUGGER_HELP: "Povoliť ladenie Grav a nasledovné nastavenia"
+ DEBUG_TWIG: "Ladenie Twig"
+ DEBUG_TWIG_HELP: "Povoliť ladenie Twig šablón"
+ SHUTDOWN_CLOSE_CONNECTION: "Uzatvárať spojenie"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Uzavrieť spojenie pred zavolaním udalosti 'onShutdown()'. Vypnuté pri aktívnom ladení"
+ DEFAULT_IMAGE_QUALITY: "Predvolená kvalita obrázkov"
+ DEFAULT_IMAGE_QUALITY_HELP: "Predvolená kvalita obrázku pri úpravách a kešovaní(85%)"
+ CACHE_ALL: "Kešovať všetky obrázky"
+ CACHE_ALL_HELP: "Spracovať všetky obrázky kešovacím systémom Gravu, hoci nepotrebujú žiadne úpravy"
+ IMAGES_DEBUG: "Vodoznak obrázku pre ladenie"
+ IMAGES_DEBUG_HELP: "Zobraziť vrstvu ponad obrázok znázorňujúcu hustotu pixelov v obrázku napríklad pri ladení pre retina obrazovky"
+ UPLOAD_LIMIT: "Limit pre nahrávané súbory"
+ UPLOAD_LIMIT_HELP: "Nastaviť maximálnu veľkosť nahrávaných súborov v bytoch (0 je neobmedzená)"
+ ENABLE_MEDIA_TIMESTAMP: "Povoliť časové údaje pre médiá"
+ SESSION: "Relácia"
+ TIMEOUT: "Časový limit"
+ ABSOLUTE_URLS: "Absolútne URL"
+ PARAMETER_SEPARATOR: "Oddeľovač parameterov"
+ TASK_COMPLETED: "Úloha dokončená"
+ EVERYTHING_UP_TO_DATE: "Všetko je aktuálne"
+ UPDATES_ARE_AVAILABLE: "k dispozícii sú aktualizácie"
+ IS_AVAILABLE_FOR_UPDATE: "je možné aktualizovať"
+ IS_NOW_AVAILABLE: "je teraz k dispozícii"
+ CURRENT: "Aktuálny"
+ UPDATE_GRAV_NOW: "Aktualizovať Grav teraz"
+ GRAV_SYMBOLICALLY_LINKED: "Grav je prepojený symbolicky, aktualizácia nebude k dispozícii"
+ UPDATING_PLEASE_WAIT: "Prebieha aktualizácia... prosím čakajte na stiahnutie"
+ OF_THIS: "tejto"
+ OF_YOUR: "Vašej"
+ HAVE_AN_UPDATE_AVAILABLE: "má k dispozícii novú verziu"
+ SAVE_AS: "Uložiť ako"
+ AND: "a"
+ UPDATE_AVAILABLE: "Aktualizácia k dispozícii"
+ METADATA_KEY: "Kľúč (napríklad 'Keywords')"
+ METADATA_VALUE: "Hodnota (napr. 'Blog, Grav')"
+ FULLY_UPDATED: "Úplne aktualizované"
+ SAVE_LOCATION: "Uložiť umiestnenie"
+ PAGE_FILE: "Šablóna stránky"
+ DEFAULT_DATE_FORMAT: "Formát dátumu stránky"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Automaticky odhadnúť"
+ IGNORE_FILES: "Ignorovať súbory"
+ IGNORE_FILES_HELP: "Ignorovať konkrétne súbory pri spracovaní stránky"
+ IGNORE_FOLDERS: "Ignorovať priečinky"
+ IGNORE_FOLDERS_HELP: "Ignorovať konkrétne priečinky pri spracovaní stránky"
+ HIDE_EMPTY_FOLDERS: "Skryť prázdne priečinky"
+ HTTP_ACCEPT_LANGUAGE: "Nastaviť jazyk podľa prehliadača"
+ OVERRIDE_LOCALE: "Zmeniť miestne nastavenie"
+ REDIRECT: "Stránka presmerovania"
+ PLUGIN_STATUS: "Stav rozšírenia"
+ INCLUDE_DEFAULT_LANG: "Zahrnúť predvolený jazyk"
+ ALLOW_URL_TAXONOMY_FILTERS: "URL filtre taxonómie"
+ REDIRECT_DEFAULT_CODE: "Predvolený kód presmerovania"
+ IGNORE_HIDDEN: "Ignorovať skryté"
+ IGNORE_HIDDEN_HELP: "Ignorovať všetky súbory a priečinky, ktoré sa začínajú BODKOU"
+ FALLBACK_TYPES: "Povolené typy súborov"
+ INLINE_TYPES: "Inline záložné typy"
+ APPEND_URL_EXT: "Pridať URL príponu"
+ PAGE_MODES: "Mód stránky"
+ PAGE_TYPES: "Typ stránky"
+ ACCESS_LEVELS: "Úrovne prístupu"
+ GROUPS: "Skupiny"
+ GROUPS_HELP: "Zoznam skupín, v ktorých je používateľ"
+ ADMIN_ACCESS: "Administrátorský prístup"
+ SITE_ACCESS: "Prístup na stránku"
+ INVALID_SECURITY_TOKEN: "Neplatný bezpečnostný token"
+ ACTIVATE: "Povoliť"
+ CACHE_PERMS: "Cache povolenia"
+ REMOVE_SUCCESSFUL: "Odstránenie úspešné"
+ REMOVE_FAILED: "Odstránenie sa nepodarilo"
+ TWIG_FIRST: "Prvý spracovať Twig"
+ SESSION_SECURE: "Zabezpečenie"
+ SESSION_HTTPONLY: "Iba HTTP"
+ REVERSE_PROXY: "Reverzné proxy"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Nesprávny formát frontmatter, nemôžem uložiť"
+ ADD_FOLDER: "Pridať priečinok"
+ PROXY_URL: "Adresa URL servera proxy"
+ PROXY_URL_HELP: "Zadajte proxy hostiteľa alebo IP adresu a PORT"
+ NOTHING_TO_SAVE: "Nie je čo uložiť"
+ FILE_ERROR_ADD: "Pri pokuse o pridanie súboru sa vyskytla chyba"
+ FILE_ERROR_UPLOAD: "Pri pokuse o odovzdanie súboru sa vyskytla chyba"
+ FILE_UNSUPPORTED: "Nepodporovaný typ súboru"
+ ADD_ITEM: "Pridať položku"
+ FILE_TOO_LARGE: "Súbor je príliš veľký, maximálna povolená veľkosť je %s podľa . Zvýšte prosím \"post_max_size\" v PHP nastaveniach"
+ INSTALLING: "Inštalujem"
+ LOADING: "Nahrávam.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "Najprv musia byť nasledujúce závislosti splnené:"
+ ERROR_INSTALLING_PACKAGES: "Chyba pri inštalácii balíčka(ov)"
+ INSTALLING_DEPENDENCIES: "Inštalácia závislostí..."
+ INSTALLING_PACKAGES: "Inštalácia balíčka(ov)..."
+ READY_TO_INSTALL_PACKAGES: "Pripravený na inštaláciu balíka(ov)"
+ PACKAGES_NOT_INSTALLED: "Neinštalované balíky"
+ PACKAGES_NEED_UPDATE: "Balíky, ktoré už sú nainštalované, ale sú príliš staré"
+ PACKAGES_SUGGESTED_UPDATE: "Balíky, ktoré už sú nainštalované a verzia je v poriadku, ale budú aktualizované na najnovšie"
+ REMOVE_THE: "Odstrániť %s"
+ CONFIRM_REMOVAL: "Určite chcete zmazať %s?"
+ REMOVED_SUCCESSFULLY: "%s úspešne odstránený"
+ ERROR_REMOVING_THE: "Chyba pri odstraňovaní %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "%s vyžaduje nasledujúce závislosti, ktoré nie sú potrebné ďalšie nainštalované balíky. Ak ich nepoužívate, môžete ich odstrániť priamo odtiaľto."
+ READY_TO_UPDATE_PACKAGES: "Pripravený na aktualizáciu balíka(ov)"
+ ERROR_UPDATING_PACKAGES: "Chyba pri aktualizácii balíka(ov)"
+ UPDATING_PACKAGES: "Aktualizácia balíka(ov).."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Balík(y) boli úspešne aktualizované."
+ UPDATING: "Aktualizuje sa"
+ GPM_RELEASES: "GPM verzie"
+ GPM_RELEASES_HELP: "Zvoľte 'Testovací' pre inštaláciu beta alebo testovacej verzie"
+ AUTO: "Auto"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stabilná"
+ TESTING: "Testovanie"
+ FRONTMATTER_PROCESS_TWIG: "Spracovať Twig frontmatterom"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Ak je povolené môžete použiť Twig premenné v oblasti prednej stránky"
+ FRONTMATTER_IGNORE_FIELDS: "Ignorovať polia vo frontmatteri"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Balík %s, bol úspešne nainštalovaný"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Stránka nie je viditeľná, zoraďovanie nepovolené"
+ INSERT: "Vložiť"
+ UNDO: "Vrátiť späť"
+ REDO: "Znova"
+ HEADERS: "Hlavičky"
+ BOLD: "Tučné písmo"
+ ITALIC: "Kurzíva"
+ STRIKETHROUGH: "Prečiarknutie"
+ SUMMARY_DELIMITER: "Oddeľovač sumáru"
+ LINK: "Odkaz"
+ IMAGE: "Obrázok"
+ BLOCKQUOTE: "Citát"
+ UNORDERED_LIST: "Nečíslovaný zoznam"
+ ORDERED_LIST: "Číslovaný zoznam"
+ EDITOR: "Editor"
+ PREVIEW: "Náhľad"
+ FULLSCREEN: "Celá obrazovka"
+ MODULAR: "Modulárny"
+ NON_VISIBLE: "Neviditeľné"
+ NON_PUBLISHED: "Neuverejnené"
+ MEDIA_TYPES: "Typy médií"
+ IMAGE_OPTIONS: "Možnosti obrázku"
+ MIME_TYPE: "Mime Typ"
+ THUMB: "Náhľad"
+ TYPE: "Typ"
+ FILE_EXTENSION: "Prípona súboru"
+ REDIS_SERVER: "Redis server"
+ REDIS_SERVER_HELP: "Adresa Redis serveru"
+ REDIS_PORT: "Redis port"
+ REDIS_PORT_HELP: "Port Redis servera"
+ REDIS_PASSWORD: "Heslo pre Redis"
+ ALL: "Všetko"
+ FROM: "od"
+ TO: "do"
+ RELEASE_DATE: "Dátum vydania"
+ SORT_BY: "Zoradiť podľa"
+ RESOURCE_FILTER: "Filter..."
+ FORCE_SSL: "Vynútiť SSL"
+ EXTERNAL_URL: "Externá URL"
+ DROPZONE_CANCEL_UPLOAD: 'Zrušiť nahrávanie'
+ DROPZONE_INVALID_FILE_TYPE: "Nie je možné nahrať súbory tohoto typu."
+ DROPZONE_REMOVE_FILE: "Odstrániť súbor"
+ PREMIUM_PRODUCT: "Prémium"
+ TOOLS: "Nástroje"
+ 2FA_CODE_INPUT: "000000"
+ VIEW_SITE_TIP: "Zobraziť stránku"
diff --git a/user/plugins/admin/languages/sl.yaml b/user/plugins/admin/languages/sl.yaml
new file mode 100644
index 0000000..8db81b1
--- /dev/null
+++ b/user/plugins/admin/languages/sl.yaml
@@ -0,0 +1,245 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_REPORT_ISSUE: "Ste naleteli na težavo? Prijavite jo na GitHub-u."
+ LOGIN_BTN: "Prijava"
+ LOGIN_BTN_FORGOT: "Pozabil"
+ LOGIN_BTN_RESET: "Ponastavi geslo"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Pošljite mi navodila"
+ LOGIN_BTN_CLEAR: "Pocisti"
+ LOGIN_BTN_CREATE_USER: "Ustvarjanje uporabnika"
+ LOGIN_LOGGED_IN: "Ste se uspešno prijavili v"
+ LOGIN_FAILED: "Prijava ni uspela"
+ LOGGED_OUT: "Odjavili ste se"
+ RESET_NEW_PASSWORD: "Vnesite novo geslo …"
+ RESET_LINK_EXPIRED: "Povezava za ponastavitev je potekla, poskusite znova"
+ RESET_PASSWORD_RESET: "Geslo je bilo ponastavljeno"
+ RESET_INVALID_LINK: "Neveljavna povezava za ponastavitev, poskusite znova"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Povezava za ponastavitev gesla je bila poslana na vaš e-poštni naslov"
+ FORGOT_FAILED_TO_EMAIL: "Pošiljanje e-navodil ni uspelo, poskusite znova pozneje"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Ni mogoče ponastaviti gesla za %s, email naslov ni nastavljen"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Uporabnik z uporabniškim imenom %s ne obstaja"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Ni mogoče ponastaviti gesla. Konfiguracija spletnega mesta ne omogoča pošiljanje emailov"
+ FORGOT_EMAIL_SUBJECT: "%s - Zahteva za novo geslo"
+ FORGOT_EMAIL_BODY: "Ponastavitev gesla Spoštovani %1$s,
Na %4$s je bil vložen zahtevek za ponastavitev gesla.
< br / > Kliknite, če želite ponastaviti geslo < br / > < br / >
Ali kopirajte sledeč URL v naslovno vrstico vašega brskalnika:
%2$s
< br / > lep pozdrav, < br / > < br / >%3$s
"
+ MANAGE_PAGES: "Upravljanje strani"
+ CONFIGURATION: "Nastavitve"
+ PAGES: "Strani"
+ PLUGINS: "Vtičniki"
+ PLUGIN: "Vtičnik"
+ THEMES: "Teme"
+ LOGOUT: "Odjava"
+ BACK: "Nazaj"
+ NEXT: "Naprej"
+ PREVIOUS: "Prejšnji"
+ ADD_PAGE: "Dodaj stran"
+ ADD_MODULAR: "Dodaj modular"
+ MOVE: "Premakni"
+ DELETE: "Izbriši"
+ VIEW: "Poglej"
+ SAVE: "Shrani"
+ NORMAL: "Normalno"
+ EXPERT: "Strokovnjak"
+ EXPAND_ALL: "Pokaži vse"
+ COLLAPSE_ALL: "Skrij vse"
+ ERROR: "Napaka"
+ CLOSE: "Zapri"
+ CANCEL: "Prekliči"
+ CONTINUE: "Nadaljuj"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Potrebna je potrditev"
+ MODAL_CHANGED_DETECTED_TITLE: "Zaznane so bile spremembe"
+ MODAL_CHANGED_DETECTED_DESC: "Imate neshranjene spremembe. Ali ste prepričani, da želite zapustiti brez shranjevanja?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Potrebna je potrditev"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Ali ste prepričani, da želite trajno izbrisati to datoteko? Tega dejanja ne morete preklicati."
+ ADD_FILTERS: "Dodaj filtre"
+ SEARCH_PAGES: "Iskanje strani"
+ VERSION: "Različica"
+ WAS_MADE_WITH: "Je bilo narejeno z"
+ BY: "Od"
+ UPDATE_THEME: "Posodobitev teme"
+ UPDATE_PLUGIN: "Posodobi vtičnik"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "teme je zdaj na voljo"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "tega vtičnika je zdaj na voljo"
+ AUTHOR: "Avtor"
+ HOMEPAGE: "Domača stan"
+ DEMO: "Predstavitev"
+ BUG_TRACKER: "Sledilnik hroščev"
+ KEYWORDS: "Ključne besede"
+ LICENSE: "Licenca"
+ DESCRIPTION: "Opis"
+ README: "Preberi me"
+ REMOVE_THEME: "Odstrani predlogo"
+ INSTALL_THEME: "Namesti predlogo"
+ THEME: "Predloga"
+ BACK_TO_THEMES: "Nazaj na predloge"
+ BACK_TO_PLUGINS: "Nazaj na Vtičnike"
+ CHECK_FOR_UPDATES: "Preveri posodobitve"
+ ADD: "Dodaj"
+ CLEAR_CACHE: "Počisti predpomnilnik"
+ CLEAR_CACHE_ALL_CACHE: "Celotno vsebino predpomnilnika"
+ CLEAR_CACHE_ASSETS_ONLY: "Počisti Assets predpomnilnik"
+ CLEAR_CACHE_IMAGES_ONLY: "Počisti predpomnilnik slik"
+ CLEAR_CACHE_CACHE_ONLY: "Počisti samo predpomnilnik"
+ CLEAR_CACHE_TMP_ONLY: "Samo tmp direktorij"
+ DASHBOARD: "Nadzorna plošča"
+ UPDATES_AVAILABLE: "Posodobitve so na voljo"
+ DAYS: "Dni"
+ UPDATE: "Posodobitev"
+ BACKUP: "Varnostna kopija"
+ STATISTICS: "Statistika"
+ TODAY: "Danes"
+ WEEK: "Teden"
+ MONTH: "Mesec"
+ LATEST_PAGE_UPDATES: "Najnovejše posodobitve strani"
+ MAINTENANCE: "Vzdrževanje"
+ UPDATED: "Posodobljeno"
+ MON: "Pon"
+ TUE: "Tor"
+ WED: "Sre"
+ THU: "Čet"
+ FRI: "Pet"
+ SAT: "Sob"
+ SUN: "Ned"
+ COPY: "Kopiraj"
+ EDIT: "Uredi"
+ CREATE: "Ustvari"
+ GRAV_ADMIN: "Grav Admin"
+ GRAV_OFFICIAL_PLUGIN: "Uradni Grav vtičnik"
+ GRAV_OFFICIAL_THEME: "Grav uradna tema"
+ PLUGIN_SYMBOLICALLY_LINKED: "Vtičnik je navidezno povezan. Posodobitve ne bodo zaznane."
+ THEME_SYMBOLICALLY_LINKED: "Tema je navidezno povezana. Posodobitve ne bodo zaznane"
+ REMOVE_PLUGIN: "Odstranite Vtičnik"
+ INSTALL_PLUGIN: "Namesti vtičnik"
+ AVAILABLE: "Na voljo"
+ INSTALLED: "Nameščeno"
+ INSTALL: "Namesti"
+ ACTIVE_THEME: "Aktivna tema"
+ SWITCHING_TO: "Preklopi na"
+ SWITCHING_TO_DESCRIPTION: "Preklapljam na opis."
+ SWITCHING_TO_CONFIRMATION: "Ali želite nadaljevati in preklopiti na temo"
+ CREATE_NEW_USER: "Dodaj uporabnika"
+ REMOVE_USER: "Odstrani uporabnika"
+ ACCESS_DENIED: "Dostop zavrnjen"
+ ACCOUNT_NOT_ADMIN: "vaš račun nima administracijskih dovoljenj"
+ PHP_INFO: "PHP Info"
+ INSTALLER: "Nameščevalec"
+ AVAILABLE_THEMES: "Razpoložljive teme"
+ AVAILABLE_PLUGINS: "Vtičniki na voljo"
+ INSTALLED_THEMES: "Nameščene teme"
+ INSTALLED_PLUGINS: "Nameščeni vtičniki"
+ BROWSE_ERROR_LOGS: "Poglej dnevnik napak"
+ SITE: "Spletno mesto"
+ INFO: "Informacije"
+ SYSTEM: "Sistem"
+ USER: "Uporabnik"
+ ADD_ACCOUNT: "Dodaj račun"
+ SWITCH_LANGUAGE: "Zamenjaj jezik"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Vtičnik uspešno omogočen"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Vtičnik uspešno onemogočen"
+ SUCCESSFULLY_CHANGED_THEME: "Uspešno zamenjana tema"
+ INSTALLATION_FAILED: "Namestitev ni uspela"
+ INSTALLATION_SUCCESSFUL: "Namestitev je uspela"
+ UNINSTALL_FAILED: "Odstranitev ni uspela"
+ UNINSTALL_SUCCESSFUL: "Odstranitev uspešna"
+ SUCCESSFULLY_SAVED: "Uspešno shranjeno"
+ SUCCESSFULLY_COPIED: "Uspešno kopirano"
+ REORDERING_WAS_SUCCESSFUL: "Uspešno preurejanje"
+ SUCCESSFULLY_DELETED: "Uspešno izbrisano"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Uspešno zamenjan jezik"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Nimate zadosti dovoljenj za opravilo"
+ CACHE_CLEARED: "Predpomnilnik počiščen"
+ METHOD: "Način"
+ ERROR_CLEARING_CACHE: "Napaka pri čiščenju predpomnilnika"
+ AN_ERROR_OCCURRED: "Prišlo je do napake"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Varnostna kopija je pripravljena za prenos"
+ DOWNLOAD_BACKUP: "Prenesi varnostno kopijo"
+ PAGES_FILTERED: "Filtrirane strani"
+ NO_PAGE_FOUND: "Stran ni bila najdena"
+ INVALID_PARAMETERS: "Neveljavni parametri"
+ NO_FILES_SENT: "Datoteke niso bile poslane"
+ EXCEEDED_FILESIZE_LIMIT: "Presežena omejitev velikosti datoteke v PHP konfiguraciji"
+ UNKNOWN_ERRORS: "Neznane napake"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Presežena omejitev velikosti datoteke v Grav konfiguraciji"
+ UNSUPPORTED_FILE_TYPE: "Nepodprt tip datoteke"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Spodletelo premikanje naložene datoteke"
+ FILE_UPLOADED_SUCCESSFULLY: "Datoteka uspešno naložena"
+ FILE_DELETED: "Dokument izbrisan"
+ FILE_COULD_NOT_BE_DELETED: "Datoteke ni mogoče izbrisati"
+ FILE_NOT_FOUND: "Datoteka ni bila najdena"
+ NO_FILE_FOUND: "Ni datoteke"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav je bil uspešno posodobljen na"
+ GRAV_UPDATE_FAILED: "Posodobitev Grava je spodletela"
+ EVERYTHING_UPDATED: "Vse posodobljeno"
+ UPDATES_FAILED: "Posodobitve spodletele"
+ LAST_BACKUP: "Zadnja var. kopija"
+ FULL_NAME: "Polno ime"
+ USERNAME: "Uporabniško ime"
+ EMAIL: "Email"
+ USERNAME_EMAIL: "Uporabniško ime ali elektronski naslov"
+ PASSWORD: "Geslo"
+ PASSWORD_CONFIRM: "Potrdite geslo"
+ TITLE: "Naslov"
+ LANGUAGE: "Jezik"
+ ACCOUNT: "Račun"
+ EMAIL_VALIDATION_MESSAGE: "Prosimo, vnesite veljaven elektronski naslov"
+ DEFAULTS: "Privzete vrednosti"
+ SITE_TITLE: "Ime spletu"
+ DEFAULT_AUTHOR: "Privzeti avtor"
+ DEFAULT_EMAIL: "Privzeti E-naslov"
+ ENABLED: "Omogočen"
+ 'YES': "Da"
+ 'NO': "Ne"
+ FORMAT: "Oblika"
+ SHORT: "Kratko"
+ LONG: "Dolgo"
+ DELIMITER: "Ločilo"
+ DELIMITER_HELP: "Ločilo povzetka (privzeto \"===\")"
+ METADATA: "Metapodatki"
+ METADATA_HELP: "Privzete vrednosti metapodatkov, ki bodo prikazane na vsaki strani, razen če niso \"povožene\""
+ NAME: "Ime"
+ CONTENT: "Vsebina"
+ REDIRECTS_AND_ROUTES: "Preusmeritve in poti"
+ CUSTOM_REDIRECTS: "Preusmeritve po meri"
+ CUSTOM_ROUTES: "Poti po meri"
+ DEFAULT: "Privzeto"
+ OPTIONS: "Možnosti"
+ PUBLISHED: "Objavljeno"
+ DATE: "Datum"
+ PUBLISHED_DATE: "Datum objave"
+ UNPUBLISHED_DATE: "Datum prenehanja objave"
+ ROBOTS: "Roboti"
+ TAXONOMIES: "Kategorizacije"
+ TAXONOMY: "Kategorizacija"
+ ADVANCED: "Napredno"
+ SETTINGS: "Nastavitve"
+ FOLDER_NUMERIC_PREFIX: "Številčni prefix mape"
+ FOLDER_NAME: "Ime mape"
+ PARENT: "Starš"
+ DEFAULT_OPTION_SELECT: "-Izberite-"
+ DISPLAY_TEMPLATE: "Prikaži predlogo"
+ ORDERING: "Vrstni red"
+ PAGE_ORDER: "Vrstni red strani"
+ MENU: "Meni"
+ CACHING: "Predpomnenje"
+ VISIBLE: "Vidno"
+ DISABLED: "Onemogočeno"
+ ORDER_BY: "Razvrsti po"
+ ORDER: "Vrstni red"
+ FOLDER: "Mapa"
+ ASCENDING: "Naraščajoče"
+ DESCENDING: "Padajoče"
+ ADD_MODULAR_CONTENT: "Dodaj modularno vsebino"
+ PAGE_TITLE: "Naslov strani"
+ PAGE_TITLE_HELP: "Pomoč za Naslov strani"
+ PAGE: "Stran"
+ MODULAR_TEMPLATE: "Modularna predloga"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Ime datoteke"
+ PARENT_PAGE: "Nadrejena stran"
+ HOME_PAGE: "Domača stran"
+ HOME_PAGE_HELP: "Strani, ki Grav uporabi kot privzeto ciljno stran"
+ EVENTS: "Dogodki"
+ LANGUAGES: "Jeziki"
+ SUPPORTED: "Podprto"
+ EXPIRES: "Poteče"
+ LIFETIME: "Življenjska doba"
+ GZIP_COMPRESSION: "Gzip kompresija"
diff --git a/user/plugins/admin/languages/sr.yaml b/user/plugins/admin/languages/sr.yaml
new file mode 100644
index 0000000..76faaca
--- /dev/null
+++ b/user/plugins/admin/languages/sr.yaml
@@ -0,0 +1,819 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Ово је Бета издање! Користите га у раду на сопствену одговорност..."
+ ADMIN_REPORT_ISSUE: "Пронашли сте грешку? Пријавите је на GitHub."
+ EMAIL_FOOTER: "Покреће се са Grav-ом - Модерним Flat File CMS-ом"
+ LOGIN_BTN: "Пријава"
+ LOGIN_BTN_FORGOT: "Заборавили сте"
+ LOGIN_BTN_RESET: "Ресетуј лозинку"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Пошаљите упуства за ресетовање лозинке"
+ LOGIN_BTN_CLEAR: "Очисти образац"
+ LOGIN_BTN_CREATE_USER: "Креирај корисника"
+ LOGIN_LOGGED_IN: "Успешно сте се пријавили"
+ LOGIN_FAILED: "Неуспела пријава"
+ LOGGED_OUT: "Одјавили сте се"
+ RESET_NEW_PASSWORD: "Молим вас да унесите нову лозинку …"
+ RESET_LINK_EXPIRED: "Линк за ресетовање је истекао, молим вас да покушате поново"
+ RESET_PASSWORD_RESET: "Лозинка је ресетована"
+ RESET_INVALID_LINK: "Коришћен је неисправан линк за ресетовање, молим вас да пробате поново"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Упуство за ресетовање лозинке је послат на вашу адресу е-поште"
+ FORGOT_FAILED_TO_EMAIL: "Неуспело слање упуства преко е-поште, молим вас да покушате поново"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Није могуће ресетовање лозинке за %s, није постављена адреса е-поште"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Корисник са корисничким именом %s не постоји"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Није могуће ресетовање лозинке. Овај сајт није подешен за слање е-поште"
+ FORGOT_EMAIL_SUBJECT: "%s Захтев за ресетовање лозинке"
+ FORGOT_EMAIL_BODY: "Ресетовање лозинке Поштовани %1$s,
Креиран је захтев %4$s за ресетивање ваше лозинке.
Кликни овде да би ресетовали своју лозинку
Алтернативно, копирајте следећи URL у свој веб претраживач:
%2$s
Срдачан поздрав, %3$s
"
+ MANAGE_PAGES: "Управљање Страницама"
+ CONFIGURATION: "Подешавање"
+ PAGES: "Странице"
+ PLUGINS: "Додаци"
+ PLUGIN: "Додатак"
+ THEMES: "Теме"
+ LOGOUT: "Одјава"
+ BACK: "Назад"
+ NEXT: "Следећа"
+ PREVIOUS: "Предходно"
+ ADD_PAGE: "Додај страницу"
+ ADD_MODULAR: "Додај Модуларну"
+ MOVE: "Помери"
+ DELETE: "Обриши"
+ UNSET: "Поништи подешавање"
+ VIEW: "Преглед"
+ SAVE: "Сачувај"
+ NORMAL: "Нормалан"
+ EXPERT: "Напредни"
+ EXPAND_ALL: "Прошири све"
+ COLLAPSE_ALL: "Скупи све"
+ ERROR: "Грешка"
+ CLOSE: "Затвори"
+ CANCEL: "Откажи"
+ CONTINUE: "Настави"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Потврда је обавезна"
+ MODAL_CHANGED_DETECTED_TITLE: "Откривене су измене"
+ MODAL_CHANGED_DETECTED_DESC: "Имате неснимљене измене. Да ли сте сигурни да желите да изађете без снимања измена?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Потврда је обавезна"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Да ли сте сигурни да желите да обришете овај фајл? Ову радњу није могуће опозвати."
+ ADD_FILTERS: "Додај филтере"
+ SEARCH_PAGES: "Претражи странице"
+ VERSION: "Верзија"
+ WAS_MADE_WITH: "Креирано помоћу"
+ BY: "Од стране"
+ UPDATE_THEME: "Ажурирај тему"
+ UPDATE_PLUGIN: "Ажурирај Додатак"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "ове теме је сада доступан"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "овог додатка је сада доступан"
+ AUTHOR: "Аутор"
+ HOMEPAGE: "Почетна страница"
+ DEMO: "Демо"
+ BUG_TRACKER: "Трагач за грешкама"
+ KEYWORDS: "Кључне речи"
+ LICENSE: "Лиценца"
+ DESCRIPTION: "Опис"
+ README: "Прочитај ме"
+ REMOVE_THEME: "Избриши тему"
+ INSTALL_THEME: "Инсталирај тему"
+ THEME: "Тема"
+ BACK_TO_THEMES: "Назад на Теме"
+ BACK_TO_PLUGINS: "Назад на Додатке"
+ CHECK_FOR_UPDATES: "Провери да ли има нових верзија"
+ ADD: "Додај"
+ CLEAR_CACHE: "Очисти кеш"
+ CLEAR_CACHE_ALL_CACHE: "Сав кеш"
+ CLEAR_CACHE_ASSETS_ONLY: "Само Додаци"
+ CLEAR_CACHE_IMAGES_ONLY: "Само Слике"
+ CLEAR_CACHE_CACHE_ONLY: "Само Кеш"
+ CLEAR_CACHE_TMP_ONLY: "Само Tmp"
+ DASHBOARD: "Командна табла"
+ UPDATES_AVAILABLE: "Доступно ажурирање"
+ DAYS: "Дани"
+ UPDATE: "Ажурирање"
+ BACKUP: "Резервна копија"
+ BACKUPS: "Резервне копије"
+ BACKUP_NOW: "Направи резервну копију одмах"
+ BACKUPS_STATS: "Статистика резервне копије"
+ BACKUPS_HISTORY: "Историја резервне копије"
+ BACKUPS_PURGE_CONFIG: "Подешавање чишћења резервне копије"
+ BACKUPS_PROFILES: "Профили резервне копије"
+ BACKUPS_COUNT: "Број резервних копија"
+ BACKUPS_PROFILES_COUNT: "Број профила"
+ BACKUPS_TOTAL_SIZE: "Заузеће простора"
+ BACKUPS_NEWEST: "Најновија резервна копија"
+ BACKUPS_OLDEST: "Најстарија резервна копија"
+ BACKUPS_PURGE: "Уклонити"
+ BACKUPS_NOT_GENERATED: "Још увел нема креираних резервних копија..."
+ BACKUPS_PURGE_NUMBER: "Корисити %s од %s слотова за резервне копије"
+ BACKUPS_PURGE_TIME: "остало је %s дана за резевне копије"
+ BACKUPS_PURGE_SPACE: "Корисити %s од %s"
+ BACKUP_DELETED: "Резервна копија је успешно обрисана"
+ BACKUP_NOT_FOUND: "Резервна копија није пронађена"
+ BACKUP_DATE: "Датум резервне копије"
+ STATISTICS: "Статистика"
+ TODAY: "Данас"
+ WEEK: "Седмица"
+ MONTH: "Месец"
+ LATEST_PAGE_UPDATES: "Најновије ажуриране странице"
+ MAINTENANCE: "Одржавање"
+ UPDATED: "Ажрурирано"
+ MON: "Пон"
+ TUE: "Уто"
+ WED: "Сре"
+ THU: "Чет"
+ FRI: "Пет"
+ SAT: "Суб"
+ SUN: "Нед"
+ COPY: "Копирај"
+ EDIT: "Измени"
+ CREATE: "Креирај"
+ GRAV_ADMIN: "Грав Админ"
+ GRAV_OFFICIAL_PLUGIN: "Грав Званични додатак"
+ GRAV_OFFICIAL_THEME: "Грав Званична тема"
+ PLUGIN_SYMBOLICALLY_LINKED: "Овај додатак је симболично линкован. Ажурирања неће бити откривена."
+ THEME_SYMBOLICALLY_LINKED: "Ова тема је симболично линкована. Ажурирања неће бити откривена"
+ REMOVE_PLUGIN: "Уклони Додатак"
+ INSTALL_PLUGIN: "Инсталирај Додатак"
+ AVAILABLE: "Доступно"
+ INSTALLED: "Инсталирано"
+ INSTALL: "Инсталирај"
+ ACTIVE_THEME: "Активна тема"
+ SWITCHING_TO: "Прелазим на"
+ SWITCHING_TO_DESCRIPTION: "Преласком на другу тему, нема гаранције да су сви распореди страница подржани, и постоји могућа појаве грешака приликом покушаја учитавања поменутих страница."
+ SWITCHING_TO_CONFIRMATION: "Да ли желите да наставите и пређете на тему"
+ CREATE_NEW_USER: "Креирај новог Корисника"
+ REMOVE_USER: "Уклони Корисника"
+ ACCESS_DENIED: "Приступ је забрањен"
+ ACCOUNT_NOT_ADMIN: "ваш налог нема администраторска овлашћења"
+ PHP_INFO: "PHP инфо"
+ INSTALLER: "Инсталатер"
+ AVAILABLE_THEMES: "Доступне теме"
+ AVAILABLE_PLUGINS: "Доступни Додаци"
+ INSTALLED_THEMES: "Инсталиране теме"
+ INSTALLED_PLUGINS: "Инсталирани Додаци"
+ BROWSE_ERROR_LOGS: "Претраживање Евиденције о грешкама"
+ SITE: "Сајт"
+ INFO: "Инфо"
+ SYSTEM: "Систем"
+ USER: "Корисник"
+ ADD_ACCOUNT: "Додај Налог"
+ SWITCH_LANGUAGE: "Промени језик"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Успешно активиран додатак"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Успешно деактивиран додатак"
+ SUCCESSFULLY_CHANGED_THEME: "Успешно промењена подразумевана тема"
+ INSTALLATION_FAILED: "Инсталација није успела"
+ INSTALLATION_SUCCESSFUL: "Инстлација је успела"
+ UNINSTALL_FAILED: "Деинсталација није успела"
+ UNINSTALL_SUCCESSFUL: "Деинсталација је успела"
+ SUCCESSFULLY_SAVED: "Успешно сачувано"
+ SUCCESSFULLY_COPIED: "Успешно копирано"
+ REORDERING_WAS_SUCCESSFUL: "Преуређивање је успело"
+ SUCCESSFULLY_DELETED: "Брисање је успело"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Промена језика је успела"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Немате довољно овлашћења за извршење задатка"
+ CACHE_CLEARED: "Кеш очишћен"
+ METHOD: "Метод"
+ ERROR_CLEARING_CACHE: "Грешка при брисању кеша"
+ AN_ERROR_OCCURRED: "Појавила се грешка"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Ваша резервна копија је спремна за преузимање"
+ DOWNLOAD_BACKUP: "Преузми резервну копију"
+ PAGES_FILTERED: "Странице су филтриране"
+ NO_PAGE_FOUND: "Страница није пронађена"
+ INVALID_PARAMETERS: "Неодоговарајући параметри"
+ NO_FILES_SENT: "Фајлови нису послани"
+ EXCEEDED_FILESIZE_LIMIT: "Премашен је лимит за величину фајла у PHP конфигурацији"
+ EXCEEDED_POSTMAX_LIMIT: "Прекорачена вредност PHP конфигурисаног post_max_size"
+ UNKNOWN_ERRORS: "Непознате грешке"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Премашен је лимит за величину фајла у Грав конфигурацији"
+ UNSUPPORTED_FILE_TYPE: "Неподржани тип фајла"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Неуспело премештање учитаног фајла"
+ FILE_UPLOADED_SUCCESSFULLY: "Фајл је успешно учитан"
+ FILE_DELETED: "Фајл је обрисан"
+ FILE_COULD_NOT_BE_DELETED: "Фајл није могао бити обрисан"
+ FILE_NOT_FOUND: "Фајл није пронађен"
+ NO_FILE_FOUND: "Нема пронађеног фајла"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Грав је успешно ажуриран на"
+ GRAV_UPDATE_FAILED: "Ажурирање Грава није успело"
+ EVERYTHING_UPDATED: "Све је ажурирано"
+ UPDATES_FAILED: "Ажурирања неуспела"
+ AVATAR_BY: "Аватар поставио"
+ AVATAR_UPLOAD_OWN: "Или учитајте лично ваш..."
+ LAST_BACKUP: "Последња резервна копија"
+ FULL_NAME: "Пуно име"
+ USERNAME: "Корисничко име"
+ EMAIL: "Адреса Е-поште"
+ USERNAME_EMAIL: "Корисничко име или адреса Е-поште"
+ PASSWORD: "Лозинка"
+ PASSWORD_CONFIRM: "Потврди лозинку"
+ TITLE: "Наслов"
+ LANGUAGE: "Језик"
+ ACCOUNT: "Налог"
+ EMAIL_VALIDATION_MESSAGE: "Мора бити исправна адреса е-поште"
+ PASSWORD_VALIDATION_MESSAGE: "Лозинка мора садржати бар један број и по једно мало и велико слово, а заједно најмање 8 или више карактера"
+ LANGUAGE_HELP: "Подесите омиљени језик"
+ MEDIA: "Медији"
+ DEFAULTS: "Подразумевано"
+ SITE_TITLE: "Наслов сајта"
+ SITE_TITLE_PLACEHOLDER: "Наслов целог сајта"
+ SITE_TITLE_HELP: "Подразумевани наслов на вашем сајту, често коришћен у темама"
+ SITE_DEFAULT_LANG: "Подразумевани језик"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Подразумевани језик који ће бити коришћен у тагу теме"
+ SITE_DEFAULT_LANG_HELP: "Подразумевани језик који ће се користити у таговима теме"
+ DEFAULT_AUTHOR: "Подразумевани Аутор"
+ DEFAULT_AUTHOR_HELP: "Подразумевано име аутора, често коришћено у темама или садржају страница"
+ DEFAULT_EMAIL: "Подразумевана адреса е-поште"
+ DEFAULT_EMAIL_HELP: "Подразумевана адреса е-поште за повезивање у темама или страницама"
+ TAXONOMY_TYPES: "Типови Таксономије"
+ TAXONOMY_TYPES_HELP: "Типови Таксономије морају бити дефинисани овде уколико желите да их користите на страницама"
+ PAGE_SUMMARY: "Сажетак Странице"
+ ENABLED: "Омогућено"
+ ENABLED_HELP: "Омогући сажетак странице (сажетак одражава исти садржај као и страница)"
+ 'YES': "Да"
+ 'NO': "Не"
+ SUMMARY_SIZE: "Величина сажетка"
+ SUMMARY_SIZE_HELP: "Број карактера на страници који ће се користити као сажетак садржаја"
+ FORMAT: "Формат"
+ FORMAT_HELP: "кратко = користи прву појаву граничника или величину; дугачко = граничник сажетка ће бити игнорисан"
+ SHORT: "Кратко"
+ LONG: "Дугачко"
+ DELIMITER: "Граничник"
+ DELIMITER_HELP: "Граничник Сажетка (подразумевано '===')"
+ METADATA: "Мета подаци"
+ METADATA_HELP: "Подразумеване вредности мета података ће бити приказане на свакој страници осим уколико су заобиђене од странице"
+ NAME: "Име"
+ CONTENT: "Садржај"
+ SIZE: "Величина"
+ ACTION: "Акција"
+ REDIRECTS_AND_ROUTES: "Преусмеравања и Путање"
+ CUSTOM_REDIRECTS: "Прилагођена преусмеравања"
+ CUSTOM_REDIRECTS_HELP: "путање за преусмеравање на друге странице. Стандардна Regex замена је исправна"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/ваш/надимак"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/ваше/преусмеравање"
+ CUSTOM_ROUTES: "Прилагођене путање"
+ CUSTOM_ROUTES_HELP: "путање за преусмеравање на друге странице. Стандардна Regex замена је исправна"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/ваш/надимак"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/ваша/путања"
+ FILE_STREAMS: "Токови фајлова"
+ DEFAULT: "Подразумевано"
+ PAGE_MEDIA: "Медији странице"
+ OPTIONS: "Опције"
+ PUBLISHED: "Објављено"
+ PUBLISHED_HELP: "Подразумевано, страница је објављена осим уколико експлицитно означите published: false или преко publish_date одредите време у будућности, односно са unpublish_date у прошлости"
+ DATE: "Датум"
+ DATE_HELP: "Променљива датум вам омогућава да одредите датум везан за ову страницу."
+ PUBLISHED_DATE: "Датум објављивања"
+ PUBLISHED_DATE_HELP: "Омогућава датум за аутоматско објављивање."
+ UNPUBLISHED_DATE: "Датум за поништење објављивања"
+ UNPUBLISHED_DATE_HELP: "Омогућава датум за аутоматско поништење објављивања."
+ ROBOTS: "Роботи"
+ TAXONOMIES: "Таксономије"
+ TAXONOMY: "Таксономија"
+ ADVANCED: "Напредно"
+ SETTINGS: "Подешавања"
+ FOLDER_NUMERIC_PREFIX: "Нумерички префикс фолдера"
+ FOLDER_NUMERIC_PREFIX_HELP: "Нумерички префикс који омогућава ручно сортирање и утицај на видљивост"
+ FOLDER_NAME: "Име фолдера"
+ FOLDER_NAME_HELP: "Име фолдера које ће бити сачувано у фајл систему за ову страницу"
+ PARENT: "Матична"
+ DEFAULT_OPTION_ROOT: "- Корен -"
+ DEFAULT_OPTION_SELECT: "- Избор -"
+ DISPLAY_TEMPLATE: "Приказ шаблона"
+ DISPLAY_TEMPLATE_HELP: "Тип странице који дефинише који Твиг шаблог креира страницу"
+ BODY_CLASSES: "Body класе"
+ ORDERING: "Редослед"
+ PAGE_ORDER: "Редослед странице"
+ OVERRIDES: "Заобилажења"
+ MENU: "Изборник-мени"
+ MENU_HELP: "Стринг који ће се користити у менију. Уколико није подешен, Наслов ће бити коришћен."
+ SLUG: "Алиас"
+ SLUG_HELP: "Променљива Алиас омогућава да посебно дефинише део URLa који одговара страници"
+ SLUG_VALIDATE_MESSAGE: "Алиас мора да садржи само мале алфанумеричке карактере и цртице"
+ PROCESS: "Процес"
+ PROCESS_HELP: "Контролише како се процесира страница. Може се подесити по страници уместо глобално"
+ DEFAULT_CHILD_TYPE: "Подразумевани тип подстранице"
+ USE_GLOBAL: "Користи глобално"
+ ROUTABLE: "Могућност усмеравања"
+ ROUTABLE_HELP: "Уколико је ова страница доступна преко URL-a"
+ CACHING: "Кеширање"
+ VISIBLE: "Видљиво"
+ VISIBLE_HELP: "Одређује да ли је страница видљива у Изборнику-менију."
+ DISABLED: "Искључено"
+ ITEMS: "Ставке"
+ ORDER_BY: "Сортирано по"
+ ORDER: "Сортирање"
+ FOLDER: "Фолдер"
+ ASCENDING: "Растући"
+ DESCENDING: "Опадајући"
+ ADD_MODULAR_CONTENT: "Додај Модуларни садржај"
+ PAGE_TITLE: "Наслов странице"
+ PAGE_TITLE_HELP: "Наслов странице"
+ PAGE: "Страница"
+ MODULAR_TEMPLATE: "Модуларни Шаблон"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Име фајла"
+ PARENT_PAGE: "Матична страница"
+ HOME_PAGE: "Основна страница"
+ HOME_PAGE_HELP: "Страница коју Грав користи као подразумевану почетну страницу"
+ DEFAULT_THEME: "Подразумевана тема"
+ DEFAULT_THEME_HELP: "Поставља подразумевану тему коју Грав користи (подразумевана је Antimatter)"
+ TIMEZONE: "Временска зона"
+ TIMEZONE_HELP: "Заобилази подразумевану временску зону на серверу"
+ SHORT_DATE_FORMAT: "Скраћени формат приказа датума"
+ SHORT_DATE_FORMAT_HELP: "Поставља скраћени формат датума који користи тема"
+ LONG_DATE_FORMAT: "Приказ дугачког формата датума"
+ LONG_DATE_FORMAT_HELP: "Подешава дугачки формат датума који могу користити теме"
+ DEFAULT_ORDERING: "Подразумевано сортирање"
+ DEFAULT_ORDERING_HELP: "Странице на листи ће бити приказане корићењем овог сортирања осим уколико се не заобиђе"
+ DEFAULT_ORDERING_DEFAULT: "Подразумевано - засновано на имену фолдера"
+ DEFAULT_ORDERING_FOLDER: "Фолдер - заснован на имену фолдера без префикса"
+ DEFAULT_ORDERING_TITLE: "Наслов - заснован на пољу наслова у заглављу"
+ DEFAULT_ORDERING_DATE: "Датум - заснован на пољу датума у заглављу"
+ DEFAULT_ORDER_DIRECTION: "Подразумевани правац редоследа"
+ DEFAULT_ORDER_DIRECTION_HELP: "Редослед страница на листи"
+ DEFAULT_PAGE_COUNT: "Подразумевани број страница"
+ DEFAULT_PAGE_COUNT_HELP: "Подразумевани максимални број страница на листи"
+ DATE_BASED_PUBLISHING: "Објављивање базирано на датуму"
+ DATE_BASED_PUBLISHING_HELP: "Аутоматско (не)објављивање постова базирано на њиховом датуму"
+ EVENTS: "Догађаји"
+ EVENTS_HELP: "Омогући или онемогући одређене догађаје. Онемогућавањем можете покварити додатке"
+ REDIRECT_DEFAULT_ROUTE: "Преусмеравање подразумеване путање"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Аутоматско преусмеравање подразумеване путање странице"
+ LANGUAGES: "Језици"
+ SUPPORTED: "Подржани"
+ SUPPORTED_HELP: "Зарезом раздвојена листа двословних ознака језика (на пример 'en,fr,de')"
+ TRANSLATIONS_ENABLED: "Преводи су омогућени"
+ TRANSLATIONS_ENABLED_HELP: "Подржава преводе у Граву, додацима и проширењима"
+ TRANSLATIONS_FALLBACK: "Резервна варијанта превода"
+ TRANSLATIONS_FALLBACK_HELP: "Прелазак на резервну варијјанту превода уколико активни језик не постоји"
+ ACTIVE_LANGUAGE_IN_SESSION: "Активни језик у сесији"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Сачувај активни језик у сесији"
+ HTTP_HEADERS: "HTTP заглавља"
+ EXPIRES: "Истичу"
+ EXPIRES_HELP: "Подешава истицање заглавља. Вредност је у секундама."
+ CACHE_CONTROL: "HTTP контрола кеша"
+ CACHE_CONTROL_HELP: "Подешено на исправну вредност контроле кеширања као што су `no-cache, no-store, must-revalidate`"
+ LAST_MODIFIED: "Последња измена"
+ LAST_MODIFIED_HELP: "Подешава последње измењено заглавље које може помоћи оптимизацију проксија и кеширање веб претраживача"
+ ETAG: "ETag"
+ ETAG_HELP: "Подешава etag заглавље што помаже идентификацији измене странице"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Подешава `Vary: Accept Encoding` заглавље да би помогло са проксијем и CDN кеширањем"
+ MARKDOWN: "Markdown"
+ MARKDOWN_EXTRA: "Додатни mаrkdown"
+ MARKDOWN_EXTRA_HELP: "Омогућава подразумевану подршку за додатни Markdown - https://michelf.ca/projects/php-markdown/extra/"
+ MARKDOWN_EXTRA_ESCAPE_FENCES: "Искључује HTML елементe у додатним markdown оградама"
+ MARKDOWN_EXTRA_ESCAPE_FENCES_HELP: "Искључује HTML елементe у додатним markdown оградама"
+ AUTO_LINE_BREAKS: "Аутоматски прелом редова"
+ AUTO_LINE_BREAKS_HELP: "Омогућава подршку за аутоматско преламање редова у markdown-у"
+ AUTO_URL_LINKS: "Аутоматски URL линкови"
+ AUTO_URL_LINKS_HELP: "Омогућава аутоматско претварање URL-ова у HTML хипер линкове"
+ ESCAPE_MARKUP: "Искључује markup"
+ ESCAPE_MARKUP_HELP: "Избегавај markup тагове у HTML објектима"
+ CACHING_HELP: "Глобални ON/OFF прекидач за активирање/деактивирање кеширање Грава"
+ CACHE_CHECK_METHOD: "Метода за проверу кеша"
+ CACHE_CHECK_METHOD_HELP: "Изаберите методу коју користи Грав за проверу да ли су фајлови са страницама промењени."
+ CACHE_DRIVER: "Драјвер за кеш"
+ CACHE_DRIVER_HELP: "Изаберите који кеш драјвер Грав треа да користи. 'Ауто детекција' покушава да пронађе најбољи за вас"
+ CACHE_PREFIX: "Префикс кеша"
+ CACHE_PREFIX_HELP: "Идентификатор за део Грав кључа. Не мењајте уколико не знате шта радите."
+ CACHE_PREFIX_PLACEHOLDER: "Изведено из основног URLa (заобилази се уношењем насумичног стринга)"
+ CACHE_PURGE_JOB: "Покрени заказани Програм за уклањање"
+ CACHE_PURGE_JOB_HELP: "За Распоредом можете периодично чистити старе Doctrine фајл кеш фолдере са овим програмом"
+ CACHE_CLEAR_JOB: "Покрени заказани Програм за чишћење"
+ CACHE_CLEAR_JOB_HELP: "Са Распоредом можете повремено да чистите Grav-ов кеш"
+ CACHE_JOB_TYPE: "Врста посла кеш"
+ CACHE_JOB_TYPE_HELP: "Избришите кеш фолдера користећи 'стандардно' брисање, или користећи 'све' фолдере"
+ CACHE_PURGE: "Уклони стари кеш"
+ LIFETIME: "Време трајања"
+ LIFETIME_HELP: "Поставља време трајања кеша у секундама. 0 = бесконачно"
+ GZIP_COMPRESSION: "Gzip компресија"
+ GZIP_COMPRESSION_HELP: "Омогућава GZip компресију Грав страница за побољшање перформанси."
+ TWIG_TEMPLATING: "Твиг шаблони"
+ TWIG_CACHING: "Твиг кеширање"
+ TWIG_CACHING_HELP: "Контролише Твигов механизам за кеширање. Оставите аквитирано ради бољих перформанси."
+ TWIG_DEBUG: "Твиг дебаговање"
+ TWIG_DEBUG_HELP: "Омогућава да се не учитава Твигова екстезија за дебаговање"
+ DETECT_CHANGES: "Откриј измене"
+ DETECT_CHANGES_HELP: "Твиг ће аутоматски рекомпајлирати Твиг кеш уколико открије промене у Твиг шаблонима"
+ AUTOESCAPE_VARIABLES: "Аутоматско избегавање променљивих"
+ AUTOESCAPE_VARIABLES_HELP: "Аутоматско заобилажење свих променљивих. Ово ће вероватно покварити ваш сајт"
+ ASSETS: "Средства"
+ CSS_PIPELINE: "CSS цевовод"
+ CSS_PIPELINE_HELP: "CSS цевовод је сједињавање вишеструких CSS извора у један фајл"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Прикључи спољне изворе у CSS цевовод"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Спољни линкови понекада имају релативне везе ка фајловима и не би требали да се сједињују"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "Прво се рендерује CSS цевовод"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Рендеровање CSS цевовода пре било ког другог CSS извора који није укључен"
+ CSS_MINIFY: "CSS сажимање"
+ CSS_MINIFY_HELP: "Сажимање CSSa током сједињавања у цевовод"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "CSS сажимање Windows Заобилажење"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Заобилажење сажимања за Windows платофме. False је подразумевано услед ThreadStackSize"
+ CSS_REWRITE: "CSS преписивање"
+ CSS_REWRITE_HELP: "Преписује било који релативни CSS URL током сједињавања у цевовод"
+ JAVASCRIPT_PIPELINE: "JavaScript цевовод"
+ JAVASCRIPT_PIPELINE_HELP: "JS цевовод је сједињавање вишеструких JS извора у један фајл"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Укључује спољне изворе у JS цевовод"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Спољни линкови понекада имају релативне везе ка фајловима и не би требали да буди сједињени у цевовод"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Прво рендеровање JS цевовода"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Рендеровање JS цевовода пре било ког другог JS извора који није укључен"
+ JAVASCRIPT_MINIFY: "JavaSript сажимање"
+ JAVASCRIPT_MINIFY_HELP: "Сажимање JS током сједињавања у цевовод"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Омогућава временске оснаке на средствима"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Омогући временске ознаке на средствима"
+ COLLECTIONS: "Колекције"
+ ERROR_HANDLER: "Управљач грешака"
+ DISPLAY_ERRORS: "Приказ грешака"
+ DISPLAY_ERRORS_HELP: "Прикажи комплетну страницу о грешци"
+ LOG_ERRORS: "Дневник о грешкама"
+ LOG_ERRORS_HELP: "Региструје грешке у /logs фолдер"
+ LOG_HANDLER: "Управљач дневицима"
+ LOG_HANDLER_HELP: "Где послати дневнике"
+ SYSLOG_FACILITY: "Опције за Syslog"
+ SYSLOG_FACILITY_HELP: "Опције за излаз Syslog-а"
+ DEBUGGER: "Debugger"
+ DEBUGGER_HELP: "Активира Грав debugger и следећа подешавања"
+ DEBUG_TWIG: "Debug Twig"
+ DEBUG_TWIG_HELP: "Активира откривање грешака у Twig шаблонима"
+ SHUTDOWN_CLOSE_CONNECTION: "Искључи отворене везе"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "Затвара везу пре позивања onShutdown(). false за debugging"
+ DEFAULT_IMAGE_QUALITY: "Подразумевани квалитет слика"
+ DEFAULT_IMAGE_QUALITY_HELP: "Подразумевани квалитет слика који ће бити коришћен током кеширања или промене величине (85%)"
+ CACHE_ALL: "Кеширај све слике"
+ CACHE_ALL_HELP: "Провуци све слике кроз Грав систем за кеширање чак иако нема промена на њима"
+ IMAGES_DEBUG: "Image debug watermark"
+ IMAGES_DEBUG_HELP: "Показује преклапање преко слика које приказују густину пиксела када ради са, на пример, ретина екранима"
+ IMAGES_SEOFRIENDLY: "SEO-оптимизована имена слика"
+ IMAGES_SEOFRIENDLY_HELP: "Када је активирано, прво је приказано име слике а затим и мањи hash који указује на предузете операције"
+ UPLOAD_LIMIT: "Лимит за величину учитаног фајла"
+ UPLOAD_LIMIT_HELP: "Подешавање максималне величине фајла у бајтовима (0 да нема ограничења)"
+ ENABLE_MEDIA_TIMESTAMP: "Омогући временске ознаке на медијима"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Додаје временску ознаку засновану на датуму последње измене за сваки појединачни медијски фајл"
+ SESSION: "Сесија"
+ SESSION_ENABLED_HELP: "Омогућава подршку за сесије у Граву"
+ TIMEOUT: "Време трајања"
+ TIMEOUT_HELP: "Подешава време трајања сесије у секундама"
+ SESSION_NAME_HELP: "Идентификатор који се користи за креирање имена за колачић сесије"
+ SESSION_UNIQUENESS: "Јединствени стринг"
+ SESSION_UNIQUENESS_HELP: "MD5 hash основне Grav's путање, нпр. `GRAV_ROOT` (default) или базиран на случајном `security.salt` стрингу."
+ ABSOLUTE_URLS: "Апсолутни URL-ови"
+ ABSOLUTE_URLS_HELP: "Апсолутни или релативни URL-ови за `base_url`"
+ PARAMETER_SEPARATOR: "Сепаратор параметара"
+ PARAMETER_SEPARATOR_HELP: "Сепаратор за прослеђене параметре који може бити промењен за Apache на Windows-у"
+ TASK_COMPLETED: "Задатак извршен"
+ EVERYTHING_UP_TO_DATE: "Све је ажурирано"
+ UPDATES_ARE_AVAILABLE: "доступно је ажурирање(а)"
+ IS_AVAILABLE_FOR_UPDATE: "је доступно за ажурирање"
+ IS_NOW_AVAILABLE: "је сада доступно"
+ CURRENT: "Важећи"
+ UPDATE_GRAV_NOW: "Ажурирај Грав сада"
+ GRAV_SYMBOLICALLY_LINKED: "Грав је симболично ликован. Ажурирање неће бити доступно"
+ UPDATING_PLEASE_WAIT: "Ажурирам... молим вас сачекајте, преузимање у току"
+ OF_THIS: "од овогa"
+ OF_YOUR: "од твога"
+ HAVE_AN_UPDATE_AVAILABLE: "има доступно ажурирање"
+ SAVE_AS: "Сачувај као"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Да ли сте сигурни да желите да обришете ову страницу и све њене подстранице? Уколико је страница преведена на друге језике, ти преводи ће бити сачувани и морају се посебно обрисати. Иначе би фолдер странице био обрисан заједно са припадајућим подстраницама. Ова акција не може бити поништена."
+ AND: "и"
+ UPDATE_AVAILABLE: "Ажурирање је доступно"
+ METADATA_KEY: "Кључ (нпр. 'Кључне речи')"
+ METADATA_VALUE: "Вредност (нпр. 'Блог, Грав')"
+ USERNAME_HELP: "Корисничко име би требало да има између 3 и 16 карактера, укључујући мала слова, бројеве, подцртицу и цртицу. Велика слова, размаци и специјални карактери нису дозвољени"
+ FULLY_UPDATED: "Потпуно ажурирано"
+ SAVE_LOCATION: "Сачувај локацију"
+ PAGE_FILE: "Шаблон странице"
+ PAGE_FILE_HELP: "Име фајла са шаблоном странице, и подразумевани шаблон приказа за ову страницу"
+ NO_USER_ACCOUNTS: "Нема пронађених корисничких налога, молим вас да креирате први..."
+ NO_USER_EXISTS: "Ниједан локални корисник постоји за овај налог, не може да сачува..."
+ REDIRECT_TRAILING_SLASH: "Коса црта / за преусмеравање"
+ REDIRECT_TRAILING_SLASH_HELP: "Изводи 301 преусмеравање уместо транспаретног руковања са косим цртама у URI-јима."
+ DEFAULT_DATE_FORMAT: "Формат датума на страницама"
+ DEFAULT_DATE_FORMAT_HELP: "Формат датума на страницама који користи Грав. Подразумевано, Грав покушава да погоди ваш формат датума, међутим ви можете одредити формат коришћењен PHP синтаксе за датуме (нпр.: Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Одреди аутоматски"
+ IGNORE_FILES: "Игонориши фајлове"
+ IGNORE_FILES_HELP: "Одређени фајлови који се не узимају у обзир приликом процесирање страница"
+ IGNORE_FOLDERS: "Игнориши фолдере"
+ IGNORE_FOLDERS_HELP: "Одређени фолдери који се не узимају у обзир приликом процесирања страница"
+ HIDE_EMPTY_FOLDERS: "Сакриј празне фолдере"
+ HIDE_EMPTY_FOLDERS_HELP: "Ако фолдер нема .md фајл, она треба бити скривена у навигацији и не може се прећи на њу"
+ HTTP_ACCEPT_LANGUAGE: "Подеси језик из веб претраживача"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Можете изабрати да покушате да подесите језик базиран на `http_accept_language` тагу у заглављу из веб претраживача"
+ OVERRIDE_LOCALE: "Заобиђи локалне поставке"
+ OVERRIDE_LOCALE_HELP: "Заобилазе се локалне поставке у PHP-у засноване на тренутном језику"
+ REDIRECT: "Преусмеравање Странице"
+ REDIRECT_HELP: "Унесите путању странице или спољни URL за ову страницу да бу је преусмерили на нпр. `/нека/путања` или `http://некисајт.срб`"
+ PLUGIN_STATUS: "Статус Додатка"
+ INCLUDE_DEFAULT_LANG: "Укључи подразумевани језик"
+ INCLUDE_DEFAULT_LANG_HELP: "Ово ће припремити све URL адресе у подразумеванoм језику са подразумеваним језиком нпр. `/sr/blog/moj-post`"
+ PAGES_FALLBACK_ONLY: "Само fallback"
+ PAGES_FALLBACK_ONLY_HELP: "Само 'fallback' за проналажење садржаја странице преко подржаних језика, подразумевано понашање је приказивање било којег пронађеног језика ако недостаје активни језик"
+ ALLOW_URL_TAXONOMY_FILTERS: "Филтери за URL таксономије"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Колекција заснована на страници омогућава да филрирате преко `/taxonomy:value`."
+ REDIRECT_DEFAULT_CODE: "Подразумевани код за преусмеравање"
+ REDIRECT_DEFAULT_CODE_HELP: "HTTP статусни код који се користи за преусмеравање"
+ IGNORE_HIDDEN: "Игнориши скривено"
+ IGNORE_HIDDEN_HELP: "Игнориши све фајлове и фолдере који почињу са ТАЧКОМ"
+ WRAPPED_SITE: "Сајт унутар друге платформе"
+ WRAPPED_SITE_HELP: "Да би теме/додаци знали да ли Грав ради унутар друге платформе"
+ FALLBACK_TYPES: "Дозвољени fallback типови"
+ FALLBACK_TYPES_HELP: "Дозвољени типови фајлова који се могу наћи приликом приступа преко Путање странице. Подразумевано за било који подржани тип медија."
+ INLINE_TYPES: "Типови фајлова за директан приказ"
+ INLINE_TYPES_HELP: "Листа типова фајлова који би требали да буду директно приказани уместо да се преузимају"
+ APPEND_URL_EXT: "Додај URL екстензије"
+ APPEND_URL_EXT_HELP: "Додаће прилагођену екстезију URL-у странице. Треба напоменути да ово значи да ће Грав потражити `..twig` шаблон"
+ PAGE_MODES: "Модови Страница"
+ PAGE_TYPES: "Типови страница"
+ PAGE_TYPES_HELP: "Одређује типове страница које Грав подржава, а редослед одређује на који тип ће се вратити ако је захтев двосмислен"
+ ACCESS_LEVELS: "Нивои приступа"
+ GROUPS: "Групе"
+ GROUPS_HELP: "Листа група у којима је корисник члан"
+ ADMIN_ACCESS: "Приступ Админа"
+ SITE_ACCESS: "Приступ сајту"
+ INVALID_SECURITY_TOKEN: "Неисправанa сигурносна ознака"
+ ACTIVATE: "Активирај"
+ TWIG_UMASK_FIX: "Исправка Umask-a"
+ TWIG_UMASK_FIX_HELP: "Подразумевано Twig креира кеширане фајлове као 0755, а исправком ово прелази у 0775"
+ CACHE_PERMS: "Овлашћења кеша"
+ CACHE_PERMS_HELP: "Подразумевања овлашења за кеш фолдер. Обично је 0755 или 0775 у зависности од поставке"
+ REMOVE_SUCCESSFUL: "Уклањање успело"
+ REMOVE_FAILED: "Уклањање неуспело"
+ HIDE_HOME_IN_URLS: "Сакриј путању за Почетак у URL-овима"
+ HIDE_HOME_IN_URLS_HELP: "Осигураће да подразумевана путања за било коју Почетну страницу да не упућује као уобичајеној путањи за Почетак"
+ TWIG_FIRST: "Прво процесирај Twig"
+ TWIG_FIRST_HELP: "Уколико активирате процесирање Twig странице, онда можете подесити да се Twig процесира пре или после markdown-a"
+ SESSION_SECURE: "Осигурана"
+ SESSION_SECURE_HELP: "Уколико је укључено, указује да комуникација за овај колаћић мора да се обавља преко криптоване везе. УПОЗОРЕЊЕ: ово треба укључити на сајтовима који искључиво користе HTTPS"
+ SESSION_HTTPONLY: "Само HTTP"
+ SESSION_HTTPONLY_HELP: "Уколико је укључено, указује да колачићи треба користити само преко HTTP-a, и да JavaScript модификације нису дозвољене"
+ REVERSE_PROXY: "Повратни Прокси"
+ REVERSE_PROXY_HELP: "Укључи ову опцију уколико приступате иза повратног проксија и имате проблем са URL-овима који имају неисправне портове"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Неисправан frontmatter, није могуће снимити"
+ ADD_FOLDER: "Додај фолдер"
+ PROXY_URL: "Прокси URL"
+ PROXY_URL_HELP: "Унесите HOST или IP адресу и PORT прокси сервера"
+ NOTHING_TO_SAVE: "Нема ништа за сачувати"
+ FILE_ERROR_ADD: "Појавила се грешка при покушају додавања фајла"
+ FILE_ERROR_UPLOAD: "Појавила се грешка при покушају учитавања фајла"
+ FILE_UNSUPPORTED: "Неподржани тип фајла"
+ ADD_ITEM: "Додај ставку"
+ FILE_TOO_LARGE: "Фајл је превелик да би био учитан, максиално дозвољена величина је %s у складу са вашим поставкама PHP-а. Повећајте `post_max_size` PHP опцију"
+ INSTALLING: "Инсталирање"
+ LOADING: "Учитавање.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "Следеће зависности прво морају да буду задовољене:"
+ ERROR_INSTALLING_PACKAGES: "Грешка приликом инсталације пакета"
+ INSTALLING_DEPENDENCIES: "Инсталација зависних пакета..."
+ INSTALLING_PACKAGES: "Инсталација пакета.."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Пакет(и) је(су) успешно инсталиран(и)."
+ READY_TO_INSTALL_PACKAGES: "Спреман за инсталацију пакета"
+ PACKAGES_NOT_INSTALLED: "Пакети нису инсталирани"
+ PACKAGES_NEED_UPDATE: "Пакети су већ инсталирани, али су превише стари"
+ PACKAGES_SUGGESTED_UPDATE: "Пакети су већ инсталирани, и њихове верзије су у реду, али ће бити ажурирани да би све било најновије"
+ REMOVE_THE: "Уклони %s"
+ CONFIRM_REMOVAL: "Да ли сте сигурни да желите да обришете овај %s?"
+ REMOVED_SUCCESSFULLY: "%s je успешно уклоњен"
+ ERROR_REMOVING_THE: "Грешка приликом уклањања %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "%s захтева следеће зависности, које нису потребне за друге инсталиране пакете. Уколико не желите да их користите, можете их директно уклонити одавде."
+ READY_TO_UPDATE_PACKAGES: "Спреман за ажурирање пакета"
+ ERROR_UPDATING_PACKAGES: "Грешка приликом ажурирања пакета"
+ UPDATING_PACKAGES: "Ажурирање пакета.."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Пакет(и) је(су) успешно ажурирани."
+ UPDATING: "Ажурирање"
+ GPM_RELEASES: "GPM верзије"
+ GPM_RELEASES_HELP: "Изаберите 'Тesting' за инсталирање бета или пробне верзије"
+ GPM_METHOD: "Метода за даљинско добављање"
+ GPM_METHOD_HELP: "Када је подешено на Ауто, Грав ће одредити да ли је fopen доступан и користиће га, или ће у супротном прећи на cURL. Да би одредили употребну једног или другог измените подешавање."
+ GPM_VERIFY_PEER: "Даљинска верификација Peer-a (SSL)"
+ GPM_VERIFY_PEER_HELP: "Неки провајдери изгледа неуспевају да провере getgrav.org SSL сертификате, чинећи да GPM је ради. Уколико је то случај са вама, искључивање ове опције вам може помоћи"
+ AUTO: "Ауто"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Стабилна"
+ TESTING: "Радна"
+ FRONTMATTER_PROCESS_TWIG: "Процес управљања Twig-ом на генеричким страницама"
+ FRONTMATTER_PROCESS_TWIG_HELP: "Када је активирана можете користити Twig конфугирационе променљиве у креирањеу страница"
+ FRONTMATTER_IGNORE_FIELDS: "Игнориши frontmatter поља"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Одрешена frontmatter поља могу садржати Twig али не треба да буду процесерана, попут 'образаца'"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Пакет %s је успешно инсталиран"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Редослед матичних страница, сортирање искључено"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Страница није видиљива, сортирање искључено"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "Сортирање преко админа није подржано зато што има ише од 200 подстраница"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Сортирање страница је искључен за ову страницу зато што Нумерички префикс фолдера није омогућен"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "НАПОМЕНА: Не можете додати медија фајлове све док не сачувате страницу. Само кликните на 'Сачувај' на врху"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "НАПОМЕНА: Страница прво мора бити сачувана да би могли да учитате фајлове у њу."
+ DROP_FILES_HERE_TO_UPLOAD: "Пренесите своје фајлове овде или кликните овде "
+ INSERT: "Убаци"
+ UNDO: "Опозови"
+ REDO: "Понови"
+ HEADERS: "Заглавља"
+ BOLD: "Подебљано"
+ ITALIC: "Курзив"
+ STRIKETHROUGH: "Прецртано"
+ SUMMARY_DELIMITER: "Граничник сажетка"
+ LINK: "Линк"
+ IMAGE: "Слика"
+ BLOCKQUOTE: "Цитат"
+ UNORDERED_LIST: "Несортирана листа"
+ ORDERED_LIST: "Сортирана листа"
+ EDITOR: "Уређивач"
+ PREVIEW: "Преглед"
+ FULLSCREEN: "Цео екран"
+ MODULAR: "Модуларно"
+ MODULAR_SETUP: "Модуларно подешавање"
+ NON_ROUTABLE: "Non-Routable"
+ NON_MODULAR: "Не-модуларно"
+ NON_VISIBLE: "Не-видљиво"
+ NON_PUBLISHED: "Не-објављено"
+ CHARACTERS: "карактери"
+ PUBLISHING: "Објављивање"
+ NOTIFICATIONS: "Обавештавања"
+ MEDIA_TYPES: "Типови медија"
+ IMAGE_OPTIONS: "Подешавање слика"
+ MIME_TYPE: "Mime типови"
+ THUMB: "Сличице"
+ TYPE: "Тип"
+ FILE_EXTENSION: "Екстензије фајлова"
+ LEGEND: "Легенда странице"
+ MEMCACHE_SERVER: "Memcache сервер"
+ MEMCACHE_SERVER_HELP: "Адреса Memcache сервера"
+ MEMCACHE_PORT: "Memcache порт"
+ MEMCACHE_PORT_HELP: "Серверски порт за Memcache"
+ MEMCACHED_SERVER: "Memcached сервер"
+ MEMCACHED_SERVER_HELP: "Адреса Memcached сервера"
+ MEMCACHED_PORT: "Memcached порт"
+ MEMCACHED_PORT_HELP: "Порт Memcached сервера"
+ REDIS_SERVER: "Redis сервер"
+ REDIS_SERVER_HELP: "Адреса Redis сервера"
+ REDIS_PORT: "Redis порт"
+ REDIS_PORT_HELP: "Порт Redis сервера"
+ REDIS_PASSWORD: "Redis лозинка/тајна"
+ ALL: "Све"
+ FROM: "од"
+ TO: "за"
+ RELEASE_DATE: "Датум објаве"
+ SORT_BY: "Сортирано по"
+ RESOURCE_FILTER: "Филтер..."
+ FORCE_SSL: "Обавезан SSL"
+ FORCE_SSL_HELP: "Глобално обавезна употреба SSL-a, уколико је укључено а сајту се приступа преко HTTP-а, Грав ће преусмерити на HTTPS страницу"
+ NEWS_FEED: "Трака са вестима"
+ EXTERNAL_URL: "Спољни URL"
+ SESSION_PATH: "Путања сесије"
+ SESSION_PATH_HELP: "Треба користити само уколико изаберете произвољну основу URL-a (мењате домен сајта / подфолдер)"
+ CUSTOM_BASE_URL: "Произвољни основни URL"
+ CUSTOM_BASE_URL_HELP: "Уколико желите да промените домен сајта или да користите другачији подфолдер од оног који користи Грав. Пример: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'Не можете да користите "%s" изван страница.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Није могуће учитати фајл %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'Није могуће преместити фајл %s у "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'Поништи учитавање'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Да ли сте сигурни да желите да поништите учитавање?'
+ DROPZONE_DEFAULT_MESSAGE: 'Пренесите своје фајлове овде или кликните овде '
+ DROPZONE_FALLBACK_MESSAGE: 'Ваш веб претраживач не поджава "пренеси и спусти" начин учитавања фајлова.'
+ DROPZONE_FALLBACK_TEXT: 'Молим вас да користите резервни образац доле за учитавање ваших фајлова на класичан начин.'
+ DROPZONE_FILE_TOO_BIG: 'Фајл је превелик ({{filesize}}MiB). Максимална величина фајла: {{maxFilesize}}MiB.'
+ DROPZONE_INVALID_FILE_TYPE: "Не можете учитати овај тип фајлова."
+ DROPZONE_MAX_FILES_EXCEEDED: "Не можете да учитате још фајлова."
+ DROPZONE_REMOVE_FILE: "Уклони фајл"
+ DROPZONE_RESPONSE_ERROR: "Сервер је одговорио са {{statusCode}} кодом."
+ PREMIUM_PRODUCT: "Премиум"
+ ADMIN_CACHING: "Омогући кеширање Админа"
+ ADMIN_CACHING_HELP: "Кеширање Админа може бити контролисано независно од корисничког дела сајта"
+ DESTINATION_NOT_SPECIFIED: "Дестинација није одређена"
+ UPLOAD_ERR_NO_TMP_DIR: "Недостаје привремени фолдер"
+ SESSION_SPLIT: "Раздвајање сесије"
+ SESSION_SPLIT_HELP: "Независно раздвајањесесија између сајта и других додатака (попут Админа)"
+ ERROR_FULL_BACKTRACE: "Пуни приказ грешке"
+ ERROR_SIMPLE: "Једноставни приказ грешке"
+ ERROR_SYSTEM: "Системска грешка"
+ IMAGES_AUTO_FIX_ORIENTATION: "Исправи орјентацију аутоматски"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Аутоматски исправљање орјентације слике базирано на Exif подацима"
+ REDIS_SOCKET: "Redis прикључак"
+ REDIS_SOCKET_HELP: "Redis прикључак"
+ NOT_SET: "Није подешено"
+ PERMISSIONS: "Овлашћења"
+ NEVER_CACHE_TWIG: "Никада не кеширај Twig"
+ NEVER_CACHE_TWIG_HELP: "Кеширање само садржаја и процесирање Twig-a за странице сваког пута. Ингорише подешавање за twig_first."
+ ALLOW_WEBSERVER_GZIP: "Дозволи WebServer Gzip"
+ ALLOW_WEBSERVER_GZIP_HELP: "Подразумевано је искључено. Када је укључено, Gzip/Deflate компресија подешена на серверу ће радити, али http конекција неће бити затворена пре onShutDown() догађаја чинећи страницу споријом"
+ OFFLINE_WARNING: "Конекција ка GPM-у не може да се успостави"
+ CLEAR_IMAGES_BY_DEFAULT: "Чисти кеш слике подразумевано"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "Подразумевано се чисти кеш процесиране слике, ово може бити искључено"
+ CLI_COMPATIBILITY: "CLI компатибилност"
+ CLI_COMPATIBILITY_HELP: "Осигурава да се користе само не-непроменљиви кеш драјвери (file, redis, memcache, итд.)"
+ REINSTALL_PLUGIN: "Реинсталирај Додатак"
+ REINSTALL_THEME: "Реинсталирај Тему"
+ REINSTALL_THE: "Реинсталирај %s"
+ CONFIRM_REINSTALL: "Да ли сте сигурни да желите да реинсталирате %s?"
+ REINSTALLED_SUCCESSFULLY: "%s реинсталиран успешно"
+ ERROR_REINSTALLING_THE: "Грешка приликом реинсталирања %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Пакет %s је успешно реинсталиран"
+ REINSTALLATION_FAILED: "Реинсталација неуспела"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "Инсталирана верзија није и најновија. Кликом на Настави ћете уклонити тренутну верзију и инсталирати најновију доступну верзију"
+ TOOLS: "Алати"
+ DIRECT_INSTALL: "Директна инсталација"
+ NO_PACKAGE_NAME: "Име пакета није одређено"
+ PACKAGE_EXTRACTION_FAILED: "Распакивање пакета није успело"
+ NOT_VALID_GRAV_PACKAGE: "Није исправан Грав пакет"
+ NAME_COULD_NOT_BE_DETERMINED: "Име није могло бити утврђено"
+ CANNOT_OVERWRITE_SYMLINKS: "Није могуће преписивање симболичких линкова"
+ ZIP_PACKAGE_NOT_FOUND: "ZIP пакет није пронађен"
+ GPM_OFFICIAL_ONLY: "Само званичини GPM"
+ GPM_OFFICIAL_ONLY_HELP: "Дозвољава директну инсталацију само са званичне GPM ризнице."
+ NO_CHILD_TYPE: "Нема типа подстранице за овај rawroute"
+ SORTABLE_PAGES: "Странице које се могу сортирати:"
+ UNSORTABLE_PAGES: "Странце које се не могу сортирати"
+ ADMIN_SPECIFIC_OVERRIDES: "Посебна заобилажења Админа"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Редослед приказа подстраница"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "Редослед у којем ће подстранице ове странице бити приказане у шаблону 'Странице' Админ додатка"
+ PWD_PLACEHOLDER: "сложени стринг од најмање 8 карактера"
+ PWD_REGEX: "Лозинка, уобичајена"
+ PWD_REGEX_HELP: "Подразумевано: Лозинка мора да садржи најмање један број и по једно Велико и мало слову, а заједно најмање 8 или више карактера"
+ USERNAME_PLACEHOLDER: "само мали слова, нпр. 'admin'"
+ USERNAME_REGEX: "Корисничко име, уобичајено"
+ USERNAME_REGEX_HELP: "Подразумевано: дозвољена су само мала слова, бројеви, цртица и подцртица. 3 - 16 карактера"
+ CONTENT_PADDING: "Одстојање око садржаја"
+ CONTENT_PADDING_HELP: "Укључује/искључује одстојање око садржаја да би добили више простора"
+ ENABLE_AUTO_METADATA: "Аутоматски metadata из Exif-a"
+ ENABLE_AUTO_METADATA_HELP: "Аутоматски генерисани metadata фајлови за слике са exif информацијама"
+ 2FA_TITLE: "2-степена Аутентификација"
+ 2FA_INSTRUCTIONS: "##### 2-степена Аутентификација\nУкључили сте **2FA** за овај налог. Молимо вас да користите своју **2FA** апликацију да би унели **6-цифарни код** да би завршили процес пријављивања."
+ 2FA_REGEN_HINT: "Да бисте повратили лозинку, потребно је да ажурирате апликацију за потврду идентитета"
+ 2FA_LABEL: "Админ приступ"
+ 2FA_FAILED: "Неисправан 2-степени аутентификациони код, молимо вас покушајте поново..."
+ 2FA_ENABLED: "2FA укључена"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "2FA шифра"
+ 2FA_SECRET_HELP: "Скенирајте овај QR код са вашом [апликацијом за аутентификацију](https://learn.getgrav.org/admin-panel/2fa#apps). Не би било лоше да направите резервну копију ваше лозинке на некој сигурној локацији, у случају да морате да поново инсталирате своју апликацију. Проверите [Грав документацију](https://learn.getgrav.org/admin-panel/2fa) за више информација "
+ 2FA_REGENERATE: "Обнови"
+ FORCE_LOWERCASE_URLS: "Обавезни URL-ови са малим словима"
+ FORCE_LOWERCASE_URLS_HELP: "Грав ће подразумевано подесити све slug-ове и путање да буду са малим словима. Уколико ово подесите на false, моћи ћете да користите slug-ове и путање и са Великим словима"
+ INTL_ENABLED: "Интеграција Intl модула"
+ INTL_ENABLED_HELP: "Користи Intl PHP модул и ради сређивања и сортирања колекције која се базира на UTF8"
+ VIEW_SITE_TIP: "Погледај сајт"
+ TOOLS_DIRECT_INSTALL_TITLE: "Директна инсталација Грав пакета"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "Инсталација пакета преко директног учитавања ZIP архива"
+ TOOLS_DIRECT_INSTALL_UPLOAD_DESC: "Можете лако инсталирати одговарајућу Грав тему , додатак , или чак нову верзију Грава у форми Zip пакета помоћу ове методе. Овај пакет не мора да се региструје преко GPM-а и омогућава вам да једноставно вратите претходну верзију или да инсталирате нову ради тестирања."
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "Инсталирај пакете преко удаљене URL референце"
+ TOOLS_DIRECT_INSTALL_URL_DESC: "Као другу могућност, такође можете да повежете цели URL са ZIP пакетом и инсталирате га преко ове удаљене URL адресе."
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Учитај и инсталирај"
+ ROUTE_OVERRIDES: "Заобилазна путања"
+ ROUTE_DEFAULT: "Подразумевана путања"
+ ROUTE_CANONICAL: "Подразумевана путања"
+ ROUTE_ALIASES: "Алијаси путање"
+ OPEN_NEW_TAB: "Отвори у новом језичку"
+ PAGEMEDIA_RESIZER: "> Следеће подешавање се односи на слике које су учитане преко странице медија. Промени ширину/висину ће аутоматски пропорционално смањити слику уколико прелази лимите постављене при уносу. Резолуција мин и макс. дефинише распоне величине за учитане слике. Поставите вредност поља на 0 да би спречили измене."
+ SESSION_INITIALIZE: "Иницирање Сесије"
+ SESSION_INITIALIZE_HELP: "Чини да Grav покрене сесију. Ова опција је потребна да би била могућа интеракција са корисником, као што је пријава, форме, итд. Овим подешавањем се не утиче на Админ додатак."
+ STRICT_YAML_COMPAT: "YAML компатибилност"
+ STRICT_YAML_COMPAT_HELP: "Прелази назад на Symfony 2.4 YAML парсер уколико подразумевани или 3.4 парсер не ради"
+ STRICT_TWIG_COMPAT: "Twig Компатибилност"
+ STRICT_TWIG_COMPAT_HELP: "Омогућава застарелу Twig autoescape опцију. Када је деактивирана, потребан је |raw филтер за добијање HTML-a јер ће Twig аутоматски ексивирати излаз"
+ SCHEDULER: "Планер"
+ SCHEDULER_INSTALL_INSTRUCTIONS: "Упуство за инсталацију"
+ SCHEDULER_INSTALLED_READY: "Инсталирај и спреман"
+ SCHEDULER_CRON_NA: "Cron није доступан"
+ SCHEDULER_NOT_ENABLED: "Није активиран"
+ SCHEDULER_SETUP: "Подешавање Планера"
+ SCHEDULER_INSTRUCTIONS: "Грав Планер вам омогућава да креирате и планирате различите послове. Он такође омогућава метод за интеграцију у Grav додатке програмски и аутоматски креираних задатака за њихово повремено покретање."
+ SCHEDULER_POST_INSTRUCTIONS: "Да би омогућили функционалност Планера, морате додати Grav Планер у ваш системски crontab фајл. Покренути горе наведену комаду у терминалу да би је аутоматски додали. Једном сачувану, освежите ову страницу да би видели статус Спреман (Ready)."
+ SCHEDULER_JOBS: "Послови прилагођеног планера"
+ SCHEDULER_STATUS: "Статус планера"
+ SCHEDULER_RUNAT: "Покрени у"
+ SCHEDULER_RUNAT_HELP: "Cron форматирана 'at' синтакса"
+ SCHEDULER_OUTPUT: "Излазни фајл"
+ SCHEDULER_OUTPUT_HELP: "Путања/име излазног фајла (из корена Грав инсталације)"
+ SCHEDULER_OUTPUT_TYPE: "Тип излаза"
+ SCHEDULER_OUTPUT_TYPE_HELP: "Или додај у фајл при сваком покретању, или поново креирај фајл при сваком покретању"
+ SCHEDULER_EMAIL: "Е-пошта"
+ SCHEDULER_EMAIL_HELP: "Адреса е-поште на коју треба послати излаз. НАПОМЕНА: потребно је да је излазни фајл подешен"
+ SECURITY: "Сигурност"
+ XSS_SECURITY: "XSS Сигурност за садржај"
+ XSS_WHITELIST_PERMISSIONS: "Бела листа дозвола"
+ XSS_WHITELIST_PERMISSIONS_HELP: "Корисници са овим дозволама ће прескочити XSS правила приликом снимања садржаја"
+ XSS_ON_EVENTS: "Филтрирај догађаје"
+ XSS_INVALID_PROTOCOLS: "Филтрирај неправилне протоколе"
+ XSS_INVALID_PROTOCOLS_LIST: "Листа неправилних протокола"
+ XSS_MOZ_BINDINGS: "Филтрирај Moz привеске"
+ XSS_HTML_INLINE_STYLES: "Филтрирај HTML inline стилове"
+ XSS_DANGEROUS_TAGS: "Филтрирај опасне HTML тагове"
+ XSS_DANGEROUS_TAGS_LIST: "Листа опасних HTML тагова"
+ XSS_ONSAVE_ISSUE: "Снимање неуспело: откривен XSS проблем..."
+ XSS_ISSUE: "НАПОМЕНА: Grav је пронашао потенцијалне XSS проблеме у %s "
+ UPLOADS_SECURITY: "Сигурност при преузимању"
+ UPLOADS_DANGEROUS_EXTENSIONS: "Опасна проширења"
+ UPLOADS_DANGEROUS_EXTENSIONS_HELP: "Блокирајте ова проширења приликом преузимања без обзира на одобрене МIME типове"
+ REPORTS: "Извештаји"
+ LOGS: "Дневници"
+ LOG_VIEWER_FILES: "Прегледач фајлова дневника"
+ LOG_VIEWER_FILES_HELP: "Фајлови у /logs/ који ће бити доступни за преглед у Алати - Дневници. e.g. 'grav' = /logs/grav.log"
+ BACKUPS_STORAGE_PURGE_TRIGGER: "Окидач за уклањање резервне копије"
+ BACKUPS_MAX_COUNT: "Максимални број резервних копија"
+ BACKUPS_MAX_COUNT_HELP: "0 за неограничено"
+ BACKUPS_MAX_SPACE: "Максимални простор за резервну копију"
+ BACKUPS_MAX_RETENTION_TIME: "Максимално време задржавања"
+ BACKUPS_MAX_RETENTION_TIME_APPEND: "у данима"
+ BACKUPS_PROFILE_NAME: "Назив резервне копије"
+ BACKUPS_PROFILE_ROOT_FOLDER: "Корени фолдер"
+ BACKUPS_PROFILE_ROOT_FOLDER_HELP: "Може бити апсолутна путања или ток"
+ BACKUPS_PROFILE_EXCLUDE_PATHS: "Искључи путање"
+ BACKUPS_PROFILE_EXCLUDE_PATHS_HELP: "Апсолутна путање за искључивање, једну на свакој линији"
+ BACKUPS_PROFILE_EXCLUDE_FILES: "Искључи фајлове"
+ BACKUPS_PROFILE_EXCLUDE_FILES_HELP: "Поједини фајлови или фолдери за искључивање, један по линији"
+ BACKUPS_PROFILE_SCHEDULE: "Омогући заказани посао"
+ BACKUPS_PROFILE_SCHEDULE_AT: "Покрени заказани посао"
+ COMMAND: "Наредба"
+ EXTRA_ARGUMENTS: "Додатни аргументи"
diff --git a/user/plugins/admin/languages/sv.yaml b/user/plugins/admin/languages/sv.yaml
new file mode 100644
index 0000000..f4f816e
--- /dev/null
+++ b/user/plugins/admin/languages/sv.yaml
@@ -0,0 +1,632 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Detta är en betaversion! Använd i produktion på egen risk..."
+ ADMIN_REPORT_ISSUE: "Hittat ett fel? Vänligen rapportera det på GitHub."
+ EMAIL_FOOTER: "Powered by Grav - The Modern Flat File CMS"
+ LOGIN_BTN: "Logga in"
+ LOGIN_BTN_FORGOT: "Glömt"
+ LOGIN_BTN_RESET: "Återställ Lösenord"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Skicka återställningsinstruktioner"
+ LOGIN_BTN_CLEAR: "Rensa Formulär"
+ LOGIN_BTN_CREATE_USER: "Skapa Användare"
+ LOGIN_LOGGED_IN: "Inloggning lyckades"
+ LOGIN_FAILED: "Inloggning misslyckades"
+ LOGGED_OUT: "Utloggad"
+ RESET_NEW_PASSWORD: "Vänligen ange ett nytt lösenord …"
+ RESET_LINK_EXPIRED: "Återställningslänken är inte giltig, försök igen"
+ RESET_PASSWORD_RESET: "Lösenordet har återställts"
+ RESET_INVALID_LINK: "Återställningslänken är inte giltig, försök igen"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Instruktioner för att återställa ditt lösenord har skickats via e-post till %s"
+ FORGOT_FAILED_TO_EMAIL: "Misslyckades att skicka instruktioner via e-post, försök igen senare"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Kan inte återställa lösenord för %s, e-postadress saknas"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Användare med användarnamn %s finns inte"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Kan inte återställa lösenord. Denna webbplats är inte konfigurerad för att skicka e-post"
+ FORGOT_EMAIL_SUBJECT: "%s Begäran om lösenordsåterställning"
+ FORGOT_EMAIL_BODY: "Återställning av lösenord Hej %1$s,
En begäran gjordes att återställa ditt lösenord på %4$s .
< br / > Klicka här för att återställa lösenordet < br / > < br / >
Alternativt Kopiera följande URL i webbläsarens adressfält:
%2$s
< br / > vänliga hälsningar, < br / > < br / >%3$s
"
+ MANAGE_PAGES: "Hantera sidor"
+ CONFIGURATION: "Inställningar"
+ PAGES: "Sidor"
+ PLUGINS: "Tillägg"
+ PLUGIN: "Tillägg"
+ THEMES: "Teman"
+ LOGOUT: "Logga ut"
+ BACK: "Tillbaka"
+ NEXT: "Nästa"
+ PREVIOUS: "Föreg."
+ ADD_PAGE: "Lägg till sida"
+ ADD_MODULAR: "Lägg till Modulär"
+ MOVE: "Flytta"
+ DELETE: "Radera"
+ UNSET: "Obestämt"
+ VIEW: "Visa"
+ SAVE: "Spara"
+ NORMAL: "Normal"
+ EXPERT: "Expert"
+ EXPAND_ALL: "Visa allt"
+ COLLAPSE_ALL: "Minimera alla"
+ ERROR: "Fel"
+ CLOSE: "Stäng"
+ CANCEL: "Avbryt"
+ CONTINUE: "Fortsätt"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Bekräftelse krävs"
+ MODAL_CHANGED_DETECTED_TITLE: "Ändringar har upptäckts"
+ MODAL_CHANGED_DETECTED_DESC: "Du har ändringar som inte sparats. Är du säker du vill lämna utan att spara?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Bekräftelse krävs"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Är du säker du vill ta bort denna fil? Denna åtgärd kan inte ångras."
+ ADD_FILTERS: "Lägg till filter"
+ SEARCH_PAGES: "Sök sidor"
+ VERSION: "Version"
+ WAS_MADE_WITH: "Gjordes med"
+ BY: "Av"
+ UPDATE_THEME: "Uppdatera tema"
+ UPDATE_PLUGIN: "Uppdatera tillägg"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "av detta tema är nu tillgänglig"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "av detta tillägg finns nu tillgänglig"
+ AUTHOR: "Skapare"
+ HOMEPAGE: "Hemsida"
+ DEMO: "Demo"
+ BUG_TRACKER: "Felrapporter"
+ KEYWORDS: "Sökord"
+ LICENSE: "Licens"
+ DESCRIPTION: "Beskrivning"
+ README: "Information"
+ REMOVE_THEME: "Ta bort tema"
+ INSTALL_THEME: "Installera tema"
+ THEME: "Tema"
+ BACK_TO_THEMES: "Tillbaka till teman"
+ BACK_TO_PLUGINS: "Tillbaka till tillägg"
+ CHECK_FOR_UPDATES: "Sök efter Uppdateringar"
+ ADD: "Lägg till"
+ CLEAR_CACHE: "Rensa cacheminne"
+ CLEAR_CACHE_ALL_CACHE: "All Cache"
+ CLEAR_CACHE_ASSETS_ONLY: "Endast filer"
+ CLEAR_CACHE_IMAGES_ONLY: "Endast bilder"
+ CLEAR_CACHE_CACHE_ONLY: "Endast cache"
+ CLEAR_CACHE_TMP_ONLY: "Endast Tmp"
+ DASHBOARD: "Dashboard"
+ UPDATES_AVAILABLE: "Uppdateringar tillgängliga"
+ DAYS: "Dagar"
+ UPDATE: "Uppdatera"
+ BACKUP: "Säkerhetskopiera"
+ BACKUPS: "Säkerhetskopior"
+ BACKUP_NOW: "Säkerhetskopiera nu"
+ BACKUPS_STATS: "Statistik för säkerhetskopiering"
+ BACKUPS_HISTORY: "Historik för säkerhetskopiering"
+ BACKUPS_PURGE_CONFIG: "Inställningar för rensning av säkerhetskopior"
+ BACKUPS_PROFILES: "Säkerhetskopieringsprofiler"
+ BACKUPS_COUNT: "Antal säkerhetskopior"
+ BACKUPS_PROFILES_COUNT: "Antal profiler"
+ BACKUPS_TOTAL_SIZE: "Använt utrymme"
+ BACKUPS_NEWEST: "Senaste säkerhetskopia"
+ BACKUPS_OLDEST: "Äldsta säkerhetskopia"
+ BACKUPS_PURGE: "Rensa"
+ BACKUPS_NOT_GENERATED: "Inga säkerhetskopior har genererats ännu..."
+ BACKUPS_PURGE_NUMBER: "Använder %s av %s utrymmen för säkerhetskopior"
+ BACKUPS_PURGE_TIME: "%s dagar av säkerhetskopior kvar"
+ BACKUPS_PURGE_SPACE: "Använder %s av %s"
+ BACKUP_DELETED: "Säkerhetskopan har tagits bort"
+ BACKUP_NOT_FOUND: "Säkerhetskopian hittades inte"
+ BACKUP_DATE: "Datum för säkerhetskopia"
+ STATISTICS: "Statistik"
+ TODAY: "Idag"
+ WEEK: "Vecka"
+ MONTH: "Månad"
+ LATEST_PAGE_UPDATES: "Senaste siduppdateringar"
+ MAINTENANCE: "Underhåll"
+ UPDATED: "Uppdaterad"
+ MON: "Mån"
+ TUE: "Tis"
+ WED: "Ons"
+ THU: "Tors"
+ FRI: "Fre"
+ SAT: "Lör"
+ SUN: "Sön"
+ COPY: "Kopiera"
+ EDIT: "Redigera"
+ CREATE: "Skapa"
+ GRAV_ADMIN: "Grav Admin"
+ GRAV_OFFICIAL_PLUGIN: "Grav officiellt tillägg"
+ GRAV_OFFICIAL_THEME: "Grav officiellt tema"
+ PLUGIN_SYMBOLICALLY_LINKED: "Tillägget är symboliskt länkat. Uppdateringar kommer inte att upptäckas."
+ THEME_SYMBOLICALLY_LINKED: "Temat är symboliskt länkat. Uppdateringar kommer inte att upptäckas"
+ REMOVE_PLUGIN: "Ta bort plugin"
+ INSTALL_PLUGIN: "Installera plugin"
+ AVAILABLE: "Tillgängligt"
+ INSTALLED: "Installerat"
+ INSTALL: "Installera"
+ ACTIVE_THEME: "Aktivt tema"
+ SWITCHING_TO: "Byter till"
+ SWITCHING_TO_DESCRIPTION: "Genom att byta till ett annat tema, kan vi inte garantera att alla sidtyper stöds, vilket kan orsaka fel när du försöker ladda om nämnda sidor."
+ SWITCHING_TO_CONFIRMATION: "Vill du fortsätta och växla till temat"
+ CREATE_NEW_USER: "Skapa ny användare"
+ REMOVE_USER: "Ta bort användare"
+ ACCESS_DENIED: "Åtkomst nekad"
+ ACCOUNT_NOT_ADMIN: "ditt konto har inte administratörsbehörigheter"
+ PHP_INFO: "PHP Info"
+ INSTALLER: "Installerare"
+ AVAILABLE_THEMES: "Tillgängliga teman"
+ AVAILABLE_PLUGINS: "Tillgängliga Plugins"
+ INSTALLED_THEMES: "Installerade teman"
+ INSTALLED_PLUGINS: "Installerade Tillägg"
+ BROWSE_ERROR_LOGS: "Sök i felloggar"
+ SITE: "Webbsida"
+ INFO: "Info"
+ SYSTEM: "System"
+ USER: "Användare"
+ ADD_ACCOUNT: "Lägg till konto"
+ SWITCH_LANGUAGE: "Byt språk"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Aktivering av tillägget lyckades"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Avaktivering av tillägget lyckades"
+ SUCCESSFULLY_CHANGED_THEME: "Byte av standardtema lyckades"
+ INSTALLATION_FAILED: "Installationen misslyckades"
+ INSTALLATION_SUCCESSFUL: "Installationen lyckades"
+ UNINSTALL_FAILED: "Avinstallationen misslyckades"
+ UNINSTALL_SUCCESSFUL: "Avinstallationen lyckades"
+ SUCCESSFULLY_SAVED: "Sparad"
+ SUCCESSFULLY_COPIED: "Kopierad"
+ REORDERING_WAS_SUCCESSFUL: "Omordning lyckades"
+ SUCCESSFULLY_DELETED: "Borttagen"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Byte av språk lyckades"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Du saknar behörigheter"
+ CACHE_CLEARED: "Cachen rensad"
+ METHOD: "Metod"
+ ERROR_CLEARING_CACHE: "Kunde inte rensa cache"
+ AN_ERROR_OCCURRED: "Ett fel uppstod"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Din säkerhetskopia är klar för nedladdning"
+ DOWNLOAD_BACKUP: "Hämta Backup"
+ PAGES_FILTERED: "Filtrerade sidor"
+ NO_PAGE_FOUND: "Ingen sida hittades"
+ INVALID_PARAMETERS: "Ogiltiga parametrar"
+ NO_FILES_SENT: "Inga filer skickades"
+ EXCEEDED_FILESIZE_LIMIT: "Överskred PHP konfigurationens storleksgräns för filer"
+ UNKNOWN_ERRORS: "Okända fel"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Överskred Grav konfigurationens storleksgräns för filer"
+ UNSUPPORTED_FILE_TYPE: "Filtypen stöds ej"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Det gick inte att flytta uppladdad fil"
+ FILE_UPLOADED_SUCCESSFULLY: "Filuppladdning lyckades"
+ FILE_DELETED: "Fil raderad"
+ FILE_COULD_NOT_BE_DELETED: "Filen kunde inte raderas"
+ FILE_NOT_FOUND: "Fil saknas"
+ NO_FILE_FOUND: "Ingen fil hittades"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav uppdaterades till"
+ GRAV_UPDATE_FAILED: "Grav uppdateringen misslyckades"
+ EVERYTHING_UPDATED: "Allt uppdaterades"
+ UPDATES_FAILED: "Uppdateringen misslyckades"
+ AVATAR_BY: "Avatar av"
+ AVATAR_UPLOAD_OWN: "Eller ladda upp din egna..."
+ LAST_BACKUP: "Senaste säkerhetskopiering"
+ FULL_NAME: "För-och efternamn"
+ USERNAME: "Användarnamn"
+ EMAIL: "E-postadress"
+ USERNAME_EMAIL: "Användarnamn eller e-post"
+ PASSWORD: "Lösenord"
+ PASSWORD_CONFIRM: "Bekräfta lösenord"
+ TITLE: "Titel"
+ LANGUAGE: "Språk"
+ ACCOUNT: "Konto"
+ EMAIL_VALIDATION_MESSAGE: "Måste vara en giltig e-postadress"
+ PASSWORD_VALIDATION_MESSAGE: "Lösenordet måste innehålla minst en siffra och en versal och gemen och minst 8 tecken"
+ LANGUAGE_HELP: "Ställ in favoritspråk"
+ MEDIA: "Media"
+ DEFAULTS: "Standard"
+ SITE_TITLE: "Webbplatsens titel"
+ SITE_TITLE_PLACEHOLDER: "Global titel för webbplats"
+ SITE_TITLE_HELP: "Förvald rubrik för din webbplats, används ofta i teman"
+ SITE_DEFAULT_LANG: "Förvalt språk"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Förvalt språk som används av temats tagg"
+ SITE_DEFAULT_LANG_HELP: "Förvalt språk som används av temats tagg"
+ DEFAULT_AUTHOR: "Förvald skribent"
+ DEFAULT_AUTHOR_HELP: "Förvald skribent, används ofta i teman och sidans innehåll"
+ DEFAULT_EMAIL: "Förvald e-post"
+ DEFAULT_EMAIL_HELP: "Förvald e-post att hänvisa till i teman eller sidor"
+ TAXONOMY_TYPES: "Taxonomi typer"
+ TAXONOMY_TYPES_HELP: "Taxonomi-typer måste definieras här om du vill använda dem i sidor"
+ PAGE_SUMMARY: "Sidsammanfattning"
+ ENABLED: "Aktiverad"
+ ENABLED_HELP: "Aktivera sammanfattning för sidor (sammanfattningen returnerar samma som innehållet på sidan)"
+ 'YES': "Ja"
+ 'NO': "Nej"
+ SUMMARY_SIZE: "Storlek för sammanfattning"
+ SUMMARY_SIZE_HELP: "Mängden tecken av en sida att användas som sammanfattning av innehåll"
+ FORMAT: "Format"
+ FORMAT_HELP: "kort = Använd den första förekomsten av avgränsare eller storlek. längre = sammanfattande avgränsare kommer ignoreras"
+ SHORT: "Kort"
+ LONG: "Lång"
+ DELIMITER: "Avgränsare"
+ DELIMITER_HELP: "Avgränsare för sammanfattning (förvalt '===')"
+ METADATA: "Metadata"
+ METADATA_HELP: "Standardvärden för metadata som ska visas på varje sida om det inte skrivs över av sidan"
+ NAME: "Namn"
+ CONTENT: "Innehåll"
+ SIZE: "Storlek"
+ ACTION: "Åtgärd"
+ REDIRECTS_AND_ROUTES: "Omdirigeringar och rutter"
+ CUSTOM_REDIRECTS: "Anpassade Redirects"
+ CUSTOM_REDIRECTS_HELP: "vägar för omdirigering till andra sidor. Standard Regex ersättning är giltig"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/ditt/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/din/omderigering"
+ CUSTOM_ROUTES: "Egna omdirigeringar"
+ CUSTOM_ROUTES_HELP: "vägar för alias till andra sidor. Standard Regex ersättning är giltig"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/ditt/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/din/rutt"
+ FILE_STREAMS: "Fil strömmar"
+ DEFAULT: "Förvalt"
+ PAGE_MEDIA: "Sidmedia"
+ OPTIONS: "Alternativ"
+ PUBLISHED: "Publicerad"
+ PUBLISHED_HELP: "Som standard publiceras en sida om du inte uttryckligen anger publicerad: falskt eller via en publish_date att vara i framtiden, eller unpublish_date i det förflutna"
+ DATE: "Datum"
+ DATE_HELP: "Variabeln datum ger dig möjlighet att uttryckligen välja ett datum som associeras med sidan."
+ PUBLISHED_DATE: "Publiceringsdatum"
+ PUBLISHED_DATE_HELP: "Kan ge ett datum för automatiskt publikation."
+ UNPUBLISHED_DATE: "Icke-publicerat datum"
+ UNPUBLISHED_DATE_HELP: "Kan ge ett datum för automatiskt avpublikation."
+ ROBOTS: "Robotar"
+ TAXONOMIES: "Taxonomier"
+ TAXONOMY: "Taxonomi"
+ ADVANCED: "Avancerat"
+ SETTINGS: "Inställningar"
+ FOLDER_NUMERIC_PREFIX: "Numeriskt prefix för katalog"
+ FOLDER_NUMERIC_PREFIX_HELP: "Numeriskt prefix som möjliggör manuell sortering och ökad synlighet"
+ FOLDER_NAME: "Mappnamn"
+ FOLDER_NAME_HELP: "Namnet på katalogen som lagras i filsystemet för sidan"
+ PARENT: "Förälder"
+ DEFAULT_OPTION_ROOT: "- Root -"
+ DEFAULT_OPTION_SELECT: "- Välj -"
+ DISPLAY_TEMPLATE: "Visa mall"
+ DISPLAY_TEMPLATE_HELP: "Sidtypen som översätter till vilken twig-mall som skapar sidan"
+ BODY_CLASSES: "Body klasser"
+ ORDERING: "Sortering"
+ PAGE_ORDER: "Sidordning"
+ OVERRIDES: "Överskrider"
+ MENU: "Meny"
+ MENU_HELP: "Strängen som kommer att användas i menyn. Om ej satt kommer titeln att användas."
+ SLUG: "Slug"
+ SLUG_HELP: "Variabeln slug möjliggör att ange sidans del i dess URL"
+ SLUG_VALIDATE_MESSAGE: "En slug innehåller bara små bokstäver, siffror och snedstreck"
+ PROCESS: "Processer"
+ PROCESS_HELP: "Kontrollera hur sidor bearbetas. Kan ställas per sida snarare än globalt"
+ DEFAULT_CHILD_TYPE: "Förvald typ för barn"
+ USE_GLOBAL: "Använd global"
+ ROUTABLE: "Tillgänglig"
+ ROUTABLE_HELP: "Om denna sida är tillgänglig via en URL"
+ CACHING: "Cachelagring"
+ VISIBLE: "Synlig"
+ VISIBLE_HELP: "Avgör om en sida är synlig i menyn."
+ DISABLED: "Inaktiverad"
+ ITEMS: "Poster"
+ ORDER_BY: "Sortera efter"
+ ORDER: "Sortering"
+ FOLDER: "Mapp"
+ ASCENDING: "Stigande"
+ DESCENDING: "Fallande"
+ ADD_MODULAR_CONTENT: "Lägg till modulärt innehåll"
+ PAGE_TITLE: "Sidtitel"
+ PAGE_TITLE_HELP: "Titeln på sidan"
+ PAGE: "Sida"
+ MODULAR_TEMPLATE: "Modulär mall"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Filnamn"
+ PARENT_PAGE: "Föräldersida"
+ HOME_PAGE: "Startsida"
+ HOME_PAGE_HELP: "Sidan som Grav använder som förvald landningssida"
+ DEFAULT_THEME: "Förvalt tema"
+ DEFAULT_THEME_HELP: "Välj standardtema för Grav (Antimatter är förvalt)"
+ TIMEZONE: "Tidszon"
+ TIMEZONE_HELP: "Skriv över standard tidzon på servern"
+ SHORT_DATE_FORMAT: "Kort format för visningsdatum"
+ SHORT_DATE_FORMAT_HELP: "Välj kort format för visningsdatum som används av teman"
+ LONG_DATE_FORMAT: "Långt format för visningsdatum"
+ LONG_DATE_FORMAT_HELP: "Välj långt format för visningsdatum som används av teman"
+ DEFAULT_ORDERING: "Standard sortering"
+ DEFAULT_ORDERING_HELP: "Sidor i en lista kommer att visa denna ordning om den inte överskrivits"
+ DEFAULT_ORDERING_DEFAULT: "Standard - baserat på mappnamn"
+ DEFAULT_ORDERING_FOLDER: "Mapp - baserat på prefixlöst mappnamn"
+ DEFAULT_ORDERING_TITLE: "Titel - baserad på titelfältet i sidhuvudet"
+ DEFAULT_ORDERING_DATE: "Datum - baserat på datumfält i sidhuvudet"
+ DEFAULT_ORDER_DIRECTION: "Förvald ordning"
+ DEFAULT_ORDER_DIRECTION_HELP: "Visa sorteringsordning av sidor i en lista"
+ DEFAULT_PAGE_COUNT: "Standard sidantal"
+ DEFAULT_PAGE_COUNT_HELP: "Standard för maximalt antal sidor i en lista"
+ DATE_BASED_PUBLISHING: "Datum-baserad publicering"
+ DATE_BASED_PUBLISHING_HELP: "Automatiskt (av)publicera inlägg baserat på datum"
+ EVENTS: "Händelser"
+ EVENTS_HELP: "Aktivera eller inaktivera specifika händelser. Inaktivering av dessa kan få vissa tillägg att sluta fungera"
+ REDIRECT_DEFAULT_ROUTE: "Standard för omdirigering"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Omdirigera automatiskt till en sidas standardrutt"
+ LANGUAGES: "Språk"
+ SUPPORTED: "Stöds"
+ SUPPORTED_HELP: "Kommaavgränsad lista över språkkoder (till exempel ' en, fr, de')"
+ TRANSLATIONS_ENABLED: "Stöd för översättning"
+ TRANSLATIONS_ENABLED_HELP: "Stöd för översättningar i Grav och tillägg"
+ TRANSLATIONS_FALLBACK: "Fallback för översättningar"
+ TRANSLATIONS_FALLBACK_HELP: "Reserv genom stödöversättningar om det aktiva språket inte finns"
+ ACTIVE_LANGUAGE_IN_SESSION: "Aktivt språk i session"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Lagra aktivt språk i session"
+ HTTP_HEADERS: "HTTP Headers"
+ EXPIRES: "Upphör"
+ EXPIRES_HELP: "Ställer in utgången i headern. Värdet är i sekunder."
+ CACHE_CONTROL: "Http-Cache-Control"
+ CACHE_CONTROL_HELP: "Ange ett giltigt cache-control värde såsom 'no-cache, no-store, must-revalidate'"
+ LAST_MODIFIED: "Senast ändrad"
+ LAST_MODIFIED_HELP: "Anger det sista modifierade header som kan hjälpa till att optimera proxy och webbläsarens cachelagring"
+ ETAG: "ETag"
+ ETAG_HELP: "Aktivera ETag för att identifiera när en sida har ändrats"
+ VARY_ACCEPT_ENCODING: "Variera accepterad kodning"
+ VARY_ACCEPT_ENCODING_HELP: "Ställer in ' Vary: Accept Encoding ' header att hjälpa till med proxy och CDN cachelagring"
+ MARKDOWN: "Markdown"
+ MARKDOWN_EXTRA: "Markdown extra"
+ MARKDOWN_EXTRA_HELP: "Aktivera stöd för Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ MARKDOWN_EXTRA_ESCAPE_FENCES: "Escape HTML-element i markdown extra staket"
+ MARKDOWN_EXTRA_ESCAPE_FENCES_HELP: "Escape HTML-element i markdown extra staket"
+ AUTO_LINE_BREAKS: "Automatiska radbrytningar"
+ AUTO_LINE_BREAKS_HELP: "Aktivera stöd för automatiska radbrytningar i markdown"
+ AUTO_URL_LINKS: "Automatisk URL-länkar"
+ AUTO_URL_LINKS_HELP: "Aktivera automatisk konvertering av webbadresser till HTML-hyperlänkar"
+ ESCAPE_MARKUP: "Escape markup"
+ ESCAPE_MARKUP_HELP: "Escape markup taggar i HTML-entiteter"
+ CACHING_HELP: "Global på/av för att aktivera/inaktivera cachelagring av Grav"
+ CACHE_CHECK_METHOD: "Cache metod"
+ CACHE_CHECK_METHOD_HELP: "Välj metod som Grav använder för att kontrollera om filer har ändrats."
+ CACHE_DRIVER: "Cachedrivrutin"
+ CACHE_DRIVER_HELP: "Välj vilken cache drivrutin Grav ska använda. 'Auto Detect' försöker hitta det bästa för dig"
+ CACHE_PREFIX: "Cache-prefix"
+ CACHE_PREFIX_HELP: "En identifierare för en del av Grav nyckeln. Ändra inte om du inte vet vad du gör."
+ CACHE_PREFIX_PLACEHOLDER: "Härrör från bas-URL (Åsidosätt genom att skriva slumpmässig sträng)"
+ CACHE_PURGE_JOB: "Kör schemalagda rensningsjobb"
+ CACHE_PURGE_JOB_HELP: "Med scheduler kan du regelbundet rensa ut gamla Doctrine file cachemappar"
+ CACHE_CLEAR_JOB: "Kör schemalagda rensningsjobb"
+ CACHE_CLEAR_JOB_HELP: "Med scheduler kan du regelbundet rensa Gravs cacheminne"
+ CACHE_JOB_TYPE: "Cache jobbtyp"
+ CACHE_JOB_TYPE_HELP: "Antingen rensa med 'standard' mappar cache rensning, eller med 'alla' mappar"
+ CACHE_PURGE: "Töm gamla cachar"
+ LIFETIME: "Livstid"
+ LIFETIME_HELP: "Anger cache-livstiden i sekunder. 0 = oändlig"
+ GZIP_COMPRESSION: "Gzip komprimering"
+ GZIP_COMPRESSION_HELP: "Aktivera GZip komprimering av Grav sidan för ökad prestanda."
+ TWIG_TEMPLATING: "Twig mallhantering"
+ TWIG_CACHING: "Twig cachelagring"
+ TWIG_CACHING_HELP: "Styr Twig caching mekanismen. Lämna detta aktiverat för bästa prestanda."
+ TWIG_DEBUG: "Twig debug"
+ TWIG_DEBUG_HELP: "Tillåter alternativet att inte ladda in Twig Debugger tillägget"
+ DETECT_CHANGES: "Upptäck ändringar"
+ DETECT_CHANGES_HELP: "Twig kommer automatiskt kompilera om Twig cachen om ändringar upptäcks i Twig mallar"
+ AUTOESCAPE_VARIABLES: "Autoescape variabler"
+ AUTOESCAPE_VARIABLES_HELP: "Autoescapes alla variabler. Detta kommer troligen att krascha din webbplats"
+ ASSETS: "Tillgångar"
+ CSS_PIPELINE: "CSS pipeline"
+ CSS_MINIFY: "Minifiera CSS"
+ JAVASCRIPT_PIPELINE: "JavaScript pipeline"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Rendera JS pipeline först"
+ JAVASCRIPT_MINIFY: "Minifiera JavaScript"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Aktivera tidsstämplar på filer"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Aktivera tidsstämplade filer"
+ COLLECTIONS: "Kollektioner"
+ ERROR_HANDLER: "Felhanterare"
+ DISPLAY_ERRORS: "Visa fel"
+ LOG_ERRORS: "Logga fel"
+ LOG_ERRORS_HELP: "Logga fel i /logs mappen"
+ DEBUGGER: "Debugger"
+ DEBUGGER_HELP: "Aktivera Grav debugger och följande inställningar"
+ DEBUG_TWIG: "Twig debug"
+ DEFAULT_IMAGE_QUALITY: "Förvald bildkvalitet"
+ CACHE_ALL: "Cachelagra alla bilder"
+ IMAGES_DEBUG: "Bild debug vattenstämpel"
+ UPLOAD_LIMIT: "Uppladdningsgräns för filer"
+ UPLOAD_LIMIT_HELP: "Ange maximal överföringsstorlek i byte (0 är obegränsad)"
+ ENABLE_MEDIA_TIMESTAMP: "Aktivera tidsstämplar på media"
+ SESSION: "Session"
+ SESSION_ENABLED_HELP: "Aktivera stöd för sessioner i Grav"
+ TIMEOUT: "Timeout"
+ TIMEOUT_HELP: "Anger sessionstimeout i sekunder"
+ SESSION_NAME_HELP: "En identifierare som används för att bilda namnet på session cookien"
+ SESSION_UNIQUENESS: "Unik sträng"
+ SESSION_UNIQUENESS_HELP: "MD5 hash av Gravs rotsökväg, dvs 'GRAV_ROOT' (standard) eller en baserat på strängen slumpmässiga 'security.salt'."
+ ABSOLUTE_URLS: "Absoluta URL: er"
+ ABSOLUTE_URLS_HELP: "Absoluta eller relativa webbadresser för 'base_url'"
+ PARAMETER_SEPARATOR: "Avgränsare för parameter"
+ PARAMETER_SEPARATOR_HELP: "Avgränsare för godkända parametrar som kan ändras för Apache på Windows"
+ TASK_COMPLETED: "Aktivitet slutförd"
+ EVERYTHING_UP_TO_DATE: "Allt är uppdaterat"
+ UPDATES_ARE_AVAILABLE: "uppdatering(ar) finns tillgängliga"
+ IS_AVAILABLE_FOR_UPDATE: "är tillgänglig för uppdatering"
+ IS_NOW_AVAILABLE: "är nu tillgängliga"
+ CURRENT: "Nuvarande"
+ UPDATE_GRAV_NOW: "Uppdatera Grav nu"
+ GRAV_SYMBOLICALLY_LINKED: "Grav är symboliskt länkat. Uppgradering är inte tillgängligt"
+ UPDATING_PLEASE_WAIT: "Uppdaterar... var god vänta, hämtar"
+ OF_THIS: "av detta"
+ OF_YOUR: "av din"
+ HAVE_AN_UPDATE_AVAILABLE: "har en uppdatering tillgänglig"
+ SAVE_AS: "Spara som"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Är du säker du vill ta bort denna sida och alla dess barn? Om sidan är översatt till andra språk, kommer dessa översättningar att sparas och måste tas bort separat. Annars tas sidans mapp bort tillsammans med dess undersidor. Denna åtgärd kan inte ångras."
+ AND: "och"
+ UPDATE_AVAILABLE: "Uppdatering finns"
+ METADATA_KEY: "Nyckel (t.ex. ' nyckelord')"
+ METADATA_VALUE: "Värde (t.ex. ' blogg, Grav')"
+ USERNAME_HELP: "Användarnamn borde vara mellan 3 och 16 tecken, inklusive små bokstäver, siffror, understreck och bindestreck. Versaler, blanksteg och specialtecken är inte tillåtna"
+ FULLY_UPDATED: "Uppdaterad"
+ SAVE_LOCATION: "Sparades till"
+ PAGE_FILE: "Sidmall"
+ PAGE_FILE_HELP: "Sidmallens filnamn, och som standard display-mallen för sidan"
+ NO_USER_ACCOUNTS: "Inga användare hittades, var god skapa en användare..."
+ NO_USER_EXISTS: "Ingen lokal användare finns för det här kontot, kan inte spara..."
+ REDIRECT_TRAILING_SLASH: "Avslutande snedstreck för omdirigering"
+ REDIRECT_TRAILING_SLASH_HELP: "Utföra en 301-omdirigering i stället för transparent hantering av avslutande snedstrecks URIs."
+ DEFAULT_DATE_FORMAT: "Datumformat för sida"
+ DEFAULT_DATE_FORMAT_HELP: "Sidans datumformat som används av Grav. Som standard försöker Grav att gissa ditt datumformat, men du kan ange ett format med hjälp av PHP: s datum syntax (t.ex.: Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Gissa automatiskt"
+ IGNORE_FILES: "Ignorera filer"
+ IGNORE_FILES_HELP: "Specifika filer att ignorera när sidorna bearbetas"
+ IGNORE_FOLDERS: "Ignorera mappar"
+ IGNORE_FOLDERS_HELP: "Specifika mappar att ignorera när sidorna bearbetas"
+ HIDE_EMPTY_FOLDERS: "Dölj tomma mappar"
+ HIDE_EMPTY_FOLDERS_HELP: "Om mappen inte har någon .md fil, bör det vara dolt i navigering samt unroutable"
+ HTTP_ACCEPT_LANGUAGE: "Ställ in språk från webbläsare"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Du kan välja att försöka ställa in språket baserat på 'http_accept_language' sidhuvudtagg i webbläsaren"
+ OVERRIDE_LOCALE: "Skriva över locale"
+ OVERRIDE_LOCALE_HELP: "Åsidosätta inställningen locale i PHP baserat på det aktuella språket"
+ REDIRECT: "Sidan omdirigeras"
+ REDIRECT_HELP: "Ange sökväg för sidan eller extern URL för denna sidan att omdirigera till. t.ex. '/ some/route' eller 'http://somesite.com'"
+ PLUGIN_STATUS: "Status för tillägg"
+ INCLUDE_DEFAULT_LANG: "Inkludera förvalt språk"
+ INCLUDE_DEFAULT_LANG_HELP: "Detta kommer att läggas till alla webbadresser på standardspråket med standardspråk. t.ex. '/sv/blog/mitt-blogginlägg'"
+ PAGES_FALLBACK_ONLY: "Reserv för sidor"
+ PAGES_FALLBACK_ONLY_HELP: "Endast 'reserv' för att hitta innehåll via språk som söjds, standardbeteendet är att visa vilket språk som helst som hittas om aktivt språk saknas"
+ ALLOW_URL_TAXONOMY_FILTERS: "URL taxonomi-filter"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Sid-baserade samlingar gör att du kan filtrera via '/taxonomy:value '."
+ REDIRECT_DEFAULT_CODE: "Standardkod för omdirigering"
+ REDIRECT_DEFAULT_CODE_HELP: "Http-statuskod för omdirigering"
+ IGNORE_HIDDEN: "Ignorera dolda"
+ IGNORE_HIDDEN_HELP: "Ignorera alla filer och mappar som börjar med en PUNKT"
+ WRAPPED_SITE: "Wrapped/inbäddad site"
+ WRAPPED_SITE_HELP: "För teman/plugins att veta om Grav är inbäddad i en annan plattform"
+ FALLBACK_TYPES: "Tillåtna fallback typer"
+ FALLBACK_TYPES_HELP: "Tillåtna filtyper som kan hittas via sidan vägen. Som standard någon av de medityper som stöds."
+ INLINE_TYPES: "Inline fallback typer"
+ INLINE_TYPES_HELP: "En lista över filtyper som bör visas inline i stället för att laddas ner"
+ APPEND_URL_EXT: "Lägg till URL-tillägget"
+ APPEND_URL_EXT_HELP: "Lägger till en anpassad förlängning till sidans URL. Observera att Grav kommer att leta efter ' . . twig' mall"
+ PAGE_MODES: "Sidinställning"
+ PAGE_TYPES: "Sidtyper"
+ PAGE_TYPES_HELP: "Bestämmer typer av sidor som Grav stöder, ordningen bestämmer vilken typ att falla tillbaka på om begäran är tvetydig"
+ ACCESS_LEVELS: "Åtkomstnivåer"
+ GROUPS: "Grupper"
+ GROUPS_HELP: "Listan över grupper som användaren är en del av"
+ ADMIN_ACCESS: "Adminåtkomst"
+ SITE_ACCESS: "Sajtåtkomst"
+ INVALID_SECURITY_TOKEN: "Ogiltig säkerhetstoken"
+ ACTIVATE: "Aktivera"
+ TWIG_UMASK_FIX: "Umask Fix"
+ TWIG_UMASK_FIX_HELP: "Som standard skapar Twig cachelagrade filer som 0755, fix växlar detta till 0775"
+ CACHE_PERMS: "Cache behörigheter"
+ CACHE_PERMS_HELP: "Standard cachemapps rättigheter. Vanligtvis 0755 eller 0775 beroende på inställning"
+ REMOVE_SUCCESSFUL: "Borttagning lyckades"
+ REMOVE_FAILED: "Borttagning misslyckades"
+ HIDE_HOME_IN_URLS: "Dölj hemvägen i URL"
+ HIDE_HOME_IN_URLS_HELP: "Kommer att säkerställa att standardrutten för alla sidor under hem inte refererar till hemmets vanliga rutt"
+ TWIG_FIRST: "Behandla Twig först"
+ TWIG_FIRST_HELP: "Om du aktiverade Twig-sidbehandling, kan du konfigurera Twig att bearbeta före eller efter markdown"
+ SESSION_SECURE: "Säker"
+ SESSION_HTTPONLY: "Endast HTTP"
+ REVERSE_PROXY: "Omvänd proxy"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Ogiltig frontmatter, kunde inte spara"
+ ADD_FOLDER: "Lägg till mapp"
+ PROXY_URL: "Proxy-URL"
+ PROXY_URL_HELP: "Ange proxy värd eller IP och PORT"
+ NOTHING_TO_SAVE: "Inget att spara"
+ FILE_ERROR_ADD: "Ett fel inträffade när filen skulle läggas till"
+ FILE_ERROR_UPLOAD: "Ett fel inträffade när filen skulle laddas upp"
+ FILE_UNSUPPORTED: "Filtypen stöds ej"
+ ADD_ITEM: "Ny"
+ FILE_TOO_LARGE: "Filen är för stor för att laddas upp, maximal filstorlek är %s enligt dina PHP-inställningar. Öka inställningen 'post_max_size'"
+ INSTALLING: "Installerar"
+ LOADING: "Laddar.."
+ ERROR_INSTALLING_PACKAGES: "Fel vid installation av paket(en)"
+ INSTALLING_DEPENDENCIES: "Installerar beroenden..."
+ INSTALLING_PACKAGES: "Installerar paket.."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Paket installerades."
+ READY_TO_INSTALL_PACKAGES: "Redo att installera paket"
+ PACKAGES_NOT_INSTALLED: "Paket installerades inte"
+ PACKAGES_NEED_UPDATE: "Paketet finns redan installerat, ny version finns"
+ PACKAGES_SUGGESTED_UPDATE: "Paket finns redan installerat, och versionen är ok, men kommer att uppdateras"
+ REMOVE_THE: "Ta bort %s"
+ CONFIRM_REMOVAL: "Är du säker du vill ta bort denna %s?"
+ REMOVED_SUCCESSFULLY: "%s har raderats"
+ ERROR_REMOVING_THE: "Fel uppstod vid borttagning av %s"
+ READY_TO_UPDATE_PACKAGES: "Redo att uppdatera paket"
+ ERROR_UPDATING_PACKAGES: "Fel vid uppdatering av paket"
+ UPDATING_PACKAGES: "Uppdaterar paket.."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Paket har uppdaterats."
+ UPDATING: "Uppdaterar"
+ GPM_RELEASES: "GPM releaser"
+ AUTO: "Automatisk"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Stabil version"
+ TESTING: "Testar"
+ FRONTMATTER_IGNORE_FIELDS: "Ignorera frontmatter fält"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "Paketet %s har installerats utan fel"
+ DROP_FILES_HERE_TO_UPLOAD: "Släpp dina filer här eller Klicka i rutan "
+ INSERT: "Infoga"
+ UNDO: "Ångra"
+ REDO: "Upprepa"
+ HEADERS: "Headers"
+ BOLD: "Fetstil"
+ ITALIC: "Kursiv"
+ STRIKETHROUGH: "Genomstruken"
+ SUMMARY_DELIMITER: "Avgänsare för sammanfattning"
+ LINK: "Länk"
+ IMAGE: "Bild"
+ BLOCKQUOTE: "Citat"
+ UNORDERED_LIST: "Osorterad lista"
+ ORDERED_LIST: "Sorterad lista"
+ EDITOR: "Redigerare"
+ PREVIEW: "Förhandsgranska"
+ FULLSCREEN: "Helskärm"
+ MODULAR: "Modulär"
+ NON_ROUTABLE: "Icke tillgänglig"
+ NON_MODULAR: "Icke modulär"
+ NON_VISIBLE: "Icke synlig"
+ NON_PUBLISHED: "Icke-publicerad"
+ CHARACTERS: "tecken"
+ PUBLISHING: "Publicering"
+ NOTIFICATIONS: "Notiser"
+ MEDIA_TYPES: "Mediatyper"
+ IMAGE_OPTIONS: "Bildalternativ"
+ MIME_TYPE: "MIME-typ"
+ THUMB: "Miniatyr"
+ TYPE: "Typ"
+ FILE_EXTENSION: "Filändelse"
+ LEGEND: "Sidförklaring"
+ MEMCACHE_SERVER: "Memcache server"
+ MEMCACHE_SERVER_HELP: "Memcache server-adress"
+ MEMCACHE_PORT: "Memcache port"
+ MEMCACHE_PORT_HELP: "Memcache server port"
+ MEMCACHED_SERVER: "Memcache server"
+ MEMCACHED_SERVER_HELP: "Memcache server-adress"
+ MEMCACHED_PORT: "Memcache port"
+ MEMCACHED_PORT_HELP: "Memcache server port"
+ REDIS_SERVER: "Redis server"
+ REDIS_SERVER_HELP: "Redis server-adress"
+ REDIS_PORT: "Redis port"
+ REDIS_PORT_HELP: "Redis serverport"
+ ALL: "Alla"
+ FROM: "från"
+ TO: "till"
+ RELEASE_DATE: "Releasedatum"
+ SORT_BY: "Sortera efter"
+ RESOURCE_FILTER: "Filtrera..."
+ FORCE_SSL: "Tvinga SSL"
+ NEWS_FEED: "Nyhetsflöde"
+ EXTERNAL_URL: "Extern URL"
+ SESSION_PATH: "Sessionens sökväg"
+ CUSTOM_BASE_URL: "Anpassad bas-URL"
+ FILEUPLOAD_PREVENT_SELF: 'Kan inte använda "%s" utanför sidor.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Det går inte att ladda upp filen %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'Det går inte att flytta filen %s till "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'Ångra uppladdning'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Är du säker på att du vill avbryta den här uppladdningen?'
+ DROPZONE_DEFAULT_MESSAGE: 'Släpp dina filer här eller Klicka i rutan '
+ DROPZONE_FALLBACK_MESSAGE: 'Din webbläsare inte stöder dra och släpp filöverföringar.'
+ DROPZONE_INVALID_FILE_TYPE: "Du kan inte ladda upp filer av den här typen."
+ DROPZONE_MAX_FILES_EXCEEDED: "Du kan inte ladda upp fler filer."
+ DROPZONE_REMOVE_FILE: "Radera fil"
+ DROPZONE_RESPONSE_ERROR: "Servern svarade med {{statusCode}} kod."
+ PREMIUM_PRODUCT: "Premium"
+ ADMIN_CACHING: "Aktivera cachelagring av Admin"
+ ADMIN_CACHING_HELP: "Cachelagring i admin kan styras självständigt från front-end"
+ DESTINATION_NOT_SPECIFIED: "Destination inte angiven"
+ UPLOAD_ERR_NO_TMP_DIR: "Saknar en temporär katalog"
+ SESSION_SPLIT: "Session Split"
+ ERROR_SYSTEM: "Systemfel"
+ IMAGES_AUTO_FIX_ORIENTATION: "Åtgärda orientering automatiskt"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Automatiskt korrigera bildens läge baserat på Exif-data"
+ REDIS_SOCKET: "Redis socket"
+ REDIS_SOCKET_HELP: "Redis socket"
+ NOT_SET: "Har inte angetts"
+ PERMISSIONS: "Behörigheter"
+ NEVER_CACHE_TWIG: "Inaktivera cache för twig"
+ ALLOW_WEBSERVER_GZIP: "Tillåt WebServer Gzip"
+ OFFLINE_WARNING: "Anslutningen till GPM inte kan fastställas"
+ CLI_COMPATIBILITY: "CLI kompatibilitet"
diff --git a/user/plugins/admin/languages/th.yaml b/user/plugins/admin/languages/th.yaml
new file mode 100644
index 0000000..68f3a82
--- /dev/null
+++ b/user/plugins/admin/languages/th.yaml
@@ -0,0 +1,690 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "นี่เป็นรุ่นทดลอง หากคุณใช้บนเว็บจริงคุณต้องยอมรับในความเสี่ยงนั้นเอง"
+ ADMIN_REPORT_ISSUE: "พบปัญหา กรุณารายงานบน GitHub"
+ EMAIL_FOOTER: "ขับเคลื่อนด้วย Grav - The Modern Flat File CMS"
+ LOGIN_BTN: "เข้าสู่ระบบ"
+ LOGIN_BTN_FORGOT: "ลืมรหัสผ่าน"
+ LOGIN_BTN_RESET: "กำหนดรหัสผ่านใหม่"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "ส่งคำแนะนำการตั้งค่าใหม่"
+ LOGIN_BTN_CLEAR: "ล้างฟอร์ม"
+ LOGIN_BTN_CREATE_USER: "สร้างผู้ใช้"
+ LOGIN_LOGGED_IN: "คุณได้ล็อคอินเรียบร้อยแล้ว"
+ LOGIN_FAILED: "ไม่สามารถเข้าสู่ระบบได้"
+ LOGGED_OUT: "คุณได้ออกจากระบบแล้ว"
+ RESET_NEW_PASSWORD: "กรุณาใส่รหัสผ่านใหม่ …"
+ RESET_LINK_EXPIRED: "ลิงก์หมดอายุ โปรดลองอีกครั้ง"
+ RESET_PASSWORD_RESET: "ตั้งค่ารหัสผ่านแล้ว"
+ RESET_INVALID_LINK: "ตั้งค่าการเชื่อมโยงที่ใช้ไม่ถูกต้อง โปรดลองอีกครั้ง"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "คำแนะนำการตั้งค่ารหัสผ่านของคุณได้ถูกส่งผ่านทางอีเมล์ %s"
+ FORGOT_FAILED_TO_EMAIL: "ไม่สามารถส่งอีเมล์แนะนำ กรุณาลองใหม่อีกครั้ง"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "ไม่สามารถรีเซ็ตรหัสผ่านสำหรับ %s ไม่มีที่อยู่อีเมลที่ถูกกำหนด"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "ไม่พบชื่อผู้ใช้ %s "
+ FORGOT_EMAIL_NOT_CONFIGURED: "ไม่สามารถรีเซ็ตรหัสผ่าน เว็บไซต์นี้ไม่ได้กำหนดค่าให้ส่งอีเมล์"
+ FORGOT_EMAIL_SUBJECT: "ขอรีเซ็ตรหัสผ่าน %s"
+ FORGOT_EMAIL_BODY: "รีเซ็ตรหัสผ่าน เรียน %1$s
คุณได้ทำการร้องขอบน %4$s ในการรีเซ็ตรหัสผ่านของคุณ.
คลิกที่นี้เพื่อรีเซ็ตรหัสผ่าน
อีกวิธีหนึ่งคือ คัดลอก URL ต่อไปนี้ในแถบอยู่ของเบราว์เซอร์ของคุณ:
%2$s
นับถือ, < br/ > < br / >%3$s
"
+ MANAGE_PAGES: "จัดการหน้า"
+ CONFIGURATION: "ตั้งค่าคอนฟิก"
+ PAGES: "หน้า"
+ PLUGINS: "ส่วนขยาย"
+ PLUGIN: "ปลั๊กอิน"
+ THEMES: "ธีม"
+ LOGOUT: "ออกจากระบบ"
+ BACK: "ย้อนกลับ"
+ NEXT: "ถัดไป"
+ PREVIOUS: "ก่อนหน้านี้"
+ ADD_PAGE: "เพิ่มหน้า"
+ ADD_MODULAR: "เพิ่มโมดูล"
+ MOVE: "ย้าย"
+ DELETE: "ลบ"
+ VIEW: "ดู"
+ SAVE: "บันทึก"
+ NORMAL: "ทั่วไป"
+ EXPERT: "ขั้นสูง"
+ EXPAND_ALL: "แสดงทั้งหมด"
+ COLLAPSE_ALL: "ซ่อนทั้งหมด"
+ ERROR: "ผิดพลาด"
+ CLOSE: "ปิด"
+ CANCEL: "ยกเลิก"
+ CONTINUE: "ดำเนินการต่อ"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "ต้องการยืนยัน"
+ MODAL_CHANGED_DETECTED_TITLE: "พบการเปลี่ยนแปลง"
+ MODAL_CHANGED_DETECTED_DESC: "คุณมีการเปลี่ยนแปลงแต่ยังไม่ได้บันทึก คุณแน่ต้องการที่จะออกโดยไม่บันทึกสิ่งที่แก้ไข?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "ต้องการยืนยัน"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "คุณแน่ใจหรือคุณต้องการลบแฟ้มนี้หรือไม่ การกระทำนี้ไม่สามารถเลิกทำ"
+ ADD_FILTERS: "เพิ่มตัวกรอง"
+ SEARCH_PAGES: "หน้าค้นหา"
+ VERSION: "เวอร์ชั่น"
+ WAS_MADE_WITH: "ทำด้วย"
+ BY: "โดย"
+ UPDATE_THEME: "ปรับปรุงธีม"
+ UPDATE_PLUGIN: "ปรับปรุงปลั๊กอิน"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "ของธีมนี้มีให้บริการแล้ว"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "ของปลั๊กอินนี้พร้อมให้ใช้งานได้"
+ AUTHOR: "ผู้เขียน"
+ HOMEPAGE: "หน้าแรก"
+ DEMO: "ตัวอย่าง"
+ BUG_TRACKER: "ตรวจสอบและติดตามข้อผิดพลาด"
+ KEYWORDS: "คีย์เวิร์ด"
+ LICENSE: "ลิขสิทธิ์"
+ DESCRIPTION: "รายละเอียด"
+ README: "โปรดอ่าน"
+ REMOVE_THEME: "ถอนการติดตั้งธีม"
+ INSTALL_THEME: "ติดตั้งธีม"
+ THEME: "ธีม"
+ BACK_TO_THEMES: "กลับไปที่ธีม"
+ BACK_TO_PLUGINS: "กลับไปที่ปลั๊กอิน"
+ CHECK_FOR_UPDATES: "ตรวจสอบการอัปเดต"
+ ADD: "เพิ่ม"
+ CLEAR_CACHE: "ล้างแคช"
+ CLEAR_CACHE_ALL_CACHE: "แคชทั้งหมด"
+ CLEAR_CACHE_ASSETS_ONLY: "Assets เท่านั้น"
+ CLEAR_CACHE_IMAGES_ONLY: "เฉพาะรูปภาพ"
+ CLEAR_CACHE_CACHE_ONLY: "แคชเท่านั้น"
+ CLEAR_CACHE_TMP_ONLY: "ชั่วคราวเท่านั้น"
+ DASHBOARD: "แผงควบคุม"
+ UPDATES_AVAILABLE: "อัพเดตที่มีอยู่"
+ DAYS: "วัน"
+ UPDATE: "อัพเดท"
+ BACKUP: "สำรองข้อมูล"
+ STATISTICS: "สถิติ"
+ TODAY: "วันนี้"
+ WEEK: "สัปดาห์"
+ MONTH: "เดือน"
+ LATEST_PAGE_UPDATES: "หน้าล่าสุด"
+ MAINTENANCE: "การบำรุงรักษา"
+ UPDATED: "ปรับปรุงเรียบร้อย"
+ MON: "จ."
+ TUE: "อ."
+ WED: "พ."
+ THU: "พฤ."
+ FRI: "ศ."
+ SAT: "ส."
+ SUN: "อา."
+ COPY: "คัดลอก"
+ EDIT: "แก้ไข"
+ CREATE: "สร้างใหม่"
+ GRAV_ADMIN: "การจัดการระบบ"
+ GRAV_OFFICIAL_PLUGIN: "ปลั๊กอิน Grav อย่างเป็นทางการ"
+ GRAV_OFFICIAL_THEME: "ธีม Grav อย่างเป็นทางการ"
+ PLUGIN_SYMBOLICALLY_LINKED: "ปลั๊กอินนี้สัญลักษณ์มีการเชื่อมโยง จะตรวจไม่พบการปรับปรุง"
+ THEME_SYMBOLICALLY_LINKED: "ธีมนี้สัญลักษณ์มีการเชื่อมโยง จะตรวจไม่พบการปรับปรุง"
+ REMOVE_PLUGIN: "ลบปลั๊กอิน"
+ INSTALL_PLUGIN: "ติดตั้งปลั๊กอิน"
+ AVAILABLE: "ใช้งานได้"
+ INSTALLED: "ติดตั้งอยู่"
+ INSTALL: "ติดตั้ง"
+ ACTIVE_THEME: "เปิดใช้งานธีม"
+ SWITCHING_TO: "สลับไป"
+ SWITCHING_TO_DESCRIPTION: "ด้วยการเปลี่ยนธีมที่แตกต่างกัน ไม่มีการรับประกันว่า จะรองรับหน้าเค้าโครงทั้งหมดได้ อาจก่อให้เกิดข้อผิดพลาดเมื่อคุณพยายามโหลดหน้าดังกล่าว"
+ SWITCHING_TO_CONFIRMATION: "คุณต้องการดำเนินต่อ และเปลี่ยนธีม"
+ CREATE_NEW_USER: "สร้างผู้ใช้ใหม่"
+ REMOVE_USER: "ลบผู้ใช้"
+ ACCESS_DENIED: "ถูกปฏิเสธการเข้าถึง"
+ ACCOUNT_NOT_ADMIN: "บัญชีของคุณไม่มีสิทธิ์ของผู้ดูแลระบบ"
+ PHP_INFO: "ข้อมูล PHP"
+ INSTALLER: "โปรแกรมติดตั้ง"
+ AVAILABLE_THEMES: "ธีมที่พร้อมใช้งาน"
+ AVAILABLE_PLUGINS: "ปลั๊กอินพร้อมใช้งาน"
+ INSTALLED_THEMES: "ธีมที่ติดตั้งแล้ว"
+ INSTALLED_PLUGINS: "ปลั๊กอินที่ติดตั้ง"
+ BROWSE_ERROR_LOGS: "ดูล็อกข้อผิดพลาด"
+ SITE: "เว็บไซต์"
+ INFO: "ข้อมูล"
+ SYSTEM: "ระบบ"
+ USER: "ผู้ใช้"
+ ADD_ACCOUNT: "เพิ่มผู้ใช้งาน"
+ SWITCH_LANGUAGE: "สลับภาษา"
+ SUCCESSFULLY_ENABLED_PLUGIN: "เปิดใช้งานปลั๊กอินเรียบร้อยแล้ว"
+ SUCCESSFULLY_DISABLED_PLUGIN: "ปิดการใช้งานปลั๊กอินเรียบร้อยแล้ว"
+ SUCCESSFULLY_CHANGED_THEME: "เปลี่ยนแปลงธีมเริ่มต้นเรียบร้อยแล้ว"
+ INSTALLATION_FAILED: "ติดตั้งล้มเหลว"
+ INSTALLATION_SUCCESSFUL: "ติดตั้งสำเร็จแล้ว"
+ UNINSTALL_FAILED: "การถอนการติดตั้งไม่สําเร็จ"
+ UNINSTALL_SUCCESSFUL: "ถอนการติดตั้งสำเร็จ"
+ SUCCESSFULLY_SAVED: "บันทึกเรียบร้อยแล้ว"
+ SUCCESSFULLY_COPIED: "คัดลอกเสร็จเรียบร้อยแล้ว"
+ REORDERING_WAS_SUCCESSFUL: "จัดลำดับใหม่เรียบร้อยแล้ว"
+ SUCCESSFULLY_DELETED: "ลบเรียบร้อยแล้ว"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "ภาษาเปลี่ยนเรียบร้อยแล้ว"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "คุณมีสิทธิ์ไม่เพียงพอสำหรับงาน"
+ CACHE_CLEARED: "ล้างแคช"
+ METHOD: "วิธีการ"
+ ERROR_CLEARING_CACHE: "ข้อผิดพลาดในการเคลียร์แคช"
+ AN_ERROR_OCCURRED: "เกิดข้อผิดพลาด"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "ข้อมูลสำรองของคุณจะพร้อมสำหรับการดาวน์โหลด"
+ DOWNLOAD_BACKUP: "ดาวน์โหลดข้อมูลสำรอง"
+ PAGES_FILTERED: "หน้าการกรอง"
+ NO_PAGE_FOUND: "ไม่พบหน้าที่ต้องการ"
+ INVALID_PARAMETERS: "พารามิเตอร์ไม่ถูกต้อง"
+ NO_FILES_SENT: "ไม่มีแฟ้มที่ส่ง"
+ EXCEEDED_FILESIZE_LIMIT: "ขนาดไฟล์เกินขีดจำกัดจากค่าที่กำหนดไว้ของ PHP"
+ UNKNOWN_ERRORS: "ไม่ทราบข้อผิด"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "ขนาดไฟล์เกินขีดจำกัดจากค่าที่กำหนดไว้ของ Grav"
+ UNSUPPORTED_FILE_TYPE: "ชนิดแฟ้มที่ไม่สนับสนุน"
+ FAILED_TO_MOVE_UPLOADED_FILE: "ไม่สามารถย้ายแฟ้มอัปโหลด"
+ FILE_UPLOADED_SUCCESSFULLY: "ไฟล์อัพโหลดเรียบร้อยแล้ว"
+ FILE_DELETED: "แฟ้มที่ถูกลบ"
+ FILE_COULD_NOT_BE_DELETED: "ไม่สามารถลบแฟ้ม"
+ FILE_NOT_FOUND: "ไม่พบไฟล์"
+ NO_FILE_FOUND: "ไม่พบไฟล์"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav ได้ทำการปรับปรุง"
+ GRAV_UPDATE_FAILED: "ปรับปรุง Grav ล้มเหลว"
+ EVERYTHING_UPDATED: "ทุกอย่างปรับปรุงเรียบร้อย"
+ UPDATES_FAILED: "การปรับปรุงล้มเหลว"
+ AVATAR_BY: "อวตารโดย"
+ AVATAR_UPLOAD_OWN: "หรืออัปโหลดของคุณเอง..."
+ LAST_BACKUP: "สำรองข้อมูลครั้งล่าสุด"
+ FULL_NAME: "ชื่อ-สกุล"
+ USERNAME: "ชื่อผู้ใช้"
+ EMAIL: "อีเมล"
+ USERNAME_EMAIL: "ชื่อผู้ใช้หรืออีเมล"
+ PASSWORD: "รหัสผ่าน"
+ PASSWORD_CONFIRM: "ยืนยันรหัสผ่าน"
+ TITLE: "หัวเรื่อง"
+ LANGUAGE: "ภาษา"
+ ACCOUNT: "บัญชีผู้ใช้"
+ EMAIL_VALIDATION_MESSAGE: "ต้องเป็น e-mail ถูกต้อง"
+ PASSWORD_VALIDATION_MESSAGE: "รหัสผ่านต้องประกอบด้วยอย่างน้อยหนึ่งหมายเลข และหนึ่งใหญ่ และอักษรพิมพ์เล็ก และอักขระอย่างน้อย 8 หรือมากกว่า"
+ LANGUAGE_HELP: "ตั้งค่าภาษาโปรด"
+ MEDIA: "สื่อ"
+ DEFAULTS: "ค่าเริ่มต้น"
+ SITE_TITLE: "ชื่อเรื่องของไซต์"
+ SITE_TITLE_PLACEHOLDER: "Site wide title"
+ SITE_TITLE_HELP: "ชื่อเริ่มต้นสำหรับไซต์ของคุณ ซึ่งจะใช้ในธีม"
+ SITE_DEFAULT_LANG: "ภาษาเริ่มต้น"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "ภาษาเริ่มต้นที่จะถูกใช้โดยแท็กของธีม "
+ SITE_DEFAULT_LANG_HELP: "ภาษาเริ่มต้นที่จะถูกใช้โดยแท็กของธีม "
+ DEFAULT_AUTHOR: "ผู้เขียนเริ่มต้น"
+ DEFAULT_AUTHOR_HELP: "ชื่อผู้เขียนเริ่มต้น มักใช้ในธีมหรือเนื้อหาของหน้า"
+ DEFAULT_EMAIL: "ค่าอีเมล์เริ่มต้น"
+ DEFAULT_EMAIL_HELP: "อีเมล์เริ่มต้นการอ้างอิงในธีมหรือเพจ"
+ TAXONOMY_TYPES: "ประเภทของ Taxonomy"
+ TAXONOMY_TYPES_HELP: "ประเภทของ Taxonomy ต้องกำหนดที่นี่หากคุณต้องการจะใช้งานในเพจ "
+ PAGE_SUMMARY: "หน้าสรุป"
+ ENABLED: "เปิดใช้งาน"
+ ENABLED_HELP: "เปิดใช้งานสรุปเพจ (ค่าสรุปเพจจะเหมือนกับเพจคอนเทนท์)"
+ 'YES': "ใช่"
+ 'NO': "ไม่ถูกต้อง"
+ SUMMARY_SIZE: "สรุปขนาด"
+ SUMMARY_SIZE_HELP: "จำนวนอักขระของหน้าเพื่อใช้เป็นเนื้อหาที่สรุป"
+ FORMAT: "รูปแบบ"
+ FORMAT_HELP: "ย่อ = ใช้เมื่อเกิดกรณีมีข้อจำกัดของขนาด หรือความยาว แบบยาว = จะมองข้ามเรื่องข้อจำกัด"
+ SHORT: "ย่อ"
+ LONG: "ยาว"
+ DELIMITER: "ตัวคั่น"
+ DELIMITER_HELP: "ตัวสรุป (ค่าเริ่มต้น ' ===')"
+ METADATA: "ข้อมูล Meta"
+ METADATA_HELP: "ค่าข้อมูลเมตาเริ่มต้นที่จะแสดงทุกหน้ายกเว้นว่าถูกแทนที่ด้วยการกำหนดในเพจ"
+ NAME: "ชื่อ"
+ CONTENT: "เนื้อหา"
+ REDIRECTS_AND_ROUTES: "เปลี่ยนเส้นทางและเส้นทาง"
+ CUSTOM_REDIRECTS: "เปลี่ยนเส้นทางที่กำหนดเอง"
+ CUSTOM_REDIRECTS_HELP: "เส้นทางการเปลี่ยนเส้นทางไปยังหน้าอื่น ๆ มาตรฐาน Regex แทนที่ ถูกต้อง"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/your/redirect"
+ CUSTOM_ROUTES: "กำหนด Routes เอง"
+ CUSTOM_ROUTES_HELP: "เส้นทางไปยังนามแฝงไปยังหน้าอื่น ๆ มาตรฐาน Regex แทนที่ถูกต้อง"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/your/route"
+ FILE_STREAMS: "ไฟล์สตรีม"
+ DEFAULT: "ค่าเริ่มต้น"
+ PAGE_MEDIA: "หน้า Media"
+ OPTIONS: "ตัวเลือก"
+ PUBLISHED: "เผยแพร่แล้ว"
+ PUBLISHED_HELP: "โดยค่าเริ่มต้น เพจจะถูกเผยแพร่เว้นแต่คุณจะได้กำหนดการเผยแพร่: false หรือโดยกำหนดวันที่ล่วงหน้า หรือหยุดการเผยแพร่เพื่อเวลาผ่านไป"
+ DATE: "วันที่"
+ DATE_HELP: "ค่าวันที่จะสามารถให้คุณกำหนดวันที่ที่เกี่ยวกับกับเพจนี้"
+ PUBLISHED_DATE: "วันเผยแพร่"
+ PUBLISHED_DATE_HELP: "สามารถกำหนดวันที่ในการเผยแพร่อัตโนมัติ"
+ UNPUBLISHED_DATE: "วันสิ้นสุดการเผยแพร่"
+ UNPUBLISHED_DATE_HELP: "สามารถกำหนดวันที่ในการหยุดเผยแพร่อัตโนมัติ."
+ ROBOTS: "Robots"
+ TAXONOMIES: "Taxonomies"
+ TAXONOMY: "Taxonomy"
+ ADVANCED: "ขั้นสูง"
+ SETTINGS: "การตั้งค่า"
+ FOLDER_NUMERIC_PREFIX: "คำนำหน้าตัวเลขของโฟลเดอร์"
+ FOLDER_NUMERIC_PREFIX_HELP: "ตัวเลขนำหน้าที่จะกำหนดการเรียงเองและมีผลกับการแสดงผล"
+ FOLDER_NAME: "ชื่อโฟลเดอร์"
+ FOLDER_NAME_HELP: "ชื่อโฟลเดอร์ที่จะเก็บในระบบไฟล์สำหรับเพจนี้ "
+ PARENT: "สืบทอด"
+ DEFAULT_OPTION_ROOT: "-ราก-"
+ DEFAULT_OPTION_SELECT: "-เลือก-"
+ DISPLAY_TEMPLATE: "แสดงเทมเพลต"
+ DISPLAY_TEMPLATE_HELP: "ประเภทเพจที่จะแปลด้วย Twig เทมเพลตในการ renders เพจ"
+ BODY_CLASSES: "Body Classes"
+ ORDERING: "การจัดเรียง"
+ PAGE_ORDER: "ลำดับหน้า"
+ OVERRIDES: "การแทน"
+ MENU: "เมนู"
+ MENU_HELP: "ข้อความที่จะใช้ในเมนู ถ้าไม่ได้กำหนดก็จะใช้ชื่อเรื่อง"
+ SLUG: "Slug"
+ SLUG_HELP: "ค่าตัวแปลใน slug จะอนุญาติให้คุณกำหนดเพจ URL ว่าต้องการให้เป็นชื่อใด"
+ SLUG_VALIDATE_MESSAGE: "slug ต้องประกอบด้วยอักษรตัวพิมพ์เล็กและเส้นประเท่านั้น"
+ PROCESS: "กระบวนการ"
+ PROCESS_HELP: "ควบคุมวิธีการประมวลผลหน้า สามารถตั้งค่าได้แต่ละหน้า แทนการใช้ค่าเริ่มต้น"
+ DEFAULT_CHILD_TYPE: "ชนิดของตัวย่อยเริ่มต้น"
+ USE_GLOBAL: "ใช้ค่าตั้งต้น"
+ ROUTABLE: "กำหนดเส้นทาง"
+ ROUTABLE_HELP: "หากเพจนี้สามารถเข้าถึงผ่าน URL"
+ CACHING: "การเก็บแคช"
+ VISIBLE: "การมองเห็น"
+ VISIBLE_HELP: "กำหนดหากหน้าจะปรากฏในการนำทาง"
+ DISABLED: "ไม่ใช้งาน"
+ ITEMS: "รายการ"
+ ORDER_BY: "เรียงตาม"
+ ORDER: "ลำดับ"
+ FOLDER: "โฟลเดอร์"
+ ASCENDING: "น้อยไปมาก"
+ DESCENDING: "มากไปน้อย"
+ ADD_MODULAR_CONTENT: "เพิ่มเนื้อหาแบบแยกส่วน"
+ PAGE_TITLE: "ชื่อหน้า"
+ PAGE_TITLE_HELP: "ชื่อของหน้า"
+ PAGE: "หน้า"
+ MODULAR_TEMPLATE: "แม่แบบโมดูลาร์"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "ชื่อไฟล์"
+ PARENT_PAGE: "หน้าหลัก"
+ HOME_PAGE: "โฮมเพจ"
+ HOME_PAGE_HELP: "หน้าที่ Grav จะใช้เป็นหน้าเริ่มต้นค่าเริ่มต้น"
+ DEFAULT_THEME: "ธีมเริ่มต้น"
+ DEFAULT_THEME_HELP: "ตั้งค่าธีมเริ่มต้นที่ Grav ใช้ (ค่าเริ่มต้นคือ Antimatter)"
+ TIMEZONE: "เขตเวลา"
+ TIMEZONE_HELP: "แทนที่เขตเวลาเริ่มต้นเซิร์ฟเวอร์"
+ SHORT_DATE_FORMAT: "รูปแบบการแสดงวันที่แบบย่อ"
+ SHORT_DATE_FORMAT_HELP: "ตั้งค่ารูปแบบวันแบบสั้นที่ธีมสามารถใช้ได้"
+ LONG_DATE_FORMAT: "แสดงรูปแบบวันแบบยาว"
+ LONG_DATE_FORMAT_HELP: "ตั้งค่ารูปแบบวันแบบยาวที่ธีมสามารถใช้ได้"
+ DEFAULT_ORDERING: "การเรียงเริ่มต้น"
+ DEFAULT_ORDERING_HELP: "เพจในลิสจะแสดงโดยใช้การเรียงลำดับนี้ เว้นเสียแต่จะมีการ Override "
+ DEFAULT_ORDERING_DEFAULT: "ค่าเริ่มต้น - ตามชื่อโฟลเดอร์"
+ DEFAULT_ORDERING_FOLDER: "โฟลเดอร์ - ตามคำนำหน้าชื่อโฟลเดอร์จากน้อย"
+ DEFAULT_ORDERING_TITLE: "ชื่อเรื่อง - ตามชื่อฟิลด์ในหัวข้อ"
+ DEFAULT_ORDERING_DATE: "วันที่ฟิลด์วันที่ในหัวข้อ"
+ DEFAULT_ORDER_DIRECTION: "เริ่มต้นตามรูปแบบการเรียง"
+ DEFAULT_ORDER_DIRECTION_HELP: "รูปแบบของเพจในลิส"
+ DEFAULT_PAGE_COUNT: "จำนวนหน้าเริ่มต้น"
+ DEFAULT_PAGE_COUNT_HELP: "จำนวนหน้าสูงสุดเริ่มต้นในรายการ"
+ DATE_BASED_PUBLISHING: "ตามวันที่เผยแพร่"
+ DATE_BASED_PUBLISHING_HELP: "ยกเลิกการเผยแพร่โดยอัตโนมัติตามวันที่นั้น"
+ EVENTS: "เหตุการณ์"
+ EVENTS_HELP: "เปิดใช้หรือปิดการใช้งานบาง events การปิดใช้งานงานเหล่านี้จะสามารถหยุดการทำงานของปลั๊กอิน"
+ REDIRECT_DEFAULT_ROUTE: "รีไดเร็กไปยัง route เริ่มต้น"
+ REDIRECT_DEFAULT_ROUTE_HELP: "รีไดเร็คอัตโนมัติไปยังเพจเร้าท์เริ่มต้น"
+ LANGUAGES: "ภาษา"
+ SUPPORTED: "รองรับ"
+ SUPPORTED_HELP: "ใช้จุลภาคคั่นแต่ละรหัสภาษาอักษร 2 ตัว (ตัวอย่างเช่น 'en, fr, de')"
+ TRANSLATIONS_ENABLED: "เปิดใช้งานการแปล"
+ TRANSLATIONS_ENABLED_HELP: "รองรับการแปลใน Grav ปลั๊กอินและส่วนขยาย"
+ TRANSLATIONS_FALLBACK: "แปลย้อนกลับ"
+ TRANSLATIONS_FALLBACK_HELP: "ย้อนกลับผ่านการรองรับการแปลภาษาหากไม่พบภาษาที่กำลังใช้งาน"
+ ACTIVE_LANGUAGE_IN_SESSION: "ภาษาที่ใช้งานในเซสชัน"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "เก็บภาษาที่ใช้งานในเซสชัน"
+ HTTP_HEADERS: "ส่วนหัว HTTP"
+ EXPIRES: "หมดอายุ"
+ EXPIRES_HELP: "ชุดหมดอายุส่วนหัว ค่าเป็นวินาที"
+ CACHE_CONTROL: "HTTP Cache-Control"
+ LAST_MODIFIED: "แก้ไขล่าสุด"
+ LAST_MODIFIED_HELP: "ชุดของการแก้ไขล่าสุดในส่วนหัวจะช่วยให้การทำงานกับพร็อกซี่และแคชเบราว์เซอร์ดีขึ้น"
+ ETAG: "ETag"
+ ETAG_HELP: "ตั้งค่าหัวข้อ etag เพื่อช่วยระบุเมื่อมีการแก้ไขหน้า"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "ค่าชุด`Vary: Accept Encoding` เฮดเดอร์ ที่จะช่วยพร็อกซี่และ CDN ในการแคชชิ่ง "
+ MARKDOWN_EXTRA_HELP: "เปิดใช้การสนับสนุนเริ่มต้นสำหรับ Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ AUTO_LINE_BREAKS: "ตัวแบ่งบรรทัดโดยอัตโนมัติ"
+ AUTO_LINE_BREAKS_HELP: "เปิดใช้งานการสนับสนุนสำหรับตัวแบ่งบรรทัดโดยอัตโนมัติใน markdown"
+ AUTO_URL_LINKS: "การเชื่อมโยง URL อัตโนมัติ"
+ AUTO_URL_LINKS_HELP: "เปิดใช้งานการแปลงอัตโนมัติของ URLs ลงใน HTML hyperlinks"
+ ESCAPE_MARKUP: "Escape markup"
+ ESCAPE_MARKUP_HELP: "Escape markup แท็กใน HTML entities"
+ CACHING_HELP: "ตั้งค่าหลักในการ เปิด/ปิด สลับ เปิดใช้/ปิดการใช้ Grav แคชชิ่ง"
+ CACHE_CHECK_METHOD: "วิธีการตรวจสอบแคช"
+ CACHE_CHECK_METHOD_HELP: "เลือกวิธีการที่ Grav ใช้ในการตรวจสอบถ้ามีการแก้ไขไฟล์เพจ"
+ CACHE_DRIVER: "แคชไดร์เวอร์"
+ CACHE_DRIVER_HELP: "เลือกแคชไดร์เวอร์ที่ Grav ควรใช้ 'ตรวจหาอัตโนมัติ' พยายามหาดีที่สุดให้คุณ"
+ CACHE_PREFIX: "แคช prefix"
+ CACHE_PREFIX_HELP: "ตัวระบุสำหรับส่วนของ Grav คีย์ อย่าเปลี่ยนมัน เว้นแต่ว่าคุณรู้ว่ากำลังทำอะไร"
+ CACHE_PREFIX_PLACEHOLDER: "มาจาก URL (แทนที่ โดยการป้อนสตริงแบบสุ่ม)"
+ LIFETIME: "ตลอดชีพ"
+ LIFETIME_HELP: "ตั้งค่าอายุการใช้งานของแคชในวินาที 0 =ไม่จำกัด"
+ GZIP_COMPRESSION: "การบีบอัด Gzip"
+ GZIP_COMPRESSION_HELP: "เปิดใช้งาน GZip บีบอัด Grav เพจเพื่อประสิทธิภาพที่เพิ่มขึ้น"
+ TWIG_TEMPLATING: "Twig เทมเพลต"
+ TWIG_CACHING: "Twig แคช"
+ TWIG_CACHING_HELP: "ควบคุม Twig caching mechanism ปล่อยให้เปิดใช้งานไว้เพื่อประสิทธิภาพสูงสุด"
+ TWIG_DEBUG: "Twig ดีบั๊ก"
+ TWIG_DEBUG_HELP: "ให้เลือกที่จะไม่โหลดส่วนขยาย Twig ดีบักเกอร์"
+ DETECT_CHANGES: "ตรวจหาการเปลี่ยนแปลง"
+ DETECT_CHANGES_HELP: "Twig คอมไพล์ Twig แคช อัตโนมัติ หากตรวจพบการเปลี่ยนแปลงใด ๆ ใน Twig เทมเพลต"
+ AUTOESCAPE_VARIABLES: "ตัวแปร Autoescape"
+ AUTOESCAPE_VARIABLES_HELP: "Autoescapes ตัวแปรทั้งหมด นี้จะเบรคเว็บไซต์ของคุณมากที่สุด"
+ ASSETS: "แอสเซ็ต"
+ CSS_PIPELINE: "ไปป์ไลน์ CSS"
+ CSS_PIPELINE_HELP: "ไปป์ไลน์ CSS คือการ ผสมผสานของทรัพยากรต่าง ๆ ของ CSS เป็นหนึ่งไฟล์"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "รวมภายนอกใน CSS ไปป์ไลน์"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Url ภายนอกบางครั้งมีการอ้างอิงไฟล์ที่สัมพันธ์กัน และไม่สามารถเป็นไปป์ไลน์"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "ไปป์ไลน์ CSS แสดงก่อน"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "เรนเดอร์ CSS ไปป์ไลน์ ก่อน CSS อื่นที่ไม่ได้ included"
+ CSS_MINIFY: "CSS minify"
+ CSS_MINIFY_HELP: "Minify CSS ในขณะประมวลผล"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "CSS minify แทนที่บน Windows "
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Minify แทนที่สำหรับ Windows แพลตฟอร์ม Falseโดยค่าเริ่มต้นเนื่องจาก ThreadStackSize"
+ CSS_REWRITE: "CSS rewrite"
+ CSS_REWRITE_HELP: "Rewrite CSS ใดๆ ซึ่งเกี่ยวข้องกับ URLs ขณะประมวลผล"
+ JAVASCRIPT_PIPELINE: "JavaScript pipeline"
+ JAVASCRIPT_PIPELINE_HELP: " JS pipeline คือผสมผสานของ JS หลายๆ ไฟล์ มาในไฟล์เดียว"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "นำเข้า JS ไปป์ไลน์จากภายนอก"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "บางครั้งมีการอ้างอิง Url ไฟล์มากจากภายนอกที่ซ้ำซ้อนกัน และทำให้ไม่สามารถเป็นไปป์ไลน์ได้"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "JS ไปป์ไลน์ เรนเดอร์ก่อน"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "เรนเดอร์ JS ไปป์ไลน์ ก่อน JS ที่อ้างถึงแต่ไม่ได้ included"
+ JAVASCRIPT_MINIFY: "JavaScript minify"
+ JAVASCRIPT_MINIFY_HELP: "Minify ของ JS during pipelining"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "เปิดใช้งานการประทับเวลาบน Assets"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "เปิดใช้ asset timestamps"
+ COLLECTIONS: "Collections"
+ ERROR_HANDLER: "จัดการข้อผิดพลาด"
+ DISPLAY_ERRORS: "แสดงข้อผิดพลาด"
+ DISPLAY_ERRORS_HELP: "แสดงหน้าผิดพลาด backtrace รายการแบบเต็มรูปแบบ"
+ LOG_ERRORS: "ล็อกข้อผิดพลาด"
+ LOG_ERRORS_HELP: "เก็บข้อผิดพลาดในโฟลเดอร์ /logs"
+ DEBUGGER: "ดีบักเกอร์"
+ DEBUGGER_HELP: "เปิดใช้งาน Grav ดีบักเกอร์และการตั้งค่าต่อไปนี้"
+ DEBUG_TWIG: "ดีบั๊ก Twig"
+ DEBUG_TWIG_HELP: "เปิดใช้งานดีบั๊ก Twig เทมเพลต"
+ SHUTDOWN_CLOSE_CONNECTION: "ปิดการเชื่อมต่อ"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "ปิดการเชื่อมต่อก่อนที่จะเรียก onShutdown() false สำหรับการดีบั๊ก "
+ DEFAULT_IMAGE_QUALITY: "คุณภาพของภาพเริ่มต้น"
+ DEFAULT_IMAGE_QUALITY_HELP: "คุณภาพเริ่มต้นจะใช้เมื่อเปลี่ยนความละเอียดหรือแคชภาพ (85%)"
+ CACHE_ALL: "แคชภาพทั้งหมด"
+ CACHE_ALL_HELP: "เรียกใช้ภาพทั้งหมดผ่านระบบแคชของ Grav แม้ว่าจะไม่มีการจัดการมีเดีย"
+ IMAGES_DEBUG: "ตรวจสอบจุดบกพร่องลายน้ำบนภาพ"
+ IMAGES_DEBUG_HELP: "แสดงการวางซ้อนบนภาพแสดงพิกเซลความลึกของภาพเมื่อทำงานกับจอเรติน่าสำหรับตัวอย่าง"
+ UPLOAD_LIMIT: "จำกัดการอัปโหลดไฟล์"
+ UPLOAD_LIMIT_HELP: "ตั้งค่าขนาดอัปโหลดสูงสุดไบต์ (0 คือไม่จำกัด)"
+ ENABLE_MEDIA_TIMESTAMP: "เปิดใช้งานการประทับเวลาบน Media"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "เพิ่มการประทับเวลาตามวันปรับเปลี่ยนล่าสุดในแต่ละ Media"
+ SESSION: "เซสชั่น"
+ SESSION_ENABLED_HELP: "เปิดใช้งานเซสชันการสนับสนุนภายใน Grav"
+ TIMEOUT: "หมดเวลา"
+ TIMEOUT_HELP: "ตั้งค่าการหมดเวลาของเซสชันเป็นวินาที"
+ SESSION_NAME_HELP: "ตัวบ่งชี้การใช้รูปแบบชื่อของคุกกี้เซสชั่น"
+ ABSOLUTE_URLS: "Absolute URLs"
+ ABSOLUTE_URLS_HELP: "Absolute หรือ relative URLs สำหรับ `base_url`"
+ PARAMETER_SEPARATOR: "แยกพารามิเตอร์"
+ PARAMETER_SEPARATOR_HELP: "แยกสำหรับส่งผ่านพารามิเตอร์ที่คุณสามารถเปลี่ยนสำหรับ Apache บน Windows"
+ TASK_COMPLETED: "งานเสร็จสมบูรณ์"
+ EVERYTHING_UP_TO_DATE: "อัพเดดล่าสุดแล้ว"
+ UPDATES_ARE_AVAILABLE: "มีเวอร์ชั่นอัพเดด"
+ IS_AVAILABLE_FOR_UPDATE: "มีพร้อมให้อัพเดด"
+ IS_NOW_AVAILABLE: "ขณะนี้มี"
+ CURRENT: "ปัจจุบัน"
+ UPDATE_GRAV_NOW: "ปรับปรุง Grav เดี๋ยวนี้"
+ GRAV_SYMBOLICALLY_LINKED: "Grav สัญลักษณ์มีการเชื่อมโยง ปรับรุ่นจะไม่พร้อมใช้งาน"
+ UPDATING_PLEASE_WAIT: "ปรับปรุง...กรุณารอสักครู่ กำลังดาวน์โหลด.."
+ OF_THIS: "ของส่วนนี้"
+ OF_YOUR: "ของคุณ"
+ HAVE_AN_UPDATE_AVAILABLE: "มีเวอร์ชั่นอัพเดด"
+ SAVE_AS: "บันทึกเป็น"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "คุณแน่ใจว่าต้องการลบเพจนี้และหน้าย่อย? หากเพจมีการแปลในภาษาอื่น เพจนั้นจะยังเก็บไว้และจะต้องทำลบต่างหาก มิฉะนั้นจะลบเพจโฟลเดอร์พร้อมด้วยหน้าย่อย ซึ่งการกระทำนี้ไม่สามารถยกเลิกกลับมาได้"
+ AND: "และ"
+ UPDATE_AVAILABLE: "พร้อมให้อัพเดต"
+ METADATA_KEY: "คีย์ (เช่น 'Keywords')"
+ METADATA_VALUE: "ค่า (เช่น 'Blog, Grav')"
+ USERNAME_HELP: "ชื่อผู้ใช้ควรอยู่ระหว่าง 3 ถึง 16 ตัว รวมทั้งตัวพิมพ์เล็ก ตัวเลข ขีดกลาง ขีดล่าง ไม่อนุญาตให้ใช้อักษรตัวพิมพ์ใหญ่ ช่องว่าง และอักขระพิเศษ"
+ FULLY_UPDATED: "ปรับปรุงอย่างเต็มที่"
+ SAVE_LOCATION: "บันทึกตำแหน่งที่ตั้ง"
+ PAGE_FILE: "เพจเทมเพลต"
+ PAGE_FILE_HELP: "ชื่อไฟล์เพจเทมเพลต ซึ่งค่าเริ่มต้นแสดงเทมเพลตสำหรับเพจนี้"
+ NO_USER_ACCOUNTS: "ไม่พบบัญชีผู้ใช้ กรุณาสร้างสักอันก่อน"
+ REDIRECT_TRAILING_SLASH: "รีไดเร็ก trailing slash"
+ REDIRECT_TRAILING_SLASH_HELP: "ทำการ 301 รีไดเร็กแทนที่ transparently handling trailing slash URIs"
+ DEFAULT_DATE_FORMAT: "รูปแบบวันที่เพจ"
+ DEFAULT_DATE_FORMAT_HELP: "รูปแบบวันที่เพจ ใช้โดย Grav ซึ่งค่าเริ่มต้น Grav พยายามเดาวันรูปแบบ อย่างไรก็ตาม คุณสามารถระบุรูปแบบโดยใช้ไวยากรณ์ของ PHP วัน (เช่น: d/m/Y H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "เดาโดยอัตโนมัติ"
+ IGNORE_FILES: "ไม่สนใจไฟล์"
+ IGNORE_FILES_HELP: "เฉพาะไฟล์ที่ละเว้นเมื่อประมวลผลเพจ"
+ IGNORE_FOLDERS: "ละเว้นโฟลเดอร์"
+ IGNORE_FOLDERS_HELP: "เฉพาะโฟลเดอร์ที่ละเว้นเมื่อประมวลผลเพจ"
+ HTTP_ACCEPT_LANGUAGE: "ตั้งค่าภาษาจากเบราว์เซอร์"
+ HTTP_ACCEPT_LANGUAGE_HELP: "คุณสามารถเลือกการตั้งค่าภาษาที่ใช้บนป้ายหัวข้อ 'http_accept_language' ในเบราว์เซอร์"
+ OVERRIDE_LOCALE: "แทนที่ locale"
+ OVERRIDE_LOCALE_HELP: "แทนที่การตั้งค่า locale ใน PHP สำหรับภาษาปัจจุบัน"
+ REDIRECT: "เพจรีไดเร็ก"
+ REDIRECT_HELP: "ใส่หน้าเส้นทางหรือ URL ภายนอกสำหรับหน้านี้ในการเปลี่ยนเส้นทาง เช่น'some/route' หรือ 'http://somesite.com'"
+ PLUGIN_STATUS: "สถานะของปลั๊กอิน"
+ INCLUDE_DEFAULT_LANG: "รวมภาษาเริ่มต้น"
+ INCLUDE_DEFAULT_LANG_HELP: "ส่วนนี้จะเตรียม Url ทั้งหมดในภาษาเริ่มต้นด้วยภาษาเริ่มต้น เช่น '/en /blog/my-post'"
+ ALLOW_URL_TAXONOMY_FILTERS: "ตัวกรอง URL Taxonomy"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Page-based collections อนุญาตให้คุณกรองด้วย `/taxonomy:value`."
+ REDIRECT_DEFAULT_CODE: "ค่ารีไดเร็กโค้ดเริ่มต้น"
+ REDIRECT_DEFAULT_CODE_HELP: "รหัสสถานะ HTTP ที่ใช้สำหรับรีไดเร็ก"
+ IGNORE_HIDDEN: "ละเว้นที่ซ่อนอยู่"
+ IGNORE_HIDDEN_HELP: "ละเว้นแฟ้มและโฟลเดอร์ที่ขึ้นต้น ด้วยจุดทั้งหมด"
+ WRAPPED_SITE: "Wrapped ไซต์"
+ WRAPPED_SITE_HELP: "สำหรับ ธีม/ปลั๊กอิน เพื่อจะรู้ หาก Grav มีการ wrapped โดยแพลตฟอร์มอื่น"
+ FALLBACK_TYPES: "อนุญาต fallback types"
+ FALLBACK_TYPES_HELP: "อนุญาตประเภทไฟล์ที่สามารถพบหากเรียกโดยเพจเราท์ ค่าเริ่มต้นรองรับประเภท Media"
+ INLINE_TYPES: "Inline fallback types"
+ INLINE_TYPES_HELP: "รายการของชนิดไฟล์ที่ควรจะเป็นแสดงแบบอินไลน์แทนการดาวน์โหลด"
+ APPEND_URL_EXT: "เพิ่มคำต่อท้าย URL"
+ APPEND_URL_EXT_HELP: "จะเพิ่มนามสกุลที่กำหนดเองไปยังเพจ URL หมายเหตุ หมายถึง Grav จะหา ' . .twig ' เทมเพลต"
+ PAGE_MODES: "โหมดหน้า"
+ PAGE_TYPES: "ชนิดหน้า"
+ ACCESS_LEVELS: "ระดับการเข้าถึง"
+ GROUPS: "กลุ่ม"
+ GROUPS_HELP: "รายการของกลุ่มผู้ใช้เป็นส่วนหนึ่งของ"
+ ADMIN_ACCESS: "เข้าถึงผู้ดูแลระบบ"
+ SITE_ACCESS: "เข้าถึงไซต์"
+ INVALID_SECURITY_TOKEN: "โทเค็นการรักษาความปลอดภัยที่ไม่ถูกต้อง"
+ ACTIVATE: "เปิดใช้งาน"
+ TWIG_UMASK_FIX: "แก้ไข Umask"
+ TWIG_UMASK_FIX_HELP: "โดยค่าเริ่มต้น Twig สร้างไฟล์แคชเป็น 0755 แก้ไขโดยเปลี่ยนเป็น 0775"
+ CACHE_PERMS: "แคช สิทธิ์"
+ CACHE_PERMS_HELP: "ค่าเริ่มต้น สิทธิ์แคชโฟลเดอร์ ปกติ 0755 หรือ 0775 ขึ้นอยู่กับการตั้งค่า"
+ REMOVE_SUCCESSFUL: "เอาออกสำเร็จ"
+ REMOVE_FAILED: "ลบล้มเหลว"
+ HIDE_HOME_IN_URLS: "ซ่อน route หน้าแรกใน URLs"
+ HIDE_HOME_IN_URLS_HELP: "จะให้แน่ใจว่า เราท์เริ่มต้นสำหรับเพจใด ๆ ภายใต้หน้าแรกอ้างอิงเส้นเราท์ประจำหน้าหลัก"
+ TWIG_FIRST: "ดำเนินการ Twig ก่อน"
+ TWIG_FIRST_HELP: "ถ้าคุณเปิดใช้งานการประเมินผล Twig เพจ คุณจะสามารถตั้งค่าว่าจะให้ Twig ทำงานก่อนหรือหลัง markdown"
+ SESSION_SECURE: "ปลอดภัย"
+ SESSION_SECURE_HELP: "ถ้าเป็นจริง บ่งชี้ว่า การสื่อสารสำหรับคุกกี้นี้ต้องผ่านการส่งผ่านรหัส คำเตือน: เปิดใช้งานบนเว็บไซต์ที่เรียกใช้บน HTTPS เท่านั้น"
+ SESSION_HTTPONLY: "HTTP เท่านั้น"
+ SESSION_HTTPONLY_HELP: "ถ้าเป็นจริง บ่งชี้ว่า ควรจะใช้คุกกี้ผ่าน HTTP เท่า และ JavaScript ปรับเปลี่ยนไม่ได้"
+ REVERSE_PROXY: "Reverse Proxy"
+ REVERSE_PROXY_HELP: "เปิดใช้งานหากคุณอยู่หลังรีเวิสพร็อกซี่ และคุณมีปัญหากับ URLs ที่กระทบจาก Ports ไม่ถูกต้อง"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "frontmatter ไม่ถูกต้อง ไม่สามารถบันทึกได้"
+ ADD_FOLDER: "เพิ่มโฟลเดอร์"
+ PROXY_URL: "URL ของพร็อกซี"
+ PROXY_URL_HELP: "กรอก proxy HOST หรือ IP และ PORT"
+ NOTHING_TO_SAVE: "ไม่มีการบันทึก"
+ FILE_ERROR_ADD: "ข้อผิดพลาดเกิดขึ้นขณะพยายามเพิ่มไฟล์"
+ FILE_ERROR_UPLOAD: "เกิดข้อผิดพลาดขณะพยายามอัปโหลดไฟล์"
+ FILE_UNSUPPORTED: "ชนิดแฟ้มที่ไม่สนับสนุน"
+ ADD_ITEM: "เพิ่มรายการ"
+ FILE_TOO_LARGE: "แฟ้มมีขนาดใหญ่เกินไปที่จะถูกอัพโหลด สูงสุดที่อนุญาตคือ %s ตาม การตั้งค่า PHP เพิ่มการตั้งค่า PHP ของคุณใน 'post_max_size'"
+ INSTALLING: "กำลังติดตั้ง"
+ LOADING: "กำลังโหลดข้อมูล.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "การอ้างอิงต่อไปนี้จำเป็นกรอกให้ครบก่อน:"
+ ERROR_INSTALLING_PACKAGES: "ข้อผิดพลาดขณะติดตั้งแพคเกจ"
+ INSTALLING_DEPENDENCIES: "กำลังติดตั้งส่วนที่เกี่ยวข้อง.."
+ INSTALLING_PACKAGES: "กำลังติดตั้งแพคเกจ"
+ PACKAGES_SUCCESSFULLY_INSTALLED: "ติดตั้งแพคเกจสำเร็จแล้ว"
+ READY_TO_INSTALL_PACKAGES: "พร้อมติดตั้งแพคเกจ"
+ PACKAGES_NOT_INSTALLED: "แพคเกจไม่ได้ติดตั้ง"
+ PACKAGES_NEED_UPDATE: "ติดตั้งแพคเกจเรียบร้อย แต่มันเก่ามาก"
+ PACKAGES_SUGGESTED_UPDATE: "แพคเกจการติดตั้ง และเวอร์ชั่นโอเค แต่จะถูกปรับปรุงให้ทันสมัย"
+ REMOVE_THE: "ลบ %s"
+ CONFIRM_REMOVAL: "คุณต้องการลบ %s นี้แน่ใจหรือไม่"
+ REMOVED_SUCCESSFULLY: "%s เอาออกเสร็จสมบูรณ์"
+ ERROR_REMOVING_THE: "ข้อผิดพลาดในการลบ %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "%s ต้องใช้อ้างอิงต่อไปนี้ ซึ่งไม่ต้องการใช้แพคเกจอื่น ๆ ติดตั้ง ถ้าคุณไม่ได้ใช้พวกเขา คุณสามารถเอาออกโดยตรงจากที่นี่"
+ READY_TO_UPDATE_PACKAGES: "พร้อมที่จะปรับปรุงแพคเกจ"
+ ERROR_UPDATING_PACKAGES: "มีข้อผิดพลาดขณะทำการอัพเดดแพคเกจ"
+ UPDATING_PACKAGES: "กำลังปรับปรุงแพคเกจ"
+ PACKAGES_SUCCESSFULLY_UPDATED: "แพคเกจปรับปรุงสำเร็จแล้ว"
+ UPDATING: "การปรับปรุง"
+ GPM_RELEASES: "รุ่น GPM"
+ GPM_RELEASES_HELP: "เลือก 'Testing' เพื่อติดตั้งรุ่น beta หรือเวอร์ชันทดสอบ"
+ GPM_METHOD: "วิธีดึงข้อมูลระยะไกล"
+ GPM_METHOD_HELP: "เมื่อตั้งค่าเป็นอัตโนมัติ Grav จะตรวจสอบว่า fopen และใช้ หรือ ถอยกลับไปดัด การบังคับ ใช้ของสิ่งหนึ่งไปเปลี่ยนการตั้งค่า"
+ GPM_VERIFY_PEER: "ระยะไกลตรวจสอบเพียร์ (SSL)"
+ GPM_VERIFY_PEER_HELP: "ผู้ให้บริการบางอย่างดูเหมือนล้ม getgrav.org ที่การตรวจสอบว่าใบรับรอง SSL สาเหตุครับไม่ทำงาน ถ้าเป็นกรณีนี้สำหรับคุณ การปิดการตั้งค่านี้อาจช่วย"
+ AUTO: "อัตโนมัติ"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "เสถียร"
+ TESTING: "การทดสอบ"
+ FRONTMATTER_PROCESS_TWIG: "กระบวนการ frontmatter Twig"
+ FRONTMATTER_PROCESS_TWIG_HELP: "เมื่อเปิดใช้งานคุณสามารถใช้ Twig พร้อมกำหนดค่าตัวแปรในหน้าหน้าเรื่อง"
+ FRONTMATTER_IGNORE_FIELDS: "ละเว้น frontmatter ฟิลด์"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "ฟิลด์บางฟิลด์ frontmatter อาจประกอบด้วย Twig แต่จะไม่สามารถประมวล ผล เช่น 'forms'"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "แพคเกจ %s ที่ติดตั้งเสร็จเรียบร้อยแล้ว"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "การตั้งค่าการเรียงในตัวหลัก ปิดการเรียงลำดับ"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "เพจไม่เปิดให้เห็น, ปิดการเรียงลำดับ"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "การจัดเรียงผ่านทางผู้ดูแลไม่รองรับเนื่องจากมีส่วนที่เกี่ยวข้องมากกว่า 200"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "การเรียงลำดับหน้าถูกปิดใช้งานสำหรับหน้านี้ เพราะว่าตัวเลขอยู่หน้าโฟลเดอร์ ใช้ไม่ได้"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "หมายเหตุ: คุณไม่สามารถเพิ่มมีเดียไฟล์จนกระทั่งคุณบันทึกหน้า เพียงคลิก 'บันทึก' ด้านบน"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "หมายเหตุ: เพจต้องถูกบันทึกก่อนที่คุณจะสามารถอัปโหลดไฟล์ไป"
+ DROP_FILES_HERE_TO_UPLOAD: "วางไฟล์ตรงนี้ หรือ คลิกในพื้นที่นี้ "
+ INSERT: "แทรก"
+ UNDO: "เรียกคืน"
+ REDO: "ทำซ้ำ"
+ HEADERS: "ส่วนหัว"
+ BOLD: "ตัวหนา"
+ ITALIC: "ตัวเอียง"
+ STRIKETHROUGH: "ขีดทับ"
+ SUMMARY_DELIMITER: "สรุปคั่น"
+ LINK: "ลิงก์"
+ IMAGE: "รูปภาพ"
+ BLOCKQUOTE: "Blockquote"
+ UNORDERED_LIST: "รายการที่ไม่ได้จัดเรียง"
+ ORDERED_LIST: "รายการจัดเรียง"
+ EDITOR: "ตัวแก้ไข"
+ PREVIEW: "ดูตัวอย่าง"
+ FULLSCREEN: "เต็มหน้าจอ"
+ MODULAR: "โมดูลาร์"
+ NON_ROUTABLE: "ไม่ Routable"
+ NON_MODULAR: "Non-Modular"
+ NON_VISIBLE: "มองไม่เห็น"
+ NON_PUBLISHED: "ไม่ได้เผยแพร่"
+ CHARACTERS: "ตัวอักษร"
+ PUBLISHING: "เผยแพร่"
+ NOTIFICATIONS: "การแจ้งเตือน"
+ MEDIA_TYPES: "ชนิดมีเดีย"
+ IMAGE_OPTIONS: "ตัวเลือกภาพ"
+ MIME_TYPE: "ชนิด mime"
+ THUMB: "รูปย่อ"
+ TYPE: "ชนิด"
+ FILE_EXTENSION: "นามสกุลไฟล์"
+ LEGEND: "เพจ Legend"
+ MEMCACHE_SERVER: "เซิร์ฟเวอร์ Memcache"
+ MEMCACHE_SERVER_HELP: "ที่อยู่เซิร์ฟเวอร์ Memcache"
+ MEMCACHE_PORT: "Memcache พอร์ต"
+ MEMCACHE_PORT_HELP: "Memcache เซิร์ฟเวอร์พอร์ต"
+ MEMCACHED_SERVER: "เซิร์ฟเวอร์ Memcache"
+ MEMCACHED_SERVER_HELP: "ที่อยู่เซิร์ฟเวอร์ Memcache"
+ MEMCACHED_PORT: "Memcache พอร์ต"
+ MEMCACHED_PORT_HELP: "Memcache เซิร์ฟเวอร์พอร์ต"
+ REDIS_SERVER: "Redis เซิร์ฟเวอร์"
+ REDIS_SERVER_HELP: "ที่อยู่ Redis เซิร์ฟเวอร์"
+ REDIS_PORT: "Redis port"
+ REDIS_PORT_HELP: "Redis เซิร์ฟเวอร์พอร์ต"
+ REDIS_PASSWORD: "Redis รหัสผ่าน/คำลับ"
+ ALL: "ทั้งหมด"
+ FROM: "จาก"
+ TO: "ถึง"
+ RELEASE_DATE: "วันที่เผยแพร่"
+ SORT_BY: "เรียงลำดับตาม"
+ RESOURCE_FILTER: "กรอง..."
+ FORCE_SSL: "บังคับใช้ SSL"
+ FORCE_SSL_HELP: "ทั่วโลกบังคับใช้ SSL ถ้าเปิดการใช้งานให้เข้าถึงเว็บไซต์ผ่าน HTTP, Grav จะเปลี่ยนเส้นทางไปยังหน้า HTTPS"
+ NEWS_FEED: "ฟีดข่าว"
+ EXTERNAL_URL: "URL ภายนอก"
+ SESSION_PATH: "Session Path"
+ SESSION_PATH_HELP: "ใช้เฉพาะเมื่อคุณเลือก URL หลักที่กำหนดขึ้นเอง (ชื่อโดเมน / โฟลเดอร์ย่อย)"
+ CUSTOM_BASE_URL: "กำหนด URL หลักเอง"
+ CUSTOM_BASE_URL_HELP: "ถ้าคุณต้องการกำหนดเว็บไซต์โดเมน หรือใช้โฟลเดอร์ย่อยแตกต่างกันกับที่ใช้ โดย Grav ตัวอย่าง: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: 'ไม่สามารถใช้ "%s" ข้างนอก หน้าเพจ ได้'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'ไม่สามารถอัปโหลดไฟล์ %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: 'ไม่สามารถย้ายไฟล์ %s ไป "%s"'
+ DROPZONE_CANCEL_UPLOAD: 'ยกเลิกการอัปโหลด'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'คุณแน่ใจหรือไม่ว่าต้องการยกเลิกการอัปโหลดนี้'
+ DROPZONE_DEFAULT_MESSAGE: 'วางไฟล์ที่นี่ หรือ คลิกที่นี่ '
+ DROPZONE_FALLBACK_MESSAGE: 'เบราว์เซอร์ของคุณไม่สนับสนุน การลากและปล่อย เพื่ออัปโหลดไฟล์'
+ DROPZONE_FALLBACK_TEXT: 'กรุณาใช้แบบฟอร์มย้อนกลับที่ด้านล่างในการอัปโหลดไฟล์ของคุณเช่นในวันเก่า'
+ DROPZONE_FILE_TOO_BIG: 'ไฟล์มีขนาดใหญ่เกินไป ({{filesize}}MiB) ขนาดของไฟล์ข้อมูลสูงสุด: {{maxFilesize}}MiB'
+ DROPZONE_INVALID_FILE_TYPE: "ไม่สามารถอัปโหลดไฟล์ประเภทนี้ได้"
+ DROPZONE_MAX_FILES_EXCEEDED: "ไม่สามารถอัปโหลดไฟล์เพิ่มได้"
+ DROPZONE_REMOVE_FILE: "ลบไฟล์"
+ DROPZONE_RESPONSE_ERROR: "เซิร์ฟเวอร์ตอบสนอง ด้วยรหัส {{statusCode}}."
+ PREMIUM_PRODUCT: "พรีเมี่ยม"
+ ADMIN_CACHING: "เปิดใช้งานแคชด้าน Admin"
+ ADMIN_CACHING_HELP: "แคชในด้าน admin สามารถควบคุมได้อย่างอิสระจากด้านหน้าเว็บไซต์"
+ DESTINATION_NOT_SPECIFIED: "ยังไม่ได้ระบุปลายทาง"
+ UPLOAD_ERR_NO_TMP_DIR: "ไม่พบโฟลเดอร์ชั่วคราว"
+ SESSION_SPLIT: "แยกเซสชั่น"
+ SESSION_SPLIT_HELP: "เซสชันระหว่างเว็บไซต์และปลั๊กอินอื่น ๆ (เช่น admin) แยกอิสระ"
+ ERROR_FULL_BACKTRACE: "ข้อผิดพลาดย้อนหลังทั้งหมด"
+ ERROR_SIMPLE: "ผิดพลาดเล็กน้อย"
+ ERROR_SYSTEM: "ข้อผิดพลาดจากระบบ"
+ IMAGES_AUTO_FIX_ORIENTATION: "แนวทางการแก้ไขปัญหาโดยอัตโนมัติ"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "การวางแนวภาพตามข้อมูล Exif ที่แก้ไขโดยอัตโนมัติ"
+ REDIS_SOCKET: "Redis socket"
+ REDIS_SOCKET_HELP: "Redis ซ็อกเก็ต"
+ NOT_SET: "ไม่ได้ตั้งค่า"
+ PERMISSIONS: "การอนุญาต"
+ NEVER_CACHE_TWIG: "ไม่ต้องแคช Twig"
+ NEVER_CACHE_TWIG_HELP: "เพียง แคเนื้อหา และกระบวนการกิ่งไม้ทุกครั้งสำหรับหน้า ละเว้นการตั้งค่า twig_first"
+ ALLOW_WEBSERVER_GZIP: "อนุญาตให้เซิร์ฟเวอร์ Gzip"
+ ALLOW_WEBSERVER_GZIP_HELP: "ปิด โดยค่าเริ่มต้น เมื่อเปิดใช้งาน การบีบอัด Gzip/Deflate เซิร์ฟเวอร์การกำหนดค่าจะทำ แต่จะไม่ปิดการเชื่อมต่อ http ก่อน onShutDown() เหตุการณ์ที่ทำให้การโหลดหน้าเว็บช้า"
+ OFFLINE_WARNING: "ไม่สามารถสร้างการเชื่อมต่อ GPM ได้"
+ CLI_COMPATIBILITY: "CLI เข้ากันได้กับ"
+ CLI_COMPATIBILITY_HELP: "ใจว่า มีใช้โปรแกรมควบคุมแคระเหยเท่านั้น (ไฟล์ redis, memcache ฯลฯ)"
+ REINSTALL_PLUGIN: "ติดตั้งปลั๊กอินใหม่"
+ REINSTALL_THEME: "ติดตั้งธีมใหม่"
+ REINSTALL_THE: "%s ติดตั้งใหม่"
+ CONFIRM_REINSTALL: "คุณแน่ใจหรือไม่ว่าคุณต้องการติดตั้งใหม่ %s?"
+ REINSTALLED_SUCCESSFULLY: "%s ติดตั้งใหม่เสร็จเรียบร้อยแล้ว"
+ ERROR_REINSTALLING_THE: "ข้อผิดพลาดในการติดตั้ง %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "แพคเกจ %s ติดตั้งเสร็จเรียบร้อยแล้ว"
+ REINSTALLATION_FAILED: "ติดตั้งใหม่ ล้มเหลว"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "รุ่นที่ติดตั้งไม่ใช่รุ่นล่าสุด คลิกดำเนินการต่อ เพื่อถอนการติดตั้งรุ่นปัจจุบันและติดตั้งรุ่นล่าสุด"
+ TOOLS: "เครื่องมือ"
+ DIRECT_INSTALL: "ติดตั้งโดยตรง"
+ NO_PACKAGE_NAME: "ไม่ได้ระบุชื่อของแพคเกจ"
+ PACKAGE_EXTRACTION_FAILED: "แยกแพคเกจล้มเหลว"
+ NOT_VALID_GRAV_PACKAGE: "ไม่ใช่เพคเกจ Grav ที่ถูกต้อง"
+ NAME_COULD_NOT_BE_DETERMINED: "ไม่ระบุชื่อ"
+ CANNOT_OVERWRITE_SYMLINKS: "ไม่สามารถเขียนทับ symlink"
+ ZIP_PACKAGE_NOT_FOUND: "ไม่พบไฟล์ zip"
+ GPM_OFFICIAL_ONLY: "GPM อย่างเป็นทางการเท่านั้น"
+ GPM_OFFICIAL_ONLY_HELP: "อนุญาตให้ติดตั้งโดยตรงจาก GPM อย่างเป็นทางการเท่านั้น"
+ NO_CHILD_TYPE: "ไม่มีชนิดเด็กสำหรับ rawroute นี้"
+ SORTABLE_PAGES: "เรียงหน้า:"
+ ADMIN_SPECIFIC_OVERRIDES: "เฉพาะผู้ดูแลแทน"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "เด็กแสดงลำดับ"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "สั่งว่า เด็กของหน้านี้ควรถูกแสดงในมุมมอง 'เพจ' ของปลั๊กอินของ Admin"
+ PWD_PLACEHOLDER: "สายอักขระที่ซับซ้อนอย่างน้อย 8 ตัวอักษรยาว"
+ PWD_REGEX: "รหัสผ่าน Regex"
+ PWD_REGEX_HELP: "โดยค่าเริ่มต้น: รหัสผ่านต้องประกอบด้วย ตัวเลข ตัวพิมพ์ใหญ่ และอักษรพิมพ์เล็ก ย่างน้อย 8 ตัวอักษรขึ้นไป"
+ USERNAME_PLACEHOLDER: "ตัวอักษรพิมพ์เล็กเท่านั้น เช่น 'admin'"
+ USERNAME_REGEX: "ชื่อผู้ใช้ Regex"
+ USERNAME_REGEX_HELP: "โดยค่าเริ่มต้น: ตัวอักษรพิมพ์เล็กเท่านั้น ตัวเลข, เส้นประ และขีด 3 - 16 ตัวอักษร"
+ CONTENT_PADDING: "ขยายเนื้อหา"
+ CONTENT_PADDING_HELP: "เปิด/ปิด ใช้งานเนื้อหาขยายทั่วพื้นที่เนื้อหาเพื่อให้พื้นที่มากขึ้น"
+ 2FA_TITLE: "2-Factor Authentication"
+ 2FA_ENABLED: "เปิดใช้งาน 2FA"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "2FA คำลับ"
+ 2FA_REGENERATE: "สร้างใหม่"
+ FORCE_LOWERCASE_URLS: "บังคับ URLs เป็นตัวเล็ก"
+ FORCE_LOWERCASE_URLS_HELP: "โดยค่าเริ่มต้น Grav จะกำหนด slugs และเส้นทางให้เป็นตัวเล็กทั้งหมด หากกำหนดค่านี้เป็น False จะสามารถใช้ตัวอักษรตัวใหญ่ได้"
+ VIEW_SITE_TIP: "ชมเว็บ"
+ TOOLS_DIRECT_INSTALL_TITLE: "ติดตั้งแพคเกจ Grav โดยตรง"
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "อัปโหลดและติดตั้ง"
+ ROUTE_OVERRIDES: "Route Overrides"
+ ROUTE_DEFAULT: "เส้นทางเริ่มต้น"
+ ROUTE_CANONICAL: "เส้นทาง Canonical"
+ ROUTE_ALIASES: "นามแฝงเส้นทาง"
diff --git a/user/plugins/admin/languages/tlh.yaml b/user/plugins/admin/languages/tlh.yaml
new file mode 100644
index 0000000..f9e0b7e
--- /dev/null
+++ b/user/plugins/admin/languages/tlh.yaml
@@ -0,0 +1,9 @@
+---
+PLUGIN_ADMIN:
+ LOGIN_BTN_FORGOT: "lIj"
+ BACK: "chap"
+ NORMAL: "motlh"
+ 'YES': "HIja'"
+ 'NO': "Qo'"
+ DISABLED: "Qotlh"
+
diff --git a/user/plugins/admin/languages/tr.yaml b/user/plugins/admin/languages/tr.yaml
new file mode 100644
index 0000000..ca5ab95
--- /dev/null
+++ b/user/plugins/admin/languages/tr.yaml
@@ -0,0 +1,717 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Bu bir beta sürümüdür! Sunucunuzda kullanmak sizin sorumluluğunuzdadır..."
+ ADMIN_REPORT_ISSUE: "Hata mı var? Lütfen GitHub aracılığıyla bildirin."
+ EMAIL_FOOTER: "Grav ile güçlendirilmiştir - Modern Düz Dosya CMS"
+ LOGIN_BTN: "Giriş"
+ LOGIN_BTN_FORGOT: "Şifremi Unuttum"
+ LOGIN_BTN_RESET: "Şifreni Sıfırla"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Sıfırlama Yönergesini Gönder"
+ LOGIN_BTN_CLEAR: "Formu Temizle"
+ LOGIN_BTN_CREATE_USER: "Kullanıcı Oluştur"
+ LOGIN_LOGGED_IN: "Başarıyla oturum açtınız"
+ LOGIN_FAILED: "Giriş başarısız"
+ LOGGED_OUT: "Oturum kapatıldı"
+ RESET_NEW_PASSWORD: "Lütfen yeni bir parola girin …"
+ RESET_LINK_EXPIRED: "Sıfırlama bağlantısının süresi doldu, lütfen tekrar deneyin"
+ RESET_PASSWORD_RESET: "Şifreniz sıfırlandı"
+ RESET_INVALID_LINK: "Hatalı sıfırlama bağlantısı, lütfen tekrar deneyin"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Şifre sıfırlama yönergesi %s adresine gönderilmiştir"
+ FORGOT_FAILED_TO_EMAIL: "Eposta yönergeleri başarısız, lütfen daha sonra tekrar deneyin"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "%s: şifre sıfırlanamıyor, eposta adresi atanmamış"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "%s adlı kullanıcı mevcut değil"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Şifre sıfırlaması gönderilemiyor. Eposta sistemi yapılandırılmadı"
+ FORGOT_EMAIL_SUBJECT: "%s Şifre Sıfırlama İsteği"
+ FORGOT_EMAIL_BODY: "Şifre Sıfırlama Sayın %1$s,
%4$s tarafından şifre sıfırlama isteği oluşturulmuştur.
Şifrenizi sıfırlamak için bu bağlantıya tıklayın
Alternatif olarak, şu url'yi tarayıcınızın adres çubuğuna kopyalayabilirsiniz:
%2$s
Saygılarımızla, %3$s
"
+ MANAGE_PAGES: "Sayfaları Yönet"
+ CONFIGURATION: "Yapılandırma"
+ PAGES: "Sayfalar"
+ PLUGINS: "Eklentiler"
+ PLUGIN: "Eklenti"
+ THEMES: "Temalar"
+ LOGOUT: "Çıkış"
+ BACK: "Geri"
+ NEXT: "Sonraki"
+ PREVIOUS: "Önceki"
+ ADD_PAGE: "Sayfa Ekle"
+ ADD_MODULAR: "Mödül Ekle"
+ MOVE: "Taşı"
+ DELETE: "Sil"
+ VIEW: "Görünüm"
+ SAVE: "Kaydet"
+ NORMAL: "Normal"
+ EXPERT: "Uzman"
+ EXPAND_ALL: "Tümünü Genişlet"
+ COLLAPSE_ALL: "Tümünü Daralt"
+ ERROR: "Hata"
+ CLOSE: "Kapat"
+ CANCEL: "İptal"
+ CONTINUE: "Devam"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Doğrulama Gerekli"
+ MODAL_CHANGED_DETECTED_TITLE: "Değişiklikler Tespit Edildi"
+ MODAL_CHANGED_DETECTED_DESC: "Kaydedilmemiş değişiklikler var. Kaydetmeden çıkmak istediğinizden emin misiniz?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Doğrulama Gerekli"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Bu dosyayı silmek istiyor musunuz? Bu işlem geri alınamaz."
+ ADD_FILTERS: "Filtre Ekle"
+ SEARCH_PAGES: "Sayfaları Ara"
+ VERSION: "Sürüm"
+ WAS_MADE_WITH: "Yapan :"
+ BY: "tarafından"
+ UPDATE_THEME: "Temayı Güncelle"
+ UPDATE_PLUGIN: "Eklentiyi Güncelle"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "bu tema artık kullanılabilir"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "bu eklenti artık kullanılabilir"
+ AUTHOR: "Yazar"
+ HOMEPAGE: "Anasayfa"
+ DEMO: "Demo"
+ BUG_TRACKER: "Hata Takipçisi"
+ KEYWORDS: "Anahtar Kelimeler"
+ LICENSE: "Lisans"
+ DESCRIPTION: "Açıklama"
+ README: "Benioku"
+ REMOVE_THEME: "Temayı Kaldır"
+ INSTALL_THEME: "Tema Yükle"
+ THEME: "Tema"
+ BACK_TO_THEMES: "Temalara geri dön"
+ BACK_TO_PLUGINS: "Eklentilere geri dön"
+ CHECK_FOR_UPDATES: "Güncelleştirmeleri Denetle"
+ ADD: "Ekle"
+ CLEAR_CACHE: "Önbelleği Temizle"
+ CLEAR_CACHE_ALL_CACHE: "Tüm Önbellek"
+ CLEAR_CACHE_ASSETS_ONLY: "Yalnızca Malzemeler"
+ CLEAR_CACHE_IMAGES_ONLY: "Yalnızca Resimler"
+ CLEAR_CACHE_CACHE_ONLY: "Yalnızca Önbellek"
+ CLEAR_CACHE_TMP_ONLY: "Sadece geçici dosyalar"
+ DASHBOARD: "Yönetim Paneli"
+ UPDATES_AVAILABLE: "Güncelleştirmeler Var"
+ DAYS: "Gün"
+ UPDATE: "Güncelle"
+ BACKUP: "Yedekle"
+ BACKUPS: "Yedeklemeler"
+ BACKUP_NOW: "Şimdi Yedekle"
+ BACKUPS_STATS: "Yedekleme İstatistikleri"
+ BACKUPS_HISTORY: "Yedekleme Geçmişi"
+ BACKUPS_PROFILES: "Yedekleme Profilleri"
+ BACKUPS_COUNT: "Yedekleme sayısı"
+ BACKUPS_PROFILES_COUNT: "Profil sayısı"
+ BACKUPS_TOTAL_SIZE: "Kullanılan alan"
+ BACKUPS_NEWEST: "En yeni yedekleme"
+ BACKUPS_OLDEST: "En eski yedekleme"
+ BACKUPS_PURGE: "Temizle"
+ STATISTICS: "İstatistikler"
+ TODAY: "Bugün"
+ WEEK: "Hafta"
+ MONTH: "Ay"
+ LATEST_PAGE_UPDATES: "Son Sayfa Güncellemeleri"
+ MAINTENANCE: "Bakım"
+ UPDATED: "Güncelleştirildi"
+ MON: "Pzt"
+ TUE: "Sal"
+ WED: "Çrş"
+ THU: "Prş"
+ FRI: "Cum"
+ SAT: "Cmt"
+ SUN: "Pzr"
+ COPY: "Kopyala"
+ EDIT: "Düzenle"
+ CREATE: "Oluştur"
+ GRAV_ADMIN: "Grav Yönetim Paneli"
+ GRAV_OFFICIAL_PLUGIN: "Resmi Grav Eklentisi"
+ GRAV_OFFICIAL_THEME: "Resmi Grav Teması"
+ PLUGIN_SYMBOLICALLY_LINKED: "Bu eklenti sembolik olarak bağlıdır. Güncelleştirmeler fark edilmeyecektir."
+ THEME_SYMBOLICALLY_LINKED: "Bu tema sembolik olarak bağlıdır. Güncelleştirmeler fark edilmeyecektir"
+ REMOVE_PLUGIN: "Eklentiyi Kaldır"
+ INSTALL_PLUGIN: "Eklenti Yükle"
+ AVAILABLE: "Mevcut"
+ INSTALLED: "Yüklü"
+ INSTALL: "Yükle"
+ ACTIVE_THEME: "Aktif Tema"
+ SWITCHING_TO: "Şuna değiştir"
+ SWITCHING_TO_DESCRIPTION: "Farklı bir temaya geçerek, tüm düzen sayfalarını, potansiyel olarak değişebileceğinin onayını vermiş olursunuz."
+ SWITCHING_TO_CONFIRMATION: "Temayı değiştirmek istiyor musunuz"
+ CREATE_NEW_USER: "Yeni Kullanıcı Oluştur"
+ REMOVE_USER: "Kullanıcıyı Sil"
+ ACCESS_DENIED: "Erişim reddedildi"
+ ACCOUNT_NOT_ADMIN: "hesabınız yönetici yetkilerine sahip değil"
+ PHP_INFO: "PHP Bilgisi"
+ INSTALLER: "Yükleyici"
+ AVAILABLE_THEMES: "Mevcut Temalar"
+ AVAILABLE_PLUGINS: "Mevcut Eklentiler"
+ INSTALLED_THEMES: "Yüklü Temalar"
+ INSTALLED_PLUGINS: "Yüklü Eklentiler"
+ BROWSE_ERROR_LOGS: "Hata Kayıtlarına Gözat"
+ SITE: "Web sitesi"
+ INFO: "Bilgi"
+ SYSTEM: "Sistem"
+ USER: "Kullanıcı"
+ ADD_ACCOUNT: "Hesap Ekle"
+ SWITCH_LANGUAGE: "Dili Değiştir"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Eklenti başarıyla etkinleştirildi"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Eklenti devre dışı bırakıldı"
+ SUCCESSFULLY_CHANGED_THEME: "Varsayılan tema başarıyla değiştirildi"
+ INSTALLATION_FAILED: "Kurulum Başarısız"
+ INSTALLATION_SUCCESSFUL: "Kurulum Başarılı"
+ UNINSTALL_FAILED: "Kaldırma Başarısız"
+ UNINSTALL_SUCCESSFUL: "Kaldırma Başarılı"
+ SUCCESSFULLY_SAVED: "Başarıyla kaydedildi"
+ SUCCESSFULLY_COPIED: "Başarıyla kopyalandı"
+ REORDERING_WAS_SUCCESSFUL: "Sıralama başarılı"
+ SUCCESSFULLY_DELETED: "Başarıyla silindi"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Dil başarıyla değiştirildi"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "Bu görev için izniniz yok"
+ CACHE_CLEARED: "Önbellek temizlendi"
+ METHOD: "Yöntem"
+ ERROR_CLEARING_CACHE: "Önbellek temizleme hatası"
+ AN_ERROR_OCCURRED: "Bir hata meydana geldi"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Yedeğiniz indirilmeye hazır"
+ DOWNLOAD_BACKUP: "Yedeği indir"
+ PAGES_FILTERED: "Filtrelenen Sayfalar"
+ NO_PAGE_FOUND: "Sayfa bulunamadı"
+ INVALID_PARAMETERS: "Hatalı Parametreler"
+ NO_FILES_SENT: "Dosya gönderilmedi"
+ EXCEEDED_FILESIZE_LIMIT: "PHP dosya boyutu sınırı aşıldı"
+ UNKNOWN_ERRORS: "Bilinmeyen hatalar"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Grav yapılandırma dosyasındaki boyut sınırı aşıldı"
+ UNSUPPORTED_FILE_TYPE: "Desteklenmeyen dosya formatı"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Yüklenilen dosya taşınamadı."
+ FILE_UPLOADED_SUCCESSFULLY: "Dosya başarıyla yüklendi"
+ FILE_DELETED: "Dosya silindi"
+ FILE_COULD_NOT_BE_DELETED: "Dosya silinemedi"
+ FILE_NOT_FOUND: "Dosya bulunamadı"
+ NO_FILE_FOUND: "Hiçbir dosya bulunamadı"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav başarıyla güncellendi, sürüm"
+ GRAV_UPDATE_FAILED: "Grav güncellemesi başarısız"
+ EVERYTHING_UPDATED: "Herşey güncellendi"
+ UPDATES_FAILED: "Güncellemeler başarısız"
+ AVATAR_BY: "Avatar sahibi :"
+ AVATAR_UPLOAD_OWN: "Ya da kendiniz bir dosya yükleyin..."
+ LAST_BACKUP: "Son Yedek"
+ FULL_NAME: "Tam isim"
+ USERNAME: "Kullanıcı Adı"
+ EMAIL: "Eposta Adresi"
+ USERNAME_EMAIL: "Kullanıcı Adı veya E-Posta"
+ PASSWORD: "Şifre"
+ PASSWORD_CONFIRM: "Parolayı yeniden girin"
+ TITLE: "Başlık"
+ LANGUAGE: "Dil"
+ ACCOUNT: "Hesap"
+ EMAIL_VALIDATION_MESSAGE: "Geçerli bir eposta adresi belirtilmeli"
+ PASSWORD_VALIDATION_MESSAGE: "Parola en az bir sayı, bir büyük harf, bir küçük harf ve en az 8 veya daha fazla karakterden oluşmalıdır"
+ LANGUAGE_HELP: "Favori dili belirleyin"
+ MEDIA: "Ortam"
+ DEFAULTS: "Varsayılanlar"
+ SITE_TITLE: "Site Başlığı"
+ SITE_TITLE_PLACEHOLDER: "Başlığı bütün sayfalara uygula"
+ SITE_TITLE_HELP: "Sitenizin varsayılan başlığı, çoğunlukla temalarda kullanılır"
+ SITE_DEFAULT_LANG: "Varsayılan Dil"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Temanın etiketinde kullanılacak varsayılan dil"
+ SITE_DEFAULT_LANG_HELP: "Temanın etiketinde kullanılacak varsayılan dil"
+ DEFAULT_AUTHOR: "Varsayılan Yazar"
+ DEFAULT_AUTHOR_HELP: "Varsayılan yazar adı, genellikle temalarda ya da sayfa içeriklerinde kullanılır"
+ DEFAULT_EMAIL: "Varsayılan Eposta"
+ DEFAULT_EMAIL_HELP: "Temalar ya da sayfalar için referans olacak varsayılan email adresi"
+ TAXONOMY_TYPES: "Taksonomi Tipleri"
+ TAXONOMY_TYPES_HELP: "Taksonomi türlerini sayfalarda kullanmak istiyorsanız, burada tanımlanmış olması gerekmektedir"
+ PAGE_SUMMARY: "Sayfa Özeti"
+ ENABLED: "Etkin"
+ ENABLED_HELP: "Sayfa özetini etkinleştir (sayfa özeti, sayfa içeriği ile aynı değeri döndürür)"
+ 'YES': "Evet"
+ 'NO': "Hayır"
+ SUMMARY_SIZE: "Özet Boyutu"
+ SUMMARY_SIZE_HELP: "İçerik özeti olarak kullanılmak üzere bir sayfadaki karakterlerin toplam sayısı"
+ FORMAT: "Biçim"
+ FORMAT_HELP: "short = use the first occurrence of delimiter or size; long = summary delimiter will be ignored"
+ SHORT: "Kısa"
+ LONG: "Uzun"
+ DELIMITER: "Ayraç"
+ DELIMITER_HELP: "Özet ayracı (varsayılan '===')"
+ METADATA: "Meta verisi"
+ METADATA_HELP: "Sayfa tarafından geçersiz kılınmadığı sürece her sayfada görüntülenen varsayılan meta veri değerleri"
+ NAME: "Ad(name)"
+ CONTENT: "İçerik"
+ REDIRECTS_AND_ROUTES: "Yönlendirmeler & Yollar"
+ CUSTOM_REDIRECTS: "Özel Yönlendirmeler"
+ CUSTOM_REDIRECTS_HELP: "routes to redirect to other pages. Standard Regex replacement is valid"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/takma/adin"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/yonlendirme/adresin"
+ CUSTOM_ROUTES: "Özel Yollar"
+ CUSTOM_ROUTES_HELP: "routes to alias to other pages. Standard Regex replacement is valid"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/takma/ad"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/ozel/yol"
+ FILE_STREAMS: "Dosya Akışları"
+ DEFAULT: "Varsayılan"
+ PAGE_MEDIA: "Sayfa İçeriği"
+ OPTIONS: "Ayarlar"
+ PUBLISHED: "Yayınlandı"
+ PUBLISHED_HELP: "By default, a page is published unless you explicitly set published: false or via a publish_date being in the future, or unpublish_date in the past"
+ DATE: "Tarih"
+ DATE_HELP: "The date variable allows you to specifically set a date associated with this page."
+ PUBLISHED_DATE: "Yayınlanma Tarihi"
+ PUBLISHED_DATE_HELP: "Can provide a date to automatically trigger publication."
+ UNPUBLISHED_DATE: "Yayından Kaldırma Tarihi"
+ UNPUBLISHED_DATE_HELP: "Can provide a date to automatically trigger un-publication."
+ ROBOTS: "Robot"
+ TAXONOMIES: "Bölümlendirmeler(taxonomy)"
+ TAXONOMY: "Taksonomi"
+ ADVANCED: "Gelişmiş"
+ SETTINGS: "Ayarlar"
+ FOLDER_NUMERIC_PREFIX: "Nümerik Klasör Öneki"
+ FOLDER_NUMERIC_PREFIX_HELP: "Numeric prefix that provides manual ordering and implies visibility"
+ FOLDER_NAME: "Klasör Adı"
+ FOLDER_NAME_HELP: "Bu sayfa için dosya sisteminde saklanacak klasörün adı"
+ PARENT: "Ebeveyn"
+ DEFAULT_OPTION_ROOT: "- Kök -"
+ DEFAULT_OPTION_SELECT: "- Seçiniz -"
+ DISPLAY_TEMPLATE: "Şablonu Görüntüle"
+ DISPLAY_TEMPLATE_HELP: "The page type that translates into which twig template renders the page"
+ BODY_CLASSES: "Body Sınıfları"
+ ORDERING: "Sıralama"
+ PAGE_ORDER: "Sayfa Sıralaması"
+ OVERRIDES: "Özelleştirmeler"
+ MENU: "Menü"
+ MENU_HELP: "The string to be used in a menu. If not set, Title will be used."
+ SLUG: "Kısa Url"
+ SLUG_HELP: "The slug variable allows you to specifically set the page's portion of the URL"
+ SLUG_VALIDATE_MESSAGE: "A slug must contain only lowercase alphanumeric characters and dashes"
+ PROCESS: "İşle"
+ PROCESS_HELP: "Control how pages are processed. Can be set per-page rather than globally"
+ DEFAULT_CHILD_TYPE: "Default Child Type"
+ USE_GLOBAL: "Genel Değeri Kullan"
+ ROUTABLE: "Yönlendirilebilir"
+ ROUTABLE_HELP: "Bu sayfa bir URL tarafından erişilebilir olursa"
+ CACHING: "Önbellekleme"
+ VISIBLE: "Görünür"
+ VISIBLE_HELP: "Bir sayfanın navigasyonda görünüp görünmeyeceğini belirler."
+ DISABLED: "Devre Dışı"
+ ITEMS: "Ögeler"
+ ORDER_BY: "Sıralama Ölçütü"
+ ORDER: "Sırala"
+ FOLDER: "Klasör"
+ ASCENDING: "Artan"
+ DESCENDING: "Azalan"
+ ADD_MODULAR_CONTENT: "Modüler İçerik Ekle"
+ PAGE_TITLE: "Sayfa Başlığı"
+ PAGE_TITLE_HELP: "Sayfanın başlığı"
+ PAGE: "Sayfa"
+ MODULAR_TEMPLATE: "Modüler Şablon"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "Dosyaadı"
+ PARENT_PAGE: "Ebeveyn Sayfa"
+ HOME_PAGE: "Ana Sayfa"
+ HOME_PAGE_HELP: "Grav'in varsayılan açılış sayfası olarak kullanacağı sayfa"
+ DEFAULT_THEME: "Varsayılan Tema"
+ DEFAULT_THEME_HELP: "Grav'in kullanacağı varsayılan şablonu ayarlayın (varsayılan Antimadde)"
+ TIMEZONE: "Zaman Dilimi"
+ TIMEZONE_HELP: "Sunucunun varsayılan saat dilimini geçersiz kıl"
+ SHORT_DATE_FORMAT: "Kısa Tarih Görünümü"
+ SHORT_DATE_FORMAT_HELP: "Temalar tarafında kullanılabilecek kısa tarih formatını ayarlayın"
+ LONG_DATE_FORMAT: "Uzun Tarih Görünümü"
+ LONG_DATE_FORMAT_HELP: "Temalar tarafında kullanılabilecek uzun tarih formatını ayarlayın"
+ DEFAULT_ORDERING: "Varsayılan sıralama"
+ DEFAULT_ORDERING_HELP: "Listede yer alan sayfalar geçersiz kılınmadıkça, bu sıralamaya göre oluşturulacaktır"
+ DEFAULT_ORDERING_DEFAULT: "Varsayılan - klasör adına göre"
+ DEFAULT_ORDERING_FOLDER: "Klasör - klasör önekine göre"
+ DEFAULT_ORDERING_TITLE: "Başlık - başlık alanına göre"
+ DEFAULT_ORDERING_DATE: "Tarih - tarihe göre"
+ DEFAULT_ORDER_DIRECTION: "Varsayılan sıralama yönü"
+ DEFAULT_ORDER_DIRECTION_HELP: "The direction of pages in a list"
+ DEFAULT_PAGE_COUNT: "Varsayılan sayfa sayısı"
+ DEFAULT_PAGE_COUNT_HELP: "Varsayılan maksimum sayfa listeleme sayısı"
+ DATE_BASED_PUBLISHING: "Tarih-bazlı yayınlama"
+ DATE_BASED_PUBLISHING_HELP: "Automatically (un)publish posts based on their date"
+ EVENTS: "Etkinlikler"
+ EVENTS_HELP: "Belirli olayları etkinleştirin ya da devredışı bırakın. Bu işlemler, bazı eklentilerin çalışmasını engelleyebilir"
+ REDIRECT_DEFAULT_ROUTE: "Varsayılan yola yönlendir"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Automatically redirect to a page's default route"
+ LANGUAGES: "Diller"
+ SUPPORTED: "Desteklenen"
+ SUPPORTED_HELP: "Comma separated list of 2 letter language codes (for example 'en,fr,de')"
+ TRANSLATIONS_ENABLED: "Çeviriler etkin"
+ TRANSLATIONS_ENABLED_HELP: "Grav, eklenti ve uzantılarında çevirileri destekleyin"
+ TRANSLATIONS_FALLBACK: "Translations fallback"
+ TRANSLATIONS_FALLBACK_HELP: "Fallback through supported translations if active language doesn't exist"
+ ACTIVE_LANGUAGE_IN_SESSION: "Oturumdaki aktif dil"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Etkin dili bu oturumda sakla"
+ HTTP_HEADERS: "HTTP Başlıkları"
+ EXPIRES: "Sona erme"
+ EXPIRES_HELP: "Sets the expires header. The value is in seconds."
+ CACHE_CONTROL: "HTTP Cache-Control"
+ CACHE_CONTROL_HELP: "Set to a valid cache-control value such as `no-cache, no-store, must-revalidate`"
+ LAST_MODIFIED: "Son değiştirilme"
+ LAST_MODIFIED_HELP: "Sets the last modified header that can help optimize proxy and browser caching"
+ ETAG: "ETag"
+ ETAG_HELP: "Sets the etag header to help identify when a page has been modified"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Sets the `Vary: Accept Encoding` header to help with proxy and CDN caching"
+ MARKDOWN_EXTRA: "Ekstra Tanım"
+ MARKDOWN_EXTRA_HELP: "Enable default support for Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ AUTO_LINE_BREAKS: "Otomatik satır sonları"
+ AUTO_LINE_BREAKS_HELP: "Markdown'da otomatik satır sonlarını aktif et"
+ AUTO_URL_LINKS: "Otomatik URL bağlantıları"
+ AUTO_URL_LINKS_HELP: "URL'leri otomatik olarak HTML linklerine çevirmeyi aktif et"
+ ESCAPE_MARKUP: "Biçimlendirmeyi süz(ESCAPE_MARKUP)"
+ ESCAPE_MARKUP_HELP: "Escape markup tags into HTML entities"
+ CACHING_HELP: "Global ON/OFF switch to enable/disable Grav caching"
+ CACHE_CHECK_METHOD: "Cache kontrol methodu"
+ CACHE_CHECK_METHOD_HELP: "Select the method that Grav uses to check if page files have been modified."
+ CACHE_DRIVER: "Önbellek sürücüsü"
+ CACHE_DRIVER_HELP: "Önbellek sürücüsü seçin. Otomatik Algıla'yı seçmeniz durumunda Grav sizin için en iyisini bulacaktır"
+ CACHE_PREFIX: "Önbellek öneki"
+ CACHE_PREFIX_HELP: "An identifier for part of the Grav key. Don't change unless you know what your doing."
+ CACHE_PREFIX_PLACEHOLDER: "Derived from base URL (override by entering random string)"
+ LIFETIME: "Yaşam süresi"
+ LIFETIME_HELP: "Sets the cache lifetime in seconds. 0 = infinite"
+ GZIP_COMPRESSION: "Gzip sıkıştırması"
+ GZIP_COMPRESSION_HELP: "Grav sayfalarında arttırılmış performans için GZip sıkıştırmasını aktif et."
+ TWIG_TEMPLATING: "Twig Şablonu"
+ TWIG_CACHING: "Twig önbellekleme"
+ TWIG_CACHING_HELP: "Twig önbellekleme mekanizmasını kontrol edin. En iyi performans için bunu etkinleştirin."
+ TWIG_DEBUG: "Twig hata ayıklama"
+ TWIG_DEBUG_HELP: "Twig Debugger uzantısını yüklememe seçeneğine izin verir"
+ DETECT_CHANGES: "Değişiklikleri tespit et"
+ DETECT_CHANGES_HELP: "Twig şablonlarında bir değişiklik algılandığında otomatik olarak yeniden derlenecektir"
+ AUTOESCAPE_VARIABLES: "Autoescape variables"
+ AUTOESCAPE_VARIABLES_HELP: "Autoescapes all variables. This will break your site most likely"
+ ASSETS: "Statik dosyalar"
+ CSS_PIPELINE: "CSS pipeline"
+ CSS_PIPELINE_HELP: "The CSS pipeline is the unification of multiple CSS resources into one file"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Include externals in CSS pipeline"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "External URLs sometimes have relative file references and shouldn't be pipelined"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "CSS pipeline render first"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Render the CSS pipeline before any other CSS references that are not included"
+ CSS_MINIFY: "CSS küçültme"
+ CSS_MINIFY_HELP: "Minify the CSS during pipelining"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "CSS minify işlemini Windows platformlarında geçersiz kıl"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Windows platformları için minify işlemini geçersiz kıl. Bu özellik ThreadStackSize nedeniyle hatalı çalışıyor"
+ CSS_REWRITE: "CSS'i yeniden yaz"
+ CSS_REWRITE_HELP: "Rewrite any CSS relative URLs during pipelining"
+ JAVASCRIPT_PIPELINE: "JavaScript Yolu"
+ JAVASCRIPT_PIPELINE_HELP: "The JS pipeline is the unification of multiple JS resources into one file"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Include externals in JS pipeline"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "External URLs sometimes have relative file references and shouldn't be pipelined"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "JS pipeline render first"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Render the JS pipeline before any other JS references that are not included"
+ JAVASCRIPT_MINIFY: "JavaScript küçültme"
+ JAVASCRIPT_MINIFY_HELP: "Minify the JS during pipelining"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "Statis dosyalarda timestampleri etkinleştir"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Statik dosyalar için timestamp'i aktif et"
+ COLLECTIONS: "Koleksiyonlar"
+ ERROR_HANDLER: "Hata yakalayıcı"
+ DISPLAY_ERRORS: "Hataları göster"
+ DISPLAY_ERRORS_HELP: "Hataları tam sayfa olarak görüntüle"
+ LOG_ERRORS: "Hataları kaydet"
+ LOG_ERRORS_HELP: "Hata günlüklerini /logs klasörüne kaydet"
+ DEBUGGER: "Hata Ayıklayıcı"
+ DEBUGGER_HELP: "Grav hata ayıklayıcısını aktif et ve aşağıdaki ayarları yap"
+ DEBUG_TWIG: "Hata Ayıkla - Twig"
+ DEBUG_TWIG_HELP: "Twig şablonlarında hata ayıklamayı aktif et"
+ SHUTDOWN_CLOSE_CONNECTION: "Shutdown close connection"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "OnShutdown() fonksiyonunu çağırmadan önce bağlantıyı kapatın. Hata ayıklama yanlış çalışabilir"
+ DEFAULT_IMAGE_QUALITY: "Varsayılan görüntü kalitesi"
+ DEFAULT_IMAGE_QUALITY_HELP: "Görselleri yeniden oluştururken ya da önbelleğe alırken kullanılacak varsayılan kalite (85%)"
+ CACHE_ALL: "Tüm görüntülere önbelleğe al"
+ CACHE_ALL_HELP: "Medya dosyalarına müdahale edilmemiş olsa bile tüm görselleri Grav'in önbellek sistemi yardımıyla çalıştır"
+ IMAGES_DEBUG: "Görüntü hata ayıklama filigranı"
+ IMAGES_DEBUG_HELP: "Show an overlay over images indicating the pixel depth of the image when working with retina for example"
+ UPLOAD_LIMIT: "Dosya yükleme limiti"
+ UPLOAD_LIMIT_HELP: "Maksimum dosya yükleme boyutunu girin (bayt)(0 değeri limitsiz olarak kabul edilir)"
+ ENABLE_MEDIA_TIMESTAMP: "Medya dosyalarında timestamp özelliğini etkinleştir"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "Appends a timestamp based on last modified date to each media item"
+ SESSION: "Oturum"
+ SESSION_ENABLED_HELP: "Grav içerisinde oturum desteğini etkinleştir"
+ TIMEOUT: "Zamanaşımı"
+ TIMEOUT_HELP: "Oturum zaman aşımını saniye olarak ayarlar"
+ SESSION_NAME_HELP: "An identifier used to form the name of the session cookie"
+ ABSOLUTE_URLS: "Mutlaka URL'ler"
+ ABSOLUTE_URLS_HELP: "`base_url` için mutlak ya da ilgili URL'ler"
+ PARAMETER_SEPARATOR: "Parameter separator"
+ PARAMETER_SEPARATOR_HELP: "Separator for passed parameters that can be changed for Apache on Windows"
+ TASK_COMPLETED: "Görev tamamlandı"
+ EVERYTHING_UP_TO_DATE: "Herşey günceş"
+ UPDATES_ARE_AVAILABLE: "güncelleştirme(ler) mevcut"
+ IS_AVAILABLE_FOR_UPDATE: "güncelleme için uygun"
+ IS_NOW_AVAILABLE: "kullanılabilir"
+ CURRENT: "Güncel"
+ UPDATE_GRAV_NOW: "Grav'ı şimdi güncelle"
+ GRAV_SYMBOLICALLY_LINKED: "Grav sembolik olarak bağlandı. Yükseltmeler kullanılamayacaktır"
+ UPDATING_PLEASE_WAIT: "Güncelleniyor... Lütfen indirilirken bekleyin"
+ OF_THIS: "of this"
+ OF_YOUR: "of your"
+ HAVE_AN_UPDATE_AVAILABLE: "kullanılabilir bir güncelleme var"
+ SAVE_AS: "Farklı kaydet"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Bu sayfayı, alt sayfalarıyla birlikte silmek istediğinize emin misiniz? Eğer sayfa başka dillere çevirilmişse bu çevirilere saklanacaktır ve ayrıca silinmelidir. Aksi halde sayfa klasörü, alt sayfalarıyla birlikte silinecektir. Bu eylem geri alınamaz."
+ AND: "ve"
+ UPDATE_AVAILABLE: "Güncelleme var"
+ METADATA_KEY: "Anahtar (örn. 'keywords')"
+ METADATA_VALUE: "Değer (örn. 'blog, grav')"
+ USERNAME_HELP: "Kullanıcı adı 3 ile 16 karakter arasında olmalıdır. Küçük harfler, sayılar, alt çizgiler ve kısa çizgi içerebilir. Büyük harf, boşluk ya da özel karakterlere izin verilmiyor"
+ FULLY_UPDATED: "Tamamen Güncellendi"
+ SAVE_LOCATION: "Kayıt konumu"
+ PAGE_FILE: "Sayfa Şablonu"
+ PAGE_FILE_HELP: "Sayfa şablonu dosya adı ve bu sayfa için varsayılan olarak görüntülenecek şablon"
+ NO_USER_ACCOUNTS: "Kullanıcı hesabı bulunamadı, önce bir tane ekleyin..."
+ REDIRECT_TRAILING_SLASH: "Redirect trailing slash"
+ REDIRECT_TRAILING_SLASH_HELP: "Perform a 301 redirect rather than transparently handling trailing slash URIs."
+ DEFAULT_DATE_FORMAT: "Sayfa tarih biçimi"
+ DEFAULT_DATE_FORMAT_HELP: "Page date format used by Grav. By default, Grav attempts to guess your date format, however you can specifiy a format using PHP's date syntax (e.g.: Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Otomatik olarak tahmin et"
+ IGNORE_FILES: "Dosyaları yoksay"
+ IGNORE_FILES_HELP: "Sayfalar işlenirken yok sayılacak belirli dosyalar"
+ IGNORE_FOLDERS: "Klasörleri yoksay"
+ IGNORE_FOLDERS_HELP: "Sayfalar işlenirken yok sayılacak belirli klasörler"
+ HTTP_ACCEPT_LANGUAGE: "Dili tarayıcıdan al"
+ HTTP_ACCEPT_LANGUAGE_HELP: "You can opt to try to set the language based on `http_accept_language` header tag in the browser"
+ OVERRIDE_LOCALE: "Yerel ayarları geçersiz kıl"
+ OVERRIDE_LOCALE_HELP: "Geçerli dili temel al ve PHP'de bulunan yerel ayarı geçersiz kıl"
+ REDIRECT: "Sayfa yönlendirmesi"
+ REDIRECT_HELP: "Enter a page route or external URL for this page to redirect to. e.g. `/some/route` or `http://somesite.com`"
+ PLUGIN_STATUS: "Eklenti durumu"
+ INCLUDE_DEFAULT_LANG: "Varsayılan dili dahil et"
+ INCLUDE_DEFAULT_LANG_HELP: "This will prepend all URLs in the default language with the default language. e.g. `/en/blog/my-post`"
+ ALLOW_URL_TAXONOMY_FILTERS: "URL Bölümlendirme(Taxonomy) Süzgeçleri"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Page-based collections allow you to filter via `/taxonomy:value`."
+ REDIRECT_DEFAULT_CODE: "Varsayılan yönlendirme kodu"
+ REDIRECT_DEFAULT_CODE_HELP: "Yönlendirmeler için kullanılacak HTTP durum kodu"
+ IGNORE_HIDDEN: "Ignore hidden"
+ IGNORE_HIDDEN_HELP: "NOKTA ile başlayan tüm dosya ve klasörleri yoksay"
+ WRAPPED_SITE: "Wrapped site"
+ WRAPPED_SITE_HELP: "For themes/plugins to know if Grav is wrapped by another platform"
+ FALLBACK_TYPES: "Allowed fallback types"
+ FALLBACK_TYPES_HELP: "Allowed file types that can be found if accessed via Page route. Defaults to any supported media type."
+ INLINE_TYPES: "Inline fallback types"
+ INLINE_TYPES_HELP: "A list of file types that should be displayed inline rather than downloaded"
+ APPEND_URL_EXT: "URL uzantısı ekle"
+ APPEND_URL_EXT_HELP: "Will add a custom extension to the Page's URL. Note, this will mean Grav will look for `..twig` template"
+ PAGE_MODES: "Sayfa Modları"
+ PAGE_TYPES: "Sayfa Tipleri"
+ ACCESS_LEVELS: "Erişim seviyeleri"
+ GROUPS: "Gruplar"
+ GROUPS_HELP: "List of groups the user is part of"
+ ADMIN_ACCESS: "Yönetici Erişimi"
+ SITE_ACCESS: "Site Erişimi"
+ INVALID_SECURITY_TOKEN: "Geçersiz Güvenlik Girdisi"
+ ACTIVATE: "Aktif Et"
+ TWIG_UMASK_FIX: "Umask Fix"
+ TWIG_UMASK_FIX_HELP: "Twig önbellek dosyaları varsayılan olarak 0755 iznine sahiptir, ayarları 0775 olarak düzeltin"
+ CACHE_PERMS: "Önbellek İzinleri"
+ CACHE_PERMS_HELP: "Varsayılan önbellek klasörü genellikle 0755 şeklindedir ya da kuruluma bağlı olarak 0775 olmalıdır"
+ REMOVE_SUCCESSFUL: "Kaldırma Başarılı"
+ REMOVE_FAILED: "Kaldırma Başarısız"
+ HIDE_HOME_IN_URLS: "URL'lerden anasayfayı gizle"
+ HIDE_HOME_IN_URLS_HELP: "Will ensure the default routes for any pages under home do not reference home's regular route"
+ TWIG_FIRST: "Önce Twig'i İşle"
+ TWIG_FIRST_HELP: "If you enabled Twig page processing, then you can configure Twig to process before or after markdown"
+ SESSION_SECURE: "Güvenli"
+ SESSION_SECURE_HELP: "If true, indicates that communication for this cookie must be over an encrypted transmission. WARNING: Enable this only on sites that run exclusively on HTTPS"
+ SESSION_HTTPONLY: "Yalnız HTTP"
+ SESSION_HTTPONLY_HELP: "If true, indicates that cookies should be used only over HTTP, and JavaScript modification is not allowed"
+ REVERSE_PROXY: "Reverse Proxy"
+ REVERSE_PROXY_HELP: "Enable this if you are behind a reverse proxy and you are having trouble with URLs containing incorrect ports"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "Geçersiz frontmatter, kaydedilmedi"
+ ADD_FOLDER: "Klasör Ekle"
+ PROXY_URL: "Proxy URL'si"
+ PROXY_URL_HELP: "Proksi sunucusunun HOST ya da IP ve PORT bilgisini gir"
+ NOTHING_TO_SAVE: "Kaydedilecek Hiçbirşey Yok"
+ FILE_ERROR_ADD: "Dosya eklenirken bir hata meydana geldi"
+ FILE_ERROR_UPLOAD: "Dosya yüklenirken bir hata meydana geldi"
+ FILE_UNSUPPORTED: "Desteklenmeyen dosya biçimi"
+ ADD_ITEM: "Öge ekle"
+ FILE_TOO_LARGE: "Bu dosya yüklenebilecek boyuttan daha büyük, PHP ayarlarınıza göre maksimum boyut %s `post_max_size` değerini artırın"
+ INSTALLING: "Kuruluyor"
+ LOADING: "Yükleniyor.."
+ DEPENDENCIES_NOT_MET_MESSAGE: "The following dependencies need to be fulfilled first:"
+ ERROR_INSTALLING_PACKAGES: "Paket(ler) kurulurken hata oluştu"
+ INSTALLING_DEPENDENCIES: "Gereksinimler kuruluyor..."
+ INSTALLING_PACKAGES: "Paket(ler) kuruluyor.."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Paket(ler) başarıyla kuruldu."
+ READY_TO_INSTALL_PACKAGES: "Kuruluma hazır paket(ler)"
+ PACKAGES_NOT_INSTALLED: "Yüklü olmayan paketler"
+ PACKAGES_NEED_UPDATE: "Paketler zaten yüklü, fakat eski"
+ PACKAGES_SUGGESTED_UPDATE: "Paketler zaten yüklü ve en güncel sürümlerinde"
+ REMOVE_THE: "Kaldır: %s"
+ CONFIRM_REMOVAL: "Bunu silmek istediğinizden emin misiniz: %s?"
+ REMOVED_SUCCESSFULLY: "%s başarıyla kaldırıldı"
+ ERROR_REMOVING_THE: "%s kaldırılırken hata"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "%s diğer kurulu paketler tarafından kullanılmayan birtakım gereksinimler içeriyor. Kullanmıyorsanız direkt olarak silebilirsiniz."
+ READY_TO_UPDATE_PACKAGES: "Güncellemeye hazır paket(ler)"
+ ERROR_UPDATING_PACKAGES: "Paket(ler) günceleştirilirken hata oluştu"
+ UPDATING_PACKAGES: "Paket(ler) güncelleştiriliyor.."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Paket(ler) başarıyla güncellendi."
+ UPDATING: "Güncelleniyor"
+ GPM_RELEASES: "GPM Duyuruları"
+ GPM_RELEASES_HELP: "Beta sürümünü veya test sürümlerini yüklemek için 'Test' seçeneğini belirleyin"
+ GPM_METHOD: "Remote Fetch Method"
+ GPM_METHOD_HELP: "When set to Auto, Grav will determine if fopen is available and use it, otherwise fall back to cURL. To force the use of one or the other switch the setting."
+ GPM_VERIFY_PEER: "Remote Verify Peer (SSL)"
+ GPM_VERIFY_PEER_HELP: "Some providers seems to fail verifying getgrav.org SSL certificate, causing GPM to not work. If this is the case for you, turning off this setting might help"
+ AUTO: "Otomatik"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "Kararlı"
+ TESTING: "Test ediliyor"
+ FRONTMATTER_PROCESS_TWIG: "Process frontmatter Twig"
+ FRONTMATTER_PROCESS_TWIG_HELP: "When enabled you can use Twig config variables in page front matter"
+ FRONTMATTER_IGNORE_FIELDS: "Ignore frontmatter fields"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "Certain frontmatter fields may contain Twig but should not be processed, such as 'forms'"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "%s başarıyla yüklendi"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Parent setting order, ordering disabled"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Sayfa görüntülenebilir değil, sıralama devredışı"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "Ordering via the admin is unsupported because there are more than 200 siblings"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Page ordering is disabled for this page because Folder Numeric Prefix is not enabled"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "NOTE: You cannot add media files until you save the page. Just click 'Save' on top"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "NOTE: Page must be saved before you can upload files to it."
+ DROP_FILES_HERE_TO_UPLOAD: "Drop your files here or click in this area "
+ INSERT: "Ekle"
+ UNDO: "Geri al"
+ REDO: "İleri al"
+ HEADERS: "Başlıklar"
+ BOLD: "Kalın"
+ ITALIC: "Eğik"
+ STRIKETHROUGH: "Üstü Çizili"
+ SUMMARY_DELIMITER: "Özet ayracı"
+ LINK: "Bağlantı"
+ IMAGE: "Resim"
+ BLOCKQUOTE: "Alıntı"
+ UNORDERED_LIST: "Sırasız Liste"
+ ORDERED_LIST: "Sıralı Liste"
+ EDITOR: "Düzenleyici"
+ PREVIEW: "Önizleme"
+ FULLSCREEN: "Tam Ekran"
+ MODULAR: "Modüler"
+ NON_ROUTABLE: "Yönlendirilemeyen"
+ NON_MODULAR: "Modüler Olmayan"
+ NON_VISIBLE: "Görünür Olmayan"
+ NON_PUBLISHED: "Yayımlanmamış"
+ CHARACTERS: "karakter"
+ PUBLISHING: "Yayımlanıyor"
+ NOTIFICATIONS: "Bildirimler"
+ MEDIA_TYPES: "Ortam Türleri"
+ IMAGE_OPTIONS: "Görüntü Seçenekleri"
+ MIME_TYPE: "Mime Türü"
+ THUMB: "Küçük Resim"
+ TYPE: "Tür"
+ FILE_EXTENSION: "Dosya Uzantısı"
+ LEGEND: "Sayfa Göstergesi"
+ MEMCACHE_SERVER: "Memcache sunucusu"
+ MEMCACHE_SERVER_HELP: "Memcache sunucu adresi"
+ MEMCACHE_PORT: "Memcache bağlantı noktası"
+ MEMCACHE_PORT_HELP: "Memcache sunucusu bağlantı noktası"
+ MEMCACHED_SERVER: "Memcached sunucusu"
+ MEMCACHED_SERVER_HELP: "Memcached sunucu adresi"
+ MEMCACHED_PORT: "Memcached bağlantı noktası"
+ MEMCACHED_PORT_HELP: "Memcached sunucusu bağlantı noktası"
+ REDIS_SERVER: "Redis sunucusu"
+ REDIS_SERVER_HELP: "Redis sunucu adresi"
+ REDIS_PORT: "Redis bağlantı noktası"
+ REDIS_PORT_HELP: "Redis sunucusu bağlantı noktası"
+ REDIS_PASSWORD: "Redis password/secret"
+ ALL: "Tümü"
+ FROM: "Başlangıç"
+ TO: "ile"
+ RELEASE_DATE: "Yayınlanma Tarihi"
+ SORT_BY: "Sıralama Ölçütü"
+ RESOURCE_FILTER: "Filtrele..."
+ FORCE_SSL: "SSL Kullanmaya Zorla"
+ FORCE_SSL_HELP: "Globally force SSL, if enabled when the site is reached through HTTP, Grav sends a redirect to the HTTPS page"
+ NEWS_FEED: "Haber Kaynağı"
+ EXTERNAL_URL: "Harici Bağlantı"
+ SESSION_PATH: "The session path"
+ SESSION_PATH_HELP: "Use only if you choose a custom base URL (you rewrite the site domain / subfolder)"
+ CUSTOM_BASE_URL: "Özelleştirilmiş kök URL"
+ CUSTOM_BASE_URL_HELP: "Use if you want to rewrite the site domain or use a different subfolder than the one used by Grav. Example: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: '"%s" sayfalar dışarısında kullanılamaz.'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: 'Unable to upload file %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: '%s dosyası "%s" konumuna taşınamadı'
+ DROPZONE_CANCEL_UPLOAD: 'Yüklemeyi iptal et'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Bu yüklemeyi iptal etmek istediğinizden emin misiniz?'
+ DROPZONE_DEFAULT_MESSAGE: 'Dosyalarınızı buraya sürükleyin ya da bu alana tıklayın '
+ DROPZONE_FALLBACK_MESSAGE: 'Tarayıcınız sürükle bırak şeklinde yüklemeleri desteklemiyor.'
+ DROPZONE_FALLBACK_TEXT: 'Please use the fallback form below to upload your files like in the older days.'
+ DROPZONE_FILE_TOO_BIG: 'Dosya fazla büyük ({{filesize}}MiB), maksimum dosya boyutu {{maxFilesize}} MiB’dir.'
+ DROPZONE_INVALID_FILE_TYPE: "Bu türdeki dosyaları yükleyemezsiniz."
+ DROPZONE_MAX_FILES_EXCEEDED: "Daha fazla dosya yükleyemezsiniz."
+ DROPZONE_REMOVE_FILE: "Dosyayı Kaldır"
+ DROPZONE_RESPONSE_ERROR: "Sunucu {{statusCode}} koduyla cevap verdi."
+ PREMIUM_PRODUCT: "Premium"
+ ADMIN_CACHING: "Admin sayfasında önbelleği etkinleştir"
+ ADMIN_CACHING_HELP: "Caching in the admin can be controlled independently from the front-end site"
+ DESTINATION_NOT_SPECIFIED: "Hedef belirtilmedi"
+ UPLOAD_ERR_NO_TMP_DIR: "Missing a temporary folder"
+ SESSION_SPLIT: "Session Split"
+ SESSION_SPLIT_HELP: "Independent split sessions between site and other plugins (such as admin)"
+ ERROR_FULL_BACKTRACE: "Tam Backtrace Hatası"
+ ERROR_SIMPLE: "Basit Hata"
+ ERROR_SYSTEM: "Sistem Hatası"
+ IMAGES_AUTO_FIX_ORIENTATION: "Fix orientation automatically"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Automatically fix the image orientation based on the Exif data"
+ REDIS_SOCKET: "Redis socket"
+ REDIS_SOCKET_HELP: "The Redis socket"
+ NOT_SET: "Not set"
+ PERMISSIONS: "İzinler"
+ NEVER_CACHE_TWIG: "Twig'i asla önbelleğe alma"
+ NEVER_CACHE_TWIG_HELP: "Sadece içeriği önbelleğe al ve sayfalar için her seferinde Twig çalışsın. twig_first seçeneği yoksayılacaktır."
+ ALLOW_WEBSERVER_GZIP: "Sunucu Gzip sıkıştırmasına izin ver"
+ ALLOW_WEBSERVER_GZIP_HELP: "Off by default. When enabled, WebServer-configured Gzip/Deflate compression will work, but http connection will not be closed before onShutDown() event causing slower page loading"
+ OFFLINE_WARNING: "GPM ile bağlantı kurulamadı"
+ CLEAR_IMAGES_BY_DEFAULT: "Clear image cache by default"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "By default processed images are cleared for all cache clears, this can be disabled"
+ CLI_COMPATIBILITY: "CLI Uyumluluğu"
+ CLI_COMPATIBILITY_HELP: "Ensures that only non-volatile Cache drivers are used (file, redis, memcache, etc.)"
+ REINSTALL_PLUGIN: "Eklentiyi yeniden kur"
+ REINSTALL_THEME: "Temayı yeniden kur"
+ REINSTALL_THE: "Reinstall the %s"
+ CONFIRM_REINSTALL: "Are you sure you want to reinstall this %s?"
+ REINSTALLED_SUCCESSFULLY: "%s reinstalled successfully"
+ ERROR_REINSTALLING_THE: "Error reinstalling the %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Package %s reinstalled successfully"
+ REINSTALLATION_FAILED: "Yeniden kurulum hatasi"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "The installed version is not the latest release. By clicking Continue, you'll remove the current version and install the latest available release"
+ TOOLS: "Araçlar"
+ DIRECT_INSTALL: "Yükleme yolu"
+ NO_PACKAGE_NAME: "Package name not specified"
+ PACKAGE_EXTRACTION_FAILED: "Package extraction failed"
+ NOT_VALID_GRAV_PACKAGE: "Not a valid Grav package"
+ NAME_COULD_NOT_BE_DETERMINED: "Name could not be determined"
+ CANNOT_OVERWRITE_SYMLINKS: "Cannot overwrite symlinks"
+ ZIP_PACKAGE_NOT_FOUND: "ZIP package could not be found"
+ GPM_OFFICIAL_ONLY: "Official GPM Only"
+ GPM_OFFICIAL_ONLY_HELP: "Only allow direct installs from the official GPM repository only."
+ NO_CHILD_TYPE: "No child type for this rawroute"
+ SORTABLE_PAGES: "Sortable Pages:"
+ UNSORTABLE_PAGES: "Sıralanamayan Sayfalar"
+ ADMIN_SPECIFIC_OVERRIDES: "Admin Specific Overrides"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "Children Display Order"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "The order that children of this page should be displayed in the 'Pages' view of Admin plugin"
+ PWD_PLACEHOLDER: "complex string at least 8 chars long"
+ PWD_REGEX: "Password Regex"
+ PWD_REGEX_HELP: "By default: Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters"
+ USERNAME_PLACEHOLDER: "lowercase chars only, e.g. 'admin'"
+ USERNAME_REGEX: "Username Regex"
+ USERNAME_REGEX_HELP: "By default: Only lowercase chars, digits, dashes, and underscores. 3 - 16 chars"
+ CONTENT_PADDING: "Content padding"
+ CONTENT_PADDING_HELP: "Enable/Disable content padding around content area to provide more space"
+ ENABLE_AUTO_METADATA: "Auto metadata from Exif"
+ ENABLE_AUTO_METADATA_HELP: "Görsel için otomatikman EXIF bilgilerini kullanarak metadata dosyası oluştur"
+ 2FA_TITLE: "2-Adımlı Doğrulama"
+ 2FA_INSTRUCTIONS: "##### 2-Adımlı Doğrulama\nHesabınızda **2FA** etkin durumda. Lütfen giriş yapabilmek için **2FA** uygulamanızı kullanarak **6-digit code** girin."
+ 2FA_REGEN_HINT: "Gizli anahtarı yeniden oluşturursanız kimlik doğrulama uygulamasını güncellemeniz gereklidir"
+ 2FA_LABEL: "Yönetici Erişimi"
+ 2FA_FAILED: "Invalid 2-Factor Authentication code, please try again...\nGeçersiz 2FA doğrulama kodu, lütfen tekrar deneyin..."
+ 2FA_ENABLED: "2FA Etkin"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "2FA Doğrulama Anahtarı"
+ 2FA_REGENERATE: "Yeniden Oluştur"
+ FORCE_LOWERCASE_URLS: "URL harflerini küçült"
+ FORCE_LOWERCASE_URLS_HELP: "Varsayılan olarak Grav, URL'de ki tüm harfleri küçültecektir, Bunu kapalı hale getirirseniz URL içinde büyük harfler kullanılabilir."
+ INTL_ENABLED: "Intl modül entegrasyonu"
+ VIEW_SITE_TIP: "Siteyi görüntüle"
+ TOOLS_DIRECT_INSTALL_TITLE: "Grav Paketlerini Doğrudan Yükle"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "ZIP Olarak Paket Yükle ve Kur"
+ TOOLS_DIRECT_INSTALL_UPLOAD_DESC: "Bu yöntemle geçerli birtema ya da eklenti paketini, hatta Grav güncelleme paketini ZIP olarak yükleyebilirsin. Bu paketin GPM ile kaydedilmesi gerekmez ve önceki bir sürüme kolayca geri dönmenizi veya test için yüklemenizi sağlar."
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "Uzak URL ile Paket Yükle"
+ TOOLS_DIRECT_INSTALL_URL_DESC: "Alternatif bir yöntem olarak, paketin tam ZIP dosyasının bulunduğu uzak adresi belirterek yükleyebilirsin."
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Yükle ve kur"
+ ROUTE_OVERRIDES: "Geçersiz Rotalar"
+ ROUTE_DEFAULT: "Varsayılan Rota"
+ ROUTE_CANONICAL: "Standart Rota"
+ ROUTE_ALIASES: "Rota Takma Adları"
diff --git a/user/plugins/admin/languages/uk.yaml b/user/plugins/admin/languages/uk.yaml
new file mode 100644
index 0000000..53bad9a
--- /dev/null
+++ b/user/plugins/admin/languages/uk.yaml
@@ -0,0 +1,552 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Це бета-реліз! Використовуйте його на продуктовому сервері на свій страх і ризик..."
+ ADMIN_REPORT_ISSUE: "Виявила проблему? Будь ласка, повідомте про це на GitHub."
+ LOGIN_BTN: "Увійти"
+ LOGIN_BTN_FORGOT: "Забув"
+ LOGIN_BTN_RESET: "Скинути пароль"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Надіслати інструкції для скидання"
+ LOGIN_BTN_CLEAR: "Очистити форму"
+ LOGIN_BTN_CREATE_USER: "Створити користувача"
+ LOGIN_LOGGED_IN: "Ви успішно увійшли"
+ LOGIN_FAILED: "Не вдалося увійти"
+ LOGGED_OUT: "Ви вийшли з системи"
+ RESET_NEW_PASSWORD: "Будь ласка, введіть новий пароль …"
+ RESET_LINK_EXPIRED: "Час на скидання минув. Будь ласка, спробуйте ще раз"
+ RESET_PASSWORD_RESET: "Пароль було скинуто"
+ RESET_INVALID_LINK: "Використовується недійсне посилання на скидання, будь ласка, спробуйте ще раз"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Інструкції для скидання пароля були відправлені на вашу електронну адресу"
+ FORGOT_FAILED_TO_EMAIL: "Не вдалося надіслати інструкції, будь ласка спробуйте пізніше"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Не вдалося скинути пароль для %s, не вказано емейл"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "Користувач з ім'ям користувача %s не існує"
+ FORGOT_EMAIL_NOT_CONFIGURED: "Не вдалося скинути пароль. Цей сайт не налаштований для відправлення електронних листів"
+ FORGOT_EMAIL_SUBJECT: "%s Запит на скидання пароля"
+ FORGOT_EMAIL_BODY: "Скидання паролю Вітаємо, %1$s!
%4$s було здійснено запит на скидання паролю.
Клацніть тут, щоб скинути пароль
Також, ви можете скопіювати наступне посилання у поле адреси вашого браузера:
%2$s
З найкращими побажаннями, %3$s
"
+ MANAGE_PAGES: "Управління сторінками"
+ CONFIGURATION: "Налаштування"
+ PAGES: "Сторінки"
+ PLUGINS: "Плагіни"
+ PLUGIN: "Плагін"
+ THEMES: "Теми"
+ LOGOUT: "Вийти"
+ BACK: "Назад"
+ NEXT: "Наступний"
+ PREVIOUS: "Попередній"
+ ADD_PAGE: "Додати сторінку"
+ ADD_MODULAR: "Додати модуль"
+ MOVE: "Перемістити"
+ DELETE: "Видалити"
+ UNSET: "Скинути"
+ VIEW: "Дивитися"
+ SAVE: "Зберегти"
+ NORMAL: "Звичайний"
+ EXPERT: "Експерт"
+ EXPAND_ALL: "Розгорнути все"
+ COLLAPSE_ALL: "Згорнути все"
+ ERROR: "Помилка"
+ CLOSE: "Закрити"
+ CANCEL: "Скасувати"
+ CONTINUE: "Продовжити"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Необхідне підтвердження"
+ MODAL_CHANGED_DETECTED_TITLE: "Виявлено зміни"
+ MODAL_CHANGED_DETECTED_DESC: "У вас є незбережені зміни. Ви впевнені, що хочете вийти без збереження?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Необхідне підтвердження"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Ви впевнені, що хочете видалити цей файл? Ця дія незворотна."
+ ADD_FILTERS: "Додати фільтри"
+ SEARCH_PAGES: "Пошук сторінок"
+ VERSION: "Версія"
+ WAS_MADE_WITH: "Базується на"
+ BY: "За"
+ UPDATE_THEME: "Оновити тему"
+ UPDATE_PLUGIN: "Оновити плагін"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "цієї теми є доступним"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "цього плагіна є доступним"
+ AUTHOR: "Автор"
+ HOMEPAGE: "Домашня сторінка"
+ DEMO: "Демо"
+ BUG_TRACKER: "Баг-трекер"
+ KEYWORDS: "Ключові слова"
+ LICENSE: "Ліцензія"
+ DESCRIPTION: "Опис"
+ README: "Детальніше"
+ REMOVE_THEME: "Вилучити тему"
+ INSTALL_THEME: "Встановити тему"
+ THEME: "Тема"
+ BACK_TO_THEMES: "Повернутись до тем"
+ BACK_TO_PLUGINS: "Повернутись до плагінів"
+ CHECK_FOR_UPDATES: "Перевірити на оновлення"
+ ADD: "Додати"
+ CLEAR_CACHE: "Очистити кеш"
+ CLEAR_CACHE_ALL_CACHE: "Увесь кеш"
+ CLEAR_CACHE_ASSETS_ONLY: "Лише активи"
+ CLEAR_CACHE_IMAGES_ONLY: "Лише зображення"
+ CLEAR_CACHE_CACHE_ONLY: "Лише кеш"
+ CLEAR_CACHE_TMP_ONLY: "Лише тимчасові файли"
+ DASHBOARD: "Панель керування"
+ UPDATES_AVAILABLE: "Доступні оновлення"
+ DAYS: "Дні"
+ UPDATE: "Оновити"
+ BACKUP: "Резервна копія"
+ BACKUPS: "Резервні копії"
+ BACKUP_NOW: "Зробити резервну копію"
+ BACKUPS_OLDEST: "Найстаріша резервна копія"
+ BACKUPS_PURGE: "Знищити"
+ STATISTICS: "Статистика"
+ TODAY: "Сьогодні"
+ WEEK: "Тиждень"
+ MONTH: "Місяць"
+ LATEST_PAGE_UPDATES: "Останні оновлені сторінки"
+ MAINTENANCE: "Технічне обслуговування"
+ UPDATED: "Оновлено"
+ MON: "Пн"
+ TUE: "Вт"
+ WED: "Ср"
+ THU: "Чт"
+ FRI: "Пт"
+ SAT: "Сб"
+ SUN: "Нд"
+ COPY: "Копіювати"
+ EDIT: "Редагувати"
+ CREATE: "Створити"
+ GRAV_ADMIN: "Панель адміністратора Grav"
+ GRAV_OFFICIAL_PLUGIN: "Офіційний плагін Grav"
+ GRAV_OFFICIAL_THEME: "Офіційна тема Grav"
+ PLUGIN_SYMBOLICALLY_LINKED: "Цей плагін символічно пов'язаний. Оновлення не будуть виявлені."
+ THEME_SYMBOLICALLY_LINKED: "Ця тема символічно пов'язана. Оновлення не будуть виявлені"
+ REMOVE_PLUGIN: "Вилучити плагін"
+ INSTALL_PLUGIN: "Встановити плагін"
+ AVAILABLE: "Доступний"
+ INSTALLED: "Встановлений"
+ INSTALL: "Встановити"
+ ACTIVE_THEME: "Активна тема"
+ SWITCHING_TO: "Переключення"
+ SWITCHING_TO_DESCRIPTION: "При перемиканні на іншу тему немає ніякої гарантії того, що підтримуються всі сторінки макетів. Можливо, це призведе до помилок при спробі завантаження зазначених сторінок."
+ SWITCHING_TO_CONFIRMATION: "Бажаєте продовжити і використати тему"
+ CREATE_NEW_USER: "Створити нового користувача"
+ REMOVE_USER: "Вилучити користувача"
+ ACCESS_DENIED: "Доступ заборонено"
+ ACCOUNT_NOT_ADMIN: "ваш обліковий запис не має прав адміністратора"
+ PHP_INFO: "Інформація PHP"
+ INSTALLER: "Встановлювач"
+ AVAILABLE_THEMES: "Доступні теми"
+ AVAILABLE_PLUGINS: "Доступні плагіни"
+ INSTALLED_THEMES: "Встановлені теми"
+ INSTALLED_PLUGINS: "Встановлені плагіни"
+ BROWSE_ERROR_LOGS: "Огляд журналу помилок"
+ SITE: "Сайт"
+ INFO: "Інформація"
+ SYSTEM: "Система"
+ USER: "Користувач"
+ ADD_ACCOUNT: "Додати обліковий запис"
+ SWITCH_LANGUAGE: "Змінити мову"
+ SUCCESSFULLY_ENABLED_PLUGIN: "Плагін успішно увімкнено"
+ SUCCESSFULLY_DISABLED_PLUGIN: "Плагін успішно вимкнено"
+ SUCCESSFULLY_CHANGED_THEME: "Типова тема успішно змінена"
+ INSTALLATION_FAILED: "Інсталяція завершилась невдачею"
+ INSTALLATION_SUCCESSFUL: "Інсталяцію успішно завершено"
+ UNINSTALL_FAILED: "Не вдалося видалити"
+ UNINSTALL_SUCCESSFUL: "Видалення пройшло успішно"
+ SUCCESSFULLY_SAVED: "Успішно збережено"
+ SUCCESSFULLY_COPIED: "Успішне копіювання"
+ REORDERING_WAS_SUCCESSFUL: "Перепризначення було успішним"
+ SUCCESSFULLY_DELETED: "Успішно видалено"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "Успішна зміна мови"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "У вас недостатньо прав для завдання"
+ CACHE_CLEARED: "Кеш очищено"
+ METHOD: "Метод"
+ ERROR_CLEARING_CACHE: "Помилка при очищенні кешу"
+ AN_ERROR_OCCURRED: "Виникла помилка"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Резервна копія готова до завантаження"
+ DOWNLOAD_BACKUP: "Завантажити резервну копію"
+ PAGES_FILTERED: "Сторінки відфільтровано"
+ NO_PAGE_FOUND: "Сторінку не знайдено"
+ INVALID_PARAMETERS: "Неправильні параметри"
+ NO_FILES_SENT: "Файлів не надіслано"
+ EXCEEDED_FILESIZE_LIMIT: "Перевищено обмеження на розмір файлу конфігурації PHP"
+ UNKNOWN_ERRORS: "Невідомі помилки"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "Перевищено обмеження на розмір файлу конфігурації Grav"
+ UNSUPPORTED_FILE_TYPE: "Непідтримуваний тип файлу"
+ FAILED_TO_MOVE_UPLOADED_FILE: "Не вдалося перемістити завантажений файл"
+ FILE_UPLOADED_SUCCESSFULLY: "Файл успішно завантажено"
+ FILE_DELETED: "Файл видалено"
+ FILE_COULD_NOT_BE_DELETED: "Неможливо видалити файл"
+ FILE_NOT_FOUND: "Файл не виявлено"
+ NO_FILE_FOUND: "Файлів не виявлено"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav успішно оновлено до"
+ GRAV_UPDATE_FAILED: "Не вдалося оновити Grav"
+ EVERYTHING_UPDATED: "Усе оновлено"
+ UPDATES_FAILED: "Не вдалося оновити"
+ AVATAR_BY: "Аватар"
+ AVATAR_UPLOAD_OWN: "Або додати власний..."
+ LAST_BACKUP: "Остання резервна копія"
+ FULL_NAME: "Повне ім'я"
+ USERNAME: "Ім'я користувача"
+ EMAIL: "Емейл"
+ USERNAME_EMAIL: "Ім'я користувача або адреса електронної пошти"
+ PASSWORD: "Пароль"
+ PASSWORD_CONFIRM: "Підтвердження паролю"
+ TITLE: "Назва"
+ LANGUAGE: "Мова"
+ ACCOUNT: "Обліковий запис"
+ EMAIL_VALIDATION_MESSAGE: "Повинна бути дійсною адресою електронної пошти"
+ PASSWORD_VALIDATION_MESSAGE: "Пароль повинен містити не менше 8 символів і серед них хоча б одну цифру та одну велику і малу літеру"
+ LANGUAGE_HELP: "Оберіть улюблену мову"
+ MEDIA: "Медіа"
+ DEFAULTS: "За визначенням"
+ SITE_TITLE: "Заголовок сайту"
+ SITE_TITLE_PLACEHOLDER: "Назва сайту"
+ SITE_TITLE_HELP: "Типова назва вашого сайту, часто використовується в темах"
+ SITE_DEFAULT_LANG: "Типова мова"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "Мова, що використовується типово по тегу у шаблонах теми."
+ SITE_DEFAULT_LANG_HELP: "Мова, що використовується типово по тегу у шаблонах теми."
+ DEFAULT_AUTHOR: "Автор за визначенням"
+ DEFAULT_AUTHOR_HELP: "Ім'я автора за визначенням, часто використовується в темах оформлення або вмісті сторінок."
+ DEFAULT_EMAIL: "Емейл за визначенням"
+ DEFAULT_EMAIL_HELP: "Адреса електронної пошти за визначенням, використовується для створення посилання на пошту в темах або на сторінках."
+ TAXONOMY_TYPES: "Типи категоризації"
+ TAXONOMY_TYPES_HELP: "Типи категоризації повинні бути визначені тут, якщо ви хочете використовувати їх на сторінках."
+ PAGE_SUMMARY: "Анонс"
+ ENABLED: "Увімкнено"
+ ENABLED_HELP: "Показувати анонс (вміст сторінки до роздільника)."
+ 'YES': "Так"
+ 'NO': "Ні"
+ SUMMARY_SIZE: "Розмір анонса"
+ SUMMARY_SIZE_HELP: "Кількість символів на сторінці, виведених в короткому змісті."
+ FORMAT: "Формат"
+ FORMAT_HELP: "короткий = використовувати перший знайдений роздільник або розмір; довгий = роздільник ігнорується."
+ SHORT: "Короткий"
+ LONG: "Довгий"
+ DELIMITER: "Роздільник"
+ DELIMITER_HELP: "Роздільник для функції «Читати далі ...» (типово '===')"
+ METADATA: "Метадані"
+ METADATA_HELP: "Типові значення метаданих, що відображаються на кожній сторінці, якщо це не визначено у конкретної сторінки."
+ NAME: "Ім'я"
+ CONTENT: "Вміст"
+ SIZE: "Розмір"
+ ACTION: "Дія"
+ REDIRECTS_AND_ROUTES: "Переадресації та маршрути"
+ CUSTOM_REDIRECTS: "Користувацькі переспрямування"
+ CUSTOM_REDIRECTS_HELP: "маршрути для переспрямування на інші сторінки. Діє заміна стандартних регулярних виразів"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/your/redirect"
+ CUSTOM_ROUTES: "Користувацька маршрутизація"
+ CUSTOM_ROUTES_HELP: "маршрути по псевдоніму на інші сторінки. Діє заміна стандартних регулярних виразів"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/your/route"
+ FILE_STREAMS: "Файлові потоки"
+ DEFAULT: "За умовчанням"
+ PAGE_MEDIA: "Сторінка медіа"
+ OPTIONS: "Опції"
+ PUBLISHED: "Опубліковано"
+ PUBLISHED_HELP: "Типово сторінка є опублікованою, якщо явно не вказано, що матеріал не опублікований (або не задана дата публікації/дата зняття з публікації)."
+ DATE: "Дата"
+ DATE_HELP: "Змінна дати дає змогу спеціально встановити дату, пов'язану з цією сторінкою."
+ PUBLISHED_DATE: "Дата публікації"
+ PUBLISHED_DATE_HELP: "Можна надати дату автоматичного запуску публікації."
+ UNPUBLISHED_DATE: "Дата зняття з публікації"
+ UNPUBLISHED_DATE_HELP: "Можна вказати дату автоматичного зняття з публікації."
+ ROBOTS: "Роботи"
+ TAXONOMIES: "Категоризації"
+ TAXONOMY: "Категоризація"
+ ADVANCED: "Додатково"
+ SETTINGS: "Налаштування"
+ FOLDER_NUMERIC_PREFIX: "Числовий префікс теки"
+ FOLDER_NUMERIC_PREFIX_HELP: "Числовий префікс, що забезпечує ручне упорядкування і має на увазі видимість числа"
+ FOLDER_NAME: "Назва теки"
+ FOLDER_NAME_HELP: "Ім'я теки, яке буде збережено в файловій системі для цієї сторінки"
+ PARENT: "Батьківський"
+ DEFAULT_OPTION_ROOT: "- Root -"
+ DEFAULT_OPTION_SELECT: "- Обрати -"
+ DISPLAY_TEMPLATE: "Шаблон відображення"
+ DISPLAY_TEMPLATE_HELP: "Тип сторінки, який визначає, яка гілка шаблону відтворює сторінку"
+ ORDERING: "Замовлення"
+ PAGE_ORDER: "Порядок сторінок"
+ OVERRIDES: "Перевизначення"
+ MENU: "Меню"
+ SLUG: "Слаґ"
+ SLUG_HELP: "Змінна слаґ дозволяє вам спеціально встановити частину URL-адреси сторінки"
+ SLUG_VALIDATE_MESSAGE: "Слаґ має містити лише малі алфавітно-цифрові символи і дефіси."
+ PROCESS: "Процес"
+ PROCESS_HELP: "Контролювати, як обробляються сторінки. Можна встановити для кожної сторінки окремо, а не глобально"
+ DEFAULT_CHILD_TYPE: "Тип вкладеного елемента"
+ ROUTABLE: "Підтримує маршрутизацію"
+ CACHING: "Кешування"
+ VISIBLE: "Видимий"
+ VISIBLE_HELP: "Визначає, чи відображається матеріал в меню."
+ DISABLED: "Вимкнено"
+ ITEMS: "Предмети"
+ ORDER_BY: "Сортувати за"
+ ORDER: "Порядок"
+ FOLDER: "Тека"
+ ASCENDING: "За зростанням"
+ DESCENDING: "За спаданням"
+ ADD_MODULAR_CONTENT: "Додати модульний вміст"
+ PAGE_TITLE: "Заголовок сторінки"
+ PAGE_TITLE_HELP: "Заголовок сторінки"
+ PAGE: "Сторінка"
+ FILENAME: "Назва файлу"
+ PARENT_PAGE: "Батьківська сторінка"
+ HOME_PAGE: "Домашня сторінка"
+ HOME_PAGE_HELP: "Сторінка, яку Grav використовуватиме як цільову сторінку за визначенням"
+ DEFAULT_THEME: "Стандартна тема"
+ DEFAULT_THEME_HELP: "Тема оформлення для Grav, яка використовується за визначенням."
+ TIMEZONE: "Часовий пояс"
+ TIMEZONE_HELP: "Перевизначення часового поясу на сервері"
+ SHORT_DATE_FORMAT: "Короткий формат відображення дати"
+ SHORT_DATE_FORMAT_HELP: "Встановіть короткий формат дати, який може використовуватися в темах оформлення."
+ LONG_DATE_FORMAT: "Довгий формат відображення дати"
+ LONG_DATE_FORMAT_HELP: "Встановіть довгий формат дати, який може використовуватися в темах оформлення."
+ DEFAULT_ORDERING: "Порядок за визначенням"
+ DEFAULT_ORDERING_HELP: "Сторінки в списку будуть виводитися з урахуванням цього порядку, якщо він не перевизначений."
+ DEFAULT_ORDERING_DEFAULT: "Типово - на основі імені теки"
+ DEFAULT_ORDERING_FOLDER: "Тека - на основі назви теки без префікса"
+ DEFAULT_ORDERING_TITLE: "Заголовок - в залежності від назви поля заголовка"
+ DEFAULT_ORDERING_DATE: "Дата - в залежності від поля дати"
+ DEFAULT_ORDER_DIRECTION: "Порядок сортування за визначенням"
+ DEFAULT_ORDER_DIRECTION_HELP: "Напрямок сортування сторінок в списку матеріалів"
+ DEFAULT_PAGE_COUNT: "Типова кількість сторінок"
+ DEFAULT_PAGE_COUNT_HELP: "Типово встановлена максимальна кількість сторінок у списку"
+ DATE_BASED_PUBLISHING: "Публікація на основі дати"
+ DATE_BASED_PUBLISHING_HELP: "Автоматично (не)публікує пости на основі їх дати"
+ EVENTS: "Події"
+ EVENTS_HELP: "Включити або відключити певні події. Відключення цих подій може привести до помилок у роботі плагінів."
+ REDIRECT_DEFAULT_ROUTE: "Перенаправлення маршруту за визначенням"
+ REDIRECT_DEFAULT_ROUTE_HELP: "Автоматично перенаправляти сторінки згідно маршруту за визначенням"
+ LANGUAGES: "Мови"
+ SUPPORTED: "Підтримується"
+ SUPPORTED_HELP: "Розділений комами список з 2-х літерними кодами мови (наприклад, 'uk,en,de')"
+ TRANSLATIONS_ENABLED: "Переклади включені"
+ TRANSLATIONS_ENABLED_HELP: "Підтримка перекладів в Grav, плагінів і розширень."
+ TRANSLATIONS_FALLBACK: "Резервний переклад"
+ TRANSLATIONS_FALLBACK_HELP: "Використовувати підтримувані переклади, якщо активна мова не існує."
+ ACTIVE_LANGUAGE_IN_SESSION: "Активна мова в сеансі"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "Зберегти активну мову в сеансі"
+ HTTP_HEADERS: "Заголовки HTTP"
+ EXPIRES: "Закінчується"
+ EXPIRES_HELP: "Встановлює заголовок Expires. Значення в секундах."
+ CACHE_CONTROL: "Кеш-контроль HTTP"
+ CACHE_CONTROL_HELP: "Встановити дійсне значення кеш-контролю, наприклад, 'no cache, no-store, must-revalidate'"
+ LAST_MODIFIED: "Востаннє змінено"
+ LAST_MODIFIED_HELP: "Встановлює останній змінений заголовок, який допоможе оптимізувати кешування проксі-сервера та браузера"
+ ETAG: "ETag"
+ ETAG_HELP: "Встановлює заголовок etag, щоб визначити, коли сторінка була змінена"
+ VARY_ACCEPT_ENCODING: "Vary: Accept Encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Встановіть заголовок `Vary: Accept Encoding`, щоб уникнути проблем при роботі з загальнодоступними проксі-серверами і CDN, що не розпізнають наявність заголовка Content-Encoding."
+ MARKDOWN_EXTRA: "Markdown Extra"
+ MARKDOWN_EXTRA_HELP: "Увімкнути підтримку для Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
+ MARKDOWN_EXTRA_ESCAPE_FENCES: "Екранування HTML-елементів (синтаксис fences) у Markdown Extra"
+ MARKDOWN_EXTRA_ESCAPE_FENCES_HELP: "Уникати HTML-елементів у Markdown Extra для синтаксису fences "
+ AUTO_LINE_BREAKS: "Автоматичне розбиття на рядки"
+ AUTO_LINE_BREAKS_HELP: "Увімкнути підтримку для автоматичного розриву рядків у markdown"
+ AUTO_URL_LINKS: "Автоматичні URL посилання"
+ AUTO_URL_LINKS_HELP: "Увімкнути автоматичне перетворення URL-адреси в HTML-код гіперпосилання"
+ ESCAPE_MARKUP: "Екранування розмітки"
+ ESCAPE_MARKUP_HELP: "Уникати тегів розмітки в HTML об'єктах"
+ CACHING_HELP: "Глобальний ON/OFF перемикач увімкнення/вимикання кешування Grav"
+ CACHE_DRIVER: "Драйвер кешування"
+ LIFETIME: "Пожиттєво"
+ GZIP_COMPRESSION: "Стискання Gzip"
+ GZIP_COMPRESSION_HELP: "Увімкніть стиснення GZip для сторінок Grav для збільшення швидкості завантаження сторінок."
+ TWIG_CACHING_HELP: "Контроль механізму кешування Twig. Залиште це увімкненим для найкращої продуктивності."
+ TWIG_DEBUG: "Зневадження Twig"
+ TWIG_DEBUG_HELP: "Дозволяє не завантажувати розширення Twig Debugger"
+ DETECT_CHANGES: "Виявити зміни"
+ ASSETS: "Активи"
+ CSS_PIPELINE: "Об'єднання CSS"
+ CSS_PIPELINE_HELP: "Пайплайн CSS дозволяє об'єднання декількох ресурсів CSS в один файл"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Включити зовнішні ресурси в CSS пайплайн"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "Зовнішні URL-адреси іноді мають пов'язані посилання на файли і не можуть бути в пайплайні"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "Візуалізувати CSS пайплайна спочатку"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Візуалізація CSS пайплайна перед будь-якими іншими посиланнями CSS, які не включені"
+ CSS_MINIFY: "Мінімізація CSS"
+ CSS_MINIFY_HELP: "Мінімізувати CSS файли під час об'єднання"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "Мінімізувати CSS (перевизначення для Windows)"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Мінімізувати перевизначення для платформ Windows. За визначенням відключено через ThreadStackSize."
+ CSS_REWRITE: "Переписати CSS"
+ CSS_REWRITE_HELP: "Переписати будь-які відносні URL-адреси CSS під час об'єднання"
+ JAVASCRIPT_MINIFY: "Мінімізація JavaScript"
+ COLLECTIONS: "Колекції"
+ ERROR_HANDLER: "Зневаджувач"
+ DISPLAY_ERRORS: "Показувати помилки"
+ LOG_ERRORS: "Журнал помилок"
+ LOG_ERRORS_HELP: "Заносити помилки до теки /logs"
+ DEBUGGER: "Зневаджувач"
+ DEBUGGER_HELP: "Увімкніть Grav-зневаджувач та наступні налаштування"
+ DEBUG_TWIG: "Зневаджувач Twig"
+ DEFAULT_IMAGE_QUALITY: "Типова якість зображення"
+ DEFAULT_IMAGE_QUALITY_HELP: "Типова якість зображення для використання під час змінення роздільної здатності або кешування зображень (85%)"
+ CACHE_ALL: "Кешувати всі зображення"
+ IMAGES_DEBUG: "Налагодження водяного знака зображення"
+ IMAGES_DEBUG_HELP: "Показати накладання над зображеннями із зазначенням глибини пікселя зображення при роботі з сіткою, наприклад."
+ IMAGES_SEOFRIENDLY: "SEO-Friendly імена зображення"
+ UPLOAD_LIMIT: "Ліміт завантаження файлу"
+ SESSION: "Сесія"
+ TIMEOUT: "Таймаут"
+ ABSOLUTE_URLS: "Абсолютні посилання"
+ TASK_COMPLETED: "Завдання завершене"
+ EVERYTHING_UP_TO_DATE: "Усе найновішої версії"
+ UPDATES_ARE_AVAILABLE: "оновлень(ня) доступно"
+ IS_AVAILABLE_FOR_UPDATE: "доступне оновлення"
+ IS_NOW_AVAILABLE: "вже доступно"
+ CURRENT: "Поточний"
+ UPDATE_GRAV_NOW: "Оновити Grav зараз"
+ UPDATING_PLEASE_WAIT: "Оновлення... зачекайте будь ласка, завантажуємо"
+ OF_THIS: "цього"
+ OF_YOUR: "з вашого"
+ HAVE_AN_UPDATE_AVAILABLE: "має доступне оновлення"
+ SAVE_AS: "Зберегти як"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Ви впевнені, що хочете видалити цю сторінку та всі дочірні сторінки? Якщо сторінка перекладена іншими мовами, ці переклади будуть збережені і повинні видалятись окремо. В іншому випадку, тека сторінки буде видалена разом із її підсторінками. Ця дія незворотна."
+ AND: "і"
+ UPDATE_AVAILABLE: "Доступне оновлення"
+ METADATA_KEY: "Ключ (наприклад, 'ключові слова')"
+ METADATA_VALUE: "Значення (напр. 'Блог, Grav')"
+ USERNAME_HELP: "Ім'я користувача повинно містити від 3 до 16 символів, включаючи малі літери, цифри, знаки підкреслення та дефіси. Великі літери, пробіли та інші спецсимволи не дозволені"
+ FULLY_UPDATED: "Повністю оновлено"
+ SAVE_LOCATION: "Зберегти місцерозташування"
+ PAGE_FILE: "Шаблон сторінки"
+ PAGE_FILE_HELP: "Ім'я файлу шаблону сторінки, який за визначенням використовується як шаблон для відображення цієї сторінки."
+ DEFAULT_DATE_FORMAT: "Формат дати"
+ DEFAULT_DATE_FORMAT_HELP: "Формат дати сторінки, який використовує Grav. Типово, Grav намагається самостійно визначити правильний формат дати, однак ви можете вказати потрібний формат за допомогою синтаксису дати PHP (наприклад, Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "Визначити автоматично"
+ IGNORE_FILES: "Ігнорувати файли"
+ IGNORE_FILES_HELP: "Певні файли, які ігноруються при обробці сторінок"
+ IGNORE_FOLDERS: "Ігнорувати теки"
+ IGNORE_FOLDERS_HELP: "Певні теки, які ігноруються при обробці сторінок"
+ HIDE_EMPTY_FOLDERS: "Приховати порожні теки"
+ HTTP_ACCEPT_LANGUAGE: "Використовувати мову переглядача"
+ HTTP_ACCEPT_LANGUAGE_HELP: "Ви можете вибрати, щоб спробувати встановити мову на основі тега заголовка 'http_accept_language' в браузері"
+ OVERRIDE_LOCALE: "Перевизначити локаль"
+ OVERRIDE_LOCALE_HELP: "Перевизначити налаштування локалі в PHP на основі поточної мови"
+ REDIRECT_HELP: "Введіть маршрут сторінки або зовнішню URL-адресу для цієї сторінки, щоб перенаправити, наприклад, '/some/route' або 'http://somesite.com'"
+ PLUGIN_STATUS: "Статус плагіна"
+ INCLUDE_DEFAULT_LANG: "Включити позначку мови"
+ INCLUDE_DEFAULT_LANG_HELP: "Це позначить у кожній URL-адресі типову мову встановлену для обраних мов. Наприклад: `/en/blog/my-post`"
+ IGNORE_HIDDEN: "Ігнорувати приховані"
+ IGNORE_HIDDEN_HELP: "Ігнорувати всі файли і теки, імена яких починаються з КРАПКИ"
+ WRAPPED_SITE: "Загорнений сайт"
+ WRAPPED_SITE_HELP: "Сповіщає для тем та плагинів, що Grav загорнуто у іншу платформу."
+ FALLBACK_TYPES: "Дозволені резервні типи"
+ FALLBACK_TYPES_HELP: "Дозволені типи файлів, які можна знайти при доступі через маршрут сторінки. Типово підтримується будь-який тип медіа."
+ APPEND_URL_EXT: "Додати закінчення URL-адрес"
+ APPEND_URL_EXT_HELP: "Це додасть користувацьке розширення до URL-адреси сторінки. Зверніть увагу, що Grav буде шукати шаблон у форматі `..twig`"
+ PAGE_MODES: "Режими сторінки"
+ PAGE_TYPES: "Типи сторінки"
+ ACCESS_LEVELS: "Рівні доступу"
+ GROUPS: "Групи"
+ ADMIN_ACCESS: "Доступ адміністратора"
+ SITE_ACCESS: "Доступ сайту"
+ INVALID_SECURITY_TOKEN: "Невірний ключ безпеки"
+ ACTIVATE: "Активувати"
+ CACHE_PERMS: "Права доступу кешу"
+ CACHE_PERMS_HELP: "За визначенням дозволи на теку кешу. Зазвичай 0755 або 0775 в залежності від налаштувань"
+ REMOVE_SUCCESSFUL: "Успішне видалення"
+ REMOVE_FAILED: "Помилка видалення"
+ HIDE_HOME_IN_URLS: "Сховати шлях до головної сторінки з адрес дочірніх"
+ HIDE_HOME_IN_URLS_HELP: "Усі дочірні сторінки всередині головної не міститимуть в своїх адресах маршрути головної (наприклад, замість /blog/mypage буде відображатися просто /mypage)."
+ SESSION_SECURE: "Безпечний"
+ SESSION_SECURE_HELP: "Якщо увімкнено, це означає, що з'єднання для цього файлу cookie має бути зашифроване. ПОПЕРЕДЖЕННЯ: Увімкніть це лише на сайтах, що працюють винятково в HTTPS."
+ SESSION_HTTPONLY: "Тільки HTTP"
+ SESSION_HTTPONLY_HELP: "Якщо увімкнено, це вказує на те, що файли cookie мають використовуватися лише через HTTP, і модифікація JavaScript не допускається."
+ REVERSE_PROXY: "Зворотний проксі-сервер"
+ REVERSE_PROXY_HELP: "Увімкніть це, якщо ви перебуваєте за зворотним проксі-сервером, і у вас виникають проблеми з URL-адресами, що містять неправильні порти"
+ ADD_FOLDER: "Додати теку"
+ INSTALLING: "Встановлення"
+ LOADING: "Завантаження..."
+ DEPENDENCIES_NOT_MET_MESSAGE: "Спершу слід виконати наступні залежності:"
+ ERROR_INSTALLING_PACKAGES: "Помилка під час встановлення пакетів"
+ INSTALLING_DEPENDENCIES: "Встановлення залежностей..."
+ INSTALLING_PACKAGES: "Встановлення пакунків..."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "Пакунки успішно встановлено."
+ READY_TO_INSTALL_PACKAGES: "Готові до встановлення пакунки"
+ PACKAGES_NOT_INSTALLED: "Пакунки не встановлено"
+ PACKAGES_NEED_UPDATE: "Пакунки вже встановлені, але занадто старі."
+ PACKAGES_SUGGESTED_UPDATE: "Пакунки вже встановлені і їх версії в порядку, але вони будуть оновлені, щоб використовувати останні версії."
+ REMOVE_THE: "Видалити %s"
+ CONFIRM_REMOVAL: "Ви впевнені, що бажаєте видалити %s?"
+ REMOVED_SUCCESSFULLY: "%s успішно видалено"
+ ERROR_REMOVING_THE: "Помилка видалення %s"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: " %s вимагає наступних залежностей, які не потрібні іншим встановленим пакункам. Якщо ви не використовуєте їх, ви можете їх видалити безпосередньо звідси."
+ READY_TO_UPDATE_PACKAGES: "Готовий оновити пакунки"
+ ERROR_UPDATING_PACKAGES: "Помилка під час оновлення пакунків"
+ UPDATING_PACKAGES: "Оновлення пакунків..."
+ PACKAGES_SUCCESSFULLY_UPDATED: "Пакунок успішно оновлено."
+ UPDATING: "Оновлення"
+ GPM_RELEASES: "Релізи GPM"
+ GPM_RELEASES_HELP: "Виберіть \"Тестування\", щоб встановити бета або тестову версії."
+ CURL: "cURL"
+ STABLE: "Стабільний"
+ TESTING: "Тестовий"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "Упорядкування вимкнено для цієї сторінки, оскільки Числовий префікс теки не ввімкнено"
+ DROP_FILES_HERE_TO_UPLOAD: "Перетягніть ваш файл сюди або клацніть у цій області "
+ IMAGE: "Зображення"
+ PUBLISHING: "Публікація"
+ NOTIFICATIONS: "Сповіщення"
+ IMAGE_OPTIONS: "Параметри зображення"
+ FILE_EXTENSION: "Розширення файлу"
+ ALL: "Усі"
+ RELEASE_DATE: "Дата випуску"
+ SORT_BY: "Впорядкувати за"
+ RESOURCE_FILTER: "Фільтр..."
+ FORCE_SSL: "Примусовий SSL"
+ FORCE_SSL_HELP: "Глобально увімкнути SSL, і якщо увімкнено, то коли сайт буде досягнутий через HTTP, Grav надсилатиме переспрямування на сторінку HTTPS"
+ NEWS_FEED: "Стрічка новин"
+ EXTERNAL_URL: "Зовнішній URL"
+ SESSION_PATH: "Шлях до сеансу"
+ SESSION_PATH_HELP: "Використовувати, лише якщо ви обрали власну базову URL-адресу (ви переписали домен сайту або вкладену теку)"
+ CUSTOM_BASE_URL: "Користувацька базова URL-адреса"
+ CUSTOM_BASE_URL_HELP: "Використовуйте, якщо ви хочете переписати домен сайту або використовувати іншу підтеку, ніж та, яку використовує Grav. Приклад: http://localhost"
+ DROPZONE_CANCEL_UPLOAD: 'Скасувати завантаження'
+ DROPZONE_DEFAULT_MESSAGE: 'Перетягніть ваш файл сюди або клацніть у цій області '
+ DROPZONE_MAX_FILES_EXCEEDED: "Ви не можете завантажити більше файлів."
+ DROPZONE_RESPONSE_ERROR: "Сервер вернув код {{statuscode}}."
+ PREMIUM_PRODUCT: "Преміум"
+ UPLOAD_ERR_NO_TMP_DIR: "Відсутня тимчасова тека"
+ SESSION_SPLIT_HELP: "Незалежні сесії між сайтом та іншими плагінами (такими як Admin)"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "Автоматично виправити орієнтацію зображення на основі даних Exif"
+ NOT_SET: "Не вибрано"
+ PERMISSIONS: "Дозволи"
+ ALLOW_WEBSERVER_GZIP: "Дозволити на веб-сервері Gzip"
+ REINSTALL_PLUGIN: "Перевстановити плагін"
+ REINSTALL_THEME: "Перевстановити тему"
+ REINSTALL_THE: "Перевстановити %s"
+ CONFIRM_REINSTALL: "Ви впевнені, що хочете перевстановити цей %s?"
+ REINSTALLED_SUCCESSFULLY: "%s перевстановлено успішно"
+ ERROR_REINSTALLING_THE: "Помилка перевстановлення %s"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Пакет %s успішно перевстановлено"
+ REINSTALLATION_FAILED: "Перевстановлення завершилось невдачею"
+ TOOLS: "Інструменти"
+ DIRECT_INSTALL: "Пряме встановлення"
+ ZIP_PACKAGE_NOT_FOUND: "ZIP-пакет не знайдено"
+ GPM_OFFICIAL_ONLY: "Тільки офіційний GPM"
+ NO_CHILD_TYPE: "Немає дочірнього типу для цього маршруту"
+ USERNAME_REGEX: "Ім'я користувача, регулярний вираз"
+ USERNAME_REGEX_HELP: "Початково: лише малі літери, цифри, дефіси та знаки підкреслення. 3 - 16 символів"
+ CONTENT_PADDING: "Відступи для контенту"
+ 2FA_TITLE: "Двохетапна перевірка"
+ 2FA_INSTRUCTIONS: "##### 2FA автентифікація. \nВи маєте увімкнену двохетапну перевірку у вашому обліковому записі. Будь ласка, використовуйте ваш * * 2FA * * додаток, щоб ввести поточний * * 6-значний код * * для завершення процесу авторизації."
+ 2FA_REGEN_HINT: "Відновлення секрету потребує від вас оновлення вашого додатку для автентифікації"
+ 2FA_LABEL: "Доступ адміністратора"
+ 2FA_FAILED: "Неприпустимий код двохетапної перевірки, будь ласка спробуйте знову..."
+ 2FA_ENABLED: "2FA увімкнена"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "2FA пароль"
+ 2FA_SECRET_HELP: "Скануйте цей QR-код в вашому [додаток для автентифікації] (https://learn.getgrav.org/admin-panel/2fa#apps). Також рекомендується зробити резервну копію секрету в безпечному місці, якщо вам буде потрібно перевстановити додаток. Перевірте [Grav docs] (https://learn.getgrav.org/admin-panel/2fa) для отримання додаткової інформації."
+ 2FA_REGENERATE: "Відновити"
+ FORCE_LOWERCASE_URLS: "Закріпити нижній регістр URL-адрес"
+ FORCE_LOWERCASE_URLS_HELP: "Типово Grav встановить всі слаґи і маршрути в нижньому регістрі. Якщо цей параметр встановлений як \"Ні\", то можна скористатися верхнім регістром у слаґах та маршрутах."
+ INTL_ENABLED: "Інтеграція модуля Intl"
+ VIEW_SITE_TIP: "Переглянути сайт"
+ TOOLS_DIRECT_INSTALL_TITLE: "Пряме встановлення пакунків Grav"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "Встановити пакунок через пряме ZIP завантаження"
+ TOOLS_DIRECT_INSTALL_UPLOAD_DESC: "Ви можете легко встановити дійсну тему Grav, плагін або навіть оновлення Grav в Zip-пакунку за допомогою цього методу. Цей пакунок не потребує реєстрації через GPM, і ви можете легко повернутися до попередньої версії або встановити для тестування."
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "Встановити пакунок через віддалене URL-посилання"
+ TOOLS_DIRECT_INSTALL_URL_DESC: "Крім того, ви можете вказати повну URL-адресу пакунка у ZIP-файлі та встановити його через це віддалене URL-посилання."
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "Завантажити та встановити"
+ ROUTE_OVERRIDES: "Зміна маршруту"
+ ROUTE_DEFAULT: "Маршрут за визначенням"
+ ROUTE_CANONICAL: "Канонічний маршрут"
+ ROUTE_ALIASES: "Псевдоніми маршруту"
+ SCHEDULER_OUTPUT: "Вихідний файл"
+ BACKUPS_PROFILE_NAME: "Ім'я резервної копії"
+ BACKUPS_PROFILE_ROOT_FOLDER: "Коренева тека"
+ COMMAND: "Команда"
diff --git a/user/plugins/admin/languages/vi.yaml b/user/plugins/admin/languages/vi.yaml
new file mode 100644
index 0000000..6f4310a
--- /dev/null
+++ b/user/plugins/admin/languages/vi.yaml
@@ -0,0 +1,73 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "Đây là phiên bản thử nghiệm! Bạn phải tự chịu rủi ro khi sử dụng trong production..."
+ ADMIN_REPORT_ISSUE: "Tìm thấy một lỗi? Xin vui lòng báo cáo trên GitHub."
+ EMAIL_FOOTER: "Cung cấp bởi Grav - The Modern Flat File CMS"
+ LOGIN_BTN: "Đăng nhập"
+ LOGIN_BTN_FORGOT: "Quên"
+ LOGIN_BTN_RESET: "Đặt lại mật khẩu"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "Gửi hướng dẫn đặt lại"
+ LOGIN_BTN_CLEAR: "Xóa dữ liệu"
+ LOGIN_BTN_CREATE_USER: "Tạo người dùng"
+ LOGIN_LOGGED_IN: "Bạn vừa đăng nhập thành công"
+ LOGIN_FAILED: "Đăng nhập thất bại"
+ LOGGED_OUT: "Bạn vừa đăng xuất"
+ RESET_NEW_PASSWORD: "Vui lòng nhập mật khẩu mới …"
+ RESET_LINK_EXPIRED: "Liên kết đặt lại mật khẩu đã hết hạn, xin vui lòng thử lại"
+ RESET_PASSWORD_RESET: "Mật khẩu đã được thiết lập lại"
+ MANAGE_PAGES: "Quản lý Trang"
+ CONFIGURATION: "Cấu hình"
+ PAGES: "Trang"
+ PLUGINS: "Tiện ích"
+ PLUGIN: "Tiện ích"
+ THEMES: "Giao diện"
+ BACK: "Quay lại"
+ ADD_PAGE: "Thêm Trang"
+ ADD_MODULAR: "Thêm Module"
+ MOVE: "Di chuyển"
+ DELETE: "Xoá"
+ VIEW: "Xem"
+ SAVE: "Lưu"
+ NORMAL: "Bình thường"
+ EXPERT: "Thông thạo"
+ ERROR: "Lỗi"
+ CLOSE: "Đóng"
+ CANCEL: "Hủy bỏ"
+ CONTINUE: "Tiếp tục"
+ VERSION: "Phiên bản"
+ AUTHOR: "Tác giả"
+ HOMEPAGE: "Trang chủ"
+ LICENSE: "Bản quyền"
+ REMOVE_THEME: "Xoá giao diện"
+ THEME: "Giao diện"
+ BACK_TO_THEMES: "Quay lại Giao diện"
+ BACK_TO_PLUGINS: "Quay lại Tiện ích"
+ ADD: "Thêm"
+ CLEAR_CACHE: "Xóa Cache"
+ DASHBOARD: "Bảng tin"
+ UPDATES_AVAILABLE: "Có bản cập nhật mới"
+ DAYS: "ngày"
+ UPDATE: "Cập Nhật"
+ STATISTICS: "Thống kê"
+ TODAY: "Hôm nay"
+ WEEK: "Tuần"
+ MONTH: "Tháng"
+ MAINTENANCE: "Bảo trì"
+ MON: "Thứ hai"
+ TUE: "Thứ ba"
+ WED: "Thứ tư"
+ THU: "Thứ năm"
+ FRI: "Thứ sáu"
+ SAT: "Thứ bảy"
+ SUN: "Chủ nhật"
+ COPY: "Sao chép"
+ EDIT: "Sửa"
+ CREATE: "Tạo mới"
+ REMOVE_PLUGIN: "Gỡ bỏ Tiện ích"
+ INSTALL_PLUGIN: "Cài đặt tiện ích"
+ AVAILABLE: "Đang có"
+ INSTALLED: "Đã cài"
+ INSTALL: "Cài đặt"
+ SYSTEM: "Hệ thống"
+ USER: "Người dùng"
+ FORMAT: "Định dạng"
diff --git a/user/plugins/admin/languages/zh-tw.yaml b/user/plugins/admin/languages/zh-tw.yaml
new file mode 100644
index 0000000..c30e365
--- /dev/null
+++ b/user/plugins/admin/languages/zh-tw.yaml
@@ -0,0 +1,706 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "這是 Beta 版!如果要用在營運中請自行承擔風險…"
+ ADMIN_REPORT_ISSUE: "發現有問題?請回報到 GitHub 上。"
+ EMAIL_FOOTER: "由 Grav 提供技術驅動 - 現代純文字檔案管理系統"
+ LOGIN_BTN: "登入"
+ LOGIN_BTN_FORGOT: "忘記密碼"
+ LOGIN_BTN_RESET: "重設密碼"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "發送重設說明"
+ LOGIN_BTN_CLEAR: "清空表單"
+ LOGIN_BTN_CREATE_USER: "建立使用者"
+ LOGIN_LOGGED_IN: "您已成功登入"
+ LOGIN_FAILED: "登入失敗"
+ LOGGED_OUT: "你已登出"
+ RESET_NEW_PASSWORD: "請輸入一個新密碼 …"
+ RESET_LINK_EXPIRED: "重設連結已過期,請再試一次"
+ RESET_PASSWORD_RESET: "密碼已重設"
+ RESET_INVALID_LINK: "使用不正確的重置連結,請再新嘗試"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "重置密碼的指示已發送到您的電子郵件地址"
+ FORGOT_FAILED_TO_EMAIL: "無法通過電子郵件發送指示,請稍後再試"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "不能重置密碼為 %s,電子郵件地址沒有被設置"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "使用者名稱 %s 不存在"
+ FORGOT_EMAIL_NOT_CONFIGURED: "不能重置密碼。此網站未被配置可發送電子郵件"
+ FORGOT_EMAIL_SUBJECT: "%s 要求重設密碼"
+ FORGOT_EMAIL_BODY: "重設密碼 親愛的 %1$s,
曾在 %4$s 要求重置您的密碼。
< br / > 按一下此處重置您的密碼 < br / > < br / >
或者,將下面的 URL 複製到瀏覽器的網址列︰
%2$s
< br / > 謝謝,< br / > < br / >%3$s
"
+ MANAGE_PAGES: "管理頁面"
+ CONFIGURATION: "組態設定"
+ PAGES: "頁面"
+ PLUGINS: "外掛"
+ PLUGIN: "外掛"
+ THEMES: "佈景主題"
+ LOGOUT: "登出"
+ BACK: "返回"
+ NEXT: "下一頁"
+ PREVIOUS: "上一頁"
+ ADD_PAGE: "新增頁面"
+ ADD_MODULAR: "新增模組"
+ MOVE: "移動"
+ DELETE: "刪除"
+ UNSET: "未設定"
+ VIEW: "檢視"
+ SAVE: "儲存"
+ NORMAL: "普通"
+ EXPERT: "進階"
+ EXPAND_ALL: "展開全部"
+ COLLAPSE_ALL: "摺疊全部"
+ ERROR: "錯誤"
+ CLOSE: "關閉"
+ CANCEL: "取消"
+ CONTINUE: "繼續"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "需要確認"
+ MODAL_CHANGED_DETECTED_TITLE: "偵測到更改"
+ MODAL_CHANGED_DETECTED_DESC: "你没有保存更改。 你是否確定要離開而不保存?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "需要確認"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "是否確定要刪除此檔?此操作無法撤銷。"
+ ADD_FILTERS: "新增過濾"
+ SEARCH_PAGES: "搜尋網頁"
+ VERSION: "版本"
+ WAS_MADE_WITH: "建立於"
+ BY: "由"
+ UPDATE_THEME: "更新主題"
+ UPDATE_PLUGIN: "更新外掛"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "這主題現在可使用"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "這個外掛現在可使用"
+ AUTHOR: "作者"
+ HOMEPAGE: "主頁"
+ DEMO: "演示"
+ BUG_TRACKER: "Bug 追蹤器"
+ KEYWORDS: "關鍵字"
+ LICENSE: "授權許可"
+ DESCRIPTION: "說明"
+ README: "讀我檔案"
+ REMOVE_THEME: "刪除主題"
+ INSTALL_THEME: "安裝主題"
+ THEME: "主題"
+ BACK_TO_THEMES: "回到主題"
+ BACK_TO_PLUGINS: "回到外掛"
+ CHECK_FOR_UPDATES: "檢查是否有更新"
+ ADD: "新增"
+ CLEAR_CACHE: "清除快取"
+ CLEAR_CACHE_ALL_CACHE: "所有快取"
+ CLEAR_CACHE_ASSETS_ONLY: "只有資產"
+ CLEAR_CACHE_IMAGES_ONLY: "只有圖片"
+ CLEAR_CACHE_CACHE_ONLY: "只有快取"
+ CLEAR_CACHE_TMP_ONLY: "只可暫時"
+ DASHBOARD: "儀表板"
+ UPDATES_AVAILABLE: "有可用的更新"
+ DAYS: "天數"
+ UPDATE: "更新"
+ BACKUP: "備份"
+ BACKUPS: "備份"
+ BACKUP_NOW: "立刻備份"
+ BACKUPS_PURGE: "清除"
+ BACKUP_DELETED: "備份檔案已經刪除完成"
+ BACKUP_NOT_FOUND: "沒有找到適合的備份"
+ BACKUP_DATE: "備份日期"
+ STATISTICS: "統計"
+ TODAY: "今天"
+ WEEK: "週"
+ MONTH: "月"
+ LATEST_PAGE_UPDATES: "最近頁面更新"
+ MAINTENANCE: "維護"
+ UPDATED: "更新"
+ MON: "週一"
+ TUE: "週二"
+ WED: "週三"
+ THU: "週四"
+ FRI: "週五"
+ SAT: "週六"
+ SUN: "週日"
+ COPY: "複製"
+ EDIT: "編輯"
+ CREATE: "建立"
+ GRAV_ADMIN: "Grav管理"
+ GRAV_OFFICIAL_PLUGIN: "Grav 官方外掛"
+ GRAV_OFFICIAL_THEME: "Grav官方主題"
+ PLUGIN_SYMBOLICALLY_LINKED: "這個外掛以 symbolic link 連結。無法檢測更新。"
+ THEME_SYMBOLICALLY_LINKED: "這個主題是symbolic link聯繫在一起。檢測不到更新。"
+ REMOVE_PLUGIN: "移除外掛"
+ INSTALL_PLUGIN: "安裝外掛"
+ AVAILABLE: "可用"
+ INSTALLED: "已安装"
+ INSTALL: "安裝"
+ ACTIVE_THEME: "目前使用的主題"
+ SWITCHING_TO: "切換到"
+ SWITCHING_TO_DESCRIPTION: "切換到一個不同的主題,不能保證所有佈局頁面都被支援,嘗試載入該頁面時可能導致錯誤。"
+ SWITCHING_TO_CONFIRMATION: "你想要繼續並切換到主題嗎"
+ CREATE_NEW_USER: "建立新使用者"
+ REMOVE_USER: "刪除使用者"
+ ACCESS_DENIED: "拒絕存取"
+ ACCOUNT_NOT_ADMIN: "您的帳戶沒有管理員的權限"
+ PHP_INFO: "PHP 資訊"
+ INSTALLER: "安裝程式"
+ AVAILABLE_THEMES: "可用的主題"
+ AVAILABLE_PLUGINS: "可用的外掛"
+ INSTALLED_THEMES: "已安裝的主題"
+ INSTALLED_PLUGINS: "已安裝的外掛"
+ BROWSE_ERROR_LOGS: "觀看錯誤日誌"
+ SITE: "網站"
+ INFO: "資訊"
+ SYSTEM: "系統"
+ USER: "使用者"
+ ADD_ACCOUNT: "新增帳戶"
+ SWITCH_LANGUAGE: "切換語言"
+ SUCCESSFULLY_ENABLED_PLUGIN: "成功啟用外掛"
+ SUCCESSFULLY_DISABLED_PLUGIN: "成功停用外掛"
+ SUCCESSFULLY_CHANGED_THEME: "成功更改預設主題"
+ INSTALLATION_FAILED: "安裝失敗"
+ INSTALLATION_SUCCESSFUL: "安裝成功"
+ UNINSTALL_FAILED: "移除失敗"
+ UNINSTALL_SUCCESSFUL: "移除成功"
+ SUCCESSFULLY_SAVED: "成功儲存"
+ SUCCESSFULLY_COPIED: "成功複製"
+ REORDERING_WAS_SUCCESSFUL: "成功重新排序"
+ SUCCESSFULLY_DELETED: "成功刪除"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "語言成功地切換"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "此任務你有沒有足夠的許可權限"
+ CACHE_CLEARED: "快取已清除"
+ METHOD: "方法"
+ ERROR_CLEARING_CACHE: "清除快取失敗"
+ AN_ERROR_OCCURRED: "發生錯誤"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "您的備份已經可以下載"
+ DOWNLOAD_BACKUP: "下載備份"
+ PAGES_FILTERED: "網頁過濾"
+ NO_PAGE_FOUND: "沒有找到頁面"
+ INVALID_PARAMETERS: "不正確的參數"
+ NO_FILES_SENT: "沒有檔案被發送"
+ EXCEEDED_FILESIZE_LIMIT: "超出 PHP 配置檔案大小的限制"
+ UNKNOWN_ERRORS: "未知的錯誤"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "超出Grav配置檔案大小的限制"
+ UNSUPPORTED_FILE_TYPE: "不支援的檔案類型"
+ FAILED_TO_MOVE_UPLOADED_FILE: "移動上傳的檔案失敗"
+ FILE_UPLOADED_SUCCESSFULLY: "檔案已成功上載"
+ FILE_DELETED: "檔案已刪除"
+ FILE_COULD_NOT_BE_DELETED: "無法刪除檔案"
+ FILE_NOT_FOUND: "找不到檔案"
+ NO_FILE_FOUND: "沒有找到檔案"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav被成功地更新到"
+ GRAV_UPDATE_FAILED: "Grav更新失敗"
+ EVERYTHING_UPDATED: "全部都已經更新"
+ UPDATES_FAILED: "更新失敗"
+ AVATAR_BY: "個人圖像來源"
+ AVATAR_UPLOAD_OWN: "或是上傳你自己的"
+ LAST_BACKUP: "最新的備份"
+ FULL_NAME: "全名"
+ USERNAME: "帳號"
+ EMAIL: "Email"
+ USERNAME_EMAIL: "用戶名或電子郵箱"
+ PASSWORD: "密碼"
+ PASSWORD_CONFIRM: "確認密碼"
+ TITLE: "職稱"
+ LANGUAGE: "語言"
+ ACCOUNT: "帳戶"
+ EMAIL_VALIDATION_MESSAGE: "必須是一個有效的電子郵件地址"
+ PASSWORD_VALIDATION_MESSAGE: "密碼必須包含至少一個數字和一個大寫和小寫字母和至少 8 個或更多字元"
+ LANGUAGE_HELP: "設置喜好語言"
+ MEDIA: "媒體"
+ DEFAULTS: "預設值"
+ SITE_TITLE: "網站標題"
+ SITE_TITLE_PLACEHOLDER: "網站廣泛標題"
+ SITE_TITLE_HELP: "您的網站的預設標題,經常在主題中使用的"
+ SITE_DEFAULT_LANG: "預設語言"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "預設語言將會採用風格檔案設定的 標籤"
+ SITE_DEFAULT_LANG_HELP: "預設語言將會採用風格檔案設定的 標籤"
+ DEFAULT_AUTHOR: "預設作者"
+ DEFAULT_AUTHOR_HELP: "預設作者姓名,經常使用在主題或頁面內容"
+ DEFAULT_EMAIL: "預設的電子郵件"
+ DEFAULT_EMAIL_HELP: "預設的電子郵件中的主題或頁面引用"
+ TAXONOMY_TYPES: "分類類型"
+ TAXONOMY_TYPES_HELP: "如果你想要在頁中使用它們,必須在這裡定義分類類型"
+ PAGE_SUMMARY: "頁面摘要"
+ ENABLED: "啟用"
+ ENABLED_HELP: "使用頁面摘要 (摘要和頁面內容相同)"
+ 'YES': "是"
+ 'NO': "否"
+ SUMMARY_SIZE: "摘要大小"
+ SUMMARY_SIZE_HELP: "使用作為內容摘要頁的字元數"
+ FORMAT: "格式"
+ FORMAT_HELP: "短 = 使用第一次發生的分隔符號或大小;長 = 摘要分隔符號將被忽略"
+ SHORT: "短"
+ LONG: "長"
+ DELIMITER: "分隔符號"
+ DELIMITER_HELP: "摘要的分隔符號 (預設 ' = = = ')"
+ METADATA: "中繼資料"
+ METADATA_HELP: "除非重寫該頁面,預設資料值將顯示在每頁的頁面"
+ NAME: "名稱"
+ CONTENT: "内容"
+ SIZE: "尺寸"
+ ACTION: "行動"
+ REDIRECTS_AND_ROUTES: "重定向及路由"
+ CUSTOM_REDIRECTS: "自訂重定向"
+ CUSTOM_REDIRECTS_HELP: "要重定向到其他頁的路徑。可使用標準的正規表示式替換"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/your/redirect"
+ CUSTOM_ROUTES: "自訂路徑"
+ CUSTOM_ROUTES_HELP: "路由到其他頁的別名。標準的正則運算式替換無效"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/your/route"
+ FILE_STREAMS: "File Streams"
+ DEFAULT: "預設"
+ PAGE_MEDIA: "頁面媒體"
+ OPTIONS: "選項"
+ PUBLISHED: "已發佈的"
+ PUBLISHED_HELP: "預設情況下,除非您顯式設置發佈發佈頁面︰ false 或通過在將來,當 publish_date 或 unpublish_date 在過去"
+ DATE: "日期"
+ DATE_HELP: "日期變數允許您設置與此頁面相關聯的日期。"
+ PUBLISHED_DATE: "發佈日期"
+ PUBLISHED_DATE_HELP: "可以提供自動觸發出版的日期。"
+ UNPUBLISHED_DATE: "停止發佈日期"
+ UNPUBLISHED_DATE_HELP: "可以提供自動觸發不出版的日期。"
+ ROBOTS: "機器人"
+ TAXONOMIES: "分類系統"
+ TAXONOMY: "分類"
+ ADVANCED: "進階"
+ SETTINGS: "設置"
+ FOLDER_NUMERIC_PREFIX: "資料夾數字類型的前綴字"
+ FOLDER_NUMERIC_PREFIX_HELP: "Numeric prefix提供手動排序和能見度"
+ FOLDER_NAME: "資料夾名稱"
+ FOLDER_NAME_HELP: "此頁的資料夾名稱將存儲在檔案系統中"
+ PARENT: "上層"
+ DEFAULT_OPTION_ROOT: "- 根 -"
+ DEFAULT_OPTION_SELECT: "- 請選擇 -"
+ DISPLAY_TEMPLATE: "顯示樣版"
+ DISPLAY_TEMPLATE_HELP: "頁面類型翻譯成twig範本呈現頁面"
+ BODY_CLASSES: "Body Classes"
+ ORDERING: "排序"
+ PAGE_ORDER: "頁面順序"
+ OVERRIDES: "覆蓋"
+ MENU: "選單"
+ MENU_HELP: "在菜單中使用的字串。 如果不設置,標題將被使用。"
+ SLUG: "Slug"
+ SLUG_HELP: "Slug變數允許您設置頁面的URL 的一部分"
+ SLUG_VALIDATE_MESSAGE: "Slug 必須包含只有小寫字母數位字元和短劃線"
+ PROCESS: "處理"
+ PROCESS_HELP: "控制如何處理頁面。可以設置每頁而不是在全球範圍內"
+ DEFAULT_CHILD_TYPE: "預設子類型"
+ USE_GLOBAL: "使用全域"
+ ROUTABLE: "可路由的"
+ ROUTABLE_HELP: "如果此頁是可被URL訪問的"
+ CACHING: "快取中"
+ VISIBLE: "可見的"
+ VISIBLE_HELP: "推測頁面是否在導航中可見。"
+ DISABLED: "停用"
+ ITEMS: "項目"
+ ORDER_BY: "排序依照"
+ ORDER: "排序"
+ FOLDER: "資料夾"
+ ASCENDING: "昇冪"
+ DESCENDING: "降冪"
+ ADD_MODULAR_CONTENT: "添加模組化的內容"
+ PAGE_TITLE: "頁面標題"
+ PAGE_TITLE_HELP: "頁面標題"
+ PAGE: "頁面"
+ MODULAR_TEMPLATE: "模組化的樣版"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "檔名"
+ PARENT_PAGE: "上層頁面"
+ HOME_PAGE: "首頁"
+ HOME_PAGE_HELP: "Grav將使用作為預設登陸頁面的頁面"
+ DEFAULT_THEME: "預設主題"
+ DEFAULT_THEME_HELP: "設置grav要使用的預設主題 (預設是Antimatter)"
+ TIMEZONE: "時區"
+ TIMEZONE_HELP: "重寫預設時區伺服器"
+ SHORT_DATE_FORMAT: "顯示短日期格式"
+ SHORT_DATE_FORMAT_HELP: "設置使用主題的短日期格式"
+ LONG_DATE_FORMAT: "顯示長日期格式"
+ LONG_DATE_FORMAT_HELP: "設置使用主題的長日期格式"
+ DEFAULT_ORDERING: "預設排序"
+ DEFAULT_ORDERING_HELP: "在清單中的頁面將呈現使用本命令,除非它被覆蓋"
+ DEFAULT_ORDERING_DEFAULT: "預設值-基於資料夾名稱"
+ DEFAULT_ORDERING_FOLDER: "資料夾-基於prefix-less資料夾名稱"
+ DEFAULT_ORDERING_TITLE: "標題 - 基於在標頭中的標題欄位"
+ DEFAULT_ORDERING_DATE: "日期-基於標頭中的日期欄位"
+ DEFAULT_ORDER_DIRECTION: "預設排序方向"
+ DEFAULT_ORDER_DIRECTION_HELP: "清單中頁面的方向"
+ DEFAULT_PAGE_COUNT: "預設頁面數量"
+ DEFAULT_PAGE_COUNT_HELP: "預設清單中的最大頁面數量"
+ DATE_BASED_PUBLISHING: "日期 - 基於出版"
+ DATE_BASED_PUBLISHING_HELP: "自動 不發佈文章基於他們的日期"
+ EVENTS: "事件"
+ EVENTS_HELP: "啟用或停用特定的事件。停用可能使外掛執行錯誤。"
+ REDIRECT_DEFAULT_ROUTE: "重定向預設路由"
+ REDIRECT_DEFAULT_ROUTE_HELP: "自動重定向到的網頁的預設路由"
+ LANGUAGES: "語言"
+ SUPPORTED: "已支援的"
+ SUPPORTED_HELP: "以逗號分隔清單的 2 封信語言代碼 (例如 'en,fr,de')"
+ TRANSLATIONS_ENABLED: "啟用翻譯"
+ TRANSLATIONS_ENABLED_HELP: "在grav支援翻譯、 外掛程式和擴展"
+ TRANSLATIONS_FALLBACK: "翻譯回退"
+ TRANSLATIONS_FALLBACK_HELP: "如果使用語言不存在通過支援翻譯回退"
+ ACTIVE_LANGUAGE_IN_SESSION: "在session中活躍的語言"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "在session中存儲使用中的語言"
+ HTTP_HEADERS: "HTTP 標頭"
+ EXPIRES: "到期"
+ EXPIRES_HELP: "設置過期標頭。值是以秒為單位。"
+ CACHE_CONTROL: "HTTP 快取控制"
+ CACHE_CONTROL_HELP: "設定一個有效的快取控制參數例如 `無快取no-cache, 無儲存no-store, 必須重新驗證must-revalidate`"
+ LAST_MODIFIED: "最後修改"
+ LAST_MODIFIED_HELP: "設置的最後的修改的標題,可以説明優化代理伺服器和瀏覽器緩存"
+ ETAG: "ETag"
+ ETAG_HELP: "設置 etag 標頭來説明識別時的網頁已被修改"
+ VARY_ACCEPT_ENCODING: "Vary accept encoding"
+ VARY_ACCEPT_ENCODING_HELP: "Sets the `Vary: Accept Encoding` header to help with proxy and CDN caching"
+ MARKDOWN: "Markdown"
+ MARKDOWN_EXTRA_HELP: "啟用預設為支援Markdown Extra - HTTPs://michelf.ca/projects/php-markdown/extra/"
+ AUTO_LINE_BREAKS: "自動換行"
+ AUTO_LINE_BREAKS_HELP: "在Markdown啟用自動插入分行符號"
+ AUTO_URL_LINKS: "自動 URL 連結"
+ AUTO_URL_LINKS_HELP: "啟用自動轉換超連結Url成 HTML hyperlinks"
+ ESCAPE_MARKUP: "Escape markup"
+ ESCAPE_MARKUP_HELP: "Escape markup tags into HTML entities"
+ CACHING_HELP: "Global ON/OFF switch to enable/disable Grav caching"
+ CACHE_CHECK_METHOD: "快取記憶體檢查方法"
+ CACHE_CHECK_METHOD_HELP: "選擇Grav檢查是否分頁檔已被修改的方法。"
+ CACHE_DRIVER: "快取驅動"
+ CACHE_DRIVER_HELP: "選擇Grav應該使用哪個緩存驅動程式。自動檢測試圖找到最適合你"
+ CACHE_PREFIX: "Cache prefix"
+ CACHE_PREFIX_HELP: "識別碼是Grav的一部分。 不要更改,除非你知道你在做什麼。"
+ CACHE_PREFIX_PLACEHOLDER: "Derived from base URL (override by entering random string)"
+ LIFETIME: "生命週期"
+ LIFETIME_HELP: "以秒為單位設置緩存存留期。0 = 無限"
+ GZIP_COMPRESSION: "Gzip 壓縮"
+ GZIP_COMPRESSION_HELP: "啟用 GZip 壓縮的重力頁的更高的性能。"
+ TWIG_TEMPLATING: "Twig 模板製作中"
+ TWIG_CACHING: "Twig 快取中"
+ TWIG_CACHING_HELP: "控制 Twig 快取機制. 請保留開啟為最佳化的效能."
+ TWIG_DEBUG: "Twig 除錯"
+ TWIG_DEBUG_HELP: "允許不載入Twig Debugger extension選項"
+ DETECT_CHANGES: "檢測更改"
+ DETECT_CHANGES_HELP: "Twig will automatically recompile the Twig cache if it detects any changes in Twig templates"
+ AUTOESCAPE_VARIABLES: "Autoescape 變數"
+ AUTOESCAPE_VARIABLES_HELP: "Autoescapes all variables. This will break your site most likely"
+ ASSETS: "資產"
+ CSS_PIPELINE: "CSS pipeline"
+ CSS_PIPELINE_HELP: "The CSS pipeline is the unification of multiple CSS resources into one file"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "Include externals in CSS pipeline"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "External URLs sometimes have relative file references and shouldn't be pipelined"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "CSS pipeline render first"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Render the CSS pipeline before any other CSS references that are not included"
+ CSS_MINIFY: "CSS 縮小化"
+ CSS_MINIFY_HELP: "在 pipelining縮小 css"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "CSS minify Windows override"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Minify Override for Windows platforms. False by default due to ThreadStackSize"
+ CSS_REWRITE: "CSS 重寫"
+ CSS_REWRITE_HELP: "Rewrite any CSS relative URLs during pipelining"
+ JAVASCRIPT_PIPELINE: "JavaScript pipeline"
+ JAVASCRIPT_PIPELINE_HELP: "The JS pipeline is the unification of multiple JS resources into one file"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "包括外部 在JS pipeline"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "External URLs sometimes have relative file references and shouldn't be pipelined"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "JS pipeline render first"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Render the JS pipeline before any other JS references that are not included"
+ JAVASCRIPT_MINIFY: "JavaScript 縮小化"
+ JAVASCRIPT_MINIFY_HELP: "Minify the JS during pipelining"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "啟動在assets上的時間郵戳"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Enable asset timestamps"
+ COLLECTIONS: "收藏清單"
+ ERROR_HANDLER: "錯誤處理"
+ DISPLAY_ERRORS: "顯示錯誤"
+ DISPLAY_ERRORS_HELP: "Display full backtrace-style error page"
+ LOG_ERRORS: "記錄錯誤"
+ LOG_ERRORS_HELP: "記錄錯誤到 /logs 資料夾"
+ DEBUGGER: "除錯器"
+ DEBUGGER_HELP: "啟用Grav除錯器和其設置"
+ DEBUG_TWIG: "除錯Twig"
+ DEBUG_TWIG_HELP: "啟用除錯的TWIG範本"
+ SHUTDOWN_CLOSE_CONNECTION: "關機關閉連接"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "關閉連接之前呼叫 onShutdown()。false 為除錯"
+ DEFAULT_IMAGE_QUALITY: "預設圖片品質"
+ DEFAULT_IMAGE_QUALITY_HELP: "當重採樣或緩存圖像 (85%) 時使用預設圖像品質"
+ CACHE_ALL: "快取所有圖片"
+ CACHE_ALL_HELP: "通過Grav的緩存系統運行的所有圖像,即使他們有沒有媒體操作"
+ IMAGES_DEBUG: "圖像排除浮水印"
+ IMAGES_DEBUG_HELP: "顯示overlay圖像標示圖像的像素數深度,例如當運行retina時"
+ UPLOAD_LIMIT: "檔案上傳限制"
+ UPLOAD_LIMIT_HELP: "設置最大上載大小以位元組為單位 (0 是無限的)"
+ ENABLE_MEDIA_TIMESTAMP: "啟用在媒體上的時間戳記"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "追加時間戳記基於每個媒體項的最後修改日期"
+ SESSION: "連線階段"
+ SESSION_ENABLED_HELP: "在 Grav啟用session 支援"
+ TIMEOUT: "逾時"
+ TIMEOUT_HELP: "設置session timeout以秒為單位"
+ SESSION_NAME_HELP: "識別者用來形成session cookie的名稱"
+ ABSOLUTE_URLS: "絕對網址"
+ ABSOLUTE_URLS_HELP: "絕對或相對 Url 為'Base_url'"
+ PARAMETER_SEPARATOR: "參數分隔符號"
+ PARAMETER_SEPARATOR_HELP: "傳遞參數的分隔符號可以在 Windows 上的 Apache 更改"
+ TASK_COMPLETED: "任務已完成"
+ EVERYTHING_UP_TO_DATE: "全部都已經是最新的"
+ UPDATES_ARE_AVAILABLE: "有可用的更新"
+ IS_AVAILABLE_FOR_UPDATE: "可進行更新"
+ IS_NOW_AVAILABLE: "現在有可用的"
+ CURRENT: "現有的"
+ UPDATE_GRAV_NOW: "現在更新Grav"
+ GRAV_SYMBOLICALLY_LINKED: "Grav 被 symbolically linked。升級將無法使用"
+ UPDATING_PLEASE_WAIT: "更新中... 請稍候,正在下載"
+ OF_THIS: "這"
+ OF_YOUR: "您的"
+ HAVE_AN_UPDATE_AVAILABLE: "有可用的更新"
+ SAVE_AS: "另存為"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "是否確實要刪除此頁面及其所有子頁?如果頁面譯成其他語言,這些譯本將被保存和必須分開刪除。否則頁面資料夾沿其子頁會被刪除。此操作無法撤銷復原。"
+ AND: "和"
+ UPDATE_AVAILABLE: "有可用更新"
+ METADATA_KEY: "鍵 (例如 'Keywords')"
+ METADATA_VALUE: "值 (例如 ' Blog, Grav')"
+ USERNAME_HELP: "使用者名稱應該是 3 到 16 個字元,包括小寫字母、 數位、 底線和連字號之間。大寫的字母、 空格和特殊字元不允許"
+ FULLY_UPDATED: "完整地更新"
+ SAVE_LOCATION: "儲存位置"
+ PAGE_FILE: "頁面範本"
+ PAGE_FILE_HELP: "頁面範本檔案名稱,並預設為此頁面的顯示範本"
+ NO_USER_ACCOUNTS: "找不到使用者帳戶,請先創建一個......"
+ REDIRECT_TRAILING_SLASH: "重定向斜杠"
+ REDIRECT_TRAILING_SLASH_HELP: "執行一個 301 重定向,而不是光處理trailing slash URIs"
+ DEFAULT_DATE_FORMAT: "頁面日期格式"
+ DEFAULT_DATE_FORMAT_HELP: "Grav的頁面日期格式。預設情況下,Grav試圖猜測您的日期格式,但是您可以使用 PHP 的date syntax設定格式 (例如︰ Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "自動猜測"
+ IGNORE_FILES: "忽略的檔案"
+ IGNORE_FILES_HELP: "處理頁面時忽略等定檔案"
+ IGNORE_FOLDERS: "忽略的資料夾"
+ IGNORE_FOLDERS_HELP: "處理頁面時忽略特定資料夾"
+ HTTP_ACCEPT_LANGUAGE: "由瀏覽器來設置語言"
+ HTTP_ACCEPT_LANGUAGE_HELP: "在瀏覽器中您可以選擇嘗試設置基於 'Http_accept_language' 標題標記的語言"
+ OVERRIDE_LOCALE: "覆蓋地區"
+ OVERRIDE_LOCALE_HELP: "基於當前語言在PHP重寫locale setting"
+ REDIRECT: "頁面重新導向"
+ REDIRECT_HELP: "輸入頁面路徑或外部連結為此頁面重定向。例如`/some/route` 或 `http://somesite.com`"
+ PLUGIN_STATUS: "外掛狀態"
+ INCLUDE_DEFAULT_LANG: "包括預設語言"
+ INCLUDE_DEFAULT_LANG_HELP: "此會將預設語言加在所有URL的開首,如 `/en/blog/my-post`"
+ ALLOW_URL_TAXONOMY_FILTERS: "URL 分類篩選器"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "Page-based collections 允許您藉由 `/taxonomy:value` 篩選。"
+ REDIRECT_DEFAULT_CODE: "預設重定向代碼"
+ REDIRECT_DEFAULT_CODE_HELP: "使用 HTTP status code來重定向"
+ IGNORE_HIDDEN: "忽略隱藏"
+ IGNORE_HIDDEN_HELP: "忽略所有檔和資料夾從 DOT開始"
+ WRAPPED_SITE: "嵌入的網站"
+ WRAPPED_SITE_HELP: "主題/外掛程式來知道是否Grav被另一個平台包裹"
+ FALLBACK_TYPES: "允許fallback類型"
+ FALLBACK_TYPES_HELP: "允許經由頁面路徑存取發現檔案類型。預設值為任何受支援的媒體類型。"
+ INLINE_TYPES: "Inline fallback 類型"
+ INLINE_TYPES_HELP: "檔案類型清單應該被顯示,而不是下載"
+ APPEND_URL_EXT: "附加 URL 副檔名"
+ APPEND_URL_EXT_HELP: "Will add a custom extension to the Page's URL. Note, this will mean Grav will look for `..twig` template"
+ PAGE_MODES: "頁面模式"
+ PAGE_TYPES: "頁面類型"
+ ACCESS_LEVELS: "存取層級"
+ GROUPS: "群組"
+ GROUPS_HELP: "List of groups the user is part of"
+ ADMIN_ACCESS: "管理存取權限"
+ SITE_ACCESS: "網站存取權限"
+ INVALID_SECURITY_TOKEN: "無效的安全令牌"
+ ACTIVATE: "啟用"
+ TWIG_UMASK_FIX: "Umask Fix"
+ TWIG_UMASK_FIX_HELP: "By default Twig creates cached files as 0755, fix switches this to 0775"
+ CACHE_PERMS: "快取權限"
+ CACHE_PERMS_HELP: "Default cache folder perms. Usually 0755 or 0775 depending on setup"
+ REMOVE_SUCCESSFUL: "刪除成功"
+ REMOVE_FAILED: "刪除失敗"
+ HIDE_HOME_IN_URLS: "在 URL 中隱藏 home 路徑"
+ HIDE_HOME_IN_URLS_HELP: "確保 home 下所有頁面的預設路徑不會對應至 home 的正常路徑(即不會在 `/home` 下)"
+ TWIG_FIRST: "首先處理 Twig"
+ TWIG_FIRST_HELP: "If you enabled Twig page processing, then you can configure Twig to process before or after markdown"
+ SESSION_SECURE: "安全"
+ SESSION_SECURE_HELP: "如果為 true,則指示此 cookie 的通信必須在加密傳輸。警告︰ 啟用此功能只有在完全在 HTTPS 運行的網站上"
+ SESSION_HTTPONLY: "只使用HTTP"
+ SESSION_HTTPONLY_HELP: "如果為 true,則指示應僅通過 HTTP,使用 cookie 和不允許 JavaScript 修改"
+ REVERSE_PROXY: "反向代理"
+ REVERSE_PROXY_HELP: "Enable this if you are behind a reverse proxy and you are having trouble with URLs containing incorrect ports"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "無效的frontmatter,不能保存"
+ ADD_FOLDER: "新增資料夾"
+ PROXY_URL: "Proxy URL"
+ PROXY_URL_HELP: "輸入代理伺服器主機或 IP 和埠"
+ NOTHING_TO_SAVE: "沒有任何變更可以儲存"
+ FILE_ERROR_ADD: "試圖添加檔時出錯"
+ FILE_ERROR_UPLOAD: "嘗試上載檔時出錯"
+ FILE_UNSUPPORTED: "不支援的檔案類型"
+ ADD_ITEM: "建立項目"
+ FILE_TOO_LARGE: "檔案太大,無法上傳,根據您的 PHP 設置 最大允許 %s 。增加你 'post_max_size' PHP 設置"
+ INSTALLING: "正在安裝"
+ LOADING: "讀取中..."
+ DEPENDENCIES_NOT_MET_MESSAGE: "The following dependencies need to be fulfilled first:"
+ ERROR_INSTALLING_PACKAGES: "安裝套裝程式時出錯"
+ INSTALLING_DEPENDENCIES: "正在安裝dependencies..."
+ INSTALLING_PACKAGES: "正在安裝套裝程式.."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "成功安裝的套裝程式。"
+ READY_TO_INSTALL_PACKAGES: "準備安裝套裝程式"
+ PACKAGES_NOT_INSTALLED: "套件未安裝"
+ PACKAGES_NEED_UPDATE: "Packages already installed, but too old"
+ PACKAGES_SUGGESTED_UPDATE: "已安裝套裝軟體和版本是好的,但將會更新令你保持最新"
+ REMOVE_THE: "Remove the %s"
+ CONFIRM_REMOVAL: "是否確實要刪除此 %s?"
+ REMOVED_SUCCESSFULLY: "%s 已成功刪除"
+ ERROR_REMOVING_THE: "刪除 %s 時出錯"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "The %s required the following dependencies, which are not required by other installed packages. If you are not using them, you can remove them directly from here."
+ READY_TO_UPDATE_PACKAGES: "準備更新程式封裝"
+ ERROR_UPDATING_PACKAGES: "更新資料包時出現錯誤"
+ UPDATING_PACKAGES: "更新程式封裝中"
+ PACKAGES_SUCCESSFULLY_UPDATED: "已成功更新套裝程式"
+ UPDATING: "更新中"
+ GPM_RELEASES: "GPM 發佈"
+ GPM_RELEASES_HELP: "Choose 'Testing' to install beta or testing versions"
+ GPM_METHOD: "Remote Fetch Method"
+ GPM_METHOD_HELP: "當設置為 Auto,重力將確定 fopen 可用和使用它,否則回退到捲曲。要強制使用一個或其他切換設置。"
+ GPM_VERIFY_PEER: "Remote Verify Peer (SSL)"
+ GPM_VERIFY_PEER_HELP: "一些提供者似乎驗證失敗 getgrav.org SSL 證書,引起 GPM 不運行。如果這是您相符的情況,關閉此設置可能會有幫助"
+ AUTO: "自動"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "穩定"
+ TESTING: "測試中"
+ FRONTMATTER_PROCESS_TWIG: "處理frontmatter Twig"
+ FRONTMATTER_PROCESS_TWIG_HELP: "當啟用時你可以在頁中front matter使用Twig 設定變數"
+ FRONTMATTER_IGNORE_FIELDS: "忽略 frontmatter 欄位"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "某些Frontmatter 的欄位可能包含Twig,但不應被處理,如 '表格'"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "安裝包 %s成功安裝"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Parent setting order,排序失效"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "不可見的頁面,排序失效"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "通過管理員排序是不受支援的,因為多過200 多個手足"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "由於未啟用 資料夾首碼數值化 , 所以此頁面排序功能關閉"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "注意︰ 您不能添加媒體檔案直到您保存網頁。只需按一下在上面的'保存'"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "注意︰ 網頁必須保存之前你可以將檔上載到它。"
+ DROP_FILES_HERE_TO_UPLOAD: "拖曳您的檔在這裡或 在此區域中按一下 "
+ INSERT: "插入"
+ UNDO: "還原"
+ REDO: "恢复"
+ HEADERS: "標題"
+ BOLD: "粗體"
+ ITALIC: "斜體"
+ STRIKETHROUGH: "刪除線"
+ SUMMARY_DELIMITER: "摘要分隔符號"
+ LINK: "鏈結"
+ IMAGE: "圖片"
+ BLOCKQUOTE: "引用文字"
+ UNORDERED_LIST: "項目符號清單"
+ ORDERED_LIST: "編號清單"
+ EDITOR: "编辑器"
+ PREVIEW: "預覽"
+ FULLSCREEN: "全螢幕"
+ MODULAR: "模組化"
+ NON_ROUTABLE: "非路由"
+ NON_MODULAR: "非模組化"
+ NON_VISIBLE: "非可見"
+ NON_PUBLISHED: "不被出版"
+ CHARACTERS: "字元"
+ PUBLISHING: "出版中"
+ NOTIFICATIONS: "通知"
+ MEDIA_TYPES: "媒體類型"
+ IMAGE_OPTIONS: "圖像選項"
+ MIME_TYPE: "Mime 類型"
+ THUMB: "縮圖"
+ TYPE: "類型"
+ FILE_EXTENSION: "檔案延伸檔名"
+ LEGEND: "頁面圖例"
+ MEMCACHE_SERVER: "Memcache 伺服器"
+ MEMCACHE_SERVER_HELP: "Memcache 伺服器位址"
+ MEMCACHE_PORT: "Memcache 埠"
+ MEMCACHE_PORT_HELP: "Memcache 伺服器埠"
+ MEMCACHED_SERVER: "Memcache 伺服器"
+ MEMCACHED_SERVER_HELP: "Memcache 伺服器位址"
+ MEMCACHED_PORT: "Memcache 埠"
+ MEMCACHED_PORT_HELP: "Memcache 伺服器埠"
+ REDIS_SERVER: "Redis伺服器"
+ REDIS_SERVER_HELP: "Redis伺服器位址"
+ REDIS_PORT: "Redis埠"
+ REDIS_PORT_HELP: "Redis伺服器埠"
+ REDIS_PASSWORD: "Redis 資料庫 密碼/機密"
+ ALL: "所有"
+ FROM: "從"
+ TO: "到"
+ RELEASE_DATE: "發佈日期 "
+ SORT_BY: "排序按"
+ RESOURCE_FILTER: "篩選..."
+ FORCE_SSL: "強制 SSL"
+ FORCE_SSL_HELP: "整體的強制 SSL,如果啟用時網站達到通過 HTTP,GRAV發送一個重定向到 HTTPS 頁"
+ NEWS_FEED: "動態消息"
+ EXTERNAL_URL: "外部URL"
+ SESSION_PATH: "Session路徑"
+ SESSION_PATH_HELP: "只有使用在如您選擇自訂的base URL (你重寫網站域名稱 / 子資料夾)"
+ CUSTOM_BASE_URL: "自訂的基本 URL"
+ CUSTOM_BASE_URL_HELP: "如果你想要重寫網站域名或使用一個不同子的資料夾,使用。示例︰ Http://localhost"
+ FILEUPLOAD_PREVENT_SELF: '在頁面之外不能使用"%s"'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: '無法上載檔 %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: '無法將檔案"%s"移到 %s'
+ DROPZONE_CANCEL_UPLOAD: '取消上傳'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: '你確定想要取消這個上傳?'
+ DROPZONE_DEFAULT_MESSAGE: '拖曳您的檔在這裡或 在此區域中按一下 '
+ DROPZONE_FALLBACK_MESSAGE: '您的瀏覽器並不支援拖曳放置檔案上傳。'
+ DROPZONE_FALLBACK_TEXT: '請使用下面的回退表單上傳你的檔案'
+ DROPZONE_FILE_TOO_BIG: '檔太大 ({{filesize}}MiB)。最大檔案大小︰ {{maxFilesize}}MiB。'
+ DROPZONE_INVALID_FILE_TYPE: "您不能上傳該類型的文件"
+ DROPZONE_MAX_FILES_EXCEEDED: "您不能上傳更多任何的檔案。"
+ DROPZONE_REMOVE_FILE: "移除檔案"
+ DROPZONE_RESPONSE_ERROR: "伺服器回應 {{statusCode}} 代碼。"
+ PREMIUM_PRODUCT: "高級版"
+ ADMIN_CACHING: "啟用緩存管理"
+ ADMIN_CACHING_HELP: "緩存在管理員能夠進行獨立控制從前端網站"
+ DESTINATION_NOT_SPECIFIED: "未指定目標位置"
+ UPLOAD_ERR_NO_TMP_DIR: "遺失暫存資料夾"
+ SESSION_SPLIT: "Session 拆分"
+ SESSION_SPLIT_HELP: "獨立拆分session在網站和其他外掛程式之間(如 admin)"
+ ERROR_FULL_BACKTRACE: "Full Backtrace 錯誤"
+ ERROR_SIMPLE: "簡易錯誤"
+ ERROR_SYSTEM: "出現系統錯誤"
+ IMAGES_AUTO_FIX_ORIENTATION: "自動修復方向"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "確認圖片Exif資料自動修正圖片方向"
+ REDIS_SOCKET: "Redis 插座"
+ REDIS_SOCKET_HELP: "Redis 插座"
+ NOT_SET: "未設定"
+ PERMISSIONS: "權限"
+ NEVER_CACHE_TWIG: "Twig 不開啟快取"
+ NEVER_CACHE_TWIG_HELP: "只快取內容,每次都會處理 Twig 。忽略 twig_first 設定。"
+ ALLOW_WEBSERVER_GZIP: "允許伺服器 Gzip"
+ ALLOW_WEBSERVER_GZIP_HELP: "預設情況下關閉。啟用後, 網頁伺服器配置的 Gzip/收縮壓縮將會執行, 但在 onShutDown() 事件導致頁面載入速度減慢之前, 網路連接將不會關閉"
+ OFFLINE_WARNING: "無法建立與 GPM 的連接"
+ CLEAR_IMAGES_BY_DEFAULT: "預設情況下清除圖片快取"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "預設情況下,將清除所有快取中的已處理圖片, 此功能可以關閉"
+ CLI_COMPATIBILITY: "CLI 兼容性"
+ CLI_COMPATIBILITY_HELP: "確認不是使用容易失去的快取驅動(檔案, Redis資料庫, memcache快取機制等)"
+ REINSTALL_PLUGIN: "重新安裝外掛"
+ REINSTALL_THEME: "重新安裝主題"
+ REINSTALL_THE: "重新安裝 %s"
+ CONFIRM_REINSTALL: "您確定要重新安裝此 %s?"
+ REINSTALLED_SUCCESSFULLY: "%s 已重新安裝成功"
+ ERROR_REINSTALLING_THE: "%s 重新安裝發生錯誤"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "外掛 %s 已重新安裝成功"
+ REINSTALLATION_FAILED: "重新安裝失敗"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "安裝的版本不是最新版。通過按一下 \"繼續\", 您將刪除目前版本並安裝最新的可用發行版本"
+ TOOLS: "工具"
+ DIRECT_INSTALL: "直接安裝"
+ NO_PACKAGE_NAME: "未指定外掛名稱"
+ PACKAGE_EXTRACTION_FAILED: "套件解壓縮失敗"
+ NOT_VALID_GRAV_PACKAGE: "這個不是一個有效的Grav外掛"
+ NAME_COULD_NOT_BE_DETERMINED: "無法確定名稱"
+ CANNOT_OVERWRITE_SYMLINKS: "無法覆蓋符號連結"
+ ZIP_PACKAGE_NOT_FOUND: "找不到 ZIP 壓縮檔案"
+ GPM_OFFICIAL_ONLY: "僅限官方 GPM"
+ GPM_OFFICIAL_ONLY_HELP: "只允許直接從官方的 GPM 資料庫安裝。"
+ NO_CHILD_TYPE: "原始路徑無子階層"
+ SORTABLE_PAGES: "可排序的頁面:"
+ ADMIN_SPECIFIC_OVERRIDES: "管理特定覆蓋"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "子頁面顯示排序"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "子頁面顯示排序將會顯示在管理外掛 'Pages' 上"
+ PWD_PLACEHOLDER: "複合字串至少8個字元長度"
+ PWD_REGEX: "密碼的正規表示式"
+ PWD_REGEX_HELP: "密碼必須包含至少一個數字和一個大寫和小寫字母和至少 8 個或更多字元"
+ USERNAME_PLACEHOLDER: "僅小寫字元, 例如 'admin'"
+ USERNAME_REGEX: "帳號名稱正規表示式"
+ USERNAME_REGEX_HELP: "預設情況下: 只有小寫字元、數位、破折號和底線。3-16 字元"
+ CONTENT_PADDING: "內容內距的設定"
+ CONTENT_PADDING_HELP: "開啟/關閉 內容內距的設定來提供更多空間"
+ ENABLE_AUTO_METADATA: "自動 metadata 取得來源 Exif"
+ ENABLE_AUTO_METADATA_HELP: "自動從圖片的exif資訊自動產生metadata 檔案"
+ 2FA_TITLE: "雙重身份驗證"
+ 2FA_INSTRUCTIONS: "## 2 因數身份驗證\n您在該帳戶上啟用了 ** 2FA **。請使用您的 ** 2FA ** 應用程式輸入當前 ** 6 位代碼 ** 完成登錄過程。"
+ 2FA_REGEN_HINT: "重新產生金鑰後,您必須於您的驗證器更新金鑰。"
+ 2FA_LABEL: "管理存取權限"
+ 2FA_FAILED: "雙重身份驗證碼無效, 請重新嘗試"
+ 2FA_ENABLED: "已啟用雙重認證"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "雙重身份驗證 加密值"
+ 2FA_REGENERATE: "重新產生"
+ FORCE_LOWERCASE_URLS: "強制網址為小寫"
+ INTL_ENABLED: "Intl 模組整合"
+ VIEW_SITE_TIP: "檢視站點"
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "上傳並安裝"
+ ROUTE_DEFAULT: "預設路徑"
+ ROUTE_ALIASES: "路徑別名"
diff --git a/user/plugins/admin/languages/zh.yaml b/user/plugins/admin/languages/zh.yaml
new file mode 100644
index 0000000..9bcccef
--- /dev/null
+++ b/user/plugins/admin/languages/zh.yaml
@@ -0,0 +1,819 @@
+---
+PLUGIN_ADMIN:
+ ADMIN_BETA_MSG: "这是一个 Beta 版本!若在生产环境中使用,请自行承担责任。"
+ ADMIN_REPORT_ISSUE: "遇到问题?请提交到 GitHub。"
+ EMAIL_FOOTER: "由 Grav 驱动 - 先进的纯文件 CMS"
+ LOGIN_BTN: "登录"
+ LOGIN_BTN_FORGOT: "忘记密码"
+ LOGIN_BTN_RESET: "重置密码"
+ LOGIN_BTN_SEND_INSTRUCTIONS: "发送重置指引"
+ LOGIN_BTN_CLEAR: "清除表单"
+ LOGIN_BTN_CREATE_USER: "创建用户"
+ LOGIN_LOGGED_IN: "您已成功登录"
+ LOGIN_FAILED: "登录失败"
+ LOGGED_OUT: "您已退出登录"
+ RESET_NEW_PASSWORD: "请输入一个新密码 …"
+ RESET_LINK_EXPIRED: "重置链接已过期,请再试一次"
+ RESET_PASSWORD_RESET: "密码已重置"
+ RESET_INVALID_LINK: "重置密码连接无效,请再试一次"
+ FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "重置密码指引已经发送到你的邮箱"
+ FORGOT_FAILED_TO_EMAIL: "重置密码指引发送失败,请稍后再试"
+ FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "因没有预留电子邮件地址,无法为 %s 重置密码"
+ FORGOT_USERNAME_DOES_NOT_EXIST: "用户 %s 不存在"
+ FORGOT_EMAIL_NOT_CONFIGURED: "无法重置密码。本站不能发送电子邮件"
+ FORGOT_EMAIL_SUBJECT: "%s 的密码重置请求"
+ FORGOT_EMAIL_BODY: "密码重置 亲爱的 %1$s,
一个重置您的密码的请求在 %4$s 生成。
点击此处重置密码
或复制以下链接到浏览器地址栏:
%2$s
祝好, %3$s
"
+ MANAGE_PAGES: "管理页面"
+ CONFIGURATION: "设置"
+ PAGES: "页面"
+ PLUGINS: "插件"
+ PLUGIN: "插件"
+ THEMES: "主题"
+ LOGOUT: "登出"
+ BACK: "返回"
+ NEXT: "下一页"
+ PREVIOUS: "上一页"
+ ADD_PAGE: "添加页面"
+ ADD_MODULAR: "添加模块"
+ MOVE: "移动"
+ DELETE: "删除"
+ UNSET: "未设定"
+ VIEW: "查看"
+ SAVE: "保存"
+ NORMAL: "标准"
+ EXPERT: "专家"
+ EXPAND_ALL: "全部展开"
+ COLLAPSE_ALL: "全部折叠"
+ ERROR: "错误"
+ CLOSE: "关闭"
+ CANCEL: "取消"
+ CONTINUE: "继续"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "需要确认"
+ MODAL_CHANGED_DETECTED_TITLE: "检测到更改"
+ MODAL_CHANGED_DETECTED_DESC: "您有未保存的更改。确定不保存并退出?"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "需要确认"
+ MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "您确定要删除这个文件吗?此操作无法撤消。"
+ ADD_FILTERS: "添加筛选器"
+ SEARCH_PAGES: "搜索页面"
+ VERSION: "版本"
+ WAS_MADE_WITH: "制作"
+ BY: "由"
+ UPDATE_THEME: "更新主题"
+ UPDATE_PLUGIN: "更新插件"
+ OF_THIS_THEME_IS_NOW_AVAILABLE: "这一主题现在可用"
+ OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "这个插件现在可用"
+ AUTHOR: "作者"
+ HOMEPAGE: "首页"
+ DEMO: "演示"
+ BUG_TRACKER: "Bug 追踪"
+ KEYWORDS: "关键字"
+ LICENSE: "许可证"
+ DESCRIPTION: "说明"
+ README: "自述文档"
+ REMOVE_THEME: "删除主题"
+ INSTALL_THEME: "安装主题"
+ THEME: "主题"
+ BACK_TO_THEMES: "返回主题列表"
+ BACK_TO_PLUGINS: "返回插件列表"
+ CHECK_FOR_UPDATES: "检查更新"
+ ADD: "增加"
+ CLEAR_CACHE: "清除缓存"
+ CLEAR_CACHE_ALL_CACHE: "所有缓存"
+ CLEAR_CACHE_ASSETS_ONLY: "仅资源"
+ CLEAR_CACHE_IMAGES_ONLY: "仅图片"
+ CLEAR_CACHE_CACHE_ONLY: "仅缓存"
+ CLEAR_CACHE_TMP_ONLY: "仅临时"
+ DASHBOARD: "控制面板"
+ UPDATES_AVAILABLE: "有可用的更新"
+ DAYS: "天"
+ UPDATE: "更新"
+ BACKUP: "备份"
+ BACKUPS: "备份"
+ BACKUP_NOW: "立即备份"
+ BACKUPS_STATS: "备份统计"
+ BACKUPS_HISTORY: "备份历史"
+ BACKUPS_PURGE_CONFIG: "备份清除配置"
+ BACKUPS_PROFILES: "备份配置文件"
+ BACKUPS_COUNT: "备份数量"
+ BACKUPS_PROFILES_COUNT: "备份配置文件数量"
+ BACKUPS_TOTAL_SIZE: "使用的空间"
+ BACKUPS_NEWEST: "最新的备份"
+ BACKUPS_OLDEST: "最早的备份"
+ BACKUPS_PURGE: "清除"
+ BACKUPS_NOT_GENERATED: "尚未生成备份…"
+ BACKUPS_PURGE_NUMBER: "使用 %s 的 %s 备份插槽"
+ BACKUPS_PURGE_TIME: "备份剩余 %s 天"
+ BACKUPS_PURGE_SPACE: "已使用 %s,共 %s"
+ BACKUP_DELETED: "备份已成功删除!"
+ BACKUP_NOT_FOUND: "找不到备份"
+ BACKUP_DATE: "备份日期"
+ STATISTICS: "统计数据"
+ TODAY: "今天"
+ WEEK: "星期"
+ MONTH: "月"
+ LATEST_PAGE_UPDATES: "最近更新"
+ MAINTENANCE: "维护"
+ UPDATED: "已更新"
+ MON: "星期一"
+ TUE: "星期二"
+ WED: "星期三"
+ THU: "星期四"
+ FRI: "星期五"
+ SAT: "星期六"
+ SUN: "星期天"
+ COPY: "复制"
+ EDIT: "编辑"
+ CREATE: "制作"
+ GRAV_ADMIN: "Grav 管理"
+ GRAV_OFFICIAL_PLUGIN: "Grav 官方插件"
+ GRAV_OFFICIAL_THEME: "Grav 官方主题"
+ PLUGIN_SYMBOLICALLY_LINKED: "这个插件由软连接连接,无法检测到更新。"
+ THEME_SYMBOLICALLY_LINKED: "这个主题由软连接连接,无法检测到更新。"
+ REMOVE_PLUGIN: "删除插件"
+ INSTALL_PLUGIN: "安装插件"
+ AVAILABLE: "可选"
+ INSTALLED: "已安装"
+ INSTALL: "安装"
+ ACTIVE_THEME: "当前主题"
+ SWITCHING_TO: "切换到"
+ SWITCHING_TO_DESCRIPTION: "切换到不同的主题时,无法保证所有页面排版均得到支持,在载入这些页面时可能会出现错误。"
+ SWITCHING_TO_CONFIRMATION: "您想要继续并切换主题吗"
+ CREATE_NEW_USER: "创建新用户"
+ REMOVE_USER: "删除用户"
+ ACCESS_DENIED: "访问被拒绝"
+ ACCOUNT_NOT_ADMIN: "您的帐户没有管理员权限"
+ PHP_INFO: "PHP 信息"
+ INSTALLER: "安装器"
+ AVAILABLE_THEMES: "可用 的主题"
+ AVAILABLE_PLUGINS: "可用的插件"
+ INSTALLED_THEMES: "已安装的主题"
+ INSTALLED_PLUGINS: "已安装的插件"
+ BROWSE_ERROR_LOGS: "浏览错误日志"
+ SITE: "站点"
+ INFO: "信息"
+ SYSTEM: "系统"
+ USER: "用户"
+ ADD_ACCOUNT: "添加帐户"
+ SWITCH_LANGUAGE: "切换语言"
+ SUCCESSFULLY_ENABLED_PLUGIN: "成功启用插件"
+ SUCCESSFULLY_DISABLED_PLUGIN: "成功禁用插件"
+ SUCCESSFULLY_CHANGED_THEME: "成功更改默认主题"
+ INSTALLATION_FAILED: "安装失败"
+ INSTALLATION_SUCCESSFUL: "安装成功"
+ UNINSTALL_FAILED: "卸载失败"
+ UNINSTALL_SUCCESSFUL: "卸载成功"
+ SUCCESSFULLY_SAVED: "保存成功"
+ SUCCESSFULLY_COPIED: "复制成功"
+ REORDERING_WAS_SUCCESSFUL: "重新排序成功"
+ SUCCESSFULLY_DELETED: "删除成功"
+ SUCCESSFULLY_SWITCHED_LANGUAGE: "切换语言成功"
+ INSUFFICIENT_PERMISSIONS_FOR_TASK: "您没有足够权限执行这个任务"
+ CACHE_CLEARED: "缓存已清除"
+ METHOD: "方法"
+ ERROR_CLEARING_CACHE: "清除缓存出错"
+ AN_ERROR_OCCURRED: "发生了一个错误"
+ YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "您的备份已可下载"
+ DOWNLOAD_BACKUP: "下载备份"
+ PAGES_FILTERED: "页面已过滤"
+ NO_PAGE_FOUND: "没有找到页面"
+ INVALID_PARAMETERS: "无效的参数"
+ NO_FILES_SENT: "没有文件被发送"
+ EXCEEDED_FILESIZE_LIMIT: "超出 PHP 配置文件大小限制"
+ EXCEEDED_POSTMAX_LIMIT: "超出 PHP 配置参数 post_max_size"
+ UNKNOWN_ERRORS: "未知错误"
+ EXCEEDED_GRAV_FILESIZE_LIMIT: "超出系统配置文件大小限制"
+ UNSUPPORTED_FILE_TYPE: "不支持的文件类型"
+ FAILED_TO_MOVE_UPLOADED_FILE: "移动上传文件失败"
+ FILE_UPLOADED_SUCCESSFULLY: "文件已成功上传"
+ FILE_DELETED: "文件已删除"
+ FILE_COULD_NOT_BE_DELETED: "无法删除文件"
+ FILE_NOT_FOUND: "找不到文件"
+ NO_FILE_FOUND: "未找到文件"
+ GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav 已成功更新到"
+ GRAV_UPDATE_FAILED: "Grav 更新失败"
+ EVERYTHING_UPDATED: "一切都是最新版本"
+ UPDATES_FAILED: "更新失败"
+ AVATAR_BY: "头像来自"
+ AVATAR_UPLOAD_OWN: "或上传你自己的..."
+ LAST_BACKUP: "最后一次备份"
+ FULL_NAME: "全名"
+ USERNAME: "用户名"
+ EMAIL: "邮箱地址"
+ USERNAME_EMAIL: "用户名或电子邮件"
+ PASSWORD: "密码"
+ PASSWORD_CONFIRM: "确认密码"
+ TITLE: "标题"
+ LANGUAGE: "语言"
+ ACCOUNT: "帐户"
+ EMAIL_VALIDATION_MESSAGE: "必须是一个有效的电子邮件地址"
+ PASSWORD_VALIDATION_MESSAGE: "密码必须包含至少一个数字和一个大写字母和小写字母,必须有 8 个或更多字符。"
+ LANGUAGE_HELP: "设置偏好语言"
+ MEDIA: "媒体"
+ DEFAULTS: "默认值"
+ SITE_TITLE: "网站标题"
+ SITE_TITLE_PLACEHOLDER: "网站长标题"
+ SITE_TITLE_HELP: "网站的默认标题,通常在主题中使用"
+ SITE_DEFAULT_LANG: "默认语言"
+ SITE_DEFAULT_LANG_PLACEHOLDER: "主题的标记中要使用的默认语言"
+ SITE_DEFAULT_LANG_HELP: "主题的标记中要使用的默认语言"
+ DEFAULT_AUTHOR: "默认作者"
+ DEFAULT_AUTHOR_HELP: "默认作者名称,通常在主题或页面内容中使用"
+ DEFAULT_EMAIL: "默认电子邮件"
+ DEFAULT_EMAIL_HELP: "在主题和页面中默认引用的电子邮件"
+ TAXONOMY_TYPES: "分类类型"
+ TAXONOMY_TYPES_HELP: "如果你想在页面中使用分类类型,请在这里定义它们"
+ PAGE_SUMMARY: "页面摘要"
+ ENABLED: "已启用"
+ ENABLED_HELP: "启用页面摘要 (摘要返回内容与页面内容相同)"
+ 'YES': "是"
+ 'NO': "否"
+ SUMMARY_SIZE: "摘要大小"
+ SUMMARY_SIZE_HELP: "用作内容摘要的页面字符数"
+ FORMAT: "格式"
+ FORMAT_HELP: "短:根据第一个分隔符或根据大小;长:忽略摘要分隔符"
+ SHORT: "短"
+ LONG: "长"
+ DELIMITER: "分隔符"
+ DELIMITER_HELP: "摘要分隔符 (默认为 '===')"
+ METADATA: "元数据"
+ METADATA_HELP: "除非被页面覆盖, 否则将在每个页面上显示的默认元数据值"
+ NAME: "名称"
+ CONTENT: "内容"
+ SIZE: "尺寸"
+ ACTION: "行动"
+ REDIRECTS_AND_ROUTES: "重定向和路由"
+ CUSTOM_REDIRECTS: "自定义重定向"
+ CUSTOM_REDIRECTS_HELP: "路由跳转到其他页面,标准正则替换有效"
+ CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/your/redirect"
+ CUSTOM_ROUTES: "自定义路由"
+ CUSTOM_ROUTES_HELP: "跳转到其他页面的路由,可以使用标准的正则表达式。"
+ CUSTOM_ROUTES_PLACEHOLDER_KEY: "/your/alias"
+ CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/your/route"
+ FILE_STREAMS: "文件流"
+ DEFAULT: "默认"
+ PAGE_MEDIA: "页面媒体"
+ OPTIONS: "选项"
+ PUBLISHED: "已发布"
+ PUBLISHED_HELP: "默认情况下,页面将会发布。除非您设置了发布为false ,或publish_date设置了未来某时刻,或unpublish_date设置了过去某时刻"
+ DATE: "日期"
+ DATE_HELP: "date变量允许您专门设置与此页面相关联的时间。"
+ PUBLISHED_DATE: "发布时间"
+ PUBLISHED_DATE_HELP: "可提供一个时间自动触发发布。"
+ UNPUBLISHED_DATE: "取消发布时间"
+ UNPUBLISHED_DATE_HELP: "可提供一个时间自动取消发布。"
+ ROBOTS: "搜索引擎抓取规则Robots"
+ TAXONOMIES: "分类"
+ TAXONOMY: "分类"
+ ADVANCED: "高级选项"
+ SETTINGS: "设置"
+ FOLDER_NUMERIC_PREFIX: "文件夹数字前缀"
+ FOLDER_NUMERIC_PREFIX_HELP: "手动排序和隐藏的数字前缀"
+ FOLDER_NAME: "文件夹名称"
+ FOLDER_NAME_HELP: "将存储此页的文件夹名称"
+ PARENT: "上级页面"
+ DEFAULT_OPTION_ROOT: "-根目录-"
+ DEFAULT_OPTION_SELECT: "-请选择-"
+ DISPLAY_TEMPLATE: "显示模板"
+ DISPLAY_TEMPLATE_HELP: "转换此页为何种模板"
+ BODY_CLASSES: "Body的样式"
+ ORDERING: "排序"
+ PAGE_ORDER: "页面顺序"
+ OVERRIDES: "覆盖"
+ MENU: "菜单"
+ MENU_HELP: "在菜单中使用的字符串。 如果不设置,标题将被使用。"
+ SLUG: "标语"
+ SLUG_HELP: "Slug变量允许您专门设置 URL 的一部分"
+ SLUG_VALIDATE_MESSAGE: "Slug 必须包含只有小写字母数字字符和短划线"
+ PROCESS: "处理"
+ PROCESS_HELP: "控制如何处理页。单页设置优先于全局设置"
+ DEFAULT_CHILD_TYPE: "默认子类型"
+ USE_GLOBAL: "使用全局"
+ ROUTABLE: "可路由"
+ ROUTABLE_HELP: "如果此页面可以从 URL 访问到"
+ CACHING: "缓存"
+ VISIBLE: "可见"
+ VISIBLE_HELP: "决定一个页面是否在导航栏可见"
+ DISABLED: "已禁用"
+ ITEMS: "项目"
+ ORDER_BY: "按顺序"
+ ORDER: "顺序"
+ FOLDER: "文件夹"
+ ASCENDING: "升序"
+ DESCENDING: "降序"
+ ADD_MODULAR_CONTENT: "添加模块化内容"
+ PAGE_TITLE: "页面标题"
+ PAGE_TITLE_HELP: "页面标题"
+ PAGE: "页面"
+ MODULAR_TEMPLATE: "模块模板"
+ FRONTMATTER: "Frontmatter"
+ FILENAME: "文件名"
+ PARENT_PAGE: "上级页面"
+ HOME_PAGE: "首页"
+ HOME_PAGE_HELP: "用作默认首页的页面"
+ DEFAULT_THEME: "默认主题"
+ DEFAULT_THEME_HELP: "设置 Grav 的默认主题(默认为 Antimatter)"
+ TIMEZONE: "时区"
+ TIMEZONE_HELP: "覆盖服务器默认时区"
+ SHORT_DATE_FORMAT: "短日期格式"
+ SHORT_DATE_FORMAT_HELP: "设定主题可使用的短日期格式"
+ LONG_DATE_FORMAT: "长日期格式"
+ LONG_DATE_FORMAT_HELP: "设定主题可使用的长日期格式"
+ DEFAULT_ORDERING: "默认排序"
+ DEFAULT_ORDERING_HELP: "除非例外,在列表中的页面将以此顺序呈现"
+ DEFAULT_ORDERING_DEFAULT: "默认值 - 基于文件夹名称"
+ DEFAULT_ORDERING_FOLDER: "文件夹 - 基于无前缀的文件夹名"
+ DEFAULT_ORDERING_TITLE: "标题 - 基于头部信息中的标题"
+ DEFAULT_ORDERING_DATE: "日期 - 基于头部信息中的日期"
+ DEFAULT_ORDER_DIRECTION: "默认排序方向"
+ DEFAULT_ORDER_DIRECTION_HELP: "列表中的页面方向"
+ DEFAULT_PAGE_COUNT: "默认页面计数"
+ DEFAULT_PAGE_COUNT_HELP: "默认的列表最大页面数"
+ DATE_BASED_PUBLISHING: "基于日期发布"
+ DATE_BASED_PUBLISHING_HELP: "根据日期自动发布或撤下文章"
+ EVENTS: "事件"
+ EVENTS_HELP: "启用或禁用特定事件。禁用可能导致插件崩溃。"
+ REDIRECT_DEFAULT_ROUTE: "重定向默认路由"
+ REDIRECT_DEFAULT_ROUTE_HELP: "自动重定向到页面默认路由"
+ LANGUAGES: "语言"
+ SUPPORTED: "已支持"
+ SUPPORTED_HELP: "以逗号分隔,两个字母的语言代号(如 'en,fr,de')"
+ TRANSLATIONS_ENABLED: "翻译已启用"
+ TRANSLATIONS_ENABLED_HELP: "在 Grav、插件和扩展中支持多语言"
+ TRANSLATIONS_FALLBACK: "多语言备选"
+ TRANSLATIONS_FALLBACK_HELP: "在当前语言不存在时回退到支持的语言"
+ ACTIVE_LANGUAGE_IN_SESSION: "在 Session 中设置语言"
+ ACTIVE_LANGUAGE_IN_SESSION_HELP: "在 Session 中保存当前语言信息"
+ HTTP_HEADERS: "HTTP 标头"
+ EXPIRES: "到期"
+ EXPIRES_HELP: "设置过期标头。值以秒为单位。"
+ CACHE_CONTROL: "HTTP 缓存控制"
+ CACHE_CONTROL_HELP: "设置为一个有效的缓存控制值,例如 `no-cache, no-store, must-revalidate`"
+ LAST_MODIFIED: "最后修改"
+ LAST_MODIFIED_HELP: "设置最后修改标头有助于优化代理和浏览器缓存"
+ ETAG: "ETag"
+ ETAG_HELP: "设置 ETag 标头来帮助识别已修改的页面"
+ VARY_ACCEPT_ENCODING: "接受编码"
+ VARY_ACCEPT_ENCODING_HELP: "设置 Vary: Accept Encoding 标头以有利于代理和 CDN 缓存"
+ MARKDOWN: "Markdown"
+ MARKDOWN_EXTRA: "Markdown extra"
+ MARKDOWN_EXTRA_HELP: "启用 Markdown Extra 的默认支持 - https://michelf.ca/projects/php-markdown/extra/"
+ MARKDOWN_EXTRA_ESCAPE_FENCES: "在 markdown extra 添加 HTML 元素"
+ MARKDOWN_EXTRA_ESCAPE_FENCES_HELP: "在 markdown extra 添加 HTML 元素"
+ AUTO_LINE_BREAKS: "自动换行"
+ AUTO_LINE_BREAKS_HELP: "在 Markdown 中启用自动换行"
+ AUTO_URL_LINKS: "自动 URL 链接"
+ AUTO_URL_LINKS_HELP: "启用自动转换 URL 为 HTML 超链接"
+ ESCAPE_MARKUP: "转义标记"
+ ESCAPE_MARKUP_HELP: "将标记转义为 HTML 实体"
+ CACHING_HELP: "全局 Grav 缓存开关"
+ CACHE_CHECK_METHOD: "缓存检查方法"
+ CACHE_CHECK_METHOD_HELP: "选择 Grav 用于检测页面是否有变动的方法"
+ CACHE_DRIVER: "缓存驱动"
+ CACHE_DRIVER_HELP: "选择 Grav 应该使用何种缓存驱动。“自动检测”会尝试为您找到最好的选择。"
+ CACHE_PREFIX: "缓存前缀"
+ CACHE_PREFIX_HELP: "属于 Grav key 一部分的标识符。除非您知道在做什么,否则不要修改。"
+ CACHE_PREFIX_PLACEHOLDER: "衍生自基础URL(输入随机字符串覆盖)"
+ CACHE_PURGE_JOB: "运行计划清除作业"
+ CACHE_PURGE_JOB_HELP: "开启定时程序功能,您可以使用此作业定期清除旧的Doctrine文件缓存文件夹"
+ CACHE_CLEAR_JOB: "运行计划清除任务"
+ CACHE_CLEAR_JOB_HELP: "使用计划程序, 您可以定期清除 Grav 的缓存"
+ CACHE_JOB_TYPE: "缓存作业类型"
+ CACHE_JOB_TYPE_HELP: "要么清除 'standard' 文件夹缓存,要么使用 'all' 文件夹"
+ CACHE_PURGE: "清除旧缓存"
+ LIFETIME: "生命周期"
+ LIFETIME_HELP: "以秒为单位设置缓存生命周期。0 为无限。"
+ GZIP_COMPRESSION: "Gzip 压缩"
+ GZIP_COMPRESSION_HELP: "为 Grav 页面启用 GZip 压缩以改善性能"
+ TWIG_TEMPLATING: "Twig 模板"
+ TWIG_CACHING: "Twig 缓存"
+ TWIG_CACHING_HELP: "控制 Twig 的缓存机制。设置为启用以获得最佳性能。"
+ TWIG_DEBUG: "Twig 调试"
+ TWIG_DEBUG_HELP: "允许不加载 Twig 调试器扩展"
+ DETECT_CHANGES: "检测更改"
+ DETECT_CHANGES_HELP: "如果 Twig 检测到任何 Twig 模板的改变,会自动重新编译 Twig 缓存。"
+ AUTOESCAPE_VARIABLES: "自动转义变量"
+ AUTOESCAPE_VARIABLES_HELP: "自动转义所有变量。这很可能会让您的网站崩溃"
+ ASSETS: "资源"
+ CSS_PIPELINE: "CSS 管道"
+ CSS_PIPELINE_HELP: "CSS 管道为多个 CSS 资源合并成的一个文件"
+ CSS_PIPELINE_INCLUDE_EXTERNALS: "在 CSS 管道中包含外部资源"
+ CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "外部的URLs 有时有相对文件引用不应该管道化"
+ CSS_PIPELINE_BEFORE_EXCLUDES: "先呈现 CSS 管道"
+ CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "在其他 CSS 参考未被包含时先呈现 CSS 管道"
+ CSS_MINIFY: "CSS 缩小"
+ CSS_MINIFY_HELP: "在管道中缩小 CSS"
+ CSS_MINIFY_WINDOWS_OVERRIDE: "CSS 缩小 Windows 重写"
+ CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "对Windows平台进行调整。根据 ThreadStackSize,默认情况下为 False"
+ CSS_REWRITE: "CSS 重写"
+ CSS_REWRITE_HELP: "管道化时重写任何 CSS 相对 URL"
+ JAVASCRIPT_PIPELINE: "JavaScript 管道化"
+ JAVASCRIPT_PIPELINE_HELP: "JavaScript管道化是统一多个JavaScript资源文件成为一个文件"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "包括外部在JavaScript管道化"
+ JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "外部 URL 有时有相对的文件引用,不应该被管道化"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "优先渲染 JavaScript 管道"
+ JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "在未被包含的其他 JavaScript 引用之前渲染 JavaScript管道"
+ JAVASCRIPT_MINIFY: "JavaScript 压缩"
+ JAVASCRIPT_MINIFY_HELP: "在管道化同时压缩 JavaScript"
+ ENABLED_TIMESTAMPS_ON_ASSETS: "在资源上启用时间戳"
+ ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "启用在资源上的时间戳"
+ COLLECTIONS: "集合"
+ ERROR_HANDLER: "错误处理"
+ DISPLAY_ERRORS: "显示错误"
+ DISPLAY_ERRORS_HELP: "显示全回溯风格错误页面"
+ LOG_ERRORS: "记录错误"
+ LOG_ERRORS_HELP: "记录错误到 /logs 文件夹"
+ LOG_HANDLER: "日志处理"
+ LOG_HANDLER_HELP: "日志输出位置"
+ SYSLOG_FACILITY: "Syslog 特性"
+ SYSLOG_FACILITY_HELP: "Syslog 输出特性"
+ DEBUGGER: "调试器"
+ DEBUGGER_HELP: "启用Grav调试器和其设置"
+ DEBUG_TWIG: "调试Twig"
+ DEBUG_TWIG_HELP: "启用调试的Twig模版"
+ SHUTDOWN_CLOSE_CONNECTION: "关机关闭连接"
+ SHUTDOWN_CLOSE_CONNECTION_HELP: "关闭连接之前调用 onShutdown()。false 为调试"
+ DEFAULT_IMAGE_QUALITY: "默认图像质量"
+ DEFAULT_IMAGE_QUALITY_HELP: "采样或缓存图像时使用的默认图像质量 (85%)"
+ CACHE_ALL: "缓存所有图片"
+ CACHE_ALL_HELP: "通过Grav的缓存系统运行的所有图像,即使他们没有媒体操作"
+ IMAGES_DEBUG: "图像调试水印"
+ IMAGES_DEBUG_HELP: "显示overlay图像标示图像的像素数深度,例如当运行retina时"
+ IMAGES_SEOFRIENDLY: "SEO加速图片名"
+ IMAGES_SEOFRIENDLY_HELP: "启用后, 首先显示图像名, 然后显示一个小哈希值, 以反映处理进度"
+ UPLOAD_LIMIT: "文件上传限制"
+ UPLOAD_LIMIT_HELP: "设置最大上传大小以位为单位(0 是无限的)"
+ ENABLE_MEDIA_TIMESTAMP: "在媒体上启用时间戳"
+ ENABLE_MEDIA_TIMESTAMP_HELP: "追加时间戳基于每个媒体项的最后修改日期"
+ SESSION: "Session"
+ SESSION_ENABLED_HELP: "在 Grav 内启用 Session 支持"
+ TIMEOUT: "超时"
+ TIMEOUT_HELP: "设置 Session 超时,以秒为单位"
+ SESSION_NAME_HELP: "用来形成 Session Cookie 的名称标识符"
+ SESSION_UNIQUENESS: "只能是字符串"
+ SESSION_UNIQUENESS_HELP: "Grav 根路径的 MD5 哈希值,如 `GRAV_ROOT` (默认值) 或基于随机 `security. salt` 字符串的哈希。"
+ ABSOLUTE_URLS: "绝对网址"
+ ABSOLUTE_URLS_HELP: "绝对或相对网址为 'base_url'"
+ PARAMETER_SEPARATOR: "参数分隔符"
+ PARAMETER_SEPARATOR_HELP: "传递参数的分隔符可以在 Windows上的Apache更改"
+ TASK_COMPLETED: "任务已完成"
+ EVERYTHING_UP_TO_DATE: "所有内容都已更新到最新"
+ UPDATES_ARE_AVAILABLE: "有可用的更新"
+ IS_AVAILABLE_FOR_UPDATE: "可进行更新"
+ IS_NOW_AVAILABLE: "现在可用"
+ CURRENT: "当前"
+ UPDATE_GRAV_NOW: "立即更新 Grav"
+ GRAV_SYMBOLICALLY_LINKED: "Grav 由软连接连接。更新将不可用"
+ UPDATING_PLEASE_WAIT: "更新……请稍候,正在下载"
+ OF_THIS: "这"
+ OF_YOUR: "您的"
+ HAVE_AN_UPDATE_AVAILABLE: "有一个可用的更新"
+ SAVE_AS: "另存为"
+ MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "是否确实要删除此页面及其所有子页面?如果页面被翻译成其他语言,这些译本将被保留而需要您分别单独删除。否则页面文件夹及其子页均会被删除。此操作无法撤消。"
+ AND: "和"
+ UPDATE_AVAILABLE: "有可用的更新"
+ METADATA_KEY: "关键字 (例如 'Keywords')"
+ METADATA_VALUE: "值 (例如“博客”、“Grav”)"
+ USERNAME_HELP: "用户名长度应为 3 到 16 个字符,允许使用小写字母、 数字、 下划线和连字符。不允许使用大写字母、 空格和特殊字符。"
+ FULLY_UPDATED: "已完全更新"
+ SAVE_LOCATION: "保存位置"
+ PAGE_FILE: "页面模板"
+ PAGE_FILE_HELP: "页面模版文件名称,并缺省为此页面的显示模版"
+ NO_USER_ACCOUNTS: "没有用户帐户,请先创建一个......"
+ NO_USER_EXISTS: "此帐户不存在本地用户, 无法保存..."
+ REDIRECT_TRAILING_SLASH: "重定向结尾的斜杠"
+ REDIRECT_TRAILING_SLASH_HELP: "执行 301 重定向而不是透明处理有结尾斜杠的 URI。"
+ DEFAULT_DATE_FORMAT: "页面日期格式"
+ DEFAULT_DATE_FORMAT_HELP: "Grav 使用的页面日期格式。默认情况下,Grav 会尝试猜测您的日期格式,然而您可以用 PHP 的日期语法指定一个格式(例如 Y-m-d H:i)"
+ DEFAULT_DATE_FORMAT_PLACEHOLDER: "自动猜测"
+ IGNORE_FILES: "忽略文件"
+ IGNORE_FILES_HELP: "处理页面时的需要忽略的特定文件"
+ IGNORE_FOLDERS: "忽略文件夹"
+ IGNORE_FOLDERS_HELP: "处理页面时的需要忽略的特定文件夹"
+ HIDE_EMPTY_FOLDERS: "隐藏空文件夹"
+ HIDE_EMPTY_FOLDERS_HELP: "如果目录中没有 .md 文件,将在导航中隐藏,就像“不可路由”一样"
+ HTTP_ACCEPT_LANGUAGE: "根据浏览器设置语言"
+ HTTP_ACCEPT_LANGUAGE_HELP: "您可以选择尝试基于浏览器中 `http_accept_language` 标头设置语言"
+ OVERRIDE_LOCALE: "覆盖区域设置"
+ OVERRIDE_LOCALE_HELP: "基于当前语言覆盖 PHP 区域设置"
+ REDIRECT: "页面重定向"
+ REDIRECT_HELP: "输入一个供这个页面重定向的页面路径或外部 URL,例如“/some/route”或“http://somesite.com”"
+ PLUGIN_STATUS: "插件状态"
+ INCLUDE_DEFAULT_LANG: "包括默认语言"
+ INCLUDE_DEFAULT_LANG_HELP: "这会将默认语言添加到所有 URL 的头部。例如 `/en/blog/my-post`"
+ PAGES_FALLBACK_ONLY: "仅备选页面"
+ PAGES_FALLBACK_ONLY_HELP: "仅 “备选” 才能通过支持的语言找到页面内容,默认行为就是显示任何在活动语言缺失时找到的语言"
+ ALLOW_URL_TAXONOMY_FILTERS: "URL 分类过滤器"
+ ALLOW_URL_TAXONOMY_FILTERS_HELP: "允许您通过 `/taxonomy:value` 筛选的基于页面的集合"
+ REDIRECT_DEFAULT_CODE: "默认重定向状态码"
+ REDIRECT_DEFAULT_CODE_HELP: "重定向的 HTTP 状态代码"
+ IGNORE_HIDDEN: "忽略隐藏"
+ IGNORE_HIDDEN_HELP: "忽略名称以一个点开头的所有文件和文件夹"
+ WRAPPED_SITE: "嵌入的网站"
+ WRAPPED_SITE_HELP: "为了让主题和插件知道 Grav 是否被另一个平台嵌入"
+ FALLBACK_TYPES: "允许的备选类型"
+ FALLBACK_TYPES_HELP: "通过页面路径访问允许发现的文件类型。默认为支持的媒体类型。"
+ INLINE_TYPES: "内联备选类型"
+ INLINE_TYPES_HELP: "应内联显示而不是下载的文件类型列表"
+ APPEND_URL_EXT: "附加 URL 扩展名"
+ APPEND_URL_EXT_HELP: "将添加定制后缀到页面的URL. 注意,这意味着Grav需要查找 `..twig` 的模板"
+ PAGE_MODES: "页面模式"
+ PAGE_TYPES: "页面类型"
+ PAGE_TYPES_HELP: "确定 Grav 支持的页面类型. 如果请求不明确确定, 顺序返回到哪个类型"
+ ACCESS_LEVELS: "访问级别"
+ GROUPS: "组"
+ GROUPS_HELP: "列表中的用户组的一部分"
+ ADMIN_ACCESS: "管理访问权限"
+ SITE_ACCESS: "网站访问权限"
+ INVALID_SECURITY_TOKEN: "无效的安全令牌"
+ ACTIVATE: "激活"
+ TWIG_UMASK_FIX: "Umask 修复"
+ TWIG_UMASK_FIX_HELP: "缺省Twig创建缓存文件以 0755, 修改开关到 0775"
+ CACHE_PERMS: "缓存的权限"
+ CACHE_PERMS_HELP: "默认缓存文件夹权限。取决于安装设置,通常为 0755 或 0775"
+ REMOVE_SUCCESSFUL: "删除成功"
+ REMOVE_FAILED: "删除失败"
+ HIDE_HOME_IN_URLS: "在 URL 中隐藏主页路径"
+ HIDE_HOME_IN_URLS_HELP: "举例:若 /blog 被设置为主页,那当开打这个选项时,/blog/page 将可以通过 /page 访问(即 /blog 这段路径将被隐藏)"
+ TWIG_FIRST: "优先处理Twig"
+ TWIG_FIRST_HELP: "如果您启用 Twig 页面处理,您可以在 markdown 之前或之后配置 Twig 处理"
+ SESSION_SECURE: "安全"
+ SESSION_SECURE_HELP: "如果为 true,则指示此 cookie 的通信必须在加密传输。警告︰ 启用此功能只有在完全在 HTTPS 运行的网站上"
+ SESSION_HTTPONLY: "仅限 HTTP"
+ SESSION_HTTPONLY_HELP: "如果 true,表示 cookies 只能通过 HTTP 使用,并且不允许 JavaScript 修改"
+ REVERSE_PROXY: "反向代理"
+ REVERSE_PROXY_HELP: "如果你在反向代理之后,并且你的URL包含不正确的端口,启用此项"
+ INVALID_FRONTMATTER_COULD_NOT_SAVE: "无效 frontmatter,不能保存"
+ ADD_FOLDER: "添加文件夹"
+ PROXY_URL: "代理 URL"
+ PROXY_URL_HELP: "输入代理服务器主机或 IP 和端口"
+ NOTHING_TO_SAVE: "没有要保存的数据"
+ FILE_ERROR_ADD: "试图添加文件时出错"
+ FILE_ERROR_UPLOAD: "尝试上载文件时出错"
+ FILE_UNSUPPORTED: "不支持的文件类型"
+ ADD_ITEM: "添加项目"
+ FILE_TOO_LARGE: "文件太大,无法上传,最大允许 %s 根据您的 PHP 设置 。增加你 'post_max_size' PHP 设置"
+ INSTALLING: "正在安装"
+ LOADING: "正在加载…"
+ DEPENDENCIES_NOT_MET_MESSAGE: "需要先满足以下依赖:"
+ ERROR_INSTALLING_PACKAGES: "安装程序包时出错"
+ INSTALLING_DEPENDENCIES: "安装依赖项..."
+ INSTALLING_PACKAGES: "安装程序包..."
+ PACKAGES_SUCCESSFULLY_INSTALLED: "成功安装的程序包。"
+ READY_TO_INSTALL_PACKAGES: "准备安装程序包"
+ PACKAGES_NOT_INSTALLED: "软件包未安装"
+ PACKAGES_NEED_UPDATE: "软件包已经安装,但太旧了"
+ PACKAGES_SUGGESTED_UPDATE: "软件包已安装,版本没有问题。但将会更新以保持更新"
+ REMOVE_THE: "删除 %s"
+ CONFIRM_REMOVAL: "是否确实要删除此 %s?"
+ REMOVED_SUCCESSFULLY: "%s 已成功删除"
+ ERROR_REMOVING_THE: "删除 %s 时出错"
+ ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "%s 所需以下的依存关系,不需要其他已安装的软件包。如果您不使用它们,您可以直接从这里删除它们。"
+ READY_TO_UPDATE_PACKAGES: "准备更新程序包"
+ ERROR_UPDATING_PACKAGES: "更新数据包时出现错误"
+ UPDATING_PACKAGES: "正在更新软件包"
+ PACKAGES_SUCCESSFULLY_UPDATED: "软件包成功更新。"
+ UPDATING: "更新中"
+ GPM_RELEASES: "GPM 发布"
+ GPM_RELEASES_HELP: "选择'Testing'来安装Beta版或测试版本"
+ GPM_METHOD: "远程读取方法"
+ GPM_METHOD_HELP: "当设置为自动,Grav将确定 fopen 可用和使用它,否则回退到cURL。要强制使用一个或其他切换设置。"
+ GPM_VERIFY_PEER: "远程验证同件 (SSL)"
+ GPM_VERIFY_PEER_HELP: "一些提供者似乎验证失败 getgrav.org SSL 证书,引起 GPM 不运行。如果这是您相符的情况,关闭此设置可能会有帮助"
+ AUTO: "自动"
+ FOPEN: "fopen"
+ CURL: "cURL"
+ STABLE: "稳定"
+ TESTING: "测试中"
+ FRONTMATTER_PROCESS_TWIG: "处理 frontmatter Twig"
+ FRONTMATTER_PROCESS_TWIG_HELP: "当启用时你可以在页中frontmatter使用Twig设定变量"
+ FRONTMATTER_IGNORE_FIELDS: "忽略frontmatter域"
+ FRONTMATTER_IGNORE_FIELDS_HELP: "某些frontmatter域可能包含Twig,但不应被处理,如 '表单'"
+ PACKAGE_X_INSTALLED_SUCCESSFULLY: "成功安装包 %s"
+ ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "父设置排序,排序失效"
+ ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "不可见的页面,排序失效"
+ ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "通过管理员排序是不受支持的,因为多过200 多个兄弟节点"
+ ORDERING_DISABLED_BECAUSE_PAGE_NO_PREFIX: "此页的页面排序被禁用,因为 文件夹数字前缀 不启用"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "注: 您不能添加媒体文件,直到您保存网页。只需单击保存在上面"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "注意: 网页必须保存之前你可以将文件上载到它。"
+ DROP_FILES_HERE_TO_UPLOAD: "将要上传的文件拖放到这里或直接单击此区域上传 "
+ INSERT: "插入"
+ UNDO: "取消"
+ REDO: "重做"
+ HEADERS: "头信息"
+ BOLD: "粗体"
+ ITALIC: "斜体"
+ STRIKETHROUGH: "删除线"
+ SUMMARY_DELIMITER: "摘要分隔符"
+ LINK: "链接"
+ IMAGE: "图片"
+ BLOCKQUOTE: "块引用"
+ UNORDERED_LIST: "无序列表"
+ ORDERED_LIST: "有序列表"
+ EDITOR: "编辑器"
+ PREVIEW: "预览"
+ FULLSCREEN: "全屏"
+ MODULAR: "模块化"
+ MODULAR_SETUP: "模块化设置"
+ NON_ROUTABLE: "非路由"
+ NON_MODULAR: "非模块化"
+ NON_VISIBLE: "非可见"
+ NON_PUBLISHED: "非发布"
+ CHARACTERS: "字符"
+ PUBLISHING: "发布"
+ NOTIFICATIONS: "通知"
+ MEDIA_TYPES: "媒体类型"
+ IMAGE_OPTIONS: "图像选项"
+ MIME_TYPE: "Mime 类型"
+ THUMB: "缩略图"
+ TYPE: "类型"
+ FILE_EXTENSION: "文件扩展名"
+ LEGEND: "页面图例"
+ MEMCACHE_SERVER: "Memcache 服务器"
+ MEMCACHE_SERVER_HELP: "Memcache 服务器地址"
+ MEMCACHE_PORT: "Memcache 端口"
+ MEMCACHE_PORT_HELP: "Memcache 服务器端口"
+ MEMCACHED_SERVER: "Memcached 服务器"
+ MEMCACHED_SERVER_HELP: "Memcached 服务器地址"
+ MEMCACHED_PORT: "Memcached 端口"
+ MEMCACHED_PORT_HELP: "Memcached 服务器端口"
+ REDIS_SERVER: "Redis 服务器"
+ REDIS_SERVER_HELP: "Redis 服务器地址"
+ REDIS_PORT: "Redis 端口"
+ REDIS_PORT_HELP: "Redis 服务器端口"
+ REDIS_PASSWORD: "Redis 密码/密文"
+ ALL: "所有"
+ FROM: "从"
+ TO: "到"
+ RELEASE_DATE: "发行日期"
+ SORT_BY: "排序方式"
+ RESOURCE_FILTER: "筛选器..."
+ FORCE_SSL: "强制使用 SSL"
+ FORCE_SSL_HELP: "全局强制 SSL,如果通过 HTTP访问站点时启用, Grav 将重定向到 HTTPS 页面"
+ NEWS_FEED: "新闻源"
+ EXTERNAL_URL: "外部 URL"
+ SESSION_PATH: "Session 路径"
+ SESSION_PATH_HELP: "只有在您选择自定义的基 URL 使用 (你重写网站域名 / 子文件夹)"
+ CUSTOM_BASE_URL: "自定义的基 URL"
+ CUSTOM_BASE_URL_HELP: "如果你想要重写网站域或使用的Grav的不同子文件夹,使用。示例: http://localhost"
+ FILEUPLOAD_PREVENT_SELF: '不能使用"%s"在页面之外。'
+ FILEUPLOAD_UNABLE_TO_UPLOAD: '无法上载文件 %s: %s'
+ FILEUPLOAD_UNABLE_TO_MOVE: '无法将移到"%s"的文件 %s'
+ DROPZONE_CANCEL_UPLOAD: '取消上传'
+ DROPZONE_CANCEL_UPLOAD_CONFIRMATION: '你确定你想要取消这个上传?'
+ DROPZONE_DEFAULT_MESSAGE: '将要上传的文件拖放到这里或直接单击此区域上传 '
+ DROPZONE_FALLBACK_MESSAGE: '您的浏览器并不支持拖放文件上传。'
+ DROPZONE_FALLBACK_TEXT: '请使用下面的回退表单上传你的文件'
+ DROPZONE_FILE_TOO_BIG: '文件太大 ({{filesize}}MiB)。最大文件大小: {{maxFilesize}}MiB。'
+ DROPZONE_INVALID_FILE_TYPE: "你不能上传此类型的文件。"
+ DROPZONE_MAX_FILES_EXCEEDED: "您可以上传任何更多的文件。"
+ DROPZONE_REMOVE_FILE: "删除文件"
+ DROPZONE_RESPONSE_ERROR: "服务器响应 {{statusCode}} 代码。"
+ PREMIUM_PRODUCT: "高级"
+ ADMIN_CACHING: "启用缓存管理"
+ ADMIN_CACHING_HELP: "管理面板中的缓存可以独立于前端站点进行控制"
+ DESTINATION_NOT_SPECIFIED: "未指定目标"
+ UPLOAD_ERR_NO_TMP_DIR: "缺少临时文件夹"
+ SESSION_SPLIT: "Session 拆分"
+ SESSION_SPLIT_HELP: "在网站和其他插件程序(如管理面板)之间独立拆分 Session"
+ ERROR_FULL_BACKTRACE: "完整的回溯错误"
+ ERROR_SIMPLE: "简单错误"
+ ERROR_SYSTEM: "系统错误"
+ IMAGES_AUTO_FIX_ORIENTATION: "自动修复方向"
+ IMAGES_AUTO_FIX_ORIENTATION_HELP: "基于 Exif 数据自动修复图像方向"
+ REDIS_SOCKET: "Redis 套接字"
+ REDIS_SOCKET_HELP: "Redis 套接字"
+ NOT_SET: "未设置"
+ PERMISSIONS: "权限"
+ NEVER_CACHE_TWIG: "从不缓存Twig"
+ NEVER_CACHE_TWIG_HELP: "只缓存内容并每次处理Twig。将忽略 twig_first 设置。"
+ ALLOW_WEBSERVER_GZIP: "允许 Web 服务器 Gzip"
+ ALLOW_WEBSERVER_GZIP_HELP: "默认是关闭的。当启用时,web 服务器配置 Gzip/Deflate 压缩会工作,但 http 连接不会造成页面加载速度较慢的 onShutDown() 事件之前被关闭"
+ OFFLINE_WARNING: "不能建立到 GPM 的连接"
+ CLEAR_IMAGES_BY_DEFAULT: "在默认情况下清除图像缓存"
+ CLEAR_IMAGES_BY_DEFAULT_HELP: "默认情况下,所有的缓存清理会清除掉已经处理理过的图像,也能将其禁用"
+ CLI_COMPATIBILITY: "CLI 兼容性"
+ CLI_COMPATIBILITY_HELP: "确保使用唯一的非易失性缓存驱动程序(文件,Redis,memcache,等等)。"
+ REINSTALL_PLUGIN: "重新安装插件"
+ REINSTALL_THEME: "重新安装主题"
+ REINSTALL_THE: "重新安装 %s"
+ CONFIRM_REINSTALL: "你确定你想要重新安装此 %s?"
+ REINSTALLED_SUCCESSFULLY: "%s 成功地重新安装"
+ ERROR_REINSTALLING_THE: "重新安装 %s 的错误"
+ PACKAGE_X_REINSTALLED_SUCCESSFULLY: "软件包 %s 成功重新安装"
+ REINSTALLATION_FAILED: "重新安装失败"
+ WARNING_REINSTALL_NOT_LATEST_RELEASE: "已安装的版本不是最新的版本。通过单击继续,您会删除当前的版本并安装最新的可用版本"
+ TOOLS: "工具"
+ DIRECT_INSTALL: "直接安装"
+ NO_PACKAGE_NAME: "未指定的包名称"
+ PACKAGE_EXTRACTION_FAILED: "包提取失败"
+ NOT_VALID_GRAV_PACKAGE: "不是有效的Grav包"
+ NAME_COULD_NOT_BE_DETERMINED: "名称不能确定"
+ CANNOT_OVERWRITE_SYMLINKS: "不能覆盖符号链接"
+ ZIP_PACKAGE_NOT_FOUND: "找不到 ZIP 包"
+ GPM_OFFICIAL_ONLY: "仅官方 GPM"
+ GPM_OFFICIAL_ONLY_HELP: "仅允许直接从官方 GPM 仓库安装。"
+ NO_CHILD_TYPE: "此 rawroute 没有子类型"
+ SORTABLE_PAGES: "可排序的页面:"
+ UNSORTABLE_PAGES: "页面不可排序"
+ ADMIN_SPECIFIC_OVERRIDES: "管理特定优先规则"
+ ADMIN_CHILDREN_DISPLAY_ORDER: "子页面的显示顺序"
+ ADMIN_CHILDREN_DISPLAY_ORDER_HELP: "此页的子页面在页面视图中的显示顺序"
+ PWD_PLACEHOLDER: "复杂的字符串至少 8 字符长"
+ PWD_REGEX: "密码的正则表达式"
+ PWD_REGEX_HELP: "默认情况下: 密码必须包含至少一个数字和一个大写和小写字母和至少 8 个或更多字符"
+ USERNAME_PLACEHOLDER: "限小写字符, 如‘admin’"
+ USERNAME_REGEX: "用户名正则表达式"
+ USERNAME_REGEX_HELP: "默认情况下: 只有小写字符、 数字、 短划线和下划线。3-16 个字符"
+ CONTENT_PADDING: "内容的内边距"
+ CONTENT_PADDING_HELP: "启用/禁用在内容区域周围的内边距,以提供更多的空间"
+ ENABLE_AUTO_METADATA: "从 Exif 自动获取元数据"
+ ENABLE_AUTO_METADATA_HELP: "通过 Exif 信息自动生成图像元数据文件"
+ 2FA_TITLE: "双因素身份验证"
+ 2FA_INSTRUCTIONS: "#####双因素身份验证\n您已在此账户启用**双因素身份验证**。建议您使用**双因素身份验证** 应用程序,输入当前**6位代码** 以便完成登录。"
+ 2FA_REGEN_HINT: "重设密码将重新登陆"
+ 2FA_LABEL: "管理访问权限"
+ 2FA_FAILED: "无效的双因素身份验证代码, 请重试…"
+ 2FA_ENABLED: "启用双因素身份验证"
+ 2FA_CODE_INPUT: "000000"
+ 2FA_SECRET: "双因素身份验证口令"
+ 2FA_SECRET_HELP: "将此QR码扫描到您的[Authenticator App](https://learn.getgrav.org/admin-panel/2fa#apps)。此外,将秘密备份在安全位置是个好主意,以防需要重新安装应用程序。查看[ Grav文档](https://learn.getgrav.org/admin-panel/2fa)以获取更多信息 "
+ 2FA_REGENERATE: "重新生成"
+ FORCE_LOWERCASE_URLS: "强制小写 url"
+ FORCE_LOWERCASE_URLS_HELP: "默认情况下为小写,设置为false时可以使用大写字母"
+ INTL_ENABLED: "Intl模块集成"
+ INTL_ENABLED_HELP: "使用intl php模块并以UTF-8作为编码基础"
+ VIEW_SITE_TIP: "查看站点"
+ TOOLS_DIRECT_INSTALL_TITLE: "直接安装 Grav 软件包"
+ TOOLS_DIRECT_INSTALL_UPLOAD_TITLE: "通过直接ZIP上传安装软件包"
+ TOOLS_DIRECT_INSTALL_UPLOAD_DESC: "您可以通过此方法轻松安装有效的 Grav 主题 ,插件 ,甚至是 Grav 更新 Zip 包。这个软件包不需要通过 GPM 注册,并允许您轻松回滚到之前的版本或安装进行测试。"
+ TOOLS_DIRECT_INSTALL_URL_TITLE: "通过远程链接来安装软件包"
+ TOOLS_DIRECT_INSTALL_URL_DESC: "或者,你也可以参考zip压缩包的完整路径 来远程安装"
+ TOOLS_DIRECT_INSTALL_UPLOAD_BUTTON: "上传并安装"
+ ROUTE_OVERRIDES: "路由覆盖"
+ ROUTE_DEFAULT: "默认路由"
+ ROUTE_CANONICAL: "Canonical 路由"
+ ROUTE_ALIASES: "路由别名"
+ OPEN_NEW_TAB: "在新标签页中打开"
+ PAGEMEDIA_RESIZER: "> 以下设置适用于通过页面媒体上传的图像。如果超过输入中设置的限制, 调整宽度/高度将自动缩小大小, 并按比例调整图像的大小。分辨率最小值和最大值定义上传图像的大小范围。将字段设置为 0, 以防止任何操作。"
+ SESSION_INITIALIZE: "正在初始化 Session"
+ SESSION_INITIALIZE_HELP: "允许 Grav 开启 Session。此功能是使任何用户交互正常工作所必需的, 例如登录、窗体等。插件“管理面板”不受此设置的影响。"
+ STRICT_YAML_COMPAT: "YAML 兼容"
+ STRICT_YAML_COMPAT_HELP: "如果原生的或 3.4 版本解析器执行失败,退回 Symfonty 2.4 YAML 解析器"
+ STRICT_TWIG_COMPAT: "Twig 兼容"
+ STRICT_TWIG_COMPAT_HELP: "启用已弃用的 Twig 自动转义设置。 禁用时, 需要 `|raw` 过滤器才能输出 HTML,因为 Twig 将自动转义输出"
+ SCHEDULER: "调度器"
+ SCHEDULER_INSTALL_INSTRUCTIONS: "安装说明"
+ SCHEDULER_INSTALLED_READY: "已安装并准备就绪"
+ SCHEDULER_CRON_NA: "Cron 不可用"
+ SCHEDULER_NOT_ENABLED: "未启用"
+ SCHEDULER_SETUP: "调度器设置"
+ SCHEDULER_INSTRUCTIONS: "Grav 调度器 允许您创建和计划自定义作业。 它还为 Grav 插件提供了一种以编程方式集成和动态添加定期运行的作业的方法。"
+ SCHEDULER_POST_INSTRUCTIONS: "要使用调度功能,你必须将 Grav Scheduler 添加到系统 crontab 文件中。在终端中运行以上命令可以自动添加。保存后,请刷新本页来查看状态是否就绪。"
+ SCHEDULER_JOBS: "自定义调度任务"
+ SCHEDULER_STATUS: "调度状态"
+ SCHEDULER_RUNAT: "运行于"
+ SCHEDULER_RUNAT_HELP: "Cron 格式化的 \"at\" 语法"
+ SCHEDULER_OUTPUT: "输出文件"
+ SCHEDULER_OUTPUT_HELP: "输出文件的路径名 (Grav 安装的根目录)"
+ SCHEDULER_OUTPUT_TYPE: "输出类型"
+ SCHEDULER_OUTPUT_TYPE_HELP: "每次运行时追加到同一文件, 或在每次运行时覆盖该文件"
+ SCHEDULER_EMAIL: "电子邮件"
+ SCHEDULER_EMAIL_HELP: "电子邮件发送到输出到。注: 需要设置输出文件"
+ SECURITY: "安全"
+ XSS_SECURITY: "内容 XSS 安全性"
+ XSS_WHITELIST_PERMISSIONS: "XSS 白名单权限"
+ XSS_WHITELIST_PERMISSIONS_HELP: "有以下权限的用户在保存内容时将跳过 XSS 规则"
+ XSS_ON_EVENTS: "事件触发时过滤"
+ XSS_INVALID_PROTOCOLS: "过滤非法的协议"
+ XSS_INVALID_PROTOCOLS_LIST: "无效的协议列表"
+ XSS_MOZ_BINDINGS: "过滤 Moz bindings"
+ XSS_HTML_INLINE_STYLES: "过滤 HTML 内联样式"
+ XSS_DANGEROUS_TAGS: "过滤危险的 HTML 标签"
+ XSS_DANGEROUS_TAGS_LIST: "危险的 HTML 标签列表"
+ XSS_ONSAVE_ISSUE: "保存失败:检测到 XSS 问题..."
+ XSS_ISSUE: "警告: 在 %s 发现了潜在的 XSS 问题"
+ UPLOADS_SECURITY: "上传安全性"
+ UPLOADS_DANGEROUS_EXTENSIONS: "危险的扩展名"
+ UPLOADS_DANGEROUS_EXTENSIONS_HELP: "禁止上传这些扩展名,忽略接受的 MIME 类型"
+ REPORTS: "报告"
+ LOGS: "日志"
+ LOG_VIEWER_FILES: "日志查看器文件"
+ LOG_VIEWER_FILES_HELP: "在 \"工具-日志\" 中将能够查看 /logs/目录中的文件。例如 'grav' = /logs/grav.log"
+ BACKUPS_STORAGE_PURGE_TRIGGER: "备份存储清除触发器"
+ BACKUPS_MAX_COUNT: "最大备份数量"
+ BACKUPS_MAX_COUNT_HELP: "0 = 无限"
+ BACKUPS_MAX_SPACE: "最大备份空间"
+ BACKUPS_MAX_RETENTION_TIME: "保留时间最大值"
+ BACKUPS_MAX_RETENTION_TIME_APPEND: "单位: 天"
+ BACKUPS_PROFILE_NAME: "备份名称"
+ BACKUPS_PROFILE_ROOT_FOLDER: "根文件夹"
+ BACKUPS_PROFILE_ROOT_FOLDER_HELP: "可以是绝对路径或流"
+ BACKUPS_PROFILE_EXCLUDE_PATHS: "排除路径"
+ BACKUPS_PROFILE_EXCLUDE_PATHS_HELP: "要排除的绝对路径, 每行一个"
+ BACKUPS_PROFILE_EXCLUDE_FILES: "排除文件"
+ BACKUPS_PROFILE_EXCLUDE_FILES_HELP: "要排除的特定文件或文件夹, 每行一个"
+ BACKUPS_PROFILE_SCHEDULE: "启用计划任务"
+ BACKUPS_PROFILE_SCHEDULE_AT: "运行计划的任务"
+ COMMAND: "命令"
+ EXTRA_ARGUMENTS: "额外参数:"
diff --git a/user/plugins/admin/pages/admin/ajax.md b/user/plugins/admin/pages/admin/ajax.md
new file mode 100644
index 0000000..4cf2493
--- /dev/null
+++ b/user/plugins/admin/pages/admin/ajax.md
@@ -0,0 +1,8 @@
+---
+title: Generic Ajax
+template: default
+expires: 0
+
+access:
+ admin.login: true
+---
diff --git a/user/plugins/admin/pages/admin/backup.md b/user/plugins/admin/pages/admin/backup.md
new file mode 100644
index 0000000..edc07d4
--- /dev/null
+++ b/user/plugins/admin/pages/admin/backup.md
@@ -0,0 +1,9 @@
+---
+title: Backup
+template: default
+expires: 0
+
+access:
+ admin.maintenance: true
+ admin.super: true
+---
diff --git a/user/plugins/admin/pages/admin/cache.md b/user/plugins/admin/pages/admin/cache.md
new file mode 100644
index 0000000..77a3c05
--- /dev/null
+++ b/user/plugins/admin/pages/admin/cache.md
@@ -0,0 +1,10 @@
+---
+title: Cache
+template: default
+expires: 0
+
+access:
+ admin.cache: true
+ admin.super: true
+ admin.maintenance: true
+---
diff --git a/user/plugins/admin/pages/admin/config.md b/user/plugins/admin/pages/admin/config.md
new file mode 100644
index 0000000..ad809c5
--- /dev/null
+++ b/user/plugins/admin/pages/admin/config.md
@@ -0,0 +1,8 @@
+---
+title: Config
+expires: 0
+
+access:
+ admin.configuration: true
+ admin.super: true
+---
diff --git a/user/plugins/admin/pages/admin/dashboard.md b/user/plugins/admin/pages/admin/dashboard.md
new file mode 100644
index 0000000..a41ff97
--- /dev/null
+++ b/user/plugins/admin/pages/admin/dashboard.md
@@ -0,0 +1,8 @@
+---
+title: Dashboard
+expires: 0
+
+access:
+ admin.login: true
+ admin.super: true
+---
diff --git a/user/plugins/admin/pages/admin/denied.md b/user/plugins/admin/pages/admin/denied.md
new file mode 100644
index 0000000..a5713e9
--- /dev/null
+++ b/user/plugins/admin/pages/admin/denied.md
@@ -0,0 +1,5 @@
+---
+title: Access Denied
+expires: 0
+---
+
diff --git a/user/plugins/admin/pages/admin/error.md b/user/plugins/admin/pages/admin/error.md
new file mode 100644
index 0000000..bc6407c
--- /dev/null
+++ b/user/plugins/admin/pages/admin/error.md
@@ -0,0 +1,10 @@
+---
+title: Error
+robots: noindex,nofollow
+template: error
+routable: false
+http_response_code: 404
+expires: 0
+---
+
+Woops! Looks like this page doesn't exist.
diff --git a/user/plugins/admin/pages/admin/feed.md b/user/plugins/admin/pages/admin/feed.md
new file mode 100644
index 0000000..b90cad1
--- /dev/null
+++ b/user/plugins/admin/pages/admin/feed.md
@@ -0,0 +1,8 @@
+---
+title: News Feed
+template: ajax
+expires: 0
+
+access:
+ admin.login: true
+---
diff --git a/user/plugins/admin/pages/admin/forgot.md b/user/plugins/admin/pages/admin/forgot.md
new file mode 100644
index 0000000..59e6fa6
--- /dev/null
+++ b/user/plugins/admin/pages/admin/forgot.md
@@ -0,0 +1,13 @@
+---
+title: Forgot password
+expires: 0
+
+form:
+ fields:
+ - name: username
+ type: text
+ placeholder: PLUGIN_ADMIN.USERNAME
+ autofocus: true
+ validate:
+ required: true
+---
diff --git a/user/plugins/admin/pages/admin/info.md b/user/plugins/admin/pages/admin/info.md
new file mode 100644
index 0000000..2733689
--- /dev/null
+++ b/user/plugins/admin/pages/admin/info.md
@@ -0,0 +1,9 @@
+---
+title: PHP Info
+template: config
+expires: 0
+
+access:
+ admin.settings: true
+ admin.super: true
+---
diff --git a/user/plugins/admin/pages/admin/installer.md b/user/plugins/admin/pages/admin/installer.md
new file mode 100644
index 0000000..f809cda
--- /dev/null
+++ b/user/plugins/admin/pages/admin/installer.md
@@ -0,0 +1,8 @@
+---
+title: Installer
+expires: 0
+
+access:
+ admin.install: true
+ admin.super: true
+---
diff --git a/user/plugins/admin/pages/admin/login.md b/user/plugins/admin/pages/admin/login.md
new file mode 100644
index 0000000..f80fd0c
--- /dev/null
+++ b/user/plugins/admin/pages/admin/login.md
@@ -0,0 +1,38 @@
+---
+title: Admin Login
+expires: 0
+
+forms:
+ login:
+ action:
+ method: post
+
+ fields:
+ username:
+ type: text
+ placeholder: PLUGIN_ADMIN.USERNAME_EMAIL
+ autofocus: true
+ validate:
+ required: true
+
+ password:
+ type: password
+ placeholder: PLUGIN_ADMIN.PASSWORD
+ validate:
+ required: true
+
+ login-twofa:
+ action:
+ method: post
+
+ fields:
+ 2fa_instructions:
+ type: display
+ markdown: true
+ content: PLUGIN_ADMIN.2FA_INSTRUCTIONS
+ 2fa_code:
+ type: text
+ id: twofa-code
+ autofocus: true
+ placeholder: PLUGIN_ADMIN.2FA_CODE_INPUT
+---
diff --git a/user/plugins/admin/pages/admin/media.md b/user/plugins/admin/pages/admin/media.md
new file mode 100644
index 0000000..f62e554
--- /dev/null
+++ b/user/plugins/admin/pages/admin/media.md
@@ -0,0 +1,9 @@
+---
+title: Media
+template: default
+expires: 0
+
+access:
+ admin.pages: true
+ admin.super: true
+---
diff --git a/user/plugins/admin/pages/admin/notifications.md b/user/plugins/admin/pages/admin/notifications.md
new file mode 100644
index 0000000..2033df6
--- /dev/null
+++ b/user/plugins/admin/pages/admin/notifications.md
@@ -0,0 +1,8 @@
+---
+title: Notifications
+template: default
+expires: 0
+
+access:
+ admin.login: true
+---
diff --git a/user/plugins/admin/pages/admin/pages-filter.md b/user/plugins/admin/pages/admin/pages-filter.md
new file mode 100644
index 0000000..0aac446
--- /dev/null
+++ b/user/plugins/admin/pages/admin/pages-filter.md
@@ -0,0 +1,9 @@
+---
+title: Pages Filter
+template: default
+expires: 0
+
+access:
+ admin.pages: true
+ admin.super: true
+---
diff --git a/user/plugins/admin/pages/admin/pages.md b/user/plugins/admin/pages/admin/pages.md
new file mode 100644
index 0000000..d73e93f
--- /dev/null
+++ b/user/plugins/admin/pages/admin/pages.md
@@ -0,0 +1,8 @@
+---
+title: Pages
+expires: 0
+
+access:
+ admin.pages: true
+ admin.super: true
+---
diff --git a/user/plugins/admin/pages/admin/plugins.md b/user/plugins/admin/pages/admin/plugins.md
new file mode 100644
index 0000000..fb24a2f
--- /dev/null
+++ b/user/plugins/admin/pages/admin/plugins.md
@@ -0,0 +1,8 @@
+---
+title: Plugins
+expires: 0
+
+access:
+ admin.plugins: true
+ admin.super: true
+---
diff --git a/user/plugins/admin/pages/admin/preview.md b/user/plugins/admin/pages/admin/preview.md
new file mode 100644
index 0000000..ea97a29
--- /dev/null
+++ b/user/plugins/admin/pages/admin/preview.md
@@ -0,0 +1,8 @@
+---
+title: Preview
+expires: 0
+
+access:
+ admin.pages: true
+ admin.super: true
+---
diff --git a/user/plugins/admin/pages/admin/register.md b/user/plugins/admin/pages/admin/register.md
new file mode 100644
index 0000000..e357427
--- /dev/null
+++ b/user/plugins/admin/pages/admin/register.md
@@ -0,0 +1,58 @@
+---
+expires: 0
+
+form:
+ fields:
+ - name: username
+ type: text
+ label: PLUGIN_ADMIN.USERNAME
+ autofocus: true
+ placeholder: PLUGIN_ADMIN.USERNAME_PLACEHOLDER
+ validate:
+ required: true
+ message: PLUGIN_LOGIN.USERNAME_NOT_VALID
+ config-pattern@: system.username_regex
+
+ - name: email
+ type: email
+ label: PLUGIN_ADMIN.EMAIL
+ placeholder: "valid email address"
+ validate:
+ type: email
+ message: PLUGIN_ADMIN.EMAIL_VALIDATION_MESSAGE
+ required: true
+
+ - name: password1
+ type: password
+ label: PLUGIN_ADMIN.PASSWORD
+ placeholder: PLUGIN_ADMIN.PWD_PLACEHOLDER
+ validate:
+ required: true
+ message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE
+ config-pattern@: system.pwd_regex
+
+ - name: password2
+ type: password
+ label: PLUGIN_ADMIN.PASSWORD_CONFIRM
+ placeholder: PLUGIN_ADMIN.PWD_PLACEHOLDER
+ validate:
+ required: true
+ message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE
+ config-pattern@: system.pwd_regex
+
+ - name: fullname
+ type: text
+ placeholder: "e.g. 'Joe Schmoe'"
+ label: PLUGIN_ADMIN.FULL_NAME
+
+ - name: title
+ type: text
+ placeholder: "e.g. 'Administrator'"
+ label: PLUGIN_ADMIN.TITLE
+
+ process:
+ register_admin_user: true
+
+---
+
+The Admin plugin has been installed, but no **admin accounts** could be found. Please create an admin account to ensure your Grav install is secure...
diff --git a/user/plugins/admin/pages/admin/reset.md b/user/plugins/admin/pages/admin/reset.md
new file mode 100644
index 0000000..cd4cc58
--- /dev/null
+++ b/user/plugins/admin/pages/admin/reset.md
@@ -0,0 +1,17 @@
+---
+title: Reset password
+expires: 0
+
+form:
+ fields:
+ - name: username
+ type: text
+ placeholder: PLUGIN_ADMIN.USERNAME
+ readonly: true
+ - name: password
+ type: password
+ placeholder: PLUGIN_ADMIN.PASSWORD
+ autofocus: true
+ - name: token
+ type: hidden
+---
diff --git a/user/plugins/admin/pages/admin/site.md b/user/plugins/admin/pages/admin/site.md
new file mode 100644
index 0000000..46cb1ee
--- /dev/null
+++ b/user/plugins/admin/pages/admin/site.md
@@ -0,0 +1,9 @@
+---
+title: Site Settings
+template: config
+expires: 0
+
+access:
+ admin.settings: true
+ admin.super: true
+---
diff --git a/user/plugins/admin/pages/admin/statistics.md b/user/plugins/admin/pages/admin/statistics.md
new file mode 100644
index 0000000..e7dea2c
--- /dev/null
+++ b/user/plugins/admin/pages/admin/statistics.md
@@ -0,0 +1,8 @@
+---
+title: Statistics
+expires: 0
+
+access:
+ admin.statistics: true
+ admin.super: true
+---
diff --git a/user/plugins/admin/pages/admin/system.md b/user/plugins/admin/pages/admin/system.md
new file mode 100644
index 0000000..f2d9246
--- /dev/null
+++ b/user/plugins/admin/pages/admin/system.md
@@ -0,0 +1,9 @@
+---
+title: Configuration
+template: config
+expires: 0
+
+access:
+ admin.configuration: true
+ admin.super: true
+---
diff --git a/user/plugins/admin/pages/admin/themes.md b/user/plugins/admin/pages/admin/themes.md
new file mode 100644
index 0000000..2a30d99
--- /dev/null
+++ b/user/plugins/admin/pages/admin/themes.md
@@ -0,0 +1,8 @@
+---
+title: Grav Themes
+expires: 0
+
+access:
+ admin.themes: true
+ admin.super: true
+---
diff --git a/user/plugins/admin/pages/admin/tools.md b/user/plugins/admin/pages/admin/tools.md
new file mode 100644
index 0000000..e2b5985
--- /dev/null
+++ b/user/plugins/admin/pages/admin/tools.md
@@ -0,0 +1,7 @@
+---
+title: Grav Tools
+expires: 0
+
+access:
+ admin.login: true
+---
diff --git a/user/plugins/admin/pages/admin/update.md b/user/plugins/admin/pages/admin/update.md
new file mode 100644
index 0000000..f1bed9f
--- /dev/null
+++ b/user/plugins/admin/pages/admin/update.md
@@ -0,0 +1,9 @@
+---
+title: Updates
+template: default
+expires: 0
+
+access:
+ admin.maintenance: true
+ admin.super: true
+---
diff --git a/user/plugins/admin/pages/admin/user.md b/user/plugins/admin/pages/admin/user.md
new file mode 100644
index 0000000..16e39e6
--- /dev/null
+++ b/user/plugins/admin/pages/admin/user.md
@@ -0,0 +1,9 @@
+---
+title: User
+expires: 0
+
+access:
+ admin.users: true
+ admin.login: true
+ admin.super: true
+---
diff --git a/user/plugins/admin/tests/_bootstrap.php b/user/plugins/admin/tests/_bootstrap.php
new file mode 100644
index 0000000..9b932bd
--- /dev/null
+++ b/user/plugins/admin/tests/_bootstrap.php
@@ -0,0 +1,11 @@
+controller = new \Grav\Plugin\Admin\AdminController();
+ }
+
+ protected function _after()
+ {
+
+ }
+
+ public function testDetermineFilenameIncludingLanguage()
+ {
+ $language = 'en-GB';
+
+ $this->assertSame('testing.en-GB.md', $this->controller->determineFilenameIncludingLanguage('testing.md', $language));
+ $this->assertSame('testing.en-GB.md', $this->controller->determineFilenameIncludingLanguage('testing.en.md', $language));
+ $this->assertSame('testing.en-GB.md', $this->controller->determineFilenameIncludingLanguage('testing.it.md', $language));
+ $this->assertSame('testing.en-GB.md', $this->controller->determineFilenameIncludingLanguage('testing.en-GB.md', $language));
+
+ $language = 'it';
+
+ $this->assertSame('testing.it.md', $this->controller->determineFilenameIncludingLanguage('testing.md', $language));
+ $this->assertSame('testing.it.md', $this->controller->determineFilenameIncludingLanguage('testing.en.md', $language));
+ $this->assertSame('testing.it.md', $this->controller->determineFilenameIncludingLanguage('testing.it.md', $language));
+ $this->assertSame('testing.it.md', $this->controller->determineFilenameIncludingLanguage('testing.en-GB.md', $language));
+ }
+}
+
diff --git a/user/plugins/admin/themes/grav/.eslintrc b/user/plugins/admin/themes/grav/.eslintrc
new file mode 100644
index 0000000..1e57de5
--- /dev/null
+++ b/user/plugins/admin/themes/grav/.eslintrc
@@ -0,0 +1,165 @@
+{
+ "env": {
+ "browser": true,
+ "node": true
+ },
+
+ "parserOptions": {
+ "ecmaVersion": 6,
+ "sourceType": "module"
+ },
+
+ "rules": {
+ "accessor-pairs": 2,
+ "array-bracket-spacing": 0,
+ "block-scoped-var": 0,
+ "brace-style": [2, "1tbs", { "allowSingleLine": true }],
+ "camelcase": 0,
+ "comma-dangle": [2, "never"],
+ "comma-spacing": [2, { "before": false, "after": true }],
+ "comma-style": [2, "last"],
+ "complexity": 0,
+ "computed-property-spacing": 0,
+ "consistent-return": 0,
+ "consistent-this": 0,
+ "constructor-super": 2,
+ "curly": [2, "multi-line"],
+ "default-case": 0,
+ "dot-location": [2, "property"],
+ "dot-notation": 0,
+ "eol-last": 2,
+ "eqeqeq": [2, "allow-null"],
+ "func-names": 0,
+ "func-style": 0,
+ "generator-star-spacing": [2, { "before": true, "after": true }],
+ "guard-for-in": 0,
+ "handle-callback-err": [2, "^(err|error)$" ],
+ "indent": [2, 4, { "SwitchCase": 1 }],
+ "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
+ "linebreak-style": 0,
+ "lines-around-comment": 0,
+ "max-nested-callbacks": 0,
+ "new-cap": [2, { "newIsCap": true, "capIsNew": false }],
+ "new-parens": 2,
+ "newline-after-var": 0,
+ "no-alert": 0,
+ "no-array-constructor": 2,
+ "no-caller": 2,
+ "no-catch-shadow": 0,
+ "no-cond-assign": 2,
+ "no-console": 0,
+ "no-constant-condition": 0,
+ "no-continue": 0,
+ "no-control-regex": 2,
+ "no-debugger": 2,
+ "no-delete-var": 2,
+ "no-div-regex": 0,
+ "no-dupe-args": 2,
+ "no-dupe-keys": 2,
+ "no-duplicate-case": 2,
+ "no-else-return": 0,
+ "no-empty": 0,
+ "no-empty-character-class": 2,
+ "no-eq-null": 0,
+ "no-eval": 2,
+ "no-ex-assign": 2,
+ "no-extend-native": 2,
+ "no-extra-bind": 2,
+ "no-extra-boolean-cast": 2,
+ "no-extra-parens": 0,
+ "no-extra-semi": 0,
+ "no-fallthrough": 2,
+ "no-floating-decimal": 2,
+ "no-func-assign": 2,
+ "no-implied-eval": 2,
+ "no-inline-comments": 0,
+ "no-inner-declarations": [2, "functions"],
+ "no-invalid-regexp": 2,
+ "no-irregular-whitespace": 2,
+ "no-iterator": 2,
+ "no-label-var": 2,
+ "no-labels": 2,
+ "no-lone-blocks": 2,
+ "no-lonely-if": 0,
+ "no-loop-func": 0,
+ "no-mixed-requires": 0,
+ "no-mixed-spaces-and-tabs": 2,
+ "no-multi-spaces": 2,
+ "no-multi-str": 2,
+ "no-multiple-empty-lines": [2, { "max": 1 }],
+ "no-native-reassign": 2,
+ "no-negated-in-lhs": 2,
+ "no-nested-ternary": 0,
+ "no-new": 2,
+ "no-new-func": 0,
+ "no-new-object": 2,
+ "no-new-require": 2,
+ "no-new-wrappers": 2,
+ "no-obj-calls": 2,
+ "no-octal": 2,
+ "no-octal-escape": 2,
+ "no-param-reassign": 0,
+ "no-path-concat": 0,
+ "no-process-env": 0,
+ "no-process-exit": 0,
+ "no-proto": 0,
+ "no-redeclare": 2,
+ "no-regex-spaces": 2,
+ "no-restricted-modules": 0,
+ "no-return-assign": 2,
+ "no-script-url": 0,
+ "no-self-compare": 2,
+ "no-sequences": 2,
+ "no-shadow": 0,
+ "no-shadow-restricted-names": 2,
+ "no-spaced-func": 2,
+ "no-sparse-arrays": 2,
+ "no-sync": 0,
+ "no-ternary": 0,
+ "no-this-before-super": 2,
+ "no-throw-literal": 2,
+ "no-trailing-spaces": 2,
+ "no-undef": 2,
+ "no-undef-init": 2,
+ "no-undefined": 0,
+ "no-underscore-dangle": 0,
+ "no-unexpected-multiline": 2,
+ "no-unneeded-ternary": 2,
+ "no-unreachable": 2,
+ "no-unused-expressions": 0,
+ "no-unused-vars": [2, { "vars": "all", "args": "none" }],
+ "no-use-before-define": 0,
+ "no-var": 0,
+ "no-void": 0,
+ "no-warning-comments": 0,
+ "no-with": 2,
+ "object-curly-spacing": 0,
+ "object-shorthand": 0,
+ "one-var": [2, { "initialized": "never" }],
+ "operator-assignment": 0,
+ "operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }],
+ "padded-blocks": 0,
+ "prefer-const": 0,
+ "quote-props": 0,
+ "quotes": [2, "single", "avoid-escape"],
+ "radix": 2,
+ "semi": [2, "always"],
+ "semi-spacing": 0,
+ "sort-vars": 0,
+ "keyword-spacing": [2, {"after": true, "overrides": {"throw": { "after": true}, "return": { "before": true }}}],
+ "space-before-blocks": [2, "always"],
+ "space-before-function-paren": [2, "never"],
+ "space-in-parens": [2, "never"],
+ "space-infix-ops": 2,
+ "space-unary-ops": [2, { "words": true, "nonwords": false }],
+ "spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!"] }],
+ "strict": 0,
+ "use-isnan": 2,
+ "valid-jsdoc": 0,
+ "valid-typeof": 2,
+ "vars-on-top": 0,
+ "wrap-iife": [2, "any"],
+ "wrap-regex": 0,
+ "yoda": [2, "never"]
+ }
+}
diff --git a/user/plugins/admin/themes/grav/app/dashboard/backup.js b/user/plugins/admin/themes/grav/app/dashboard/backup.js
new file mode 100644
index 0000000..d3131ad
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/dashboard/backup.js
@@ -0,0 +1,42 @@
+import $ from 'jquery';
+import { translations } from 'grav-config';
+import request from '../utils/request';
+import { Instances as Charts } from './chart';
+
+$('[data-backup][data-ajax*="backup/"]').on('click', function() {
+ let element = $(this);
+ let url = element.data('ajax');
+ const inDropdown = element.closest('.dropdown-menu');
+
+ (inDropdown.length ? inDropdown : element)
+ .closest('.button-group').find('> button:first')
+ .attr('disabled', 'disabled')
+ .find('> .fa').removeClass('fa-life-ring').addClass('fa-spin fa-refresh');
+
+ request(url, (/* response */) => {
+ if (Charts && Charts.backups) {
+ Charts.backups.updateData({ series: [0, 100] });
+ Charts.backups.element.find('.numeric').html(`0 ${translations.PLUGIN_ADMIN.DAYS.toLowerCase()} `);
+ }
+
+ (inDropdown.length ? inDropdown : element)
+ .closest('.button-group').find('> button:first')
+ .removeAttr('disabled')
+ .find('> .fa').removeClass('fa-spin fa-refresh').addClass('fa-life-ring');
+ });
+});
+
+$('[data-backup][data-ajax*="backupDelete"]').on('click', function() {
+ let element = $(this);
+ let url = element.data('ajax');
+ const tr = element.closest('tr');
+ tr.addClass('deleting');
+
+ request(url, (response) => {
+ if (response.status === 'success') {
+ tr.remove();
+ } else {
+ tr.removeClass('deleting');
+ }
+ });
+});
diff --git a/user/plugins/admin/themes/grav/app/dashboard/cache.js b/user/plugins/admin/themes/grav/app/dashboard/cache.js
new file mode 100644
index 0000000..e65fd49
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/dashboard/cache.js
@@ -0,0 +1,49 @@
+import $ from 'jquery';
+import { config } from 'grav-config';
+import request from '../utils/request';
+
+const getUrl = (type = '') => {
+ if (type) {
+ type = `cleartype:${type}/`;
+ }
+
+ return `${config.base_url_relative}/cache.json/task${config.param_sep}clearCache/${type}admin-nonce${config.param_sep}${config.admin_nonce}`;
+};
+
+export default class Cache {
+ constructor() {
+ this.element = $('[data-clear-cache]');
+ $('body').on('click', '[data-clear-cache]', (event) => this.clear(event, event.target));
+ }
+
+ clear(event, element) {
+ let type = '';
+
+ if (event && event.preventDefault) { event.preventDefault(); }
+ if (typeof event === 'string') { type = event; }
+
+ element = element ? $(element) : $(`[data-clear-cache-type="${type}"]`);
+ type = type || $(element).data('clear-cache-type') || '';
+ let url = element.data('clearCache') || getUrl(type);
+
+ this.disable();
+
+ request(url, () => this.enable());
+ }
+
+ enable() {
+ this.element
+ .removeAttr('disabled')
+ .find('> .fa').removeClass('fa-refresh fa-spin').addClass('fa-trash');
+ }
+
+ disable() {
+ this.element
+ .attr('disabled', 'disabled')
+ .find('> .fa').removeClass('fa-trash').addClass('fa-refresh fa-spin');
+ }
+}
+
+let Instance = new Cache();
+
+export { Instance };
diff --git a/user/plugins/admin/themes/grav/app/dashboard/chart.js b/user/plugins/admin/themes/grav/app/dashboard/chart.js
new file mode 100644
index 0000000..cb5fa60
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/dashboard/chart.js
@@ -0,0 +1,138 @@
+import $ from 'jquery';
+import chartist from 'chartist';
+import { translations } from 'grav-config';
+import { Instance as gpm } from '../utils/gpm';
+import { Instance as updates } from '../updates';
+
+// let isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
+
+export const defaults = {
+ data: {
+ series: [100, 0]
+ },
+ options: {
+ Pie: {
+ donut: true,
+ donutWidth: 10,
+ startAngle: 0,
+ total: 100,
+ showLabel: false,
+ height: 150,
+ // chartPadding: !isFirefox ? 10 : 25 // workaround for older versions of firefox
+ chartPadding: 5
+ },
+ Bar: {
+ height: 164,
+ chartPadding: 20, // workaround for older versions of firefox
+
+ axisX: {
+ showGrid: false,
+ labelOffset: {
+ x: 0,
+ y: 0
+ }
+ },
+ axisY: {
+ offset: 15,
+ showLabel: true,
+ showGrid: true,
+ labelOffset: {
+ x: 5,
+ y: 5
+ },
+ scaleMinSpace: 25
+ }
+ }
+ }
+};
+
+export default class Chart {
+ constructor(element, options = {}, data = {}) {
+ this.element = $(element) || [];
+ if (!this.element[0]) { return; }
+
+ let type = (this.element.data('chart-type') || 'pie').toLowerCase();
+ this.type = type.charAt(0).toUpperCase() + type.substr(1).toLowerCase();
+
+ options = Object.assign({}, defaults.options[this.type], options);
+ data = Object.assign({}, defaults.data, data);
+ Object.assign(this, {
+ options,
+ data
+ });
+ this.chart = chartist[this.type](this.element.find('.ct-chart').empty()[0], this.data, this.options);
+ this.chart.on('created', () => {
+ this.element.find('.hidden').removeClass('hidden');
+
+ // FIX: workaround for chartist issue not allowing HTML in labels anymore
+ // https://github.com/gionkunz/chartist-js/issues/937
+ this.element.find('.ct-label').each((index, label) => {
+ label = $(label);
+ const text = label.html().replace('<', '<').replace('>', '>');
+ label.html(text);
+ });
+ });
+ }
+
+ updateData(data) {
+ Object.assign(this.data, data);
+ this.chart.update(this.data);
+ }
+};
+
+export class UpdatesChart extends Chart {
+ constructor(element, options = {}, data = {}) {
+ super(element, options, data);
+
+ this.chart.on('draw', (data) => this.draw(data));
+
+ gpm.on('fetched', (response) => {
+ if (!response.payload) { return; }
+
+ let payload = response.payload.grav;
+ let missing = (response.payload.resources.total + (payload.isUpdatable ? 1 : 0)) * 100 / (response.payload.installed + (payload.isUpdatable ? 1 : 0));
+ let updated = 100 - missing;
+
+ this.updateData({ series: [updated, missing] });
+
+ if (response.payload.resources.total) {
+ updates.maintenance('show');
+ }
+ });
+ }
+
+ draw(data) {
+ if (data.index) { return; }
+
+ let notice = translations.PLUGIN_ADMIN[data.value === 100 ? 'FULLY_UPDATED' : 'UPDATES_AVAILABLE'];
+ this.element.find('.numeric span').text(`${Math.round(data.value)}%`);
+ this.element.find('.js__updates-available-description').html(notice);
+ this.element.find('.hidden').removeClass('hidden');
+ }
+
+ updateData(data) {
+ super.updateData(data);
+
+ // missing updates
+ if (this.data.series[0] < 100) {
+ this.element.closest('#updates').find('[data-update-packages]').fadeIn();
+ }
+ }
+}
+
+let charts = {};
+
+$('[data-chart-name]').each(function() {
+ let element = $(this);
+ let name = element.data('chart-name') || '';
+ let options = element.data('chart-options') || {};
+ let data = element.data('chart-data') || {};
+
+ if (name === 'updates') {
+ charts[name] = new UpdatesChart(element, options, data);
+ } else {
+ charts[name] = new Chart(element, options, data);
+ }
+});
+
+export let Instances = charts;
diff --git a/user/plugins/admin/themes/grav/app/dashboard/index.js b/user/plugins/admin/themes/grav/app/dashboard/index.js
new file mode 100644
index 0000000..7adee0d
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/dashboard/index.js
@@ -0,0 +1,12 @@
+import Chart, { UpdatesChart, Instances } from './chart';
+import { Instance as Cache } from './cache';
+import './backup';
+
+export default {
+ Chart: {
+ Chart,
+ UpdatesChart,
+ Instances
+ },
+ Cache
+};
diff --git a/user/plugins/admin/themes/grav/app/dashboard/update.js b/user/plugins/admin/themes/grav/app/dashboard/update.js
new file mode 100644
index 0000000..acf1bb7
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/dashboard/update.js
@@ -0,0 +1 @@
+// See ../updates/update.js
diff --git a/user/plugins/admin/themes/grav/app/forms/fields/array.js b/user/plugins/admin/themes/grav/app/forms/fields/array.js
new file mode 100644
index 0000000..1b241dd
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/fields/array.js
@@ -0,0 +1,193 @@
+import $ from 'jquery';
+import Sortable from 'sortablejs';
+
+let body = $('body');
+
+class Template {
+ constructor(container) {
+ this.container = $(container);
+
+ if (this.getName() === undefined) {
+ this.container = this.container.closest('[data-grav-array-name]');
+ }
+ }
+
+ getName() {
+ return this.container.data('grav-array-name') || '';
+ }
+
+ getKeyPlaceholder() {
+ return this.container.data('grav-array-keyname') || 'Key';
+ }
+
+ getValuePlaceholder() {
+ return this.container.data('grav-array-valuename') || 'Value';
+ }
+
+ isValueOnly() {
+ return this.container.find('[data-grav-array-mode="value_only"]:first').length || false;
+ }
+
+ isTextArea() {
+ return this.container.data('grav-array-textarea') || false;
+ }
+
+ shouldBeDisabled() {
+ // check for toggleables, if field is toggleable and it's not enabled, render disabled
+ let toggle = this.container.closest('.form-field').find('[data-grav-field="toggleable"] input[type="checkbox"]');
+ return toggle.length && toggle.is(':not(:checked)');
+ }
+
+ getNewRow() {
+ let tpl = '';const value = this.isTextArea()
+ ? ``
+ : ` `;
+
+ if (this.isValueOnly()) {
+ tpl += `
+
+
+ ${value}
+ `;
+ } else {
+ tpl += `
+
+
+
+ ${value}
+ `;
+ }
+
+ tpl += `
+
+
+
`;
+
+ return tpl;
+ }
+}
+
+export default class ArrayField {
+ constructor() {
+ body.on('input', '[data-grav-array-type="key"], [data-grav-array-type="value"]', (event) => this.actionInput(event));
+ body.on('click touch', '[data-grav-array-action]:not([data-grav-array-action="sort"])', (event) => this.actionEvent(event));
+
+ this.arrays = $();
+
+ $('[data-grav-field="array"]').each((index, list) => this.addArray(list));
+ $('body').on('mutation._grav', this._onAddedNodes.bind(this));
+ }
+
+ addArray(list) {
+ list = $(list);
+
+ list.find('[data-grav-array-type="container"]').each((index, container) => {
+ container = $(container);
+ if (container.data('array-sort') || container[0].hasAttribute('data-array-nosort')) { return; }
+
+ container.data('array-sort', new Sortable(container.get(0), {
+ handle: '.fa-bars',
+ animation: 150
+ }));
+ });
+ }
+
+ actionInput(event) {
+ let element = $(event.target);
+ let type = element.data('grav-array-type');
+
+ this._setTemplate(element);
+
+ let template = element.data('array-template');
+ let keyElement = type === 'key' ? element : element.siblings('[data-grav-array-type="key"]:first');
+ let valueElement = type === 'value' ? element : element.siblings('[data-grav-array-type="value"]:first');
+
+ let escaped_name = !template.isValueOnly() ? keyElement.val() : this.getIndexFor(element);
+ escaped_name = escaped_name.toString().replace(/\[/g, '%5B').replace(/]/g, '%5D');
+ let name = `${template.getName()}[${escaped_name}]`;
+ valueElement.attr('name', !valueElement.val() ? template.getName() : name);
+
+ this.refreshNames(template);
+ }
+
+ actionEvent(event) {
+ event && event.preventDefault();
+ let element = $(event.target);
+ let action = element.data('grav-array-action');
+ let container = element.parents('[data-grav-array-type="container"]');
+
+ this._setTemplate(element);
+
+ this[`${action}Action`](element);
+
+ let siblings = container.find('> div');
+ container[siblings.length > 1 ? 'removeClass' : 'addClass']('one-child');
+ }
+
+ addAction(element) {
+ let template = element.data('array-template');
+ let row = element.closest('[data-grav-array-type="row"]');
+
+ row.after(template.getNewRow());
+ }
+
+ remAction(element) {
+ let template = element.data('array-template');
+ let row = element.closest('[data-grav-array-type="row"]');
+ let isLast = !row.siblings().length;
+
+ if (isLast) {
+ let newRow = $(template.getNewRow());
+ row.after(newRow);
+ newRow.find('[data-grav-array-type="value"]:last').attr('name', template.getName());
+ }
+
+ row.remove();
+ this.refreshNames(template);
+ }
+
+ refreshNames(template) {
+ if (!template.isValueOnly()) { return; }
+
+ let row = template.container.find('> div > [data-grav-array-type="row"]');
+ let inputs = row.find('[name]:not([name=""])');
+
+ inputs.each((index, input) => {
+ input = $(input);
+ let name = input.attr('name');
+ name = name.replace(/\[\d+\]$/, `[${index}]`);
+ input.attr('name', name);
+ });
+
+ if (!inputs.length) {
+ row.find('[data-grav-array-type="value"]').attr('name', template.getName());
+ }
+ }
+
+ getIndexFor(element) {
+ let template = element.data('array-template');
+ let row = element.closest('[data-grav-array-type="row"]');
+
+ return template.container.find(`${template.isValueOnly() ? '> div ' : ''} > [data-grav-array-type="row"]`).index(row);
+ }
+
+ _setTemplate(element) {
+ if (!element.data('array-template')) {
+ element.data('array-template', new Template(element.closest('[data-grav-array-name]')));
+ }
+ }
+
+ _onAddedNodes(event, target/* , record, instance */) {
+ let arrays = $(target).find('[data-grav-field="array"]');
+ if (!arrays.length) { return; }
+
+ arrays.each((index, list) => {
+ list = $(list);
+ if (!~this.arrays.index(list)) {
+ this.addArray(list);
+ }
+ });
+ }
+}
+
+export let Instance = new ArrayField();
diff --git a/user/plugins/admin/themes/grav/app/forms/fields/collections.js b/user/plugins/admin/themes/grav/app/forms/fields/collections.js
new file mode 100644
index 0000000..b9d4855
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/fields/collections.js
@@ -0,0 +1,261 @@
+import $ from 'jquery';
+import Sortable from 'sortablejs';
+import '../../utils/jquery-utils';
+
+export default class CollectionsField {
+ constructor() {
+ this.lists = $();
+
+ $('[data-type="collection"]').each((index, list) => this.addList(list));
+ $('body').on('mutation._grav', this._onAddedNodes.bind(this));
+
+ }
+
+ addList(list) {
+ list = $(list);
+ this.lists = this.lists.add(list);
+
+ list.on('click', '> .collection-actions [data-action="add"]', (event) => this.addItem(event));
+ list.on('click', '> ul > li > .item-actions [data-action="delete"]', (event) => this.removeItem(event));
+ list.on('click', '> ul > li > .item-actions [data-action="collapse"]', (event) => this.collapseItem(event));
+ list.on('click', '> ul > li > .item-actions [data-action="expand"]', (event) => this.expandItem(event));
+ list.on('click', '> .collection-actions [data-action-sort="date"]', (event) => this.sortItems(event));
+ list.on('click', '> .collection-actions [data-action="collapse_all"]', (event) => this.collapseItems(event));
+ list.on('click', '> .collection-actions [data-action="expand_all"]', (event) => this.expandItems(event));
+ list.on('input change', '[data-key-observe]', (event) => this.observeKey(event));
+
+ list.find('[data-collection-holder]').each((index, container) => {
+ container = $(container);
+ if (container.data('collection-sort') || container[0].hasAttribute('data-collection-nosort')) { return; }
+
+ container.data('collection-sort', new Sortable(container.get(0), {
+ forceFallback: false,
+ handle: '.collection-sort',
+ animation: 150,
+ onUpdate: () => this.reindex(container)
+ }));
+ });
+
+ this._updateActionsStateBasedOnMinMax(list);
+ }
+
+ addItem(event) {
+ let button = $(event.currentTarget);
+ let position = button.data('action-add') || 'bottom';
+ let list = $(button.closest('[data-type="collection"]'));
+ let template = $(list.find('> [data-collection-template="new"]').data('collection-template-html'));
+
+ this._updateActionsStateBasedOnMinMax(list);
+ let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
+ let maxItems = list.data('max');
+ if (typeof maxItems !== 'undefined' && items.length >= maxItems) {
+ return;
+ }
+
+ list.find('> [data-collection-holder]')[position === 'top' ? 'prepend' : 'append'](template);
+ this.reindex(list);
+
+ items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
+ let topAction = list.closest('[data-type="collection"]').find('[data-action-add="top"]');
+ let sortAction = list.closest('[data-type="collection"]').find('[data-action="sort"]');
+
+ if (items.length) {
+ if (topAction.length) { topAction.parent().removeClass('hidden'); }
+ if (sortAction.length && items.length > 1) { sortAction.removeClass('hidden'); }
+ }
+
+ // refresh toggleables in a list
+ $('[data-grav-field="toggleable"] input[type="checkbox"]').trigger('change');
+ }
+
+ removeItem(event) {
+ let button = $(event.currentTarget);
+ let item = button.closest('[data-collection-item]');
+ let list = $(button.closest('[data-type="collection"]'));
+
+ let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
+ let minItems = list.data('min');
+
+ if (typeof minItems !== 'undefined' && items.length <= minItems) {
+ return;
+ }
+
+ item.remove();
+ this.reindex(list);
+
+ items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
+ let topAction = list.closest('[data-type="collection"]').find('[data-action-add="top"]');
+ let sortAction = list.closest('[data-type="collection"]').find('[data-action="sort"]');
+
+ if (!items.length) {
+ if (topAction.length) { topAction.parent().addClass('hidden'); }
+ }
+
+ if (sortAction.length && items.length <= 1) { sortAction.addClass('hidden'); }
+ this._updateActionsStateBasedOnMinMax(list);
+ }
+
+ collapseItems(event) {
+ let button = $(event.currentTarget);
+ let items = $(button.closest('[data-type="collection"]')).find('> ul > [data-collection-item] > .item-actions [data-action="collapse"]');
+
+ items.click();
+ }
+
+ collapseItem(event) {
+ let button = $(event.currentTarget);
+ let item = button.closest('[data-collection-item]');
+
+ button.attr('data-action', 'expand').removeClass('fa-chevron-circle-down').addClass('fa-chevron-circle-right');
+ item.addClass('collection-collapsed');
+ }
+
+ expandItems(event) {
+ let button = $(event.currentTarget);
+ let items = $(button.closest('[data-type="collection"]')).find('> ul > [data-collection-item] > .item-actions [data-action="expand"]');
+
+ items.click();
+ }
+
+ expandItem(event) {
+ let button = $(event.currentTarget);
+ let item = button.closest('[data-collection-item]');
+
+ button.attr('data-action', 'collapse').removeClass('fa-chevron-circle-right').addClass('fa-chevron-circle-down');
+ item.removeClass('collection-collapsed');
+ }
+
+ sortItems(event) {
+ let button = $(event.currentTarget);
+ let sortby = button.data('action-sort');
+ let sortby_dir = button.data('action-sort-dir') || 'asc';
+ let list = $(button.closest('[data-type="collection"]'));
+ let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
+
+ items.sort((a, b) => {
+ let A = $(a).find('[name$="[' + sortby + ']"]');
+ let B = $(b).find('[name$="[' + sortby + ']"]');
+ let sort;
+
+ if (sortby_dir === 'asc') {
+ sort = (A.val() < B.val()) ? -1 : (A.val() > B.val()) ? 1 : 0;
+ } else {
+ sort = (A.val() > B.val()) ? -1 : (A.val() < B.val()) ? 1 : 0;
+ }
+
+ return sort;
+ }).each((_, container) => {
+ $(container).parent().append(container);
+ });
+
+ this.reindex(list);
+ }
+
+ observeKey(event) {
+ let input = $(event.target);
+ let value = input.val();
+ let item = input.closest('[data-collection-key]');
+
+ item.data('collection-key-backup', item.data('collection-key')).data('collection-key', value);
+ this.reindex(null, item);
+ }
+
+ reindex(list, items) {
+ items = items || $(list).closest('[data-type="collection"]').find('> ul > [data-collection-item]');
+
+ items.each((index, item) => {
+ item = $(item);
+
+ let observed = item.find('[data-key-observe]');
+ let observedValue = observed.val();
+ let hasCustomKey = observed.length;
+ let currentKey = item.data('collection-key-backup');
+
+ item.attr('data-collection-key', hasCustomKey ? observedValue : index);
+
+ ['name', 'data-grav-field-name', 'for', 'id', 'data-grav-file-settings', 'data-grav-array-name'].forEach((prop) => {
+ item.find('[' + prop + '], [_' + prop + ']').each(function() {
+ let element = $(this);
+ let indexes = [];
+ let array_index = null;
+ let regexps = [
+ new RegExp('\\[(\\d+|\\*|' + currentKey + ')\\]', 'g'),
+ new RegExp('\\.(\\d+|\\*|' + currentKey + ')\\.', 'g')
+ ];
+
+ // special case to preserve array field index keys
+ if (prop === 'name' && element.data('gravArrayType')) {
+ const match_index = element.attr(prop).match(/\[[0-9]{1,}\]$/);
+ const pattern = element.closest('[data-grav-array-name]').data('gravArrayName');
+ if (match_index && pattern) {
+ array_index = match_index[0];
+ element.attr(prop, `${pattern}${match_index[0]}`);
+ return;
+ }
+ }
+
+ if (hasCustomKey && !observedValue) {
+ element.attr(`_${prop}`, element.attr(prop));
+ element.attr(prop, null);
+ return;
+ }
+
+ if (element.attr(`_${prop}`)) {
+ element.attr(prop, element.attr(`_${prop}`));
+ element.attr(`_${prop}`, null);
+ }
+
+ element.parents('[data-collection-key]').map((idx, parent) => indexes.push($(parent).attr('data-collection-key')));
+ indexes.reverse();
+
+ let matchedKey = currentKey;
+ let replaced = element.attr(prop).replace(regexps[0], (/* str, p1, offset */) => {
+ let extras = '';
+ if (array_index) { extras = array_index; console.log(indexes, extras); }
+
+ matchedKey = indexes.shift() || matchedKey;
+ return `[${matchedKey}]${extras}`;
+ });
+
+ replaced = replaced.replace(regexps[1], (/* str, p1, offset */) => {
+ matchedKey = indexes.shift() || matchedKey;
+ return `.${matchedKey}.`;
+ });
+
+ element.attr(prop, replaced);
+ });
+ });
+ });
+ }
+
+ _onAddedNodes(event, target/* , record, instance */) {
+ let collections = $(target).find('[data-type="collection"]');
+ if (!collections.length) { return; }
+
+ collections.each((index, collection) => {
+ collection = $(collection);
+ if (!~this.lists.index(collection)) {
+ this.addList(collection);
+ }
+ });
+ }
+
+ _updateActionsStateBasedOnMinMax(list) {
+ let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
+ let minItems = list.data('min');
+ let maxItems = list.data('max');
+
+ list.find('> .collection-actions [data-action="add"]').attr('disabled', false);
+ list.find('> ul > li > .item-actions [data-action="delete"]').attr('disabled', false);
+
+ if (typeof minItems !== 'undefined' && items.length <= minItems) {
+ list.find('> ul > li > .item-actions [data-action="delete"]').attr('disabled', true);
+ }
+
+ if (typeof maxItems !== 'undefined' && items.length >= maxItems) {
+ list.find('> .collection-actions [data-action="add"]').attr('disabled', true);
+ }
+ }
+}
+
+export let Instance = new CollectionsField();
diff --git a/user/plugins/admin/themes/grav/app/forms/fields/colorpicker.js b/user/plugins/admin/themes/grav/app/forms/fields/colorpicker.js
new file mode 100644
index 0000000..d609de0
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/fields/colorpicker.js
@@ -0,0 +1,581 @@
+import $ from 'jquery';
+import clamp from 'mout/math/clamp';
+import bind from 'mout/function/bind';
+import { rgbstr2hex, hsb2hex, hex2hsb, hex2rgb, parseHex } from '../../utils/colors';
+
+const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
+const body = $('body');
+
+const MOUSEDOWN = 'mousedown touchstart MSPointerDown pointerdown';
+const MOUSEMOVE = 'mousemove touchmove MSPointerMove pointermove';
+const MOUSEUP = 'mouseup touchend MSPointerUp pointerup';
+const FOCUSIN = isFirefox ? 'focus' : 'focusin';
+
+export default class ColorpickerField {
+ constructor(selector) {
+ this.selector = selector;
+ this.field = $(this.selector);
+ this.options = Object.assign({}, this.field.data('grav-colorpicker'));
+ this.built = false;
+ this.attach();
+
+ if (this.options.update) {
+ this.field.on('change._grav_colorpicker', (event, field, hex, opacity) => {
+ let backgroundColor = hex;
+ let rgb = hex2rgb(hex);
+
+ if (opacity < 1) {
+ backgroundColor = 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + opacity + ')';
+ }
+
+ let target = field.closest(this.options.update);
+ if (!target.length) {
+ target = field.siblings(this.options.update);
+ }
+ if (!target.length) {
+ target = field.parent('.g-colorpicker').find(this.options.update);
+ }
+
+ target.css({ backgroundColor });
+ });
+ }
+ }
+
+ attach() {
+ body.on(FOCUSIN, this.selector, (event) => this.show(event, event.currentTarget));
+ body.on(MOUSEDOWN, this.selector + ' .g-colorpicker, ' + this.selector + ' .g-colorpicker i', this.bound('iconClick'));
+ body.on('keydown', this.selector, (event) => {
+ switch (event.keyCode) {
+ case 9: // tab
+ this.hide();
+ break;
+ case 13: // enter
+ case 27: // esc
+ this.hide();
+ event.currentTarget.blur();
+ break;
+ }
+ return true;
+ });
+
+ // Update on keyup
+ body.on('keyup', this.selector, (event) => {
+ this.updateFromInput(true, event.currentTarget);
+ return true;
+ });
+
+ // Update on paste
+ body.on('paste', this.selector, (event) => {
+ setTimeout(() => this.updateFromInput(true, event.currentTarget), 1);
+ });
+ }
+
+ show(event, target) {
+ target = $(target);
+
+ if (!this.built) {
+ this.build();
+ }
+
+ this.element = target;
+ this.reposition();
+ this.wrapper.addClass('cp-visible');
+ this.updateFromInput();
+
+ let mainContainer = $('#admin-main .content-wrapper').data('scrollbar').getViewElement();
+ this.wrapper.on(MOUSEDOWN, '.cp-grid, .cp-slider, .cp-opacity-slider', this.bound('bodyDown'));
+ body.on(MOUSEMOVE, this.bound('bodyMove'));
+ body.on(MOUSEDOWN, this.bound('bodyClick'));
+ body.on(MOUSEUP, this.bound('targetReset'));
+ $(mainContainer).on('scroll', this.bound('reposition'));
+ }
+
+ hide() {
+ if (!this.built) { return; }
+ this.wrapper.removeClass('cp-visible');
+
+ let mainContainer = $('#admin-main .content-wrapper').data('scrollbar').getViewElement();
+ this.wrapper.undelegate(MOUSEDOWN, '.cp-grid, .cp-slider, .cp-opacity-slider', this.bound('bodyDown'));
+ body.off(MOUSEMOVE, this.bound('bodyMove'));
+ body.off(MOUSEDOWN, this.bound('bodyClick'));
+ body.off(MOUSEUP, this.bound('targetReset'));
+ $(mainContainer).off('scroll', this.bound('reposition'));
+ }
+
+ build() {
+ this.wrapper = $('
');
+ this.slider = $('
').appendTo(this.wrapper).append($('
'));
+ this.opacitySlider = $('
').appendTo(this.wrapper).append($('
'));
+ this.grid = $('
').appendTo(this.wrapper).append($('
')).append($('
'));
+
+ $('
').appendTo(this.grid.find('.cp-picker'));
+
+ let tabs = $('
').appendTo(this.wrapper);
+ this.tabs = {
+ hue: $('
').text('HUE').appendTo(tabs),
+ brightness: $('
').text('BRI').appendTo(tabs),
+ saturation: $('
').text('SAT').appendTo(tabs),
+ wheel: $('
').text('WHEEL').appendTo(tabs),
+ transparent: $('
').text('TRANSPARENT').appendTo(tabs)
+ };
+
+ tabs.on(MOUSEDOWN, '> div', (event) => {
+ let element = $(event.currentTarget);
+ if (element.is(this.tabs.transparent)) {
+ let sliderHeight = this.opacitySlider.height();
+
+ this.opacity = 0;
+ this.opacitySlider.find('.cp-picker').css({ 'top': clamp(sliderHeight - (sliderHeight * this.opacity), 0, sliderHeight) });
+ this.move(this.opacitySlider, { manualOpacity: true });
+ return;
+ }
+
+ let active = tabs.find('.active');
+ let mode = active.attr('class').replace(/\s|active|cp-tab-/g, '');
+ let newMode = element.attr('class').replace(/\s|active|cp-tab-/g, '');
+
+ this.wrapper.removeClass('cp-mode-' + mode).addClass('cp-mode-' + newMode);
+ active.removeClass('active');
+ element.addClass('active');
+
+ this.mode = newMode;
+ this.updateFromInput();
+ });
+
+ this.wrapper.appendTo('.content-wrapper');
+
+ this.built = true;
+ this.mode = 'hue';
+ }
+
+ reposition() {
+ let ct = $('.content-wrapper')[0];
+ let offset = this.element[0].getBoundingClientRect();
+ let ctOffset = ct.getBoundingClientRect();
+ let delta = { x: 0, y: 0 };
+
+ if (this.options.offset) {
+ delta.x = this.options.offset.x || 0;
+ delta.y = this.options.offset.y || 0;
+ }
+
+ this.wrapper.css({
+ top: offset.top + offset.height + ct.scrollTop - ctOffset.top + delta.y,
+ left: offset.left + ct.scrollLeft - ctOffset.left + delta.x
+ });
+ }
+
+ iconClick(event) {
+ if (this.wrapper && this.wrapper.hasClass('cp-visible')) { return true; }
+
+ event && event.preventDefault();
+
+ let input = $(event.currentTarget).find('input');
+ setTimeout(() => input.focus(), 50);
+ }
+
+ bodyMove(event) {
+ event && event.preventDefault();
+
+ if (this.target) { this.move(this.target, event); }
+ }
+
+ bodyClick(event) {
+ let target = $(event.target);
+
+ if (!target.closest('.cp-wrapper').length && !target.is(this.selector)) {
+ this.hide();
+ }
+ }
+
+ bodyDown(event) {
+ event && event.preventDefault();
+
+ this.target = $(event.currentTarget);
+ this.move(this.target, event, true);
+ }
+
+ targetReset(event) {
+ event && event.preventDefault();
+
+ this.target = null;
+ }
+
+ move(target, event) {
+ let input = this.element;
+ let picker = target.find('.cp-picker');
+ let clientRect = target[0].getBoundingClientRect();
+ let offsetX = clientRect.left + window.scrollX;
+ let offsetY = clientRect.top + window.scrollY;
+ let x = Math.round((event ? event.pageX : 0) - offsetX);
+ let y = Math.round((event ? event.pageY : 0) - offsetY);
+ let wx;
+ let wy;
+ let r;
+ let phi;
+
+ // Touch support
+ let touchEvents = event.changedTouches || (event.originalEvent && event.originalEvent.changedTouches);
+ if (event && touchEvents) {
+ x = (touchEvents ? touchEvents[0].pageX : 0) - offsetX;
+ y = (touchEvents ? touchEvents[0].pageY : 0) - offsetY;
+ }
+
+ if (event && event.manualOpacity) {
+ y = clientRect.height;
+ }
+
+ // Constrain picker to its container
+ if (x < 0) x = 0;
+ if (y < 0) y = 0;
+ if (x > clientRect.width) x = clientRect.width;
+ if (y > clientRect.height) y = clientRect.height;
+
+ // Constrain color wheel values to the wheel
+ if (target.parent('.cp-mode-wheel').length && picker.parent('.cp-grid').length) {
+ wx = 75 - x;
+ wy = 75 - y;
+ r = Math.sqrt(wx * wx + wy * wy);
+ phi = Math.atan2(wy, wx);
+
+ if (phi < 0) phi += Math.PI * 2;
+ if (r > 75) {
+ x = 75 - (75 * Math.cos(phi));
+ y = 75 - (75 * Math.sin(phi));
+ }
+
+ x = Math.round(x);
+ y = Math.round(y);
+ }
+
+ // Move the picker
+ if (target.hasClass('cp-grid')) {
+ picker.css({
+ top: y,
+ left: x
+ });
+
+ this.updateFromPicker(input, target);
+ } else {
+ picker.css({
+ top: y
+ });
+ this.updateFromPicker(input, target);
+ }
+ }
+
+ updateFromInput(dontFireEvent, element) {
+ element = element ? $(element) : this.element;
+ let value = element.val();
+ let opacity = value.replace(/\s/g, '').match(/^rgba?\([0-9]{1,3},[0-9]{1,3},[0-9]{1,3},(.+)\)/);
+ let hex;
+ let hsb;
+
+ value = rgbstr2hex(value) || value;
+ opacity = opacity ? clamp(opacity[1], 0, 1) : 1;
+
+ if (!(hex = parseHex(value))) { hex = '#ffffff'; }
+ hsb = hex2hsb(hex);
+
+ if (this.built) {
+ // opacity
+ this.opacity = opacity;
+ var sliderHeight = this.opacitySlider.height();
+ this.opacitySlider.find('.cp-picker').css({ 'top': clamp(sliderHeight - (sliderHeight * this.opacity), 0, sliderHeight) });
+
+ // bg color
+ let gridHeight = this.grid.height();
+ let gridWidth = this.grid.width();
+ let r;
+ let phi;
+ let x;
+ let y;
+
+ sliderHeight = this.slider.height();
+
+ switch (this.mode) {
+ case 'wheel':
+ // Set grid position
+ r = clamp(Math.ceil(hsb.s * 0.75), 0, gridHeight / 2);
+ phi = hsb.h * Math.PI / 180;
+ x = clamp(75 - Math.cos(phi) * r, 0, gridWidth);
+ y = clamp(75 - Math.sin(phi) * r, 0, gridHeight);
+ this.grid.css({ backgroundColor: 'transparent' }).find('.cp-picker').css({
+ top: y,
+ left: x
+ });
+
+ // Set slider position
+ y = 150 - (hsb.b / (100 / gridHeight));
+ if (hex === '') y = 0;
+ this.slider.find('.cp-picker').css({ top: y });
+
+ // Update panel color
+ this.slider.css({
+ backgroundColor: hsb2hex({
+ h: hsb.h,
+ s: hsb.s,
+ b: 100
+ })
+ });
+ break;
+
+ case 'saturation':
+ // Set grid position
+ x = clamp((5 * hsb.h) / 12, 0, 150);
+ y = clamp(gridHeight - Math.ceil(hsb.b / (100 / gridHeight)), 0, gridHeight);
+ this.grid.find('.cp-picker').css({
+ top: y,
+ left: x
+ });
+
+ // Set slider position
+ y = clamp(sliderHeight - (hsb.s * (sliderHeight / 100)), 0, sliderHeight);
+ this.slider.find('.cp-picker').css({ top: y });
+
+ // Update UI
+ this.slider.css({
+ backgroundColor: hsb2hex({
+ h: hsb.h,
+ s: 100,
+ b: hsb.b
+ })
+ });
+ this.grid.find('.cp-grid-inner').css({ opacity: hsb.s / 100 });
+ break;
+
+ case 'brightness':
+ // Set grid position
+ x = clamp((5 * hsb.h) / 12, 0, 150);
+ y = clamp(gridHeight - Math.ceil(hsb.s / (100 / gridHeight)), 0, gridHeight);
+ this.grid.find('.cp-picker').css({
+ top: y,
+ left: x
+ });
+
+ // Set slider position
+ y = clamp(sliderHeight - (hsb.b * (sliderHeight / 100)), 0, sliderHeight);
+ this.slider.find('.cp-picker').css({ top: y });
+
+ // Update UI
+ this.slider.css({
+ backgroundColor: hsb2hex({
+ h: hsb.h,
+ s: hsb.s,
+ b: 100
+ })
+ });
+ this.grid.find('.cp-grid-inner').css({ opacity: 1 - (hsb.b / 100) });
+ break;
+ case 'hue':
+ default:
+ // Set grid position
+ x = clamp(Math.ceil(hsb.s / (100 / gridWidth)), 0, gridWidth);
+ y = clamp(gridHeight - Math.ceil(hsb.b / (100 / gridHeight)), 0, gridHeight);
+ this.grid.find('.cp-picker').css({
+ top: y,
+ left: x
+ });
+
+ // Set slider position
+ y = clamp(sliderHeight - (hsb.h / (360 / sliderHeight)), 0, sliderHeight);
+ this.slider.find('.cp-picker').css({ top: y });
+
+ // Update panel color
+ this.grid.css({
+ backgroundColor: hsb2hex({
+ h: hsb.h,
+ s: 100,
+ b: 100
+ })
+ });
+ break;
+ }
+ }
+
+ if (!dontFireEvent) { element.val(this.getValue(hex)); }
+
+ (this.element || element).trigger('change._grav_colorpicker', [element, hex, opacity]);
+
+ }
+
+ updateFromPicker(input, target) {
+ var getCoords = function(picker, container) {
+
+ var left, top;
+ if (!picker.length || !container) return null;
+ left = picker[0].getBoundingClientRect().left;
+ top = picker[0].getBoundingClientRect().top;
+
+ return {
+ x: left - container[0].getBoundingClientRect().left + (picker[0].offsetWidth / 2),
+ y: top - container[0].getBoundingClientRect().top + (picker[0].offsetHeight / 2)
+ };
+
+ };
+
+ let hex;
+ let hue;
+ let saturation;
+ let brightness;
+ let x;
+ let y;
+ let r;
+ let phi;
+
+ // Panel objects
+ let grid = this.wrapper.find('.cp-grid');
+ let slider = this.wrapper.find('.cp-slider');
+ let opacitySlider = this.wrapper.find('.cp-opacity-slider');
+
+ // Picker objects
+ let gridPicker = grid.find('.cp-picker');
+ let sliderPicker = slider.find('.cp-picker');
+ let opacityPicker = opacitySlider.find('.cp-picker');
+
+ // Picker positions
+ let gridPos = getCoords(gridPicker, grid);
+ let sliderPos = getCoords(sliderPicker, slider);
+ let opacityPos = getCoords(opacityPicker, opacitySlider);
+
+ // Sizes
+ let gridWidth = grid[0].getBoundingClientRect().width;
+ let gridHeight = grid[0].getBoundingClientRect().height;
+ let sliderHeight = slider[0].getBoundingClientRect().height;
+ let opacitySliderHeight = opacitySlider[0].getBoundingClientRect().height;
+
+ let value = this.element.val();
+ value = rgbstr2hex(value) || value;
+ if (!(hex = parseHex(value))) { hex = '#ffffff'; }
+
+ // Handle colors
+ if (target.hasClass('cp-grid') || target.hasClass('cp-slider')) {
+
+ // Determine HSB values
+ switch (this.mode) {
+ case 'wheel':
+ // Calculate hue, saturation, and brightness
+ x = (gridWidth / 2) - gridPos.x;
+ y = (gridHeight / 2) - gridPos.y;
+ r = Math.sqrt(x * x + y * y);
+ phi = Math.atan2(y, x);
+ if (phi < 0) phi += Math.PI * 2;
+ if (r > 75) {
+ r = 75;
+ gridPos.x = 69 - (75 * Math.cos(phi));
+ gridPos.y = 69 - (75 * Math.sin(phi));
+ }
+ saturation = clamp(r / 0.75, 0, 100);
+ hue = clamp(phi * 180 / Math.PI, 0, 360);
+ brightness = clamp(100 - Math.floor(sliderPos.y * (100 / sliderHeight)), 0, 100);
+ hex = hsb2hex({
+ h: hue,
+ s: saturation,
+ b: brightness
+ });
+
+ // Update UI
+ slider.css({
+ backgroundColor: hsb2hex({
+ h: hue,
+ s: saturation,
+ b: 100
+ })
+ });
+ break;
+
+ case 'saturation':
+ // Calculate hue, saturation, and brightness
+ hue = clamp(parseInt(gridPos.x * (360 / gridWidth), 10), 0, 360);
+ saturation = clamp(100 - Math.floor(sliderPos.y * (100 / sliderHeight)), 0, 100);
+ brightness = clamp(100 - Math.floor(gridPos.y * (100 / gridHeight)), 0, 100);
+ hex = hsb2hex({
+ h: hue,
+ s: saturation,
+ b: brightness
+ });
+
+ // Update UI
+ slider.css({
+ backgroundColor: hsb2hex({
+ h: hue,
+ s: 100,
+ b: brightness
+ })
+ });
+ grid.find('.cp-grid-inner').css({ opacity: saturation / 100 });
+ break;
+
+ case 'brightness':
+ // Calculate hue, saturation, and brightness
+ hue = clamp(parseInt(gridPos.x * (360 / gridWidth), 10), 0, 360);
+ saturation = clamp(100 - Math.floor(gridPos.y * (100 / gridHeight)), 0, 100);
+ brightness = clamp(100 - Math.floor(sliderPos.y * (100 / sliderHeight)), 0, 100);
+ hex = hsb2hex({
+ h: hue,
+ s: saturation,
+ b: brightness
+ });
+
+ // Update UI
+ slider.css({
+ backgroundColor: hsb2hex({
+ h: hue,
+ s: saturation,
+ b: 100
+ })
+ });
+ grid.find('.cp-grid-inner').css({ opacity: 1 - (brightness / 100) });
+ break;
+
+ default:
+ // Calculate hue, saturation, and brightness
+ hue = clamp(360 - parseInt(sliderPos.y * (360 / sliderHeight), 10), 0, 360);
+ saturation = clamp(Math.floor(gridPos.x * (100 / gridWidth)), 0, 100);
+ brightness = clamp(100 - Math.floor(gridPos.y * (100 / gridHeight)), 0, 100);
+ hex = hsb2hex({
+ h: hue,
+ s: saturation,
+ b: brightness
+ });
+
+ // Update UI
+ grid.css({
+ backgroundColor: hsb2hex({
+ h: hue,
+ s: 100,
+ b: 100
+ })
+ });
+ break;
+
+ }
+ }
+
+ // Handle opacity
+ if (target.hasClass('cp-opacity-slider')) {
+ this.opacity = parseFloat(1 - (opacityPos.y / opacitySliderHeight)).toFixed(2);
+ }
+
+ // Adjust case
+ input.val(this.getValue(hex));
+
+ // Handle change event
+ this.element.trigger('change._grav_colorpicker', [this.element, hex, this.opacity]);
+
+ }
+
+ getValue(hex) {
+ if (this.opacity === 1) { return hex; }
+ let rgb = hex2rgb(hex);
+
+ return 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + this.opacity + ')';
+ }
+
+ bound(name) {
+ let bound = this._bound || (this._bound = {});
+ return bound[name] || (bound[name] = bind(this[name], this));
+ }
+}
+
+export let Instance = new ColorpickerField('[data-grav-colorpicker]');
diff --git a/user/plugins/admin/themes/grav/app/forms/fields/cron.js b/user/plugins/admin/themes/grav/app/forms/fields/cron.js
new file mode 100644
index 0000000..19d136d
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/fields/cron.js
@@ -0,0 +1,145 @@
+import $ from 'jquery';
+import '../../utils/cron-ui';
+import { translations } from 'grav-config';
+
+export default class CronField {
+ constructor() {
+ this.items = $();
+
+ $('[data-grav-field="cron"]').each((index, cron) => this.addCron(cron));
+ $('body').on('mutation._grav', this._onAddedNodes.bind(this));
+ }
+
+ addCron(cron) {
+ cron = $(cron);
+ this.items = this.items.add(cron);
+
+ cron.find('.cron-selector').each((index, container) => {
+ container = $(container);
+ const input = container.closest('[data-grav-field]').find('input');
+
+ container.jqCron({
+ numeric_zero_pad: true,
+ enabled_minute: true,
+ multiple_dom: true,
+ multiple_month: true,
+ multiple_mins: true,
+ multiple_dow: true,
+ multiple_time_hours: true,
+ multiple_time_minutes: true,
+ default_period: 'hour',
+ default_value: input.val() || '* * * * *',
+ no_reset_button: false,
+ bind_to: input,
+ bind_method: {
+ set: function($element, value) {
+ $element.val(value);
+ }
+ },
+ texts: {
+ en: {
+ empty: translations.GRAV_CORE['CRON.EVERY'],
+ empty_minutes: translations.GRAV_CORE['CRON.EVERY'],
+ empty_time_hours: translations.GRAV_CORE['CRON.EVERY_HOUR'],
+ empty_time_minutes: translations.GRAV_CORE['CRON.EVERY_MINUTE'],
+ empty_day_of_week: translations.GRAV_CORE['CRON.EVERY_DAY_OF_WEEK'],
+ empty_day_of_month: translations.GRAV_CORE['CRON.EVERY_DAY_OF_MONTH'],
+ empty_month: translations.GRAV_CORE['CRON.EVERY_MONTH'],
+ name_minute: translations.GRAV_CORE['NICETIME.MINUTE'],
+ name_hour: translations.GRAV_CORE['NICETIME.HOUR'],
+ name_day: translations.GRAV_CORE['NICETIME.DAY'],
+ name_week: translations.GRAV_CORE['NICETIME.WEEK'],
+ name_month: translations.GRAV_CORE['NICETIME.MONTH'],
+ name_year: translations.GRAV_CORE['NICETIME.YEAR'],
+ text_period: translations.GRAV_CORE['CRON.TEXT_PERIOD'],
+ text_mins: translations.GRAV_CORE['CRON.TEXT_MINS'],
+ text_time: translations.GRAV_CORE['CRON.TEXT_TIME'],
+ text_dow: translations.GRAV_CORE['CRON.TEXT_DOW'],
+ text_month: translations.GRAV_CORE['CRON.TEXT_MONTH'],
+ text_dom: translations.GRAV_CORE['CRON.TEXT_DOM'],
+ error1: translations.GRAV_CORE['CRON.ERROR1'],
+ error2: translations.GRAV_CORE['CRON.ERROR2'],
+ error3: translations.GRAV_CORE['CRON.ERROR3'],
+ error4: translations.GRAV_CORE['CRON.ERROR4'],
+ weekdays: translations.GRAV_CORE['DAYS_OF_THE_WEEK'],
+ months: translations.GRAV_CORE['MONTHS_OF_THE_YEAR']
+ }
+ }
+ });
+ });
+ }
+
+ _onAddedNodes(event, target/* , record, instance */) {
+ let crons = $(target).find('[data-grav-field="cron"]');
+ if (!crons.length) { return; }
+
+ crons.each((index, list) => {
+ list = $(list);
+ if (!~this.items.index(list)) {
+ this.addCron(list);
+ }
+ });
+ }
+}
+
+export let Instance = new CronField();
+
+/*
+
+// cron-selector
+$(document).ready(function() {
+
+ $('.cron-selector').each(function(index, wrapper) {
+ wrapper = $(wrapper);
+ const input = wrapper.closest('[data-grav-field]').find('input');
+
+ wrapper.jqCron({
+ enabled_minute: true,
+ multiple_dom: true,
+ multiple_month: true,
+ multiple_mins: true,
+ multiple_dow: true,
+ multiple_time_hours: true,
+ multiple_time_minutes: true,
+ default_period: 'week',
+ default_value: input.val() || '3 * * * *',
+ no_reset_button: false,
+ bind_to: input,
+ bind_method: {
+ set: function($element, value) {
+ $element.val(value);
+ }
+ },
+ texts: {
+ en: {
+ empty: 'every',
+ empty_minutes: 'every',
+ empty_time_hours: 'every hour',
+ empty_time_minutes: 'every minute',
+ empty_day_of_week: 'every day of the week',
+ empty_day_of_month: 'every day of the month',
+ empty_month: 'every month',
+ name_minute: 'minute',
+ name_hour: 'hour',
+ name_day: 'day',
+ name_week: 'week',
+ name_month: 'month',
+ name_year: 'year',
+ 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',
+ weekdays: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
+ months: ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']
+ }
+ }
+ });
+ });
+});
+*/
diff --git a/user/plugins/admin/themes/grav/app/forms/fields/datetime.js b/user/plugins/admin/themes/grav/app/forms/fields/datetime.js
new file mode 100644
index 0000000..db2b8a4
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/fields/datetime.js
@@ -0,0 +1,63 @@
+import $ from 'jquery';
+import { config } from 'grav-config';
+import 'eonasdan-bootstrap-datetimepicker';
+
+export default class DateTimeField {
+
+ get defaults() {
+ return {
+ showTodayButton: true,
+ showClear: true,
+ locale: config.language || 'en',
+ icons: {
+ time: 'fa fa-clock-o',
+ date: 'fa fa-calendar-o',
+ up: 'fa fa-chevron-up',
+ down: 'fa fa-chevron-down',
+ previous: 'fa fa-chevron-left',
+ next: 'fa fa-chevron-right',
+ today: 'fa fa-bullseye',
+ clear: 'fa fa-trash-o',
+ close: 'fa fa-remove'
+ }
+ };
+ }
+
+ constructor(options) {
+ this.items = $();
+ this.options = Object.assign({}, this.defaults, options);
+
+ $('[data-grav-datetime]').each((index, field) => this.addItem(field));
+ $('body').on('mutation._grav', this._onAddedNodes.bind(this));
+
+ }
+
+ addItem(list) {
+ list = $(list);
+ this.items = this.items.add(list);
+
+ if (list.data('DateTimePicker')) { return; }
+
+ let options = Object.assign({}, this.options, list.data('grav-datetime') || {});
+ list.datetimepicker(options).on('dp.show dp.update', this._disableDecades);
+ list.siblings('.field-icons').on('click', () => list.mousedown().focus());
+ }
+
+ _onAddedNodes(event, target/* , record, instance */) {
+ let fields = $(target).find('[data-grav-datetime]');
+ if (!fields.length) { return; }
+
+ fields.each((index, field) => {
+ field = $(field);
+ if (!~this.items.index(field)) {
+ this.addItem(field);
+ }
+ });
+ }
+
+ _disableDecades() {
+ $('.datepicker-years .picker-switch').removeAttr('title').on('click', (e) => e.stopPropagation());
+ }
+}
+
+export let Instance = new DateTimeField();
diff --git a/user/plugins/admin/themes/grav/app/forms/fields/editor.js b/user/plugins/admin/themes/grav/app/forms/fields/editor.js
new file mode 100644
index 0000000..f958a17
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/fields/editor.js
@@ -0,0 +1,237 @@
+import $ from 'jquery';
+import Buttons, { strategies as buttonStrategies } from './editor/buttons';
+import codemirror from 'codemirror';
+import { watch } from 'watchjs';
+import jsyaml from 'js-yaml';
+
+global.jsyaml = jsyaml;
+
+// Modes
+import 'codemirror/mode/css/css';
+import 'codemirror/mode/gfm/gfm';
+import 'codemirror/mode/htmlmixed/htmlmixed';
+import 'codemirror/mode/javascript/javascript';
+import 'codemirror/mode/markdown/markdown';
+import 'codemirror/mode/php/php';
+import 'codemirror/mode/sass/sass';
+import 'codemirror/mode/twig/twig';
+import 'codemirror/mode/xml/xml';
+import 'codemirror/mode/yaml/yaml';
+
+// Add-ons
+import 'codemirror/addon/edit/continuelist';
+import 'codemirror/addon/mode/overlay';
+import 'codemirror/addon/selection/active-line';
+import 'codemirror/addon/lint/lint';
+import 'codemirror/addon/lint/lint.css';
+import 'codemirror/addon/lint/css-lint';
+import 'codemirror/addon/lint/javascript-lint';
+import 'codemirror/addon/lint/json-lint';
+import 'codemirror/addon/lint/yaml-lint';
+
+let IS_MOUSEDOWN = false;
+const ThemesMap = ['paper'];
+const Defaults = {
+ codemirror: {
+ mode: 'htmlmixed',
+ theme: 'paper',
+ lineWrapping: true,
+ dragDrop: true,
+ autoCloseTags: true,
+ matchTags: true,
+ autoCloseBrackets: true,
+ matchBrackets: true,
+ indentUnit: 4,
+ indentWithTabs: false,
+ tabSize: 4,
+ hintOptions: { completionSingle: false },
+ extraKeys: { 'Enter': 'newlineAndIndentContinueMarkdownList' }
+ }
+};
+
+export default class EditorField {
+ constructor(options) {
+ let body = $('body');
+ this.editors = $();
+ this.options = Object.assign({}, Defaults, options);
+ this.buttons = Buttons;
+ this.buttonStrategies = buttonStrategies;
+
+ watch(Buttons, (/* key, modifier, prev, next */) => {
+ this.editors.each((index, editor) => $(editor).data('toolbar').renderButtons());
+ });
+
+ $('[data-grav-editor]').each((index, editor) => this.addEditor(editor));
+
+ $(() => { body.trigger('grav-editor-ready'); });
+ body.on('mutation._grav', this._onAddedNodes.bind(this));
+
+ body.on('mouseup._grav', () => {
+ if (!IS_MOUSEDOWN) { return true; }
+ body.unbind('mousemove._grav');
+ IS_MOUSEDOWN = false;
+ });
+ body.on('mousedown._grav', '.grav-editor-resizer', (event) => {
+ event && event.preventDefault();
+ IS_MOUSEDOWN = true;
+
+ let target = $(event.currentTarget);
+ let container = target.siblings('.grav-editor-content');
+ let editor = container.find('.CodeMirror');
+ let codemirror = container.find('textarea').data('codemirror');
+
+ body.on('mousemove._grav', (event) => {
+ editor.css('height', Math.max(100, event.pageY - container.offset().top));
+ codemirror.refresh();
+ });
+ });
+ }
+
+ addButton(button, options) {
+ if (options && (options.before || options.after)) {
+ let index = this.buttons.navigation.findIndex((obj) => {
+ let key = Object.keys(obj).shift();
+ return obj[key].identifier === (options.before || options.after);
+ });
+
+ if (!~index) {
+ options = 'end';
+ } else {
+ this.buttons.navigation.splice(options.before ? index : index + 1, 0, button);
+ }
+
+ }
+
+ if (options === 'start') { this.buttons.navigation.splice(0, 0, button); }
+ if (!options || options === 'end') { this.buttons.navigation.push(button); }
+ }
+
+ addEditor(textarea) {
+ textarea = $(textarea);
+ let options = Object.assign(
+ {},
+ this.options.codemirror,
+ textarea.data('grav-editor').codemirror
+ );
+ let theme = options.theme || 'paper';
+
+ this.editors = this.editors.add(textarea);
+ if (theme && !~ThemesMap.indexOf(theme)) {
+ ThemesMap.push(theme);
+ let themeCSS = `https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.12.0/theme/${theme}.min.css`;
+ $('head').append($('
').attr('href', themeCSS));
+ }
+
+ if (options.mode === 'yaml') {
+ Object.assign(options.extraKeys, { Tab: function(cm) { cm.replaceSelection(' ', 'end'); }});
+ }
+
+ let editor = codemirror.fromTextArea(textarea.get(0), options);
+ textarea.data('codemirror', editor);
+ textarea.data('toolbar', new Toolbar(textarea));
+ textarea.addClass('code-mirrored');
+
+ if (options.toolbar === false) {
+ textarea.data('toolbar').ui.navigation.addClass('grav-editor-hide-toolbar');
+ }
+
+ editor.on('change', () => editor.save());
+ }
+
+ _onAddedNodes(event, target/* , record, instance */) {
+ let editors = $(target).find('[data-grav-editor]');
+ if (!editors.length) { return; }
+
+ editors.each((index, editor) => {
+ editor = $(editor);
+ if (!~this.editors.index(editor)) {
+ this.addEditor(editor);
+ }
+ });
+ }
+}
+
+export class Toolbar {
+ static templates() {
+ return {
+ navigation: `
+
+ `
+ };
+ }
+
+ constructor(editor) {
+ this.editor = $(editor);
+ this.codemirror = this.editor.data('codemirror');
+ this.buttons = Buttons.navigation;
+ this.ui = {
+ navigation: $(Toolbar.templates().navigation)
+ };
+
+ this.editor.parent('.grav-editor-content')
+ .before(this.ui.navigation)
+ .after(this.ui.states);
+
+ this.renderButtons();
+ }
+
+ renderButtons() {
+ let map = { 'actions': 'navigation', 'modes': 'states'};
+
+ ['actions', 'modes'].forEach((type) => {
+ this.ui.navigation.find(`.grav-editor-${type}`).empty().append('
');
+ Buttons[map[type]].forEach((button) => this.renderButton(button, type));
+ });
+ }
+
+ renderButton(button, type, location = null) {
+ Object.keys(button).forEach((key) => {
+ let obj = button[key];
+ if (!obj.modes) { obj.modes = []; }
+ if (!~this.codemirror.options.ignore.indexOf(key) && (!obj.modes.length || obj.modes.indexOf(this.codemirror.options.mode) > -1)) {
+ let hint = obj.title ? `data-hint="${obj.title}"` : '';
+ let element = $(`
${obj.label} `);
+ (location || this.ui.navigation.find(`.grav-editor-${type} ul:not(.dropdown-menu)`)).append(element);
+
+ if (obj.shortcut) {
+ this.addShortcut(obj.identifier, obj.shortcut, element);
+ }
+
+ obj.action && obj.action.call(obj.action, {
+ codemirror: this.codemirror,
+ button: element,
+ textarea: this.editor,
+ ui: this.ui
+ });
+
+ if (obj.children) {
+ let childrenContainer = $('');
+ element.addClass('button-group').find('a').wrap('
');
+ element.find('a').append('
');
+ element.append(childrenContainer);
+ obj.children.forEach((child) => this.renderButton(child, type, childrenContainer));
+ }
+ }
+ });
+ }
+
+ addShortcut(identifier, shortcut, element) {
+ let map = {};
+ if (!Array.isArray(shortcut)) {
+ shortcut = [shortcut];
+ }
+
+ shortcut.forEach((key) => {
+ map[key] = () => {
+ element.trigger(`click.editor.${identifier}`, [this.codemirror]);
+ };
+ });
+
+ this.codemirror.addKeyMap(map);
+ }
+}
+
+export let Instance = new EditorField();
diff --git a/user/plugins/admin/themes/grav/app/forms/fields/editor/buttons.js b/user/plugins/admin/themes/grav/app/forms/fields/editor/buttons.js
new file mode 100644
index 0000000..56a18fd
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/fields/editor/buttons.js
@@ -0,0 +1,416 @@
+import $ from 'jquery';
+import { config, translations } from 'grav-config';
+import request from '../../../utils/request';
+
+let replacer = ({ name, replace, codemirror, button, mode = 'replaceSelections', runner }) => {
+ button.on(`click.editor.${name}`, () => {
+ strategies[mode]({ token: '$1', template: replace, codemirror, runner });
+ });
+};
+
+export let strategies = {
+ replaceSelections({ template, token, codemirror, runner }) {
+ let replacements = [];
+ let ranges = [];
+ let selections = codemirror.getSelections();
+ let list = codemirror.listSelections();
+ let accumulator = {};
+
+ selections.forEach((selection, index) => {
+ let markup = template.replace(token, selection);
+
+ let cursor = markup.indexOf('$cur');
+ let { line, ch } = list[index].anchor;
+
+ markup = markup.replace('$cur', '');
+ markup = runner ? runner(selection, markup, list) : markup;
+ replacements.push(markup);
+
+ if (!accumulator[line]) { accumulator[line] = 0; }
+
+ ch += accumulator[line] + (cursor === -1 ? markup.length : cursor);
+ let range = { ch, line };
+
+ ranges.push({ anchor: range, head: range });
+ accumulator[line] += markup.length - selection.length;
+ });
+
+ codemirror.replaceSelections(replacements);
+ codemirror.setSelections(ranges);
+ codemirror.focus();
+ },
+ replaceLine({ template, token, codemirror, runner }) {
+ let list = codemirror.listSelections();
+ let range;
+
+ list.forEach((selection) => {
+ let lines = {
+ min: Math.min(selection.anchor.line, selection.head.line),
+ max: Math.max(selection.anchor.line, selection.head.line)
+ };
+
+ codemirror.eachLine(lines.min, lines.max + 1, (handler) => {
+ let markup = template.replace(token, handler.text);
+ let line = codemirror.getLineNumber(handler);
+ markup = runner ? runner(handler, markup) : markup;
+ codemirror.replaceRange(markup, { line, ch: 0 }, { line, ch: markup.length });
+ range = { line, ch: markup.length };
+ });
+ });
+
+ codemirror.setSelection(range, range, 'end');
+ codemirror.focus();
+ },
+ replaceRange() {}
+};
+
+const flipDisabled = (codemirror, button, type) => {
+ let hasHistory = codemirror.historySize()[type];
+ let element = button.find('a');
+ button[hasHistory ? 'removeClass' : 'addClass']('button-disabled');
+
+ if (!hasHistory) {
+ element.attr('title-disabled', element.attr('title'));
+ element.attr('data-hint-disabled', element.attr('data-hint'));
+ element.removeAttr('title').removeAttr('data-hint');
+ } else {
+ element.attr('title', element.attr('title-disabled'));
+ element.attr('data-hint', element.attr('data-hint-disabled'));
+ element.removeAttr('title-disabled').removeAttr('data-hint-disabled');
+ }
+};
+
+export default {
+ navigation: [
+ {
+ undo: {
+ identifier: 'undo',
+ title: translations.PLUGIN_ADMIN.UNDO,
+ label: '
',
+ modes: [],
+ action({ codemirror, button, textarea}) {
+ button.addClass('button-disabled');
+ codemirror.on('change', () => flipDisabled(codemirror, button, 'undo'));
+ button.on('click.editor.undo', () => {
+ codemirror.undo();
+ });
+ }
+ }
+ },
+ {
+ redo: {
+ identifier: 'redo',
+ title: translations.PLUGIN_ADMIN.REDO,
+ label: '
',
+ modes: [],
+ action({ codemirror, button, textarea}) {
+ button.addClass('button-disabled');
+ codemirror.on('change', () => flipDisabled(codemirror, button, 'redo'));
+ button.on('click.editor.redo', () => {
+ codemirror.redo();
+ });
+ }
+ }
+ },
+ {
+ headers: {
+ identifier: 'headers',
+ title: translations.PLUGIN_ADMIN.HEADERS,
+ label: '',
+ modes: ['gfm', 'markdown'],
+ children: [
+ {
+ h1: {
+ identifier: 'h1',
+ label: '1',
+ modes: ['gfm', 'markdown'],
+ action({ codemirror, button, textarea }) {
+ replacer({ name: 'h1', replace: '# $1', codemirror, button, mode: 'replaceLine' });
+ }
+ }
+ },
+ {
+ h2: {
+ identifier: 'h2',
+ label: '2',
+ modes: ['gfm', 'markdown'],
+ action({ codemirror, button, textarea }) {
+ replacer({ name: 'h2', replace: '## $1', codemirror, button, mode: 'replaceLine' });
+ }
+ }
+ },
+ {
+ h3: {
+ identifier: 'h3',
+ label: '3',
+ modes: ['gfm', 'markdown'],
+ action({ codemirror, button, textarea }) {
+ replacer({ name: 'h3', replace: '### $1', codemirror, button, mode: 'replaceLine' });
+ }
+ }
+ },
+ {
+ h4: {
+ identifier: 'h4',
+ label: '4',
+ modes: ['gfm', 'markdown'],
+ action({ codemirror, button, textarea }) {
+ replacer({ name: 'h4', replace: '#### $1', codemirror, button, mode: 'replaceLine' });
+ }
+ }
+ },
+ {
+ h5: {
+ identifier: 'h5',
+ label: '5',
+ modes: ['gfm', 'markdown'],
+ action({ codemirror, button, textarea }) {
+ replacer({ name: 'h5', replace: '##### $1', codemirror, button, mode: 'replaceLine' });
+ }
+ }
+ },
+ {
+ h6: {
+ identifier: 'h6',
+ label: '6',
+ modes: ['gfm', 'markdown'],
+ action({ codemirror, button, textarea }) {
+ replacer({ name: 'h6', replace: '###### $1', codemirror, button, mode: 'replaceLine' });
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ bold: {
+ identifier: 'bold',
+ title: translations.PLUGIN_ADMIN.BOLD,
+ label: '
',
+ modes: ['gfm', 'markdown'],
+ shortcut: ['Ctrl-B', 'Cmd-B'],
+ action({ codemirror, button, textarea }) {
+ replacer({ name: 'bold', replace: '**$1$cur**', codemirror, button });
+ }
+ }
+ }, {
+ italic: {
+ identifier: 'italic',
+ title: translations.PLUGIN_ADMIN.ITALIC,
+ label: '
',
+ modes: ['gfm', 'markdown'],
+ shortcut: ['Ctrl-I', 'Cmd-I'],
+ action({ codemirror, button, textarea }) {
+ replacer({ name: 'italic', replace: '_$1$cur_', codemirror, button });
+ }
+ }
+ }, {
+ strike: {
+ identifier: 'strike',
+ title: translations.PLUGIN_ADMIN.STRIKETHROUGH,
+ label: '
',
+ modes: ['gfm', 'markdown'],
+ action({ codemirror, button, textarea }) {
+ replacer({ name: 'strike', replace: '~~$1$cur~~', codemirror, button });
+ }
+ }
+ }, {
+ delimiter: {
+ identifier: 'delimiter',
+ title: translations.PLUGIN_ADMIN.SUMMARY_DELIMITER,
+ label: '
',
+ modes: ['gfm', 'markdown'],
+ action({ codemirror, button, textarea }) {
+ replacer({ name: 'delimiter', replace: `${config.site.delimiter}$1`, codemirror, button, mode: 'replaceLine' });
+ }
+ }
+ }, {
+ link: {
+ identifier: 'link',
+ title: translations.PLUGIN_ADMIN.LINK,
+ label: '
',
+ modes: ['gfm', 'markdown'],
+ shortcut: ['Ctrl-K', 'Cmd-K'],
+ action({ codemirror, button, textarea }) {
+ replacer({ name: 'link', replace: '[$1]($cur)', codemirror, button });
+ }
+ }
+ }, {
+ image: {
+ identifier: 'image',
+ title: translations.PLUGIN_ADMIN.IMAGE,
+ label: '
',
+ modes: ['gfm', 'markdown'],
+ action({ codemirror, button, textarea }) {
+ replacer({ name: 'image', replace: '', codemirror, button });
+ }
+ }
+ }, {
+ blockquote: {
+ identifier: 'blockquote',
+ title: translations.PLUGIN_ADMIN.BLOCKQUOTE,
+ label: '
',
+ modes: ['gfm', 'markdown'],
+ action({ codemirror, button, textarea }) {
+ replacer({ name: 'blockquote', replace: '> $1', codemirror, button, mode: 'replaceLine' });
+ }
+ }
+ }, {
+ listUl: {
+ identifier: 'listUl',
+ title: translations.PLUGIN_ADMIN.UNORDERED_LIST,
+ label: '
',
+ modes: ['gfm', 'markdown'],
+ action({ codemirror, button, textarea }) {
+ replacer({ name: 'listUl', replace: '* $1', codemirror, button, mode: 'replaceLine' });
+ }
+ }
+ }, {
+ listOl: {
+ identifier: 'listOl',
+ title: translations.PLUGIN_ADMIN.ORDERED_LIST,
+ label: '
',
+ modes: ['gfm', 'markdown'],
+ action({ codemirror, button, textarea }) {
+ replacer({
+ name: 'listOl',
+ replace: '. $1',
+ codemirror,
+ button,
+ mode: 'replaceLine',
+ runner: function(line, markup) {
+ let lineNo = codemirror.getLineNumber(line);
+ let previousLine = codemirror.getLine(lineNo - 1) || '';
+ let match = previousLine.match(/^(\d+)\./);
+ let prefix = 1 + (match ? Number(match[1]) : 0);
+
+ return `${prefix}${markup}`;
+ }
+ });
+ }
+ }
+ }
+ ],
+ states: [{
+ code: {
+ identifier: 'editor',
+ title: translations.PLUGIN_ADMIN.EDITOR,
+ label: '
',
+ action({ codemirror, button, textarea, ui }) {
+ if (textarea.data('grav-editor-mode') === 'editor') {
+ button.addClass('editor-active');
+ }
+
+ button.on('click.states.editor', () => {
+ button.siblings().removeClass('editor-active');
+ button.addClass('editor-active');
+ textarea.data('grav-editor-mode', 'editor');
+ let previewContainer = textarea.data('grav-editor-preview-container');
+ let content = textarea.parent('.grav-editor-content');
+
+ content.addClass('is-active');
+ ui.navigation.find('.grav-editor-actions').css('visibility', 'visible');
+ if (previewContainer) {
+ previewContainer.removeClass('is-active');
+ }
+ });
+ }
+ }
+ }, {
+ preview: {
+ identifier: 'preview',
+ title: translations.PLUGIN_ADMIN.PREVIEW,
+ label: '
',
+ modes: ['gfm', 'markdown'],
+ action({ codemirror, button, textarea, ui }) {
+ if (textarea.data('grav-editor-mode') === 'preview') {
+ button.addClass('editor-active');
+ }
+ button.on('click.states.preview', () => {
+ let previewContainer = textarea.data('grav-editor-preview-container');
+ let content = textarea.parent('.grav-editor-content');
+ button.siblings().removeClass('editor-active');
+ button.addClass('editor-active');
+ textarea.data('grav-editor-mode', 'preview');
+
+ if (!previewContainer) {
+ previewContainer = $('
');
+ content.after(previewContainer);
+ textarea.data('grav-editor-preview-container', previewContainer);
+ }
+
+ previewContainer.css({ height: content.height() });
+ previewContainer.addClass('is-active');
+ content.removeClass('is-active');
+ ui.navigation.find('.grav-editor-actions').css('visibility', 'hidden');
+
+ let url = `${textarea.data('grav-urlpreview')}/task${config.param_sep}processmarkdown`;
+ let params = textarea.closest('form').serializeArray();
+ let body = {};
+ params.map((obj) => { body[obj.name] = obj.value; });
+ request(url, {
+ method: 'post',
+ body
+ }, (response) => previewContainer.html(response.preview));
+ });
+ }
+ }
+ }, {
+ fullscreen: {
+ identifier: 'fullscreen',
+ title: translations.PLUGIN_ADMIN.FULLSCREEN,
+ label: '
',
+ action({ codemirror, button, textarea }) {
+ button.on('click.editor.fullscreen', () => {
+ let container = textarea.closest('.grav-editor');
+ let wrapper = codemirror.getWrapperElement();
+ let contentWrapper = $('.content-wrapper');
+
+ if (!container.hasClass('grav-editor-fullscreen')) {
+ textarea.data('fullScreenRestore', {
+ scrollTop: global.pageYOffset,
+ scrollLeft: global.pageXOffset,
+ width: wrapper.style.width,
+ height: wrapper.style.height
+ });
+
+ wrapper.style.width = '';
+ wrapper.style.height = textarea.parent('.grav-editor-content').height() + 'px';
+ global.document.documentElement.style.overflow = 'hidden';
+
+ let hints = container.find('.grav-editor-toolbar .hint--top');
+
+ if (hints) {
+ hints.removeClass('hint--top').addClass('hint--bottom');
+ $(hints[hints.length - 1]).addClass('hint--bottom-left');
+ }
+ if (contentWrapper) { contentWrapper.css('overflow', 'visible'); }
+ } else {
+ global.document.documentElement.style.overflow = '';
+ let state = textarea.data('fullScreenRestore');
+
+ wrapper.style.width = state.width;
+ wrapper.style.height = state.height;
+ global.scrollTo(state.scrollLeft, state.scrollTop);
+
+ let hints = container.find('.grav-editor-toolbar .hint--bottom');
+
+ if (hints) {
+ hints.removeClass('hint--bottom').addClass('hint--top');
+ $(hints[hints.length - 1]).removeClass('hint--bottom-left');
+ }
+ if (contentWrapper) { contentWrapper.css('overflow', 'auto'); }
+ }
+
+ container.toggleClass('grav-editor-fullscreen');
+
+ setTimeout(() => {
+ codemirror.refresh();
+ // this.preview.parent().css('height', this.code.height());
+ $(global).trigger('resize');
+ }, 5);
+ });
+ }
+ }
+ }]
+};
diff --git a/user/plugins/admin/themes/grav/app/forms/fields/filepicker.js b/user/plugins/admin/themes/grav/app/forms/fields/filepicker.js
new file mode 100644
index 0000000..eb844cd
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/fields/filepicker.js
@@ -0,0 +1,140 @@
+import $ from 'jquery';
+import { config, uri_params } from 'grav-config';
+import request from '../../utils/request';
+
+// const insertTextAt = (string, index, text) => [string.slice(0, index), text, string.slice(index)].join('');
+
+export default class FilePickerField {
+
+ constructor(options) {
+ this.items = $();
+ this.options = Object.assign({}, this.defaults, options);
+
+ $('[data-grav-filepicker]').each((index, element) => this.addItem(element));
+ $('body').on('mutation._grav', this._onAddedNodes.bind(this));
+ }
+
+ _onAddedNodes(event, target/* , record, instance */) {
+ let fields = $(target).find('[data-grav-filepicker]');
+ if (!fields.length) { return; }
+
+ fields.each((index, field) => {
+ field = $(field);
+ if (!~this.items.index(field)) {
+ this.addItem(field);
+ }
+ });
+ }
+
+ addItem(element) {
+ element = $(element);
+ this.items = this.items.add(element);
+
+ let tag = element.prop('tagName').toLowerCase();
+ let isInput = tag === 'input' || tag === 'select';
+
+ let field = (isInput ? element : element.find('input, select'));
+
+ let folder = '';
+ let thumbs = {};
+
+ if (!field.length || field.get(0).selectize) { return; }
+
+ let getData = function getData(field, callback, mode = 'all') {
+ let url = config.current_url + `.json/task${config.param_sep}getFilesInFolder`;
+ let parent = field.closest('[data-grav-filepicker]');
+ let name = parent.data('name');
+ let value = parent.data('value');
+ let params = JSON.stringify(uri_params || '{}');
+
+ request(url, {
+ method: 'post',
+ body: { name, params }
+ }, (response) => {
+ if (typeof response.files === 'undefined') {
+ return;
+ }
+
+ let data = [];
+ thumbs = response.thumbs || {};
+
+ for (let i = 0; i < response.files.length; i++) {
+ if (mode === 'selected' && response.files[i] !== value) { continue; }
+ data.push({ 'name': response.files[i], 'status': 'available', thumb: thumbs[response.files[i]] || '' });
+ }
+
+ for (let i = 0; i < response.pending.length; i++) {
+ if (mode === 'selected' && response.pending[i] !== value) { continue; }
+ data.push({ 'name': response.pending[i], 'status': 'pending', thumb: thumbs[response.pending[i]] || '' });
+ }
+
+ folder = response.folder;
+ callback(data, value);
+ });
+ };
+
+ let imagesPreview = field.closest('[data-preview-images]').length > 0;
+ let selectedIsRendered = false;
+
+ let renderOption = function renderOption(item, escape) {
+ let image = '';
+ if (imagesPreview && folder && (!item.status || item.status === 'available') && item.name.match(/\.(jpg|jpeg|png|gif)$/i)) {
+ // const fallback2x = insertTextAt(`${config.base_url_relative}/../${folder}/${item.name}`, -4, '@2x');
+ // const fallback3x = insertTextAt(`${config.base_url_relative}/../${folder}/${item.name}`, -4, '@3x');
+ const source = thumbs[item.name] || `${config.base_url_relative}/../${folder}/${item.name}`;
+
+ // onerror="if(this.src==='${fallback2x}'){this.src='${fallback3x}';}else{this.src='${fallback2x}'}"
+ image = `
`;
+ }
+
+ return `
+
+ ${image} ${escape(item.name)}
+
+
`;
+ };
+
+ field.selectize({
+ plugins: ['required-fix'],
+ valueField: 'name',
+ labelField: 'name',
+ searchField: 'name',
+ optgroups: [
+ {$order: 1, value: 'pending', label: 'Pending'},
+ {$order: 2, value: 'available', label: 'Available'}
+ ],
+ optgroupField: 'status',
+ // lockOptgroupOrder: true,
+ create: false,
+ preload: false, // 'focus',
+ render: {
+ option: function(item, escape) {
+ return renderOption(item, escape);
+ },
+
+ item: function(item, escape) {
+ return renderOption(item, escape);
+ }
+ },
+
+ onInitialize: function() {
+ this.load((callback) => getData(field, (data) => callback(data), 'selected'));
+ },
+
+ onLoad: function(/* data */) {
+ if (!selectedIsRendered) {
+ let name = this.getValue();
+ this.updateOption(name, { name });
+
+ selectedIsRendered = true;
+ }
+ },
+
+ onFocus: function() {
+ this.load((callback) => getData(field, (data) => callback(data)));
+ }
+ });
+ }
+}
+
+export let Instance = new FilePickerField();
diff --git a/user/plugins/admin/themes/grav/app/forms/fields/files.js b/user/plugins/admin/themes/grav/app/forms/fields/files.js
new file mode 100644
index 0000000..1c61c1b
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/fields/files.js
@@ -0,0 +1,383 @@
+import $ from 'jquery';
+import Dropzone from 'dropzone';
+import EXIF from 'exif-js';
+import request from '../../utils/request';
+import { config, translations } from 'grav-config';
+
+// translations
+const Dictionary = {
+ dictCancelUpload: translations.PLUGIN_ADMIN.DROPZONE_CANCEL_UPLOAD,
+ dictCancelUploadConfirmation: translations.PLUGIN_ADMIN.DROPZONE_CANCEL_UPLOAD_CONFIRMATION,
+ dictDefaultMessage: translations.PLUGIN_ADMIN.DROPZONE_DEFAULT_MESSAGE,
+ dictFallbackMessage: translations.PLUGIN_ADMIN.DROPZONE_FALLBACK_MESSAGE,
+ dictFallbackText: translations.PLUGIN_ADMIN.DROPZONE_FALLBACK_TEXT,
+ dictFileTooBig: translations.PLUGIN_ADMIN.DROPZONE_FILE_TOO_BIG,
+ dictInvalidFileType: translations.PLUGIN_ADMIN.DROPZONE_INVALID_FILE_TYPE,
+ dictMaxFilesExceeded: translations.PLUGIN_ADMIN.DROPZONE_MAX_FILES_EXCEEDED,
+ dictRemoveFile: translations.PLUGIN_ADMIN.DROPZONE_REMOVE_FILE,
+ dictResponseError: translations.PLUGIN_ADMIN.DROPZONE_RESPONSE_ERROR
+};
+
+Dropzone.autoDiscover = false;
+Dropzone.options.gravPageDropzone = {};
+Dropzone.confirm = (question, accepted, rejected) => {
+ let doc = $(document);
+ let modalSelector = '[data-remodal-id="delete-media"]';
+
+ let removeEvents = () => {
+ doc.off('confirmation', modalSelector, accept);
+ doc.off('cancellation', modalSelector, reject);
+
+ $(modalSelector).find('.remodal-confirm').removeClass('pointer-events-disabled');
+ };
+
+ let accept = () => {
+ accepted && accepted();
+ removeEvents();
+ };
+
+ let reject = () => {
+ rejected && rejected();
+ removeEvents();
+ };
+
+ $.remodal.lookup[$(modalSelector).data('remodal')].open();
+ doc.on('confirmation', modalSelector, accept);
+ doc.on('cancellation', modalSelector, reject);
+};
+
+const DropzoneMediaConfig = {
+ timeout: 0,
+ thumbnailWidth: 200,
+ thumbnailHeight: 150,
+ addRemoveLinks: false,
+ dictDefaultMessage: translations.PLUGIN_ADMIN.DROP_FILES_HERE_TO_UPLOAD.replace(/</g, '<').replace(/>/g, '>'),
+ dictRemoveFileConfirmation: '[placeholder]',
+ previewTemplate: `
+
`.trim()
+};
+
+global.EXIF = EXIF;
+
+const ACCEPT_FUNC = function(file, done, settings) {
+ const resolution = settings.resolution;
+ if (!resolution) return done();
+
+ const reader = new FileReader();
+ let error = '';
+ const hasMin = (resolution.min && (resolution.min.width || resolution.min.height));
+ const hasMax = (resolution.max && (resolution.max.width || resolution.max.height));
+ if (hasMin || (!(settings.resizeWidth || settings.resizeHeight) && hasMax)) {
+ reader.onload = function(event) {
+ const image = new Image();
+ image.src = event.target.result;
+ image.onload = function() {
+ if (resolution.min) {
+ Object.keys(resolution.min).forEach((attr) => {
+ if (resolution.min[attr] && this[attr] < resolution.min[attr]) {
+ error += translations.PLUGIN_FORM.RESOLUTION_MIN.replace(/{{attr}}/g, attr).replace(/{{min}}/g, resolution.min[attr]);
+ }
+ });
+ }
+
+ if (!(settings.resizeWidth || settings.resizeHeight)) {
+ if (resolution.max) {
+ Object.keys(resolution.max).forEach((attr) => {
+ if (resolution.max[attr] && this[attr] > resolution.max[attr]) {
+ error += translations.PLUGIN_FORM.RESOLUTION_MAX.replace(/{{attr}}/g, attr).replace(/{{max}}/g, resolution.max[attr]);
+ }
+ });
+ }
+ }
+
+ return error ? done(error) : done();
+ };
+ };
+
+ reader.readAsDataURL(file);
+ } else {
+ return error ? done(error) : done();
+ }
+};
+
+export default class FilesField {
+ constructor({ container = '.dropzone.files-upload', options = {} } = {}) {
+ this.container = $(container);
+ if (!this.container.length) { return; }
+
+ this.urls = {};
+ this.customPost = this.container.data('filePostAdd') || {};
+ this.options = Object.assign({}, Dictionary, DropzoneMediaConfig, {
+ klass: this,
+ url: this.container.data('file-url-add') || config.current_url,
+ acceptedFiles: this.container.data('media-types'),
+ init: this.initDropzone
+ }, this.container.data('dropzone-options'), options);
+
+ this.options = Object.assign({}, this.options, {
+ accept: function(file, done) { ACCEPT_FUNC(file, done, this.options); }
+ });
+
+ this.dropzone = new Dropzone(container, this.options);
+ this.dropzone.on('complete', this.onDropzoneComplete.bind(this));
+ this.dropzone.on('success', this.onDropzoneSuccess.bind(this));
+ this.dropzone.on('removedfile', this.onDropzoneRemovedFile.bind(this));
+ this.dropzone.on('sending', this.onDropzoneSending.bind(this));
+ this.dropzone.on('error', this.onDropzoneError.bind(this));
+
+ this.container.on('mouseenter', '[data-dz-view]', (e) => {
+ const value = JSON.parse(this.container.find('[name][type="hidden"]').val() || '{}');
+ const target = $(e.currentTarget);
+ const file = target.parent('.dz-preview').find('.dz-filename');
+ const filename = encodeURI(file.text());
+
+ const URL = Object.keys(value).filter((key) => value[key].name === filename).shift();
+ target.attr('href', `${config.base_url_simple}/${URL}`);
+ });
+
+ }
+
+ initDropzone() {
+ let files = this.options.klass.container.find('[data-file]');
+ let dropzone = this;
+ if (!files.length) { return; }
+
+ files.each((index, file) => {
+ file = $(file);
+ let data = file.data('file');
+ let mock = {
+ name: data.name,
+ size: data.size,
+ type: data.type,
+ status: Dropzone.ADDED,
+ accepted: true,
+ url: this.options.url,
+ removeUrl: data.remove
+ };
+
+ dropzone.files.push(mock);
+ dropzone.options.addedfile.call(dropzone, mock);
+ if (mock.type.match(/^image\//)) {
+ dropzone.options.thumbnail.call(dropzone, mock, data.path);
+ dropzone.createThumbnailFromUrl(mock, data.path);
+ }
+
+ file.remove();
+ });
+
+ }
+
+ getURI() {
+ return this.container.data('mediaUri') || '';
+ }
+
+ onDropzoneSending(file, xhr, formData) {
+ if (Object.keys(this.customPost).length) {
+ Object.keys(this.customPost).forEach((key) => {
+ formData.append(key, this.customPost[key]);
+ });
+ } else {
+ formData.append('name', this.options.dotNotation);
+ formData.append('task', 'filesupload');
+ formData.append('uri', this.getURI());
+ }
+
+ formData.append('admin-nonce', config.admin_nonce);
+ }
+
+ onDropzoneSuccess(file, response, xhr) {
+ response = typeof response === 'string' ? JSON.parse(response) : response;
+ if (this.options.reloadPage) {
+ global.location.reload();
+ }
+
+ // store params for removing file from session before it gets saved
+ if (response.session) {
+ file.sessionParams = response.session;
+ file.removeUrl = this.options.url;
+
+ // Touch field value to force a mutation detection
+ const input = this.container.find('[name][type="hidden"]');
+ const value = input.val();
+ input.val(value + ' ');
+ }
+
+ return this.handleError({
+ file,
+ data: response,
+ mode: 'removeFile',
+ msg: `
${translations.PLUGIN_ADMIN.FILE_ERROR_UPLOAD} ${file.name}
+
${response.message} `
+ });
+ }
+
+ onDropzoneComplete(file) {
+ if (!file.accepted && !file.rejected) {
+ let data = {
+ status: 'error',
+ message: `${translations.PLUGIN_ADMIN.FILE_UNSUPPORTED}: ${file.name.match(/\..+/).join('')}`
+ };
+
+ return this.handleError({
+ file,
+ data,
+ mode: 'removeFile',
+ msg: `
${translations.PLUGIN_ADMIN.FILE_ERROR_ADD} ${file.name}
+
${data.message} `
+ });
+ }
+
+ if (this.options.reloadPage) {
+ global.location.reload();
+ }
+ }
+
+ b64_to_utf8(str) {
+ str = str.replace(/\s/g, '');
+ return decodeURIComponent(escape(window.atob(str)));
+ }
+
+ onDropzoneRemovedFile(file, ...extra) {
+ if (!file.accepted || file.rejected) { return; }
+ let url = file.removeUrl || this.urls.delete || this.options.url;
+ let path = (url || '').match(/path:(.*)\//);
+ let body = { filename: file.name, uri: this.getURI() };
+
+ if (file.sessionParams) {
+ body.task = 'filessessionremove';
+ body.session = file.sessionParams;
+ }
+
+ const customPost = this.container.data('filePostRemove') || {};
+ if (Object.keys(customPost).length) {
+ body = {};
+ Object.keys(customPost).forEach((key) => {
+ body[key] = customPost[key];
+ });
+ }
+
+ body['filename'] = file.name;
+ body['admin-nonce'] = config.admin_nonce;
+
+ request(url, { method: 'post', body }, () => {
+ if (!path) { return; }
+
+ path = this.b64_to_utf8(path[1]);
+ let input = this.container.find('[name][type="hidden"]');
+ let data = JSON.parse(input.val() || '{}');
+ delete data[path];
+ input.val(JSON.stringify(data));
+ });
+ }
+
+ onDropzoneError(file, response, xhr) {
+ let message = xhr ? response.error.message : response;
+ $(file.previewElement).find('[data-dz-errormessage]').html(message);
+
+ return this.handleError({
+ file,
+ data: { status: 'error' },
+ msg: `
${message} `
+ });
+ }
+
+ handleError(options) {
+ let { file, data, mode, msg } = options;
+ if (data.status !== 'error' && data.status !== 'unauthorized') { return; }
+
+ switch (mode) {
+ case 'addBack':
+ if (file instanceof File) {
+ this.dropzone.addFile.call(this.dropzone, file);
+ } else {
+ this.dropzone.files.push(file);
+ this.dropzone.options.addedfile.call(this.dropzone, file);
+ this.dropzone.options.thumbnail.call(this.dropzone, file, file.extras.url);
+ }
+
+ break;
+ case 'removeFile':
+ default:
+ if (~this.dropzone.files.indexOf(file)) {
+ file.rejected = true;
+ this.dropzone.removeFile.call(this.dropzone, file, { silent: true });
+ }
+
+ break;
+ }
+
+ let modal = $('[data-remodal-id="generic"]');
+ modal.find('.error-content').html(msg);
+ $.remodal.lookup[modal.data('remodal')].open();
+ }
+}
+
+export function UriToMarkdown(uri) {
+ uri = uri.replace(/@3x|@2x|@1x/, '');
+ uri = uri.replace(/\(/g, '%28');
+ uri = uri.replace(/\)/g, '%29');
+
+ return uri.match(/\.(jpe?g|png|gif|svg|mp4|webm|ogv|mov)$/i) ? `` : `[${decodeURI(uri)}](${uri})`;
+}
+
+let instances = [];
+let cache = $();
+const onAddedNodes = (event, target/* , record, instance */) => {
+ let files = $(target).find('.dropzone.files-upload');
+ if (!files.length) { return; }
+
+ files.each((index, file) => {
+ file = $(file);
+ if (!~cache.index(file)) {
+ addNode(file);
+ }
+ });
+};
+
+const addNode = (container) => {
+ container = $(container);
+ let input = container.find('input[type="file"]');
+ let settings = container.data('grav-file-settings') || {};
+
+ if (settings.accept && ~settings.accept.indexOf('*')) {
+ settings.accept = [''];
+ }
+
+ let options = {
+ url: container.data('file-url-add') || (container.closest('form').attr('action') || config.current_url) + '.json',
+ paramName: settings.paramName || 'file',
+ dotNotation: settings.name || 'file',
+ acceptedFiles: settings.accept ? settings.accept.join(',') : input.attr('accept') || container.data('media-types'),
+ maxFilesize: typeof settings.filesize !== 'undefined' ? settings.filesize : 256,
+ maxFiles: settings.limit || null,
+ resizeWidth: settings.resizeWidth || null,
+ resizeHeight: settings.resizeHeight || null,
+ resizeQuality: settings.resizeQuality || null,
+ accept: function(file, done) { ACCEPT_FUNC(file, done, settings); }
+ };
+
+ cache = cache.add(container);
+ container = container[0];
+ instances.push(new FilesField({ container, options }));
+};
+
+export let Instances = (() => {
+ $('.dropzone.files-upload').each((i, container) => addNode(container));
+ $('body').on('mutation._grav', onAddedNodes);
+
+ return instances;
+})();
diff --git a/user/plugins/admin/themes/grav/app/forms/fields/iconpicker.js b/user/plugins/admin/themes/grav/app/forms/fields/iconpicker.js
new file mode 100755
index 0000000..244f3a8
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/fields/iconpicker.js
@@ -0,0 +1,298 @@
+import $ from 'jquery';
+
+/* Icon Picker by QueryLoop
+ * Author: @eliorivero
+ * URL: http://queryloop.com/
+ * License: GPLv2
+ */
+
+var defaults = {
+ 'mode': 'dialog', // show overlay 'dialog' panel or slide down 'inline' panel
+ 'closeOnPick': true, // whether to close panel after picking or 'no'
+ 'save': 'class', // save icon 'class' or 'code'
+ 'size': '',
+ 'classes': {
+ 'launcher': '', // extra classes for launcher buttons
+ 'clear': 'remove-times', // extra classes for button that removes preview and clears field
+ 'highlight': '', // extra classes when highlighting an icon
+ 'close': '' // extra classes for close button
+ },
+ 'iconSets': { // example data structure. Used to specify which launchers will be created
+ 'genericon': 'Genericon', // create a launcher to pick genericon icons
+ 'fa': 'FontAwesome' // create a launcher to pick fontawesome icons
+ }
+};
+
+class QL_Icon_Picker {
+
+ constructor(element, options) {
+ this.iconSet = '';
+ this.iconSetName = '';
+ this.$field = '';
+ this.element = element;
+ this.settings = $.extend({}, defaults, options);
+ this._defaults = defaults;
+ this.init();
+ }
+
+ init() {
+ var $brick = $(this.element);
+ var pickerId = $brick.data('pickerid');
+ var $preview = $('
');
+
+ this.$field = $brick.find('input');
+
+ // Add preview area
+ this.makePreview($brick, pickerId, $preview);
+
+ // Make button to clear field and remove preview
+ this.makeClear(pickerId, $preview);
+
+ // Make buttons that open the panel of icons
+ this.makeLaunchers($brick, pickerId);
+
+ // Prepare display styles, inline and dialog
+ this.makeDisplay($brick);
+ }
+
+ makePreview($brick, pickerId, $preview) {
+ var $icon = $('
');
+ var iconValue = this.$field.val();
+
+ $preview.prependTo($brick);
+ $icon.prependTo($preview);
+ if (iconValue !== '') {
+ $preview.addClass('icon-preview-on');
+ $icon.addClass(iconValue);
+ }
+ }
+
+ makeClear(pickerId, $preview) {
+ var base = this;
+ var $clear = $('
');
+
+ // Hide button to remove icon and preview and append it to preview area
+ $clear.hide().prependTo($preview);
+ // If there's a icon saved in the field, show remove icon button
+ if (base.$field.val() !== '') {
+ $clear.show();
+ }
+
+ $preview.on('click', '.remove-icon', function(e) {
+ e.preventDefault();
+ base.$field.val('');
+ $preview.removeClass('icon-preview-on').find('i').removeClass();
+ $(this).hide();
+ });
+ }
+
+ makeDisplay($brick) {
+ var base = this;
+ var close = base.settings.classes.close;
+ var $body = $('body');
+
+ var $close = $('
');
+
+ if (base.settings.mode === 'inline') {
+ $brick.find('.icon-set').append($close).removeClass('dialog').addClass('ip-inline ' + base.settings.size).parent().addClass('icon-set-wrap');
+ } else if (base.settings.mode === 'dialog') {
+ $('.icon-set').addClass('dialog ' + base.settings.size);
+ if ($('.icon-picker-overlay').length <= 0) {
+ $body.append('
').append($close);
+ }
+ }
+ $body
+ .on('click', '.icon-picker-close, .icon-picker-overlay', function(e) {
+ e.preventDefault();
+ base.closePicker($brick, $(base.iconSet), base.settings.mode);
+ })
+ .on('mouseenter mouseleave', '.icon-picker-close', function(e) {
+ if (e.type === 'mouseenter') {
+ $(this).addClass(close);
+ } else {
+ $(this).removeClass(close);
+ }
+ });
+ }
+
+ makeLaunchers($brick) {
+ var base = this;
+ var dataIconSets = $brick.data('iconsets');
+ var iconSet;
+
+ if (typeof dataIconSets === 'undefined') {
+ dataIconSets = base.settings.iconSets;
+ }
+ for (iconSet in dataIconSets) {
+ if (dataIconSets.hasOwnProperty(iconSet)) {
+ $brick.append('
' + dataIconSets[iconSet] + ' ');
+ }
+ }
+
+ $brick.find('.launch-icons').on('click', function(e) {
+ e.preventDefault();
+ var $self = $(this);
+ var theseIcons = $self.data('icons');
+
+ base.iconSetName = theseIcons;
+ base.iconSet = '.' + theseIcons + '-set';
+
+ // Initialize picker
+ base.iconPick($brick);
+
+ // Show icon picker
+ base.showPicker($brick, $(base.iconSet), base.settings.mode);
+ });
+ }
+
+ iconPick($brick) {
+ var base = this;
+ var highlight = 'icon-highlight ' + base.settings.classes.highlight;
+
+ $(base.iconSet).on('click', 'li', function(e) {
+ e.preventDefault();
+ var $icon = $(this);
+ var icon = $icon.data(base.settings.save);
+
+ // Mark as selected
+ $('.icon-selected').removeClass('icon-selected');
+ $icon.addClass('icon-selected');
+ if (base.$field.data('format') === 'short') {
+ icon = icon.slice(6);
+ }
+
+ // Save icon value to field
+ base.$field.val(icon);
+
+ // Close icon picker
+ if (base.settings.closeOnPick) {
+ base.closePicker($brick, $icon.closest(base.iconSet), base.settings.mode);
+ }
+
+ // Set preview
+ base.setPreview($icon.data('class'));
+
+ // Broadcast event passing the selected icon.
+ $('body').trigger('iconselected.queryloop', icon);
+ });
+ $(base.iconSet).on('mouseenter mouseleave', 'li', function(e) {
+ if (e.type === 'mouseenter') {
+ $(this).addClass(highlight);
+ } else {
+ $(this).removeClass(highlight);
+ }
+ });
+ }
+
+ showPicker($brick, $icons, mode) {
+ if (mode === 'inline') {
+ $('.icon-set').removeClass('ip-inline-open');
+ $brick.find($icons).toggleClass('ip-inline-open');
+ } else if (mode === 'dialog') {
+ $brick.find('.icon-picker-close').addClass('make-visible');
+ $brick.find('.icon-picker-overlay').addClass('make-visible');
+ $icons.addClass('dialog-open');
+ }
+
+ $icons.find('.icon-selected').removeClass('icon-selected');
+ var selectedIcon = this.$field.val().replace(' ', '.');
+ if (selectedIcon !== '') {
+ if (this.settings.save === 'class') {
+ $icons.find('.' + selectedIcon).addClass('icon-selected');
+ } else {
+ $icons.find('[data-code="' + selectedIcon + '"]').addClass('icon-selected');
+ }
+ }
+ // Broadcast event when the picker is shown passing the picker mode.
+ $('body').trigger('iconpickershow.queryloop', mode);
+ }
+
+ closePicker($brick, $icons, mode) {
+ // Remove event so they don't fire from a different picker
+ $(this.iconSet).off('click', 'li');
+
+ if (mode === 'inline') {
+ $brick.find($icons).removeClass('ip-inline-open');
+ } else if (mode === 'dialog') {
+ $('.icon-picker-close, .icon-picker-overlay').removeClass('make-visible');
+ }
+ // Broadcast event when the picker is closed passing the picker mode.
+ $('body').trigger('iconpickerclose.queryloop', mode);
+ $('.icon-set').removeClass('dialog-open');
+ }
+
+ setPreview(preview) {
+ var $preview = $(this.element).find('.icon-preview');
+
+ $preview.addClass('icon-preview-on').find('i').removeClass()
+ .addClass(this.iconSetName)
+ .addClass(preview);
+ $preview.find('a').show();
+ }
+}
+
+/* Grav */
+// extend $ with 3rd party QL Icon Picker
+$.fn.qlIconPicker = function(options) {
+ this.each(function() {
+ if (!$.data(this, 'plugin_qlIconPicker')) {
+ $.data(this, 'plugin_qlIconPicker', new QL_Icon_Picker(this, options));
+ }
+ });
+
+ return this;
+};
+
+export default class IconpickerField {
+
+ constructor(options) {
+ this.items = $();
+ this.options = Object.assign({}, this.defaults, options);
+
+ $('[data-grav-iconpicker]').each((index, element) => this.addItem(element));
+ $('body').on('mutation._grav', this._onAddedNodes.bind(this));
+ }
+
+ _onAddedNodes(event, target/* , record, instance */) {
+ let fields = $(target).find('[data-grav-iconpicker]');
+ if (!fields.length) { return; }
+
+ fields.each((index, field) => {
+ field = $(field);
+ if (!~this.items.index(field)) {
+ this.addItem(field);
+ }
+ });
+ }
+
+ addItem(element) {
+ element = $(element);
+ this.items = this.items.add(element);
+ element.find('.icon-picker').qlIconPicker({
+ 'save': 'class'
+ });
+
+ // hack to remove extra icon sets that are just copies
+ $('.icon-set:not(:first)').remove();
+ }
+}
+
+export let Instance = new IconpickerField();
+
+// Fix to close the dialog when clicking outside
+$(document).on('click', (event) => {
+ const target = $(event.target);
+ const match = '.icon-set.dialog-open, .launch-icons[data-icons]';
+ if (!target.is(match) && !target.closest(match).length) {
+ const dialogs = $('.icon-set.dialog-open');
+
+ // skip if there's no dialog open
+ if (dialogs.length) {
+ dialogs.each((index, dialog) => {
+ const picker = $(dialog).siblings('.icon-picker');
+ const data = picker.data('plugin_qlIconPicker');
+ data.closePicker(picker, $(data.iconSet), data.settings.mode);
+ });
+ }
+ }
+});
diff --git a/user/plugins/admin/themes/grav/app/forms/fields/index.js b/user/plugins/admin/themes/grav/app/forms/fields/index.js
new file mode 100644
index 0000000..9ad371c
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/fields/index.js
@@ -0,0 +1,72 @@
+import FilepickerField, { Instance as FilepickerFieldInstance } from './filepicker';
+import SelectizeField, { Instance as SelectizeFieldInstance } from './selectize';
+import ArrayField, { Instance as ArrayFieldInstance } from './array';
+import CollectionsField, { Instance as CollectionsFieldInstance } from './collections';
+import DateTimeField, { Instance as DateTimeFieldInstance } from './datetime';
+import EditorField, { Instance as EditorFieldInstance } from './editor';
+import ColorpickerField, { Instance as ColorpickerFieldInstance } from './colorpicker';
+import FilesField, { Instance as FilesFieldInstance } from './files';
+import MediapickerField, { Instance as MediapickerInstance } from './mediapicker';
+import MultilevelField, { Instance as MultilevelInstance } from './multilevel';
+import SelectUniqueField, { Instance as SelectUniqueInstance } from './selectunique';
+import IconpickerField, { Instance as IconpickerInstance } from './iconpicker';
+import CronField, { Instance as CronFieldInstance } from './cron';
+import TextField, { Instance as TextFieldInstance } from './text';
+
+export default {
+ FilepickerField: {
+ FilepickerField,
+ Instance: FilepickerFieldInstance
+ },
+ SelectizeField: {
+ SelectizeField,
+ Instance: SelectizeFieldInstance
+ },
+ ArrayField: {
+ ArrayField,
+ Instance: ArrayFieldInstance
+ },
+ CollectionsField: {
+ CollectionsField,
+ Instance: CollectionsFieldInstance
+ },
+ DateTimeField: {
+ DateTimeField,
+ Instance: DateTimeFieldInstance
+ },
+ EditorField: {
+ EditorField,
+ Instance: EditorFieldInstance
+ },
+ ColorpickerField: {
+ ColorpickerField,
+ Instance: ColorpickerFieldInstance
+ },
+ FilesField: {
+ FilesField,
+ Instance: FilesFieldInstance
+ },
+ SelectUniqueField: {
+ SelectUniqueField,
+ Instance: SelectUniqueInstance
+ },
+ MediapickerField: {
+ MediapickerField,
+ Instance: MediapickerInstance
+ },
+ MultilevelField: {
+ MultilevelField,
+ Instance: MultilevelInstance
+ },
+ IconpickerField: {
+ IconpickerField,
+ Instance: IconpickerInstance
+ },
+ CronField: {CronField,
+ Insance: CronFieldInstance
+ },
+ TextField: {TextField,
+ Insance: TextFieldInstance
+ }
+};
+
diff --git a/user/plugins/admin/themes/grav/app/forms/fields/mediapicker.js b/user/plugins/admin/themes/grav/app/forms/fields/mediapicker.js
new file mode 100644
index 0000000..9769738
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/fields/mediapicker.js
@@ -0,0 +1,92 @@
+import $ from 'jquery';
+import Scrollbar from '../../utils/scrollbar';
+import { Instance as pagesTree } from '../../pages/tree';
+
+const queries = {
+ tree: '.pages-list-container .mediapicker-scroll',
+ thumb: '.thumbs-list-container .mediapicker-scroll'
+};
+
+$(function() {
+ let modal = '';
+ let body = $('body');
+
+ let treescrolls = [];
+ let thumbscrolls = [];
+
+ $(queries.tree).each((index, element) => {
+ treescrolls.push(new Scrollbar(element, { autoshow: true }));
+ });
+
+ $(queries.thumb).each((index, element) => {
+ thumbscrolls.push(new Scrollbar(element, { autoshow: true }));
+ });
+
+ // let treescroll = new Scrollbar('.pages-list-container .mediapicker-scroll', { autoshow: true });
+ // let thumbscroll = new Scrollbar('.thumbs-list-container .mediapicker-scroll', { autoshow: true });
+
+ // Thumb Resizer
+ $(document).on('input change', '.media-container .media-range', function(event) {
+ const target = $(event.currentTarget);
+ const container = target.closest('.remodal');
+ let cards = container.find('.media-container div.card-item');
+ let width = target.val() + 'px';
+ cards.each(function() {
+ $(this).css('width', width);
+ });
+
+ treescrolls.forEach((tree) => tree.update());
+ thumbscrolls.forEach((thumb) => thumb.update());
+ });
+
+ $(document).on('opened', '.remodal', function() {
+ setTimeout(function() {
+ treescrolls.forEach((tree) => tree.update());
+ thumbscrolls.forEach((thumb) => thumb.update());
+ }, 10);
+ });
+
+ body.on('click', '[data-mediapicker-modal-trigger]', function(event) {
+ const element = $(event.currentTarget);
+ let modal_identifier = $(this).data('grav-mediapicker-unique-identifier');
+ let modal_element = body.find(`[data-remodal-unique-identifier="${modal_identifier}"]`);
+ modal = $.remodal.lookup[modal_element.data('remodal')];
+
+ if (!modal) {
+ modal_element.remodal();
+ modal = $.remodal.lookup[modal_element.data('remodal')];
+ }
+
+ $(queries.tree).filter((index, item) => !$(item).data('scrollbar')).each((index, item) => {
+ treescrolls.push(new Scrollbar(item, { autoshow: true }));
+ });
+
+ $(queries.thumb).filter((index, item) => !$(item).data('scrollbar')).each((index, item) => {
+ thumbscrolls.push(new Scrollbar(item, { autoshow: true }));
+ });
+
+ modal.open();
+ modal.dataField = element.find('input');
+
+ treescrolls.forEach((tree) => tree.update());
+ thumbscrolls.forEach((thumb) => thumb.update());
+
+ // load all media
+ modal_element.find('.js__files').trigger('fillView');
+
+ setTimeout(() => pagesTree.reload(), 100);
+ });
+
+ /* handle media modal click actions */
+ body.on('click', '[data-remodal-mediapicker] .media-container.in-modal .admin-media-details a', (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+
+ let val = $(event.target).parents('.js__media-element').data('file-url');
+ let string = val.replace(/ /g, '%20');
+
+ modal.dataField.val(string);
+
+ modal.close();
+ });
+});
diff --git a/user/plugins/admin/themes/grav/app/forms/fields/multilevel.js b/user/plugins/admin/themes/grav/app/forms/fields/multilevel.js
new file mode 100644
index 0000000..fc008ec
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/fields/multilevel.js
@@ -0,0 +1,279 @@
+import $ from 'jquery';
+
+$(function() {
+
+ const getField = function getField(level, name) {
+ let levelMargin = level * 50;
+ let top = (level === 0 ? 'top' : '');
+
+ let the_name = 'name="' + name + '"';
+ if (level === 0) {
+ // top
+ the_name = 'data-attr-name="' + name + '"';
+ }
+
+ let field = `
+
+ `;
+
+ return field;
+ };
+
+ const hasChildInputs = function hasChildInputs($element) {
+ if ($element.attr('name')) {
+ return false;
+ }
+
+ return true;
+ };
+
+ const getTopItems = function getTopItems(element) {
+ return $(element + ' .js__multilevel-field.top');
+ };
+
+ const refreshControls = function refreshControls(unique_identifier) {
+ let element = '[data-grav-multilevel-field]';
+ if (unique_identifier) {
+ element = '[data-grav-multilevel-field][data-id="' + unique_identifier + '"]';
+ }
+
+ const hideButtons = function hideButtons() {
+ $(element + ' .js__add-sibling').addClass('hidden');
+ $(element + ' .js__add-children').addClass('hidden');
+ };
+
+ const restoreAddSiblingButtons = function restoreAddSiblingButtons() {
+ $(element + ' .children-wrapper').each(function() {
+ let elements = $(this).children();
+ elements.last().each(function() {
+ let field = $(this);
+ if (!$(this).hasClass('js__multilevel-field')) {
+ field = $(this).find('.js__multilevel-field').first();
+ }
+ field.find('.js__add-sibling').removeClass('hidden');
+ });
+ });
+
+ // add sibling to the last top element
+ $(element + ' .js__multilevel-field.top').last().find('.js__add-sibling').removeClass('hidden');
+ };
+
+ const restoreAddChildrenButtons = function restoreAddChildrenButtons() {
+ $(element + ' .js__multilevel-field').each(function() {
+ if ($(this).siblings('.children-wrapper').length === 0 || $(this).siblings('.children-wrapper').find('.js__multilevel-field').length === 0) {
+ $(this).find('.js__add-children').removeClass('hidden');
+ }
+ });
+ };
+
+ const preventRemovingLastTopItem = function preventRemovingLastTopItem() {
+ let top_items = getTopItems(element);
+ if (top_items.length === 1) {
+ top_items.first().find('.js__remove-item').addClass('hidden');
+ }
+ };
+
+ hideButtons();
+ restoreAddSiblingButtons();
+ restoreAddChildrenButtons();
+ preventRemovingLastTopItem();
+ };
+
+ const changeAllOccurrencesInTree = function($el, current_name, new_name) {
+ $el.parents('[data-grav-multilevel-field]').find('input').each(function() {
+ let $input = $(this);
+ if ($input.attr('name')) {
+ $input.attr('name', $input.attr('name').replace(current_name, new_name));
+ }
+ if ($input.attr('data-attr-name')) {
+ $input.attr('data-attr-name', $input.attr('data-attr-name').replace(current_name, new_name));
+ }
+ });
+ };
+
+ $(document).ready(function() {
+ refreshControls();
+ });
+
+ $(document).on('mouseleave', '[data-grav-multilevel-field]', function(event) {
+ let top_items = getTopItems('[data-id="' + $(this).data('id') + '"]');
+ let has_top_items_without_children = false;
+ let element_content = '';
+ top_items.each(function() {
+ let item = $(this);
+ if ($(item).siblings('.children-wrapper').find('input').length === 0) {
+ has_top_items_without_children = true;
+ element_content = item.find('input').val();
+ }
+
+ });
+
+ if (has_top_items_without_children) {
+ if (element_content) {
+ alert('Warning: if you save now, the element ' + element_content + ', without children, will be removed, because it\'s invalid YAML');
+ } else {
+ alert('Warning: if you save now, the top elements without children will be removed, because it\'s invalid YAML');
+ }
+
+ }
+ });
+
+ $(document).on('click', '[data-grav-multilevel-field] .js__add-children', function(event) {
+ let element = $(this);
+ let unique_container_id = element.closest('.js__multilevel-field').data('id');
+ let level = element.data('level') + 1;
+
+ const getParentOfElement = function getParentOfElement(element) {
+ let parent = element.closest('.js__multilevel-field').parent().first();
+ if (parent.find('.children-wrapper').length === 0) {
+ $(parent).append('
');
+ }
+ parent = parent.find('.children-wrapper').first();
+
+ return parent;
+ };
+
+ const getNameFromParentInput = function getNameFromParentInput(parentInput, attr) {
+ if (parentInput.hasClass('children-wrapper')) {
+ parentInput = parentInput.siblings('.js__multilevel-field').first().find('input');
+ }
+
+ return parentInput.attr(attr) + '[' + parentInput.val() + ']';
+ };
+
+ const getInputFromChildrenWrapper = function getInputFromChildrenWrapper(parentChildrenWrapper) {
+ return parentChildrenWrapper.siblings('.js__multilevel-field').first().find('input');
+ };
+
+ let parentChildrenWrapper = getParentOfElement(element);
+ let parentInput = getInputFromChildrenWrapper(parentChildrenWrapper);
+
+ let attr = 'name';
+ if (parentInput.closest('.js__multilevel-field').hasClass('top')) {
+ attr = 'data-attr-name';
+ }
+
+ parentInput.attr(attr, parentInput.attr(attr).replace('[]', ''));
+
+ let name = getNameFromParentInput(parentInput, attr);
+ let field = getField(level, name);
+
+ $(parentChildrenWrapper).append(field);
+ refreshControls(unique_container_id);
+ });
+
+ $(document).on('click', '[data-grav-multilevel-field] .js__add-sibling', function(event) {
+ let element = $(this);
+ let unique_container_id = element.closest('.js__multilevel-field').data('id');
+ let level = element.data('level');
+ element.closest('.children-wrapper').find('.js__add-sibling').addClass('hidden');
+
+ let sibling = null;
+ let is_top = false;
+
+ if (element.closest('.js__multilevel-field').hasClass('top')) {
+ is_top = true;
+ }
+
+ if (is_top) {
+ sibling = element.closest('.js__multilevel-field').first().find('input').last();
+ } else {
+ sibling = element.siblings('input').first();
+ if (!sibling) {
+ sibling = element.closest('.children-wrapper').first().find('input').last();
+ }
+ }
+
+ const getParentOfElement = function getParentOfElement(element) {
+ let parent = element.closest('.js__multilevel-field').parent().first();
+ if (!parent.hasClass('element-wrapper')) {
+ if (parent.find('.element-wrapper').length === 0) {
+ $(parent).append('
');
+ }
+
+ parent = parent.find('.element-wrapper').first();
+ }
+
+ return parent;
+ };
+
+ const getNameFromSibling = function getNameFromSibling(parent, sibling, is_top = false) {
+ let name = sibling.attr('name');
+
+ if (hasChildInputs(sibling)) {
+ let val = sibling.data('attr-name') + '[]';
+ sibling.removeAttr('name');
+ return val;
+ }
+
+ let last_index = name.lastIndexOf('[');
+ let almost_there = name.substr(last_index + 1);
+ let last_tag = almost_there.substr(0, almost_there.length - 1);
+
+ if ($.isNumeric(last_tag)) {
+ name = name.replace('[' + last_tag + ']', '[' + (parseInt(last_tag, 10) + 1) + ']');
+ } else {
+ if (is_top) {
+ name = name.replace('[' + last_tag + ']', '');
+ } else {
+ name = name + '[1]';
+
+ // change sibling name attr if necessary
+ if (sibling.attr('name').slice('-2') !== '[0]') {
+ changeAllOccurrencesInTree(sibling, sibling.attr('name'), sibling.attr('name') + '[0]');
+ }
+ }
+ }
+
+ return name;
+ };
+
+ let parent = getParentOfElement(element);
+ let name = getNameFromSibling(parent, sibling, is_top);
+
+ let field = getField(level, name);
+ $(field).insertAfter(parent);
+
+ refreshControls(unique_container_id);
+ });
+
+ $(document).on('click', '[data-grav-multilevel-field] .js__remove-item', function(event) {
+ $(this).parents('.element-wrapper').first().remove();
+ let unique_container_id = $(this).closest('.js__multilevel-field').data('id');
+ refreshControls(unique_container_id);
+ });
+
+ // Store old value before editing a field
+ $(document).on('focusin', '[data-grav-multilevel-field] input', function(event) {
+ $(this).data('current-value', $(this).val());
+ });
+
+ // Handle field edited event
+ $(document).on('change', '[data-grav-multilevel-field] input', function(event) {
+ let $el = $(this);
+ let old_value = $el.data('current-value');
+ let new_value = $el.val();
+
+ let full_name = $el.attr('name') || $el.attr('data-attr-name'); // first-level items have `data-attr-name` instead of `name`
+
+ let old_name_attr = full_name + '[' + old_value + ']';
+ let new_name_attr = full_name + '[' + new_value + ']';
+
+ changeAllOccurrencesInTree($el, old_name_attr, new_name_attr);
+ });
+
+});
diff --git a/user/plugins/admin/themes/grav/app/forms/fields/selectize.js b/user/plugins/admin/themes/grav/app/forms/fields/selectize.js
new file mode 100644
index 0000000..dc49c96
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/fields/selectize.js
@@ -0,0 +1,58 @@
+import $ from 'jquery';
+import 'selectize';
+import '../../utils/selectize-required-fix';
+import '../../utils/selectize-option-click';
+
+const PagesRoute = {
+ option: function(item, escape) {
+ const label = escape(item.text).split(' ');
+ const arrows = label.shift();
+ const slug = label.shift();
+
+ return `
+ ${arrows}
+
+ ${slug.replace('(', '/').replace(')', '')}
+ ${label.join(' ')}
+
+
`;
+ }
+};
+
+export default class SelectizeField {
+ constructor(options = {}) {
+ this.options = Object.assign({}, options);
+ this.elements = [];
+
+ $('[data-grav-selectize]').each((index, element) => this.add(element));
+ $('body').on('mutation._grav', this._onAddedNodes.bind(this));
+ }
+
+ add(element) {
+ element = $(element);
+ let tag = element.prop('tagName').toLowerCase();
+ let isInput = tag === 'input' || tag === 'select';
+
+ let data = (isInput ? element.closest('[data-grav-selectize]') : element).data('grav-selectize') || {};
+ let field = (isInput ? element : element.find('input, select'));
+
+ if (field.attr('name') === 'data[route]') {
+ data = $.extend({}, data, { render: PagesRoute });
+ }
+
+ if (!field.length || field.get(0).selectize) { return; }
+ const plugins = $.merge(data.plugins ? data.plugins : [], ['required-fix']);
+ field.selectize($.extend({}, data, { plugins }));
+
+ this.elements.push(field.data('selectize'));
+ }
+
+ _onAddedNodes(event, target/* , record, instance */) {
+ let fields = $(target).find('select.fancy, input.fancy, [data-grav-selectize]');
+ if (!fields.length) { return; }
+
+ fields.each((index, field) => this.add(field));
+ }
+}
+
+export let Instance = new SelectizeField();
diff --git a/user/plugins/admin/themes/grav/app/forms/fields/selectunique.js b/user/plugins/admin/themes/grav/app/forms/fields/selectunique.js
new file mode 100644
index 0000000..89adaff
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/fields/selectunique.js
@@ -0,0 +1,160 @@
+import $ from 'jquery';
+import forIn from 'mout/object/forIn';
+// import { config } from 'grav-config';
+
+const Data = {};
+export default class SelectUniqueField {
+
+ constructor(options) {
+ const body = $('body');
+ this.items = $();
+ this.options = Object.assign({}, this.defaults, options);
+
+ $('[data-select-observe]').each((index, element) => this.addSelect(element)).last().trigger('change', { load: true });
+ body.on('mutation._grav', this._onAddedNodes.bind(this));
+ body.on('mutation_removed._grav', this._onRemovedNodes.bind(this));
+ }
+
+ _onAddedNodes(event, target, record, instance) {
+ let fields = $(target).find('[data-select-observe]');
+ if (!fields.length) { return; }
+
+ fields.each((index, field) => {
+ field = $(field);
+ if (!~this.items.index(field)) {
+ this.addSelect(field);
+ }
+ });
+ }
+
+ _onRemovedNodes(event, data/* , instance */) {
+ const target = $(data.target);
+ const holder = target.data('collectionHolder');
+ if (!holder) { return false; }
+
+ const node = $(data.mutation.removedNodes);
+ const value = node.find('[data-select-observe]').val();
+ if (value) {
+ Data[holder].state[value] = value;
+ }
+
+ target.find('[data-select-observe]').each((index, field) => {
+ field = $(field);
+
+ if (field.val() !== value) {
+ this.updateOptions(field);
+ }
+ });
+ }
+
+ addSelect(element) {
+ this.items = this.items.add(element);
+ element = $(element);
+
+ const value = element.attr('value');
+ const holder = element.closest('[data-collection-holder]').data('collectionHolder');
+ const options = element.closest('[data-select-unique]').data('selectUnique');
+
+ if (!Data[holder]) {
+ let data = {};
+ if (Array.isArray(options)) {
+ options.slice(0).map((item) => { data[item] = item; });
+ } else {
+ data = Object.assign({}, options);
+ }
+
+ Data[holder] = { original: null, state: null };
+ Data[holder].original = Object.assign({}, data);
+ Data[holder].state = Object.assign({}, data);
+ }
+
+ this.updateOptions(element);
+
+ element.data('originalValue', value);
+ element.on('change', (event, extras) => {
+ const target = $(event.currentTarget);
+ if (target.data('dummyChange')) {
+ target.data('dummyChange', false);
+ return false;
+ }
+
+ this.refreshOptions(target, extras && extras.load ? null : element.data('originalValue'));
+ element.data('originalValue', target.val());
+ });
+ }
+
+ updateOptions(element) {
+ element = $(element);
+ const value = element.attr('value');
+ const holder = element.closest('[data-collection-holder]').data('collectionHolder');
+
+ forIn(Data[holder].state, (v, k) => {
+ const selected = k === value ? 'selected="selected"' : '';
+
+ if (element.get(0).selectize) {
+ const selectize = element.data('selectize');
+ selectize.removeOption(k);
+ selectize.addOption({ value: k, text: v });
+ } else {
+ element.append(`
${v} `);
+ }
+
+ if (selected) {
+ if (element.get(0).selectize) {
+ const selectize = element.data('selectize');
+ selectize.setValue(k);
+ }
+ delete Data[holder].state[value];
+ }
+ });
+ }
+
+ refreshOptions(element, originalValue) {
+ const value = element.val();
+ const holder = element.closest('[data-collection-holder]').data('collectionHolder');
+ delete Data[holder].state[value];
+
+ if (originalValue && Data[holder].original[originalValue]) {
+ Data[holder].state[originalValue] = Data[holder].original[originalValue];
+ }
+
+ this.items.each((index, select) => {
+ select = $(select);
+ if (select[0] === element[0]) { return; }
+
+ const selectedValue = select.val();
+ select.data('dummyChange', true);
+
+ if (select.get(0).selectize) {
+ const selectize = select.data('selectize');
+
+ if (selectize) {
+ selectize.clearOptions();
+
+ if (selectedValue) {
+ selectize.addOption({
+ value: selectedValue,
+ text: Data[holder].original[selectedValue] || selectedValue
+ });
+ }
+
+ forIn(Data[holder].state, (v, k) => {
+ selectize.addOption({ value: k, text: v });
+ });
+
+ selectize.setValue(selectedValue, true);
+ }
+ } else {
+ select.empty();
+ forIn(Data[holder].state, (v, k) => {
+ const selected = k === selectedValue ? 'selected="selected"' : '';
+ select.append(`
${v} `);
+ });
+ }
+
+ select.data('dummyChange', false);
+ });
+ }
+}
+
+export let Instance = new SelectUniqueField();
diff --git a/user/plugins/admin/themes/grav/app/forms/fields/text.js b/user/plugins/admin/themes/grav/app/forms/fields/text.js
new file mode 100644
index 0000000..f6d21cd
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/fields/text.js
@@ -0,0 +1,13 @@
+import $ from 'jquery';
+
+$(document).ready(function() {
+ $('.copy-to-clipboard').click(function(event) {
+ var $tempElement = $('
');
+ $('body').append($tempElement);
+ $tempElement.val($(this).prev('input').val()).select();
+ document.execCommand('Copy');
+ $tempElement.remove();
+
+ $(this).attr('data-hint', 'Copied to clipboard!').addClass('hint--left');
+ });
+});
diff --git a/user/plugins/admin/themes/grav/app/forms/form.js b/user/plugins/admin/themes/grav/app/forms/form.js
new file mode 100644
index 0000000..06ab0ef
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/form.js
@@ -0,0 +1,139 @@
+import $ from 'jquery';
+
+/* Dependencies for checking if changes happened since load on a form
+import toastr from '../utils/toastr';
+import { translations } from 'grav-config';
+import { Instance as FormState } from './state';
+*/
+
+export default class Form {
+ constructor(form) {
+ this.form = $(form);
+ if (!this.form.length || this.form.prop('tagName').toLowerCase() !== 'form') { return; }
+
+ /* Option for not saving while nothing in a form has changed
+
+ this.form.on('submit', (event) => {
+ if (FormState.equals()) {
+ event.preventDefault();
+ toastr.info(translations.PLUGIN_ADMIN.NOTHING_TO_SAVE);
+ }
+ }); */
+
+ this._attachShortcuts();
+ this._attachToggleables();
+ this._attachDisabledFields();
+ this._submitUncheckedFields();
+
+ this.observer = new MutationObserver(this.addedNodes);
+ this.form.each((index, form) => this.observer.observe(form, { subtree: true, childList: true }));
+ }
+
+ _attachShortcuts() {
+ // CTRL + S / CMD + S - shortcut for [Save] when available
+ let saveTask = $('#titlebar [name="task"][value="save"]');
+
+ if (saveTask.length) {
+ $(global).on('keydown', function(event) {
+ const key = String.fromCharCode(event.which).toLowerCase();
+ if (!event.shiftKey && ((event.ctrlKey && !event.altKey) || event.metaKey) && key === 's') {
+ event.preventDefault();
+ saveTask.click();
+ }
+ });
+ }
+ }
+
+ _attachToggleables() {
+ let query = '[data-grav-field="toggleable"] input[type="checkbox"]';
+
+ this.form.on('change', query, (event) => {
+ let toggle = $(event.target);
+ let enabled = toggle.is(':checked');
+ let parent = toggle.closest('.form-field');
+ let label = parent.find('label.toggleable');
+ let fields = parent.find('.form-data');
+ let inputs = fields.find('input, select, textarea, button');
+
+ label.add(fields).css('opacity', enabled ? '' : 0.7);
+ inputs.map((index, input) => {
+ let isSelectize = input.selectize;
+ input = $(input);
+
+ if (isSelectize) {
+ isSelectize[enabled ? 'enable' : 'disable']();
+ } else {
+ input.prop('disabled', !enabled);
+ }
+ });
+ });
+
+ this.form.find(query).trigger('change');
+ }
+
+ _attachDisabledFields() {
+ let prefix = '.form-field-toggleable .form-data';
+ let query = [];
+
+ ['input', 'select', 'label[for]', 'textarea', '.selectize-control'].forEach((item) => {
+ query.push(`${prefix} ${item}`);
+ });
+
+ this.form.on('mousedown', query.join(', '), (event) => {
+ let input = $(event.target);
+ let isFor = input.prop('for');
+ let isSelectize = (input.hasClass('selectize-control') || input.parents('.selectize-control')).length;
+
+ if (isFor) { input = $(`[id="${isFor}"]`); }
+ if (isSelectize) { input = input.closest('.selectize-control').siblings('select[name]'); }
+
+ if (!input.prop('disabled')) { return true; }
+
+ let toggle = input.closest('.form-field').find('[data-grav-field="toggleable"] input[type="checkbox"]');
+ toggle.trigger('click');
+ });
+ }
+
+ _submitUncheckedFields() {
+ let submitted = false;
+ this.form.each((index, form) => {
+ form = $(form);
+ form.on('submit', () => {
+ // workaround for MS Edge, submitting multiple forms at the same time
+ if (submitted) { return false; }
+
+ let formId = form.attr('id');
+ let unchecked = form.find('input[type="checkbox"]:not(:checked):not(:disabled)');
+ let submit = form.find('[type="submit"]').add(`[form="${formId}"][type="submit"]`);
+
+ if (!unchecked.length) { return true; }
+
+ submit.addClass('pointer-events-disabled');
+ unchecked.each((index, element) => {
+ element = $(element);
+ let name = element.prop('name');
+ let fake = $(`
`);
+ form.append(fake);
+ });
+ submitted = true;
+ return true;
+ });
+ });
+ }
+
+ addedNodes(mutations) {
+ mutations.forEach((mutation) => {
+ if (mutation.type !== 'childList') { return; }
+
+ if (mutation.addedNodes) {
+ $('body').trigger('mutation._grav', mutation.target, mutation, this);
+ }
+
+ if (mutation.removedNodes) {
+ $('body').trigger('mutation_removed._grav', { target: mutation.target, mutation }, this);
+ }
+ });
+ }
+}
+
+export let Instance = new Form('form#blueprints');
diff --git a/user/plugins/admin/themes/grav/app/forms/index.js b/user/plugins/admin/themes/grav/app/forms/index.js
new file mode 100644
index 0000000..604cf1b
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/index.js
@@ -0,0 +1,16 @@
+import FormState, { Instance as FormStateInstance } from './state';
+import Form, { Instance as FormInstance } from './form';
+
+import Fields from './fields';
+
+export default {
+ Form: {
+ Form,
+ Instance: FormInstance
+ },
+ Fields,
+ FormState: {
+ FormState,
+ Instance: FormStateInstance
+ }
+};
diff --git a/user/plugins/admin/themes/grav/app/forms/state.js b/user/plugins/admin/themes/grav/app/forms/state.js
new file mode 100644
index 0000000..13ce895
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/forms/state.js
@@ -0,0 +1,150 @@
+import $ from 'jquery';
+import Immutable from 'immutable';
+import immutablediff from 'immutablediff';
+import '../utils/jquery-utils';
+
+let FormLoadState = {};
+
+const DOMBehaviors = {
+ attach() {
+ this.preventUnload();
+ this.preventClickAway();
+ },
+
+ preventUnload() {
+ let selector = '[name="task"][value^="save"], [data-delete-action]';
+ if ($._data(window, 'events') && ($._data(window, 'events').beforeunload || []).filter((event) => event.namespace === '_grav').length) {
+ return;
+ }
+
+ // Allow some elements to leave the page without native confirmation
+ $(selector).on('click._grav', function(event) {
+ $(global).off('beforeunload');
+ });
+
+ // Catch browser uri change / refresh attempt and stop it if the form state is dirty
+ $(global).on('beforeunload._grav', () => {
+ if (Instance.equals() === false) {
+ return 'You have made changes on this page that you have not yet confirmed. If you navigate away from this page you will lose your unsaved changes.';
+ }
+ });
+ },
+
+ preventClickAway() {
+ let selector = 'a[href]:not([href^="#"]):not([target="_blank"]):not([href^="javascript:"])';
+
+ if ($._data($(selector).get(0), 'events') && ($._data($(selector).get(0), 'events').click || []).filter((event) => event.namespace === '_grav')) {
+ return;
+ }
+
+ // Prevent clicking away if the form state is dirty
+ // instead, display a confirmation before continuing
+ $(selector).on('click._grav', function(event) {
+ let isClean = Instance.equals();
+ if (isClean === null || isClean) { return true; }
+
+ event.preventDefault();
+
+ let destination = $(this).attr('href');
+ let modal = $('[data-remodal-id="changes"]');
+ let lookup = $.remodal.lookup[modal.data('remodal')];
+ let buttons = $('a.button', modal);
+
+ let handler = function(event) {
+ event.preventDefault();
+ let action = $(this).data('leave-action');
+
+ buttons.off('click', handler);
+ lookup.close();
+
+ if (action === 'continue') {
+ $(global).off('beforeunload');
+ global.location.href = destination;
+ }
+ };
+
+ buttons.on('click', handler);
+ lookup.open();
+ });
+ }
+};
+
+export default class FormState {
+ constructor(options = {
+ ignore: [],
+ form_id: 'blueprints'
+ }) {
+ this.options = options;
+ this.refresh();
+
+ if (!this.form || !this.fields.length) { return; }
+ FormLoadState = this.collect();
+ this.loadState = FormLoadState;
+ DOMBehaviors.attach();
+ }
+
+ refresh() {
+ this.form = $(`form#${this.options.form_id}`).filter(':noparents(.remodal)');
+ this.fields = $(`form#${this.options.form_id} *, [form="${this.options.form_id}"]`).filter(':input:not(.no-form)').filter(':noparents(.remodal)');
+
+ return this;
+ }
+
+ collect() {
+ if (!this.form || !this.fields.length) { return; }
+
+ let values = {};
+ this.refresh().fields.each((index, field) => {
+ field = $(field);
+ let name = field.prop('name');
+ let type = field.prop('type');
+ let tag = field.prop('tagName').toLowerCase();
+ let value;
+
+ if (name.startsWith('toggleable_') || name === 'data[lang]' || name === 'data[redirect]') {
+ return;
+ }
+
+ switch (type) {
+ case 'checkbox':
+ value = field.is(':checked');
+ break;
+ case 'radio':
+ if (!field.is(':checked')) { return; }
+ value = field.val();
+ break;
+ default:
+ value = field.val();
+ }
+
+ if (tag === 'select' && value === null) {
+ value = '';
+ }
+
+ if (Array.isArray(value)) {
+ value = value.join('|');
+ }
+
+ if (name && !~this.options.ignore.indexOf(name)) {
+ values[name] = value;
+ }
+ });
+
+ return Immutable.OrderedMap(values);
+ }
+
+ diff() {
+ return immutablediff(FormLoadState, this.collect());
+ }
+
+ // When the form doesn't exist or there are no fields, `equals` returns `null`
+ // for this reason, _NEVER_ check with !Instance.equals(), use Instance.equals() === false
+ equals() {
+ if (!this.form || !this.fields.length) { return null; }
+ return Immutable.is(FormLoadState, this.collect());
+ }
+};
+
+export let Instance = new FormState();
+
+export { DOMBehaviors };
diff --git a/user/plugins/admin/themes/grav/app/main.js b/user/plugins/admin/themes/grav/app/main.js
new file mode 100644
index 0000000..0c55316
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/main.js
@@ -0,0 +1,78 @@
+import $ from 'jquery';
+import GPM, { Instance as gpm } from './utils/gpm';
+import KeepAlive from './utils/keepalive';
+import Updates, { Instance as updates, Notifications, Feed } from './updates';
+import Dashboard from './dashboard';
+import Pages from './pages';
+import Forms from './forms';
+import Scrollbar, { Instance as contentScrollbar } from './utils/scrollbar';
+import './plugins';
+import './themes';
+import { Filter as MediaFilter, Instance as MediaFilterInstance} from './media';
+import toastr from './utils/toastr';
+import request from './utils/request';
+import './utils/2fa';
+import './tools';
+
+// bootstrap jQuery extensions
+import './utils/bootstrap-transition';
+import './utils/bootstrap-collapse';
+
+// tabs memory
+import './utils/tabs-memory';
+
+// Main Sidebar
+import Sidebar, { Instance as sidebar } from './utils/sidebar';
+
+// starts the keep alive, auto runs every X seconds
+KeepAlive.start();
+
+// Sidebar auto-refresh
+global.setInterval(() => {
+ contentScrollbar.update();
+ sidebar.scroller.update();
+}, 150);
+
+// global event to catch sidebar_state changes
+$(global).on('sidebar_state._grav', () => {
+ /* Should not be needed since Gemini Scrollbar v1.4.0
+ * - Auto-update scrollbar on resize
+ *
+ * $('.admin-menu-wrapper').data('scrollbar').update();
+ * $('#admin-main .content-wrapper').data('scrollbar').update();
+ */
+
+ Object.keys(Dashboard.Chart.Instances).forEach((chart) => {
+ setTimeout(() => Dashboard.Chart.Instances[chart].chart.update(), 10);
+ });
+});
+
+export default {
+ GPM: {
+ GPM,
+ Instance: gpm
+ },
+ KeepAlive,
+ Dashboard,
+ Pages,
+ Forms,
+ Scrollbar: {
+ Scrollbar,
+ Instance: contentScrollbar
+ },
+ Updates: {
+ Updates,
+ Notifications,
+ Feed,
+ Instance: updates
+ },
+ Sidebar: {
+ Sidebar,
+ Instance: sidebar
+ },
+ MediaFilter: {
+ MediaFilter,
+ Instance: MediaFilterInstance
+ },
+ Utils: { request, toastr }
+};
diff --git a/user/plugins/admin/themes/grav/app/media/index.js b/user/plugins/admin/themes/grav/app/media/index.js
new file mode 100644
index 0000000..72bb14b
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/media/index.js
@@ -0,0 +1,230 @@
+import $ from 'jquery';
+import { config, uri_params } from 'grav-config';
+import request from '../utils/request';
+
+export default class Filter {
+ constructor() {
+ this.URI = `${config.base_url_relative}/media-manager/`;
+ }
+
+ filter(name, value) {
+ let filtered = [];
+ let keys = Object.keys(uri_params);
+ if (!~keys.indexOf(name)) { keys.push(name); }
+
+ keys.forEach((key) => {
+ let filter = Filter.cleanValue(key === name ? value : uri_params[key]);
+ if (filter !== '*') {
+ filtered.push(`${key}${config.param_sep}${filter}`);
+ }
+ });
+
+ global.location = this.URI + filtered.join('/');
+ }
+
+ static cleanValue(value) {
+ return encodeURIComponent(value.replace('/', '\\'));
+ }
+}
+
+export let Instance = new Filter();
+var isLoading = false;
+
+var filters = {};
+var global_index = 1;
+var files_ended = false;
+const MEDIA_PAGINATION_INTERVAL = 20;
+
+/* handle changing file type / date filter */
+$('body').on('change', '.thumbs-list-container select.filter', (event) => {
+ let target = $(event.currentTarget);
+ let filterName = target.data('name');
+ let filterValue = target.val();
+
+ if (filterValue) {
+ filters[filterName] = filterValue;
+ } else {
+ delete filters[filterName];
+ }
+
+ filterFiles();
+});
+
+/* initialize media uploader */
+if ($('.thumbs-list-container .dropzone')[0]) {
+ $('.thumbs-list-container .dropzone')[0].dropzone.on('queuecomplete', function() {
+ let body = {};
+ if (filters.page) { body.page = filters.page; }
+ if (filters.date) { body.date = filters.date; }
+ if (filters.type) { body.type = filters.type; }
+
+ $('.dropzone')[0].dropzone.files.forEach(function(file) { file.previewElement.remove(); });
+ $('.dropzone').first().removeClass('dz-started');
+
+ request(`${config.base_url_relative}/media-manager.json/task${config.param_sep}clearMediaCache`, { method: 'post', body }, () => {
+ filterFiles();
+ });
+ });
+}
+
+/* handle loading media */
+var loadMedia = function loadMedia(filters, callback) {
+ var url = `${config.base_url_relative}/media.json/tmpl${config.param_sep}media-list-content/index${config.param_sep}${global_index}`;
+
+ if (filters.page) {
+ url += `/page${config.param_sep}${(filters.page).split('/').join('%5C')}`;
+ }
+ if (filters.type && filters.type !== '*') {
+ url += `/type${config.param_sep}${filters.type}`;
+ }
+ if (filters.date && filters.date !== '*') {
+ url += `/date${config.param_sep}${filters.date}`;
+ }
+
+ if (!isLoading) {
+ isLoading = true;
+
+ $('.spinning-wheel').show();
+ $.get(url, function(content) {
+ $('.js__files').append(content);
+ $('.spinning-wheel').hide();
+ $('.media-container .media-range').trigger('change');
+ isLoading = false;
+ global_index++;
+
+ callback(content);
+ });
+ }
+};
+
+var cleanFilesList = function cleanFilesList() {
+ $('.js__files .card-item').remove();
+};
+
+var resetActiveStateInSidebar = function resetActiveStateInSidebar() {
+ $('.pages-list-container .row').removeClass('active'); // clear active state in sidebar
+};
+
+var showEmptyState = function showEmptyState() {
+ $('.thumbs-list-container').append('
No media found
');
+};
+
+var filterFiles = function filterFiles() {
+ cleanFilesList();
+ global_index = 0;
+ files_ended = false;
+ $('.empty-space').remove();
+ loadMedia(filters, function(content) {
+ if (!content.trim().length) {
+ showEmptyState();
+ } else {
+ if (!filters.page && (!filters.date || filters.date === '*') && (!filters.type || filters.type === '*')) {
+ $('.js__files').trigger('fillView');
+ }
+ }
+ });
+};
+
+/* handle changing page */
+$('body').on('click', '.pages-list-container .js__page-link', (event) => {
+ var page = $(event.target).data('page');
+ filters['page'] = page;
+
+ $('.media-list-title .page-indicator').html(page); // set indication
+ $('.js__reset-pages-filter').removeClass('hidden'); // activate reset pages icon
+ resetActiveStateInSidebar();
+ $(event.target).parents('.row').addClass('active'); // set active state in sidebar
+ $('.js__file-uploader').removeClass('hidden');
+
+ // customize processing URL, as the page changes dynamically
+ if ($('.dropzone')[0]) {
+ $('.dropzone')[0].dropzone.on('processing', function(file) {
+ this.options.url = `${config.base_url_relative}/media-manager${page}.json/task${config.param_sep}addmedia`;
+ });
+ }
+
+ $('.js__button-clear-media-cache').addClass('hidden');
+ filterFiles();
+
+ disableInfiniteScrolling(); // only infinite scroll on main list, not inside single pages
+});
+
+/* handle clearing page filter */
+$('body').on('click', '.js__reset-pages-filter', (event) => {
+ $('.media-list-title .page-indicator').html('All Pages'); // set indication
+ cleanFilesList();
+ resetActiveStateInSidebar();
+ $('.js__reset-pages-filter').addClass('hidden'); // remove reset pages icon
+ $('.js__file-uploader').addClass('hidden');
+ $('.js__button-clear-media-cache').removeClass('hidden');
+ delete filters['page'];
+
+ filterFiles();
+});
+
+/* handle infinite loading */
+var enableInfiniteScrolling = function enableInfiniteScrolling() {
+ $('.spinning-wheel').hide();
+ var view = $('.mediapicker-scroll').last();
+ var gemini = view.data('scrollbar');
+ if (gemini) {
+ gemini = gemini.getViewElement();
+ }
+
+ if (!gemini || !gemini.length && !view.length) { return; }
+
+ $(gemini || view).on('scroll', function() {
+ if (($(this).scrollTop() + $(this).innerHeight() + 100) >= $(this)[0].scrollHeight) {
+ fillView();
+ }
+ });
+};
+
+var loadNextBatch = function loadNextBatch(callback) {
+ if (files_ended) {
+ return;
+ }
+
+ loadMedia({}, function(content) {
+ if (!$(content).length || ((content.split('card-item').length - 1) < MEDIA_PAGINATION_INTERVAL)) {
+ files_ended = true;
+ } else {
+ if (callback) {
+ callback();
+ }
+ }
+
+ $('.media-container .media-range').trigger('input');
+ });
+};
+
+var fillView = function fillView() {
+ if (!$('.js__files').find('.card-item').last().offset()) {
+ setTimeout(function() {
+ // retry later
+ fillView();
+ }, 300);
+
+ return;
+ }
+
+ if ($('.js__files').find('.card-item').last().offset().top - 1 <= $('.media-container').height()) {
+ loadNextBatch(function() {
+ fillView();
+ });
+ }
+};
+
+/* disable infinite loading */
+var disableInfiniteScrolling = function disableInfiniteScrolling() {
+ $('.spinning-wheel').hide();
+ $('.content-wrapper').unbind('scroll');
+};
+
+$('.js__files').on('fillView', function(event) {
+ // the first batch got the max number of media files, try loading more
+ if (($('.js__files')[0].innerHTML.split('card-item').length - 1) === MEDIA_PAGINATION_INTERVAL) {
+ fillView();
+ enableInfiniteScrolling();
+ }
+});
diff --git a/user/plugins/admin/themes/grav/app/pages/filter.js b/user/plugins/admin/themes/grav/app/pages/filter.js
new file mode 100644
index 0000000..d06446a
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/pages/filter.js
@@ -0,0 +1,148 @@
+import $ from 'jquery';
+import { config, translations } from 'grav-config';
+import request from '../utils/request';
+import debounce from 'debounce';
+import { Instance as pagesTree } from './tree';
+import 'selectize';
+import '../utils/selectize-required-fix.js';
+import '../utils/storage';
+
+/* @formatter:off */
+/* eslint-disable */
+const options = [
+ { flag: translations.PLUGIN_ADMIN.MODULAR, key: 'Modular', cat: 'mode' },
+ { flag: translations.PLUGIN_ADMIN.VISIBLE, key: 'Visible', cat: 'mode' },
+ { flag: translations.PLUGIN_ADMIN.ROUTABLE, key: 'Routable', cat: 'mode' },
+ { flag: translations.PLUGIN_ADMIN.PUBLISHED, key: 'Published', cat: 'mode' },
+ { flag: translations.PLUGIN_ADMIN.NON_MODULAR, key: 'NonModular', cat: 'mode' },
+ { flag: translations.PLUGIN_ADMIN.NON_VISIBLE, key: 'NonVisible', cat: 'mode' },
+ { flag: translations.PLUGIN_ADMIN.NON_ROUTABLE, key: 'NonRoutable', cat: 'mode' },
+ { flag: translations.PLUGIN_ADMIN.NON_PUBLISHED, key: 'NonPublished', cat: 'mode' }
+];
+/* @formatter:on */
+/* eslint-enable */
+
+export default class PagesFilter {
+ constructor(filters, search) {
+ this.filters = $(filters);
+ this.search = $(search);
+ this.options = options;
+ this.tree = pagesTree;
+ let storage = JSON.parse(localStorage.getItem('grav:admin:pages:filter') || '{}');
+
+ if (!this.filters.length || !this.search.length) { return; }
+
+ this.labels = this.filters.data('filter-labels');
+
+ this.search.on('input', debounce(() => this.filter(), 250));
+ this.filters.on('change', () => this.filter());
+
+ // restore state
+ if (storage.flags || storage.query) {
+ this.setValues(storage);
+ this.filter();
+ }
+
+ this._initSelectize();
+ }
+
+ filter(value) {
+ let data = { flags: '', query: '' };
+
+ if (typeof value === 'object') {
+ Object.assign(data, value);
+ }
+ if (typeof value === 'string') {
+ data.query = value;
+ }
+ if (typeof value === 'undefined') {
+ data.flags = this.filters.val();
+ data.query = this.search.val();
+ }
+
+ if (!Object.keys(data).filter((key) => data[key] !== '').length) {
+ this.resetValues();
+ return;
+ }
+
+ data.flags = data.flags.replace(/(\s{1,})?,(\s{1,})?/g, ',');
+ this.setValues({ flags: data.flags, query: data.query }, 'silent');
+
+ request(`${config.base_url_relative}/pages-filter.json/task${config.param_sep}filterPages`, {
+ method: 'post',
+ body: data
+ }, (response) => {
+ this.refreshDOM(response);
+ });
+ }
+
+ refreshDOM(response) {
+ let items = $('[data-nav-id]');
+
+ if (!response) {
+ items.removeClass('search-match').show();
+ this.tree.restore();
+
+ return;
+ }
+
+ items.removeClass('search-match').hide();
+
+ response.results.forEach((page) => {
+ let match = items.filter(`[data-nav-id="${page}"]`).addClass('search-match').show();
+ match.parents('[data-nav-id]').addClass('search-match').show();
+
+ this.tree.expand(page, 'no-store');
+ });
+ }
+
+ setValues({ flags = '', query = ''}, silent) {
+ let flagsArray = flags.replace(/(\s{1,})?,(\s{1,})?/g, ',').split(',');
+ if (this.filters.val() !== flags) {
+ let selectize = this.filters.data('selectize');
+ this.filters[selectize ? 'setValue' : 'val'](flagsArray, silent);
+ }
+ if (this.search.val() !== query) { this.search.val(query); }
+
+ localStorage.setItem('grav:admin:pages:filter', JSON.stringify({ flags, query }));
+ }
+
+ resetValues() {
+ this.setValues('', 'silent');
+ this.refreshDOM();
+ }
+
+ _initSelectize() {
+ let extras = {
+ type: this.filters.data('filter-types') || {},
+ access: this.filters.data('filter-access-levels') || {}
+ };
+
+ Object.keys(extras).forEach((cat) => {
+ Object.keys(extras[cat]).forEach((key) => {
+ this.options.push({
+ cat,
+ key,
+ flag: extras[cat][key]
+ });
+ });
+ });
+
+ this.filters.selectize({
+ maxItems: null,
+ valueField: 'key',
+ labelField: 'flag',
+ searchField: ['flag', 'key'],
+ options: this.options,
+ optgroups: this.labels,
+ optgroupField: 'cat',
+ optgroupLabelField: 'name',
+ optgroupValueField: 'id',
+ optgroupOrder: this.labels.map((item) => item.id),
+ plugins: ['optgroup_columns', 'required-fix']
+ });
+ }
+}
+
+let Instance = new PagesFilter('input[name="page-filter"]', 'input[name="page-search"]');
+export { Instance };
diff --git a/user/plugins/admin/themes/grav/app/pages/index.js b/user/plugins/admin/themes/grav/app/pages/index.js
new file mode 100644
index 0000000..f9d98f9
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/pages/index.js
@@ -0,0 +1,53 @@
+import $ from 'jquery';
+import Sortable from 'sortablejs';
+import PageFilters, { Instance as PageFiltersInstance } from './filter';
+import Page from './page';
+
+const pad = (n, s) => (`000${n}`).substr(-s);
+
+// Pages Ordering
+let Ordering = null;
+let orderingElement = $('#ordering');
+if (orderingElement.length) {
+ Ordering = new Sortable(orderingElement.get(0), {
+ filter: '.ignore',
+ onUpdate: function() {
+ /* Old single page index behavior
+
+ let item = $(event.item);
+ let index = orderingElement.children().index(item) + 1;
+ $('[data-order]').val(index);
+ */
+
+ let indexes = [];
+ const children = orderingElement.children();
+ const padZero = (children.length + '').split('').length;
+ children.each((index, item) => {
+ item = $(item);
+ indexes.push(item.data('id'));
+ item.find('.page-order').text(`${pad(index + 1, padZero)}.`);
+ });
+
+ $('[data-order]').val(indexes.join(','));
+ }
+ });
+
+ $(document).on('input', '[name="data[folder]"]', (event) => {
+ const target = $(event.currentTarget);
+ const activeOrder = $('[data-id][data-active-id]');
+
+ activeOrder.data('id', target.val());
+
+ Ordering.options.onUpdate();
+ });
+
+}
+
+export default {
+ Ordering,
+ Page,
+ PageFilters: {
+ PageFilters,
+ Instance: PageFiltersInstance
+ }
+};
diff --git a/user/plugins/admin/themes/grav/app/pages/page/add.js b/user/plugins/admin/themes/grav/app/pages/page/add.js
new file mode 100644
index 0000000..5848749
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/pages/page/add.js
@@ -0,0 +1,67 @@
+import $ from 'jquery';
+import '../../utils/jquery-utils';
+import request from '../../utils/request';
+import { config } from 'grav-config';
+
+let custom = false;
+let folder = $('[data-remodal-id="modal"] input[name="data[folder]"], [data-remodal-id="modular"] input[name="data[folder]"]');
+let title = $('[data-remodal-id="modal"] input[name="data[title]"], [data-remodal-id="modular"] input[name="data[title]"]');
+let getFields = (type, target) => {
+ target = $(target);
+ let query = `[data-remodal-id="${target.closest('[data-remodal-id]').data('remodal-id')}"]`;
+
+ return {
+ title: type === 'title' ? $(target) : $(`${query} input[name="data[title]"]`),
+ folder: type === 'folder' ? $(target) : $(`${query} input[name="data[folder]"]`)
+ };
+};
+
+title.on('input focus blur', (event) => {
+ if (custom) { return true; }
+ let elements = getFields('title', event.currentTarget);
+
+ let slug = $.slugify(elements.title.val(), {custom: {"'": ''}});
+ elements.folder.val(slug);
+});
+
+folder.on('input', (event) => {
+ let elements = getFields('folder', event.currentTarget);
+
+ let input = elements.folder.get(0);
+ let value = elements.folder.val();
+ let selection = {
+ start: input.selectionStart,
+ end: input.selectionEnd
+ };
+
+ value = value.toLowerCase().replace(/\s/g, '-').replace(/[^a-z0-9_\-]/g, '');
+ elements.folder.val(value);
+ custom = !!value;
+
+ // restore cursor position
+ input.setSelectionRange(selection.start, selection.end);
+
+});
+
+folder.on('focus blur', (event) => getFields('title', event.currentTarget).title.trigger('input'));
+
+$(document).on('change', '[name="data[route]"]', (event) => {
+ const rawroute = $(event.currentTarget).val();
+ const pageTemplate = $('[name="data[name]"]');
+ const URI = `${config.base_url_relative}/ajax.json/task${config.param_sep}getChildTypes`;
+
+ if (pageTemplate.length === 0) {
+ return;
+ }
+
+ request(URI, {
+ method: 'post',
+ body: { rawroute }
+ }, (response) => {
+ const type = response.child_type;
+ if (type !== '' && type !== 'default') {
+ pageTemplate.val(type);
+ pageTemplate.data('selectize').setValue(type);
+ }
+ });
+});
diff --git a/user/plugins/admin/themes/grav/app/pages/page/delete.js b/user/plugins/admin/themes/grav/app/pages/page/delete.js
new file mode 100644
index 0000000..1182820
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/pages/page/delete.js
@@ -0,0 +1,15 @@
+import $ from 'jquery';
+
+$(document).on('click', '[data-remodal-target="delete"]', function() {
+ let confirm = $('[data-remodal-id="delete"] [data-delete-action]');
+ let link = $(this).data('delete-url');
+
+ confirm.data('delete-action', link);
+});
+
+$(document).on('click', '[data-delete-action]', function() {
+ let remodal = $.remodal.lookup[$('[data-remodal-id="delete"]').data('remodal')];
+
+ global.location.href = $(this).data('delete-action');
+ remodal.close();
+});
diff --git a/user/plugins/admin/themes/grav/app/pages/page/disable-buttons.js b/user/plugins/admin/themes/grav/app/pages/page/disable-buttons.js
new file mode 100644
index 0000000..fbe7109
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/pages/page/disable-buttons.js
@@ -0,0 +1,5 @@
+import $ from 'jquery';
+
+$('.disable-after-click').on('click', function() {
+ $(this).addClass('pointer-events-disabled');
+});
diff --git a/user/plugins/admin/themes/grav/app/pages/page/index.js b/user/plugins/admin/themes/grav/app/pages/page/index.js
new file mode 100644
index 0000000..c8f21fd
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/pages/page/index.js
@@ -0,0 +1,47 @@
+import $ from 'jquery';
+import './add';
+import './move';
+import './delete';
+import './unset';
+import './disable-buttons';
+import PageMedia, { Instance as PageMediaInstances } from './media';
+import './multilang';
+
+const switcher = $('input[type="radio"][name="mode-switch"]');
+
+if (switcher) {
+ let link = switcher.closest(':checked').data('leave-url');
+ let fakeLink = $(`
`);
+
+ switcher.parent().append(fakeLink);
+
+ switcher.siblings('label').on('mousedown touchdown', (event) => {
+ event.preventDefault();
+
+ // let remodal = $.remodal.lookup[$('[data-remodal-id="changes"]').data('remodal')];
+ let confirm = $('[data-remodal-id="changes"] [data-leave-action="continue"]');
+
+ confirm.one('click', () => {
+ $(global).on('beforeunload._grav');
+ fakeLink.off('click._grav');
+
+ $(event.target).trigger('click');
+ });
+
+ fakeLink.trigger('click._grav');
+ });
+
+ switcher.on('change', (event) => {
+ let radio = $(event.target);
+ link = radio.data('leave-url');
+
+ setTimeout(() => fakeLink.attr('href', link).get(0).click(), 5);
+ });
+}
+
+export default {
+ Media: {
+ PageMedia,
+ PageMediaInstances
+ }
+};
diff --git a/user/plugins/admin/themes/grav/app/pages/page/media.js b/user/plugins/admin/themes/grav/app/pages/page/media.js
new file mode 100644
index 0000000..1a814ac
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/pages/page/media.js
@@ -0,0 +1,201 @@
+import $ from 'jquery';
+import request from '../../utils/request';
+import FilesField, { UriToMarkdown } from '../../forms/fields/files';
+import { config, translations } from 'grav-config';
+import { Instance as Editor } from '../../forms/fields/editor';
+import Sortable from 'sortablejs';
+
+const previewTemplate = `
+
`.trim();
+
+export default class PageMedia extends FilesField {
+ constructor({ container = '#grav-dropzone', options = {} } = {}) {
+ options = Object.assign(options, { previewTemplate });
+ super({ container, options });
+ if (!this.container.length) { return; }
+
+ this.urls = {
+ fetch: `${this.container.data('media-url')}/task${config.param_sep}listmedia`,
+ add: `${this.container.data('media-url')}/task${config.param_sep}addmedia`,
+ delete: `${this.container.data('media-url')}/task${config.param_sep}delmedia`
+ };
+
+ this.dropzone.options.url = this.urls.add;
+
+ if (typeof this.options.fetchMedia === 'undefined' || this.options.fetchMedia) {
+ this.fetchMedia();
+ }
+
+ if (typeof this.options.attachDragDrop === 'undefined' || this.options.attachDragDrop) {
+ this.attachDragDrop();
+ }
+
+ const field = $(`[name="${this.container.data('dropzone-field')}"]`);
+
+ if (field.length) {
+ this.sortable = new Sortable(this.container.get(0), {
+ animation: 150,
+ // forceFallback: true,
+ setData: (dataTransfer, target) => {
+ target = $(target);
+ let uri = encodeURI(target.find('.dz-filename').text());
+ let shortcode = UriToMarkdown(uri);
+ this.dropzone.disable();
+ target.addClass('hide-backface');
+ dataTransfer.effectAllowed = 'copy';
+ dataTransfer.setData('text', shortcode);
+ },
+ onSort: () => {
+ let names = [];
+ this.container.find('[data-dz-name]').each((index, file) => {
+ file = $(file);
+ const name = file.text().trim();
+ names.push(name);
+ });
+
+ field.val(names.join(','));
+ }
+ });
+ }
+ }
+
+ fetchMedia() {
+ const order = this.container.closest('.form-field').find('[name="data[header][media_order]"]').val();
+ const body = { uri: this.getURI(), order };
+ let url = this.urls.fetch;
+
+ request(url, { method: 'post', body }, (response) => {
+ let results = response.results;
+
+ Object.keys(results).forEach((name) => {
+ let data = results[name];
+ let mock = { name, size: data.size, accepted: true, extras: data };
+
+ this.dropzone.files.push(mock);
+ this.dropzone.options.addedfile.call(this.dropzone, mock);
+ this.dropzone.options.thumbnail.call(this.dropzone, mock, data.url);
+ });
+
+ this.container.find('.dz-preview').prop('draggable', 'true');
+ });
+ }
+
+ onDropzoneSending(file, xhr, formData) {
+ /*
+ // Cannot call super because Safari and IE API don't implement `delete`
+ super.onDropzoneSending(file, xhr, formData);
+ formData.delete('task');
+ */
+
+ formData.append('name', this.options.dotNotation || file.name);
+ formData.append('admin-nonce', config.admin_nonce);
+ formData.append('uri', this.getURI());
+ }
+
+ onDropzoneComplete(file) {
+ super.onDropzoneComplete(file);
+ this.sortable.options.onSort();
+
+ // accepted
+ $('.dz-preview').prop('draggable', 'true');
+ }
+
+ onDropzoneRemovedFile(file, ...extra) {
+ super.onDropzoneRemovedFile(file, ...extra);
+ this.sortable.options.onSort();
+ }
+
+ attachDragDrop() {
+ this.container.delegate('[data-dz-insert]', 'click', (e) => {
+ let target = $(e.currentTarget).parent('.dz-preview').find('.dz-filename');
+ let editor = Editor.editors.filter((index, editor) => $(editor).attr('name') === 'data[content]');
+
+ if (editor.length) {
+ editor = editor.data('codemirror');
+ editor.focus();
+
+ let filename = encodeURI(target.text());
+ let shortcode = UriToMarkdown(filename);
+ editor.doc.replaceSelection(shortcode);
+ }
+ });
+
+ this.container.delegate('[data-dz-view]', 'mouseenter', (e) => {
+ let target = $(e.currentTarget);
+ let file = target.parent('.dz-preview').find('.dz-filename');
+ let filename = encodeURI(file.text());
+ let URL = target.closest('[data-media-path]').data('media-path');
+ let original = this.dropzone.files.filter((file) => encodeURIComponent(file.name) === filename).shift();
+
+ original = original.extras.original || encodeURIComponent(original.name);
+
+ target.attr('href', `${URL}/${original}`);
+ });
+
+ this.container.delegate('[data-dz-metadata]', 'click', (e) => {
+ e.preventDefault();
+ const target = $(e.currentTarget);
+ const file = target.parent('.dz-preview').find('.dz-filename');
+ const filename = encodeURI(file.text());
+
+ let fileObj = this.dropzone.files.filter((file) => file.name === global.decodeURI(filename)).shift() || {};
+
+ if (!fileObj.extras) {
+ fileObj.extras = { metadata: [] };
+ }
+
+ if (Array.isArray(fileObj.extras.metadata) && !fileObj.extras.metadata.length) {
+ fileObj.extras.metadata = { '': `${global.decodeURI(filename)}.meta.yaml doesn't exist` };
+ }
+
+ fileObj = fileObj.extras;
+
+ const modal_element = $('body').find('[data-remodal-id="metadata"]');
+ const modal = $.remodal.lookup[modal_element.data('remodal')];
+
+ modal_element.find('h1 strong').html(filename);
+ if (fileObj.url) {
+ modal_element.find('.meta-preview').html(`
`);
+ }
+
+ const container = modal_element.find('.meta-content').html('
').find('ul');
+ Object.keys(fileObj.metadata).forEach((meta) => {
+ container.append(`
${meta ? meta + ':' : ''} ${fileObj.metadata[meta]}`);
+ });
+
+ modal.open();
+ });
+
+ this.container.delegate('.dz-preview', 'dragstart', (e) => {
+ let target = $(e.currentTarget);
+ let uri = encodeURI(target.find('.dz-filename').text());
+ let shortcode = UriToMarkdown(uri);
+ this.dropzone.disable();
+ target.addClass('hide-backface');
+ e.originalEvent.dataTransfer.effectAllowed = 'copy';
+ e.originalEvent.dataTransfer.setData('text', shortcode);
+ });
+
+ this.container.delegate('.dz-preview', 'dragend', (e) => {
+ let target = $(e.currentTarget);
+ this.dropzone.enable();
+ target.removeClass('hide-backface');
+ });
+ }
+}
+
+export let Instance = new PageMedia();
diff --git a/user/plugins/admin/themes/grav/app/pages/page/move.js b/user/plugins/admin/themes/grav/app/pages/page/move.js
new file mode 100644
index 0000000..4070969
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/pages/page/move.js
@@ -0,0 +1,13 @@
+import $ from 'jquery';
+
+$('[data-page-move] button[name="task"][value="save"]').on('click', function() {
+ let route = $('form#blueprints:first select[name="data[route]"]');
+ let moveTo = $('[data-page-move] select').val();
+
+ if (route.length && route.val() !== moveTo) {
+ let selectize = route.data('selectize');
+ route.val(moveTo);
+
+ if (selectize) selectize.setValue(moveTo);
+ }
+});
diff --git a/user/plugins/admin/themes/grav/app/pages/page/multilang.js b/user/plugins/admin/themes/grav/app/pages/page/multilang.js
new file mode 100644
index 0000000..d91b862
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/pages/page/multilang.js
@@ -0,0 +1,22 @@
+import $ from 'jquery';
+
+$('[name="task"][value="saveas"], [name="task"][value="switchlanguage"]').on('mousedown touchstart', (event) => {
+ let fields = ['lang', 'redirect'];
+ let element = $(event.currentTarget);
+ let form = $(`#${element.attr('form')}`);
+
+ if (!form.length) { return; }
+ fields.forEach((field) => {
+ let value = element.attr(field);
+ if (!value) { return; }
+ let input = form.find(`[name="data[${field}]"]`);
+ if (!input.length) {
+ input = $(`
`);
+ form.append(input);
+ }
+
+ input.val(value);
+ });
+
+ return true;
+});
diff --git a/user/plugins/admin/themes/grav/app/pages/page/unset.js b/user/plugins/admin/themes/grav/app/pages/page/unset.js
new file mode 100644
index 0000000..d64ee17
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/pages/page/unset.js
@@ -0,0 +1,18 @@
+import $ from 'jquery';
+
+$(document).on('click', '.dz-unset', function() {
+
+ const file_upload = $(this).closest('.files-upload');
+ $(this).closest('.dz-image-preview').remove();
+ const unset_image = $(this).closest('.dz-image-preview').find('[data-dz-name]').text().trim();
+ const images = JSON.parse(file_upload.find('input[data-grav-field="hidden"]').val()) || {};
+ let image_array = {};
+
+ $.each(images, function(ind, obj) {
+ if (!ind.endsWith(unset_image)) {
+ image_array[ind] = obj;
+ }
+ });
+
+ file_upload.find('input[data-grav-field="hidden"]').val(JSON.stringify(image_array));
+});
diff --git a/user/plugins/admin/themes/grav/app/pages/tree.js b/user/plugins/admin/themes/grav/app/pages/tree.js
new file mode 100644
index 0000000..a6609e7
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/pages/tree.js
@@ -0,0 +1,135 @@
+import $ from 'jquery';
+import '../utils/storage';
+
+const sessionKey = 'grav:admin:pages';
+
+if (!sessionStorage.getItem(sessionKey)) {
+ sessionStorage.setItem(sessionKey, '{}');
+}
+
+export default class PagesTree {
+ constructor(query, elements = undefined) {
+ this.query = query;
+ this.elements = $(elements !== undefined ? elements : this.query);
+ this.session = JSON.parse(sessionStorage.getItem(sessionKey) || '{}');
+
+ if (!this.elements.length) { return; }
+
+ this.restore();
+
+ this.elements.find('.page-icon').on('click', (event) => this.toggle(event.target));
+ this.elements.data('tree_init', 1);
+
+ $('[data-page-toggleall]').on('click', (event) => {
+ let element = $(event.target).closest('[data-page-toggleall]');
+ let action = element.data('page-toggleall');
+
+ this[action]();
+ });
+ }
+
+ reload() {
+ const elements = $(this.query).filter((index, element) => !$(element).data('tree_init'));
+ if (!elements.length) { return; }
+ this.constructor(this.query, elements);
+ }
+
+ toggle(elements, dontStore = false) {
+ if (typeof elements === 'string') {
+ elements = $(`[data-nav-id="${elements}"]`).find('[data-toggle="children"]');
+ }
+
+ elements = $(elements || this.elements);
+ elements.each((index, element) => {
+ element = $(element);
+ let state = this.getState(element.closest('[data-toggle="children"]'));
+ this[state.isOpen ? 'collapse' : 'expand'](state.id, dontStore);
+ });
+ }
+
+ collapse(elements, dontStore = false) {
+ if (typeof elements === 'string') {
+ elements = $(`[data-nav-id="${elements}"]`).find('[data-toggle="children"]');
+ }
+
+ elements = $(elements || this.elements);
+ elements.each((index, element) => {
+ element = $(element);
+ let state = this.getState(element);
+
+ if (state.isOpen) {
+ state.children.hide();
+ state.icon.removeClass('children-open').addClass('children-closed');
+ if (!dontStore) { delete this.session[state.id]; }
+ }
+ });
+
+ const scroller = elements.closest('.mediapicker-scroll');
+ if (scroller.length && scroller.data('scrollbar')) {
+ scroller.data('scrollbar').update();
+ }
+
+ if (!dontStore) { this.save(); }
+ }
+
+ expand(elements, dontStore = false) {
+ if (typeof elements === 'string') {
+ let element = $(`[data-nav-id="${elements}"]`);
+ let parents = element.parents('[data-nav-id]');
+
+ // loop back through parents, we don't want to expand an hidden child
+ if (parents.length) {
+ parents = parents.find('[data-toggle="children"]:first');
+ parents = parents.add(element.find('[data-toggle="children"]:first'));
+ return this.expand(parents, dontStore);
+ }
+
+ elements = element.find('[data-toggle="children"]:first');
+ }
+
+ elements = $(elements || this.elements);
+ elements.each((index, element) => {
+ element = $(element);
+ let state = this.getState(element);
+
+ if (!state.isOpen) {
+ state.children.show();
+ state.icon.removeClass('children-closed').addClass('children-open');
+ if (!dontStore) { this.session[state.id] = 1; }
+ }
+ });
+
+ const scroller = elements.closest('.mediapicker-scroll');
+ if (scroller.length && scroller.data('scrollbar')) {
+ scroller.data('scrollbar').update();
+ }
+
+ if (!dontStore) { this.save(); }
+ }
+
+ restore() {
+ this.collapse(null, true);
+
+ Object.keys(this.session).forEach((key) => {
+ this.expand(key, 'no-store');
+ });
+ }
+
+ save() {
+ return sessionStorage.setItem(sessionKey, JSON.stringify(this.session));
+ }
+
+ getState(element) {
+ element = $(element);
+
+ return {
+ id: element.closest('[data-nav-id]').data('nav-id'),
+ children: element.closest('li.page-item').find('ul:first'),
+ icon: element.find('.page-icon'),
+ get isOpen() { return this.icon.hasClass('children-open'); }
+ };
+ }
+}
+
+let Instance = new PagesTree('[data-toggle="children"]');
+export { Instance };
diff --git a/user/plugins/admin/themes/grav/app/plugins/index.js b/user/plugins/admin/themes/grav/app/plugins/index.js
new file mode 100644
index 0000000..b220d62
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/plugins/index.js
@@ -0,0 +1,92 @@
+import $ from 'jquery';
+import packages from '../utils/packages';
+import camelCase from 'mout/string/camelCase';
+import debounce from 'debounce';
+import contains from 'mout/string/contains';
+
+// Plugins sliders details
+$('.gpm-name, .gpm-actions').on('click', function(e) {
+ let element = $(this);
+ let target = $(e.target);
+ let tag = target.prop('tagName').toLowerCase();
+
+ if (tag === 'a' || element.parent('a').length || target.parent('a').length) { return true; }
+
+ let wrapper = element.siblings('.gpm-details').find('.table-wrapper');
+
+ wrapper.slideToggle({
+ duration: 350,
+ complete: () => {
+ let visible = wrapper.is(':visible');
+ wrapper
+ .closest('tr')
+ .find('.gpm-details-expand i')
+ .removeClass('fa-chevron-' + (visible ? 'down' : 'up'))
+ .addClass('fa-chevron-' + (visible ? 'up' : 'down'));
+ }
+ });
+});
+
+// Removing plugin
+$(document).on('click', '[data-plugin-action="remove-package"]', (event) => {
+ packages.handleRemovingPackage('plugin', event);
+});
+
+// Reinstall plugin
+$(document).on('click', '[data-plugin-action="reinstall-package"]', (event) => {
+ packages.handleReinstallPackage('plugin', event);
+});
+
+$(document).on('click', '[data-plugin-action="remove-dependency-package"]', (event) => {
+ packages.handleRemovingDependency('plugin', event);
+});
+
+// Trigger the add new plugin / update plugin modal
+$(document).on('click', '[data-plugin-action="start-package-installation"]', (event) => {
+ packages.handleGettingPackageDependencies('plugin', event, 'install');
+});
+
+// Trigger the update all plugins modal
+$(document).on('click', '[data-plugin-action="start-packages-update"]', (event) => {
+ packages.handleGettingPackageDependencies('plugin', event);
+});
+
+// Install a plugin dependencies and the plugin
+$(document).on('click', '[data-plugin-action="install-dependencies-and-package"]', (event) => {
+ packages.handleInstallingDependenciesAndPackage('plugin', event);
+});
+
+// Install a plugin
+$(document).on('click', '[data-plugin-action="install-package"]', (event) => {
+ packages.handleInstallingPackage('plugin', event);
+});
+
+// Sort plugins/themes dropdown
+$(document).on('change', '.sort-actions select', (event) => {
+ let direction = $('.sort-actions .sort-icon .fa').hasClass('fa-sort-amount-desc') ? 'desc' : 'asc';
+ let sorting = $(event.currentTarget).val();
+
+ packages.Sort[camelCase(`by-${sorting}`)](direction);
+});
+
+// Sort plugins/themes icon
+$(document).on('click', '.sort-icon', (event) => {
+ let icon = $(event.currentTarget).find('.fa');
+ let current = icon.hasClass('fa-sort-amount-asc') ? 'asc' : 'desc';
+ let opposite = current === 'asc' ? 'desc' : 'asc';
+
+ icon.removeClass(`fa-sort-amount-${current}`).addClass(`fa-sort-amount-${opposite}`);
+ $('.sort-actions select').trigger('change');
+});
+
+// Filter plugin/theme
+$(document).on('input', '[data-gpm-filter]', debounce((event) => {
+ let value = $($(event.currentTarget)).val();
+ let items = $('[data-gpm-plugin], [data-gpm-theme]');
+
+ items.hide().filter((index, item) => {
+ item = $(item);
+
+ return contains(item.data('gpm-plugin'), value) || contains(item.data('gpm-theme'), value) || contains(item.data('gpm-name').toLowerCase(), value.toLowerCase());
+ }).show();
+}, 250));
diff --git a/user/plugins/admin/themes/grav/app/themes/index.js b/user/plugins/admin/themes/grav/app/themes/index.js
new file mode 100644
index 0000000..bc0dad8
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/themes/index.js
@@ -0,0 +1,46 @@
+import $ from 'jquery';
+import packages from '../utils/packages';
+
+// Themes Switcher Warning
+$(document).on('mousedown', '[data-remodal-target="theme-switch-warn"]', (event) => {
+ let name = $(event.target).closest('[data-gpm-theme]').find('.gpm-name a:first').text();
+ let remodal = $('.remodal.theme-switcher');
+
+ remodal.find('strong').text(name);
+ remodal.find('.button.continue').attr('href', $(event.target).attr('href'));
+});
+
+// Removing theme
+$(document).on('click', '[data-theme-action="remove-package"]', (event) => {
+ packages.handleRemovingPackage('theme', event);
+});
+
+// Reinstall theme
+$(document).on('click', '[data-theme-action="reinstall-package"]', (event) => {
+ packages.handleReinstallPackage('theme', event);
+});
+
+$(document).on('click', '[data-theme-action="remove-dependency-package"]', (event) => {
+ packages.handleRemovingDependency('theme', event);
+});
+
+// Opened the add new theme / update theme modal
+$(document).on('click', '[data-theme-action="start-package-installation"]', (event) => {
+ packages.handleGettingPackageDependencies('theme', event, 'install');
+});
+
+// Trigger the update all themes modal
+$(document).on('click', '[data-theme-action="start-packages-update"]', (event) => {
+ packages.handleGettingPackageDependencies('theme', event);
+});
+
+// Install a theme dependencies and the theme
+$(document).on('click', '[data-theme-action="install-dependencies-and-package"]', (event) => {
+ packages.handleInstallingDependenciesAndPackage('theme', event);
+});
+
+// Install a theme
+$(document).on('click', '[data-theme-action="install-package"]', (event) => {
+ packages.handleInstallingPackage('theme', event);
+});
+
diff --git a/user/plugins/admin/themes/grav/app/tools/index.js b/user/plugins/admin/themes/grav/app/tools/index.js
new file mode 100644
index 0000000..37d4b65
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/tools/index.js
@@ -0,0 +1 @@
+import './logs';
diff --git a/user/plugins/admin/themes/grav/app/tools/logs.js b/user/plugins/admin/themes/grav/app/tools/logs.js
new file mode 100644
index 0000000..ae80b25
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/tools/logs.js
@@ -0,0 +1,14 @@
+import $ from 'jquery';
+import { setParam } from 'mout/queryString';
+
+const prepareQuery = (key, value) => {
+ return setParam(global.location.href, key, value);
+};
+
+$(document).on('change', '.logs-content .block-select select[name]', (event) => {
+ const target = $(event.currentTarget);
+ const name = target.attr('name');
+ const value = target.val();
+
+ global.location.href = prepareQuery(name, value);
+});
diff --git a/user/plugins/admin/themes/grav/app/updates/channel-switcher.js b/user/plugins/admin/themes/grav/app/updates/channel-switcher.js
new file mode 100644
index 0000000..8531eb8
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/updates/channel-switcher.js
@@ -0,0 +1,24 @@
+import $ from 'jquery';
+import request from '../utils/request';
+
+const switcher = $('input[type="radio"][name="channel-switch"]');
+
+if (switcher) {
+ switcher.on('change', (event) => {
+ let radio = $(event.target);
+ let url = `${radio.parent('[data-url]').data('url')}`;
+
+ request(url, {
+ method: 'post',
+ body: {
+ task: 'gpmRelease',
+ release: radio.val()
+ }
+ },
+ (response) => {
+ if (response.reload) {
+ global.location.reload();
+ }
+ });
+ });
+}
diff --git a/user/plugins/admin/themes/grav/app/updates/check.js b/user/plugins/admin/themes/grav/app/updates/check.js
new file mode 100644
index 0000000..4991f02
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/updates/check.js
@@ -0,0 +1,26 @@
+import $ from 'jquery';
+import { Instance as gpm } from '../utils/gpm';
+import { translations } from 'grav-config';
+import toastr from '../utils/toastr';
+
+// Check for updates trigger
+$('[data-gpm-checkupdates]').on('click', function() {
+ let element = $(this);
+ element.find('i').addClass('fa-spin');
+
+ gpm.fetch((response) => {
+ element.find('i').removeClass('fa-spin');
+ let payload = response.payload;
+
+ if (!payload) { return; }
+ if (!payload.grav.isUpdatable && !payload.resources.total) {
+ toastr.success(translations.PLUGIN_ADMIN.EVERYTHING_UP_TO_DATE);
+ } else {
+ var grav = payload.grav.isUpdatable ? 'Grav v' + payload.grav.available : '';
+ var resources = payload.resources.total ? payload.resources.total + ' ' + translations.PLUGIN_ADMIN.UPDATES_ARE_AVAILABLE : '';
+
+ if (!resources) { grav += ' ' + translations.PLUGIN_ADMIN.IS_AVAILABLE_FOR_UPDATE; }
+ toastr.info(grav + (grav && resources ? ' ' + translations.PLUGIN_ADMIN.AND + ' ' : '') + resources);
+ }
+ }, true);
+});
diff --git a/user/plugins/admin/themes/grav/app/updates/feed.js b/user/plugins/admin/themes/grav/app/updates/feed.js
new file mode 100644
index 0000000..e06a8a6
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/updates/feed.js
@@ -0,0 +1,68 @@
+import $ from 'jquery';
+import { config } from 'grav-config';
+import request from '../utils/request';
+
+const URI = `${config.base_url_relative}/ajax.json/task${config.param_sep}getNewsFeed`;
+
+class Feed {
+ constructor() {
+ this.data = null;
+ }
+
+ fetch(refresh = false, callback = function() {}) {
+ request(URI, {
+ method: 'post',
+ body: { refresh }
+ }, (response) => {
+ this.data = response;
+ callback(response);
+ });
+ }
+
+ refresh(refresh = false) {
+ const feed = $('#news-feed .widget-content');
+ if (!feed.length) { return; }
+
+ let loader = feed.find('.widget-loader');
+ loader.find('div').remove();
+ loader.find('.fa-warning').removeClass('fa-warning').addClass('fa-refresh fa-spin');
+ loader.show();
+
+ feed.find('> ul').hide();
+
+ if (!this.data || this.data.error || refresh) {
+ this.fetch(refresh, this.updateContent.bind(this));
+ } else {
+ this.updateContent();
+ }
+ }
+
+ updateContent() {
+ const feed = $('#news-feed .widget-content');
+ if (!feed.length) { return; }
+
+ let loader = feed.find('.widget-loader').hide();
+ let content = feed.find('> ul').empty().show();
+
+ if (this.data.error || this.data.status === 'error') {
+ loader.show().find('div').remove();
+ loader.find('.fa-refresh').removeClass('fa-refresh fa-spin').addClass('fa-warning');
+ loader.append(`
${this.data.error ? this.data.error.message : this.data.message || 'Unable to download news feed'}
`);
+
+ return;
+ }
+
+ if (this.data && this.data.feed_data) {
+ content.append(this.data.feed_data);
+ }
+ }
+}
+
+let feed = new Feed();
+$(document).ready(() => feed.refresh());
+$(document).on('click', '[data-refresh="feed"]', (event) => {
+ event.preventDefault();
+ feed.refresh(true);
+});
+
+export default feed;
diff --git a/user/plugins/admin/themes/grav/app/updates/index.js b/user/plugins/admin/themes/grav/app/updates/index.js
new file mode 100644
index 0000000..0c4e598
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/updates/index.js
@@ -0,0 +1,186 @@
+import $ from 'jquery';
+import unique from 'mout/array/unique';
+import { config, translations } from 'grav-config';
+import formatBytes from '../utils/formatbytes';
+import { Instance as gpm } from '../utils/gpm';
+import Notifications from './notifications';
+import Feed from './feed';
+import './check';
+import './update';
+import './channel-switcher';
+
+export default class Updates {
+ constructor(payload = {}) {
+ this.setPayload(payload);
+ this.task = `task${config.param_sep}`;
+ }
+
+ setPayload(payload = {}) {
+ this.payload = payload;
+
+ return this;
+ }
+
+ fetch(force = false) {
+ gpm.fetch((response) => this.setPayload(response), force);
+
+ return this;
+ }
+
+ maintenance(mode = 'hide') {
+ let element = $('#updates [data-update-packages]');
+
+ element[mode === 'show' ? 'fadeIn' : 'fadeOut']();
+
+ if (mode === 'hide') {
+ $('.badges.with-updates').removeClass('with-updates').find('.badge.updates').remove();
+ }
+
+ return this;
+ }
+
+ grav() {
+ let payload = this.payload.grav;
+
+ if (payload && payload.isUpdatable) {
+ let task = this.task;
+ let bar = '';
+
+ if (!payload.isSymlink) {
+ bar += `
${translations.PLUGIN_ADMIN.UPDATE_GRAV_NOW} `;
+ } else {
+ bar += `
`;
+ }
+
+ bar += `
+ Grav
v${payload.available} ${translations.PLUGIN_ADMIN.IS_NOW_AVAILABLE}!
(${translations.PLUGIN_ADMIN.CURRENT} v${payload.version})
+ `;
+
+ let element = $('[data-gpm-grav]').removeClass('hidden');
+
+ if (element.is(':empty')) {
+ element.hide();
+ }
+
+ element
+ .addClass('grav')
+ .html(`${bar}`)
+ .slideDown(150)
+ .parent('#messages').addClass('default-box-shadow');
+ }
+
+ $('#grav-update-button').on('click', function() {
+ $(this).html(`${translations.PLUGIN_ADMIN.UPDATING_PLEASE_WAIT} ${formatBytes(payload.assets['grav-update'].size)}..`);
+ });
+
+ return this;
+ }
+
+ resources() {
+ if (!this.payload || !this.payload.resources || !this.payload.resources.total) {
+ return this.maintenance('hide');
+ }
+
+ let is_current_package_latest = true;
+ let map = ['plugins', 'themes'];
+ let singles = ['plugin', 'theme'];
+ let { plugins, themes } = this.payload.resources;
+
+ if (!this.payload.resources.total) { return this; }
+
+ [plugins, themes].forEach(function(resources, index) {
+ if (!resources || Array.isArray(resources)) { return; }
+ let length = Object.keys(resources).length;
+ let type = map[index];
+
+ // sidebar
+ $(`#admin-menu a[href$="/${map[index]}"]`)
+ .find('.badges')
+ .addClass('with-updates')
+ .find('.badge.updates').text(length);
+
+ var type_translation = '';
+ // update all
+
+ if (type === 'plugins') {
+ type_translation = translations.PLUGIN_ADMIN.PLUGINS;
+ } else {
+ type_translation = translations.PLUGIN_ADMIN.THEMES;
+ }
+
+ let updateAll = $(`.grav-update.${type}`);
+ updateAll.css('display', 'block').html(`
+
+ ${translations.PLUGIN_ADMIN.UPDATE} ${translations.PLUGIN_ADMIN.ALL} ${type_translation}
+
+ ${length} ${translations.PLUGIN_ADMIN.OF_YOUR} ${type_translation.toLowerCase()} ${translations.PLUGIN_ADMIN.HAVE_AN_UPDATE_AVAILABLE}
+
+ `);
+
+ let existing_slugs = $('[data-update-packages]').attr('data-packages-slugs') || '';
+
+ if (existing_slugs) {
+ existing_slugs = existing_slugs.split(',');
+ } else {
+ existing_slugs = [];
+ }
+
+ let slugs = unique(existing_slugs.concat(Object.keys(resources))).join();
+ $('[data-update-packages]').attr('data-packages-slugs', `${slugs}`);
+
+ Object.keys(resources).forEach(function(item) {
+ // listing page
+ let container = $(`[data-gpm-${singles[index]}="${item}"]`);
+ let element = container.find('.gpm-name');
+ let url = element.find('a');
+ let content_wrapper = container.parents('.content-wrapper');
+
+ if (type === 'plugins' && !element.find('.badge.update').length) {
+ element.append(`
${translations.PLUGIN_ADMIN.UPDATE_AVAILABLE}! `);
+ content_wrapper.addClass('has-updates');
+ } else if (type === 'themes') {
+ element.append(`
`);
+ content_wrapper.addClass('has-updates');
+ }
+
+ // details page
+ if (container.length) {
+ let details = $(`.grav-update.${singles[index]}`);
+ if (details.length) {
+ let releaseType = resources[item].type === 'testing' ? '
test release ' : '';
+ details.html(`
+
+ ${translations.PLUGIN_ADMIN.UPDATE} ${singles[index].charAt(0).toUpperCase() + singles[index].substr(1).toLowerCase()}
+
+ v${resources[item].available} ${releaseType} ${translations.PLUGIN_ADMIN.OF_THIS} ${singles[index]} ${translations.PLUGIN_ADMIN.IS_NOW_AVAILABLE}!
+
+ `).css('display', 'block');
+
+ is_current_package_latest = false;
+ }
+ }
+ });
+
+ $('[data-update-packages]').removeClass('hidden');
+ });
+
+ $('.content-wrapper').addClass('updates-checked');
+
+ if (!is_current_package_latest) {
+ $('.warning-reinstall-not-latest-release').removeClass('hidden');
+ }
+ }
+}
+
+let Instance = new Updates();
+export { Instance, Notifications, Feed };
+
+// automatically refresh UI for updates (graph, sidebar, plugin/themes pages) after every fetch
+gpm.on('fetched', (response, raw) => {
+ Instance.setPayload(response.payload || {});
+ Instance.grav().resources();
+});
+
+if (config.enable_auto_updates_check === '1') {
+ gpm.fetch();
+}
diff --git a/user/plugins/admin/themes/grav/app/updates/notifications.js b/user/plugins/admin/themes/grav/app/updates/notifications.js
new file mode 100644
index 0000000..b58b115
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/updates/notifications.js
@@ -0,0 +1,170 @@
+import $ from 'jquery';
+import { config } from 'grav-config';
+import request from '../utils/request';
+
+const canFetchNotifications = () => config.notifications.enabled;
+const notificationsFilters = () => config.notifications.filters;
+
+class Notifications {
+
+ static addShowAllInFeed() {
+ $('#notifications ul').append('
Show all ');
+ }
+
+ static showNotificationInFeed(notification) {
+ let notifications = $('#notifications').removeClass('hidden');
+
+ let loader = notifications.find('.widget-loader').hide();
+ let content = notifications.find('.widget-content > ul').show();
+ loader.find('div').remove();
+ loader.find('.fa-warning').removeClass('fa-warning').addClass('fa-refresh fa-spin');
+
+ content
+ .append(notification)
+ .find('li:nth-child(n+11)').addClass('hidden'); // hide all items > 10
+
+ if (content.find('li.hidden').length) {
+ Notifications.addShowAllInFeed();
+ }
+ }
+
+ static showNotificationInTop(notification) {
+ const container = $('.top-notifications-container');
+ const dummy = $('
').html(notification);
+
+ container.removeClass('hidden').append(dummy.children());
+ dummy.children().slideDown(150);
+ }
+
+ static showNotificationInDashboard(notification) {
+ const container = $('.dashboard-notifications-container');
+ const dummy = $('
').html(notification);
+
+ container.removeClass('hidden').append(dummy.children());
+ dummy.children().slideDown(150);
+ }
+
+ static showNotificationInPlugins(notification) {
+ const container = $('.plugins-notifications-container');
+ const dummy = $('
').html(notification);
+
+ container.removeClass('hidden').append(dummy.children());
+ dummy.children().slideDown(150);
+ }
+
+ static showNotificationInThemes(notification) {
+ const container = $('.themes-notifications-container');
+ const dummy = $('
').html(notification);
+
+ container.removeClass('hidden').append(dummy.children());
+ dummy.children().slideDown(150);
+ }
+
+ static processLocation(location, notification) {
+ switch (location) {
+ case 'feed':
+ Notifications.showNotificationInFeed(notification);
+ break;
+ case 'top':
+ if (!notification.read) {
+ Notifications.showNotificationInTop(notification);
+ }
+ break;
+ case 'dashboard':
+ if (!notification.read) {
+ Notifications.showNotificationInDashboard(notification);
+ }
+ break;
+ case 'plugins':
+ if (!notification.read) {
+ Notifications.showNotificationInPlugins(notification);
+ }
+ break;
+ case 'themes':
+ if (!notification.read) {
+ Notifications.showNotificationInThemes(notification);
+ }
+ break;
+ }
+ }
+
+ // Grav.default.Notifications.fetch()
+ fetch({ filter = notificationsFilters(), refresh = false } = {}) {
+ if (!canFetchNotifications()) {
+ return false;
+ }
+
+ let feed = $('#notifications');
+ let loader = feed.find('.widget-loader');
+ let content = feed.find('.widget-content > ul');
+ loader.find('div').remove();
+ loader.find('.fa-warning').removeClass('fa-warning').addClass('fa-refresh fa-spin');
+ loader.show();
+ content.hide();
+
+ let processNotifications = (response) => {
+ let notifications = response.notifications;
+
+ $('#notifications').find('.widget-content > ul').empty();
+
+ if (notifications) {
+ Object.keys(notifications).forEach((location) => Notifications.processLocation(location, notifications[location]));
+ }
+ };
+
+ request(`${config.base_url_relative}/task${config.param_sep}getNotifications`, {
+ method: 'post',
+ body: { refresh, filter }
+ }, (response) => {
+ processNotifications(response);
+ }).catch(() => {
+ let widget = $('#notifications .widget-content');
+ widget
+ .find('.widget-loader')
+ .find('div').remove();
+
+ widget
+ .find('.widget-loader')
+ .append('
Failed to retrieve notifications
')
+ .find('.fa-spin')
+ .removeClass('fa-spin fa-refresh').addClass('fa-warning');
+ });
+ }
+}
+
+let notifications = new Notifications();
+export default notifications;
+
+if (canFetchNotifications()) {
+ notifications.fetch();
+
+ /* Hide a notification and store it hidden
+ //
+ $(document).on('click', '[data-notification-action="hide-notification"]', (event) => {
+ let notification_id = $(event.target).parents('.hide-notification').data('notification-id');
+
+ let url = `${config.base_url_relative}/notifications.json/task${config.param_sep}hideNotification/notification_id${config.param_sep}${notification_id}`;
+
+ request(url, { method: 'post' }, () => {});
+
+ $(event.target).parents('.single-notification').hide();
+ });
+ */
+
+ $(document).on('click', '[data-notification-action="hide-notification"]', (event) => {
+ const target = $(event.currentTarget);
+ const notification = target.parent();
+
+ notification.slideUp(() => notification.remove());
+ });
+
+ $(document).on('click', '[data-notification-action="show-all-notifications"]', (event) => {
+ $('#notifications .show-all').hide();
+ $('#notifications .hidden').removeClass('hidden');
+ });
+
+ $(document).on('click', '[data-refresh="notifications"]', (event) => {
+ event.preventDefault();
+ notifications.fetch({ filter: ['feed'], refresh: true });
+ });
+}
diff --git a/user/plugins/admin/themes/grav/app/updates/update.js b/user/plugins/admin/themes/grav/app/updates/update.js
new file mode 100644
index 0000000..fa4da70
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/updates/update.js
@@ -0,0 +1,19 @@
+import $ from 'jquery';
+import request from '../utils/request';
+
+// Dashboard update and Grav update
+$('body').on('click', '[data-maintenance-update]', function() {
+ let element = $(this);
+ let url = element.data('maintenanceUpdate');
+
+ element.attr('disabled', 'disabled').find('> .fa').removeClass('fa-cloud-download').addClass('fa-refresh fa-spin');
+
+ request(url, (response) => {
+ if (response.type === 'updategrav') {
+ $('[data-gpm-grav]').remove();
+ $('#footer .grav-version').html(response.version);
+ }
+
+ element.removeAttr('disabled').find('> .fa').removeClass('fa-refresh fa-spin').addClass('fa-cloud-download');
+ });
+});
diff --git a/user/plugins/admin/themes/grav/app/utils/2fa.js b/user/plugins/admin/themes/grav/app/utils/2fa.js
new file mode 100644
index 0000000..bb95a0e
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/2fa.js
@@ -0,0 +1,32 @@
+import $ from 'jquery';
+import { config } from 'grav-config';
+import request from '../utils/request';
+
+const body = $('body');
+
+// Dashboard update and Grav update
+body.on('click', '[data-2fa-regenerate]', function(event) {
+ event.preventDefault();
+ let element = $(this);
+ let url = `${config.base_url_relative}/ajax.json/task${config.param_sep}regenerate2FASecret`;
+
+ element.attr('disabled', 'disabled').find('> .fa').addClass('fa-spin');
+
+ request(url, { method: 'post' }, (response) => {
+ $('[data-2fa-image]').attr('src', response.image);
+ $('[data-2fa-secret]').text(response.secret);
+ $('[data-2fa-value]').val(response.secret.replace(' ', ''));
+
+ element.removeAttr('disabled').find('> .fa').removeClass('fa-spin');
+ });
+});
+
+const toggleSecret = () => {
+ const toggle = $('#toggle_twofa_enabled1');
+ const secret = $('.twofa-secret');
+
+ secret[toggle.is(':checked') ? 'addClass' : 'removeClass']('show');
+};
+
+body.on('click', '.twofa-toggle input', toggleSecret);
+toggleSecret();
diff --git a/user/plugins/admin/themes/grav/app/utils/bootstrap-collapse.js b/user/plugins/admin/themes/grav/app/utils/bootstrap-collapse.js
new file mode 100644
index 0000000..f8bd31d
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/bootstrap-collapse.js
@@ -0,0 +1,210 @@
+import jQuery from 'jquery';
+
+/* ========================================================================
+ * Bootstrap: collapse.js v3.4.0
+ * http://getbootstrap.com/javascript/#collapse
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+/* jshint latedef: false */
+
++(function($) {
+ 'use strict';
+
+ // COLLAPSE PUBLIC CLASS DEFINITION
+ // ================================
+
+ var Collapse = function(element, options) {
+ this.$element = $(element);
+ this.options = $.extend({}, Collapse.DEFAULTS, options);
+ this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
+ '[data-toggle="collapse"][data-target="#' + element.id + '"]');
+ this.transitioning = null;
+
+ if (this.options.parent) {
+ this.$parent = this.getParent();
+ } else {
+ this.addAriaAndCollapsedClass(this.$element, this.$trigger);
+ }
+
+ if (this.options.toggle) this.toggle();
+ };
+
+ Collapse.VERSION = '3.4.0';
+
+ Collapse.TRANSITION_DURATION = 350;
+
+ Collapse.DEFAULTS = {
+ toggle: true
+ };
+
+ Collapse.prototype.dimension = function() {
+ var hasWidth = this.$element.hasClass('width');
+ return hasWidth ? 'width' : 'height';
+ };
+
+ Collapse.prototype.show = function() {
+ if (this.transitioning || this.$element.hasClass('in')) return;
+
+ var activesData;
+ var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing');
+
+ if (actives && actives.length) {
+ activesData = actives.data('bs.collapse');
+ if (activesData && activesData.transitioning) return;
+ }
+
+ var startEvent = $.Event('show.bs.collapse');
+ this.$element.trigger(startEvent);
+ if (startEvent.isDefaultPrevented()) return;
+
+ if (actives && actives.length) {
+ Plugin.call(actives, 'hide');
+ activesData || actives.data('bs.collapse', null);
+ }
+
+ var dimension = this.dimension();
+
+ this.$element
+ .removeClass('collapse')
+ .addClass('collapsing')[dimension](0)
+ .attr('aria-expanded', true);
+
+ this.$trigger
+ .removeClass('collapsed')
+ .attr('aria-expanded', true);
+
+ this.transitioning = 1;
+
+ var complete = function() {
+ this.$element
+ .removeClass('collapsing')
+ .addClass('collapse in')[dimension]('');
+ this.transitioning = 0;
+ this.$element
+ .trigger('shown.bs.collapse');
+ };
+
+ if (!$.support.transition) return complete.call(this);
+
+ var scrollSize = $.camelCase(['scroll', dimension].join('-'));
+
+ this.$element
+ .one('bsTransitionEnd', $.proxy(complete, this))
+ .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]);
+ };
+
+ Collapse.prototype.hide = function() {
+ if (this.transitioning || !this.$element.hasClass('in')) return;
+
+ var startEvent = $.Event('hide.bs.collapse');
+ this.$element.trigger(startEvent);
+ if (startEvent.isDefaultPrevented()) return;
+
+ var dimension = this.dimension();
+
+ this.$element[dimension](this.$element[dimension]())[0].offsetHeight;
+
+ this.$element
+ .addClass('collapsing')
+ .removeClass('collapse in')
+ .attr('aria-expanded', false);
+
+ this.$trigger
+ .addClass('collapsed')
+ .attr('aria-expanded', false);
+
+ this.transitioning = 1;
+
+ var complete = function() {
+ this.transitioning = 0;
+ this.$element
+ .removeClass('collapsing')
+ .addClass('collapse')
+ .trigger('hidden.bs.collapse');
+ };
+
+ if (!$.support.transition) return complete.call(this);
+
+ this.$element[dimension](0)
+ .one('bsTransitionEnd', $.proxy(complete, this))
+ .emulateTransitionEnd(Collapse.TRANSITION_DURATION);
+ };
+
+ Collapse.prototype.toggle = function() {
+ this[this.$element.hasClass('in') ? 'hide' : 'show']();
+ };
+
+ Collapse.prototype.getParent = function() {
+ return $(this.options.parent)
+ .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
+ .each($.proxy(function(i, element) {
+ var $element = $(element);
+ this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element);
+ }, this))
+ .end();
+ };
+
+ Collapse.prototype.addAriaAndCollapsedClass = function($element, $trigger) {
+ var isOpen = $element.hasClass('in');
+
+ $element.attr('aria-expanded', isOpen);
+ $trigger
+ .toggleClass('collapsed', !isOpen)
+ .attr('aria-expanded', isOpen);
+ };
+
+ function getTargetFromTrigger($trigger) {
+ var href;
+ var target = $trigger.attr('data-target') ||
+ (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, ''); // strip for ie7
+
+ return $(target);
+ }
+
+ // COLLAPSE PLUGIN DEFINITION
+ // ==========================
+
+ function Plugin(option) {
+ return this.each(function() {
+ var $this = $(this);
+ var data = $this.data('bs.collapse');
+ var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option === 'object' && option);
+
+ if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false;
+ if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)));
+ if (typeof option === 'string') data[option]();
+ });
+ }
+
+ var old = $.fn.collapse;
+
+ $.fn.collapse = Plugin;
+ $.fn.collapse.Constructor = Collapse;
+
+ // COLLAPSE NO CONFLICT
+ // ====================
+
+ $.fn.collapse.noConflict = function() {
+ $.fn.collapse = old;
+ return this;
+ };
+
+ // COLLAPSE DATA-API
+ // =================
+
+ $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function(e) {
+ var $this = $(this);
+
+ if (!$this.attr('data-target')) e.preventDefault();
+
+ var $target = getTargetFromTrigger($this);
+ var data = $target.data('bs.collapse');
+ var option = data ? 'toggle' : $this.data();
+
+ Plugin.call($target, option);
+ });
+
+}(jQuery));
diff --git a/user/plugins/admin/themes/grav/app/utils/bootstrap-transition.js b/user/plugins/admin/themes/grav/app/utils/bootstrap-transition.js
new file mode 100644
index 0000000..a0255bb
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/bootstrap-transition.js
@@ -0,0 +1,52 @@
+import jQuery from 'jquery';
+
++(function($) {
+ 'use strict';
+
+ // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
+ // ============================================================
+
+ function transitionEnd() {
+ var el = document.createElement('bootstrap');
+
+ var transEndEventNames = {
+ WebkitTransition: 'webkitTransitionEnd',
+ MozTransition: 'transitionend',
+ OTransition: 'oTransitionEnd otransitionend',
+ transition: 'transitionend'
+ };
+
+ for (var name in transEndEventNames) {
+ if (el.style[name] !== undefined) {
+ return { end: transEndEventNames[name] };
+ }
+ }
+
+ return false; // explicit for ie8 ( ._.)
+ }
+
+ // http://blog.alexmaccaw.com/css-transitions
+ $.fn.emulateTransitionEnd = function(duration) {
+ var called = false;
+ var $el = this;
+ $(this).one('bsTransitionEnd', function() { called = true; });
+ var callback = function() { if (!called) $($el).trigger($.support.transition.end); };
+ setTimeout(callback, duration);
+ return this;
+ };
+
+ $(function() {
+ $.support.transition = transitionEnd();
+
+ if (!$.support.transition) return;
+
+ $.event.special.bsTransitionEnd = {
+ bindType: $.support.transition.end,
+ delegateType: $.support.transition.end,
+ handle: function(e) {
+ if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments);
+ }
+ };
+ });
+
+}(jQuery));
diff --git a/user/plugins/admin/themes/grav/app/utils/colors.js b/user/plugins/admin/themes/grav/app/utils/colors.js
new file mode 100644
index 0000000..caf467c
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/colors.js
@@ -0,0 +1,152 @@
+// Parses a string and returns a valid hex string when possible
+// parseHex('#fff') => '#ffffff'
+export const parseHex = (string) => {
+ string = string.replace(/[^A-F0-9]/ig, '');
+ if (string.length !== 3 && string.length !== 6) return '';
+ if (string.length === 3) {
+ string = string[0] + string[0] + string[1] + string[1] + string[2] + string[2];
+ }
+
+ return '#' + string.toLowerCase();
+};
+
+// Converts an HSB object to an RGB object
+// hsb2rgb({h: 0, s: 0, b: 100}) => {r: 255, g: 255, b: 255}
+export const hsb2rgb = (hsb) => {
+ let rgb = {};
+ let h = Math.round(hsb.h);
+ let s = Math.round(hsb.s * 255 / 100);
+ let v = Math.round(hsb.b * 255 / 100);
+ if (s === 0) {
+ rgb.r = rgb.g = rgb.b = v;
+ } else {
+ var t1 = v;
+ var t2 = (255 - s) * v / 255;
+ var t3 = (t1 - t2) * (h % 60) / 60;
+ if (h === 360) h = 0;
+ if (h < 60) {
+ rgb.r = t1;
+ rgb.b = t2;
+ rgb.g = t2 + t3;
+ } else if (h < 120) {
+ rgb.g = t1;
+ rgb.b = t2;
+ rgb.r = t1 - t3;
+ } else if (h < 180) {
+ rgb.g = t1;
+ rgb.r = t2;
+ rgb.b = t2 + t3;
+ } else if (h < 240) {
+ rgb.b = t1;
+ rgb.r = t2;
+ rgb.g = t1 - t3;
+ } else if (h < 300) {
+ rgb.b = t1;
+ rgb.g = t2;
+ rgb.r = t2 + t3;
+ } else if (h < 360) {
+ rgb.r = t1;
+ rgb.g = t2;
+ rgb.b = t1 - t3;
+ } else {
+ rgb.r = 0;
+ rgb.g = 0;
+ rgb.b = 0;
+ }
+ }
+ return {
+ r: Math.round(rgb.r),
+ g: Math.round(rgb.g),
+ b: Math.round(rgb.b)
+ };
+};
+
+// Converts an RGB object to a HEX string
+// rgb2hex({r: 255, g: 255, b: 255}) => #ffffff
+export const rgb2hex = (rgb) => {
+ var hex = [
+ rgb.r.toString(16),
+ rgb.g.toString(16),
+ rgb.b.toString(16)
+ ];
+
+ hex.forEach((val, nr) => {
+ if (val.length === 1) hex[nr] = '0' + val;
+ });
+
+ return '#' + hex.join('');
+};
+
+// Converts and RGB(a) string to a HEX string
+// rgbstr2hex('rgba(255, 255, 255, 0.5)') => #ffffff
+export const rgbstr2hex = (rgb) => {
+ rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
+
+ return (rgb && rgb.length === 4) ? '#' +
+ ('0' + parseInt(rgb[1], 10).toString(16)).slice(-2) +
+ ('0' + parseInt(rgb[2], 10).toString(16)).slice(-2) +
+ ('0' + parseInt(rgb[3], 10).toString(16)).slice(-2) : '';
+};
+
+// Converts an HSB object to a HEX string
+// hsb2hex({h: 0, s: 0, b: 100}) => #ffffff
+export const hsb2hex = (hsb) => {
+ return rgb2hex(hsb2rgb(hsb));
+};
+
+// Converts a HEX string to an HSB object
+// hex2hsb('#ffffff') => {h: 0, s: 0, b: 100}
+export const hex2hsb = (hex) => {
+ let hsb = rgb2hsb(hex2rgb(hex));
+ if (hsb.s === 0) hsb.h = 360;
+
+ return hsb;
+};
+
+// Converts an RGB object to an HSB object
+// rgb2hsb({r: 255, g: 255, b: 255}) => {h: 0, s: 0, b: 100}
+export const rgb2hsb = (rgb) => {
+ let hsb = {
+ h: 0,
+ s: 0,
+ b: 0
+ };
+ let min = Math.min(rgb.r, rgb.g, rgb.b);
+ let max = Math.max(rgb.r, rgb.g, rgb.b);
+ let delta = max - min;
+ hsb.b = max;
+ hsb.s = max !== 0 ? 255 * delta / max : 0;
+ if (hsb.s !== 0) {
+ if (rgb.r === max) {
+ hsb.h = (rgb.g - rgb.b) / delta;
+ } else if (rgb.g === max) {
+ hsb.h = 2 + (rgb.b - rgb.r) / delta;
+ } else {
+ hsb.h = 4 + (rgb.r - rgb.g) / delta;
+ }
+ } else {
+ hsb.h = -1;
+ }
+ hsb.h *= 60;
+ if (hsb.h < 0) {
+ hsb.h += 360;
+ }
+ hsb.s *= 100 / 255;
+ hsb.b *= 100 / 255;
+
+ return hsb;
+};
+
+// Converts a HEX string to an RGB object
+// hex2rgb('#ffffff') => {r: 255, g: 255, b: 255}
+export const hex2rgb = (hex) => {
+ hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
+
+ return {
+ /* jshint ignore:start */
+ r: hex >> 16,
+ g: (hex & 0x00FF00) >> 8,
+ b: (hex & 0x0000FF)
+ /* jshint ignore:end */
+ };
+};
diff --git a/user/plugins/admin/themes/grav/app/utils/cron-ui.js b/user/plugins/admin/themes/grav/app/utils/cron-ui.js
new file mode 100644
index 0000000..5dc7196
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/cron-ui.js
@@ -0,0 +1,864 @@
+/* eslint-disable */
+import $ from 'jquery';
+/*
+ * This file is part of the Arnapou jqCron package.
+ *
+ * (c) Arnaud Buathier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Default settings
+ */
+var jqCronDefaultSettings = {
+ texts: {},
+ monthdays: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31],
+ hours: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
+ hour_labels: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"],
+ minutes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
+ lang: 'en',
+ enabled_minute: false,
+ enabled_hour: true,
+ enabled_day: true,
+ enabled_week: true,
+ enabled_month: true,
+ enabled_year: true,
+ multiple_dom: false,
+ multiple_month: false,
+ multiple_mins: false,
+ multiple_dow: false,
+ multiple_time_hours: false,
+ multiple_time_minutes: false,
+ numeric_zero_pad: false,
+ default_period: 'day',
+ default_value: '',
+ no_reset_button: true,
+ disabled: false,
+ bind_to: null,
+ bind_method: {
+ set: function($element, value) {
+ $element.is(':input') ? $element.val(value) : $element.data('jqCronValue', value);
+ },
+ get: function($element) {
+ return $element.is(':input') ? $element.val() : $element.data('jqCronValue');
+ }
+ }
+};
+
+/**
+ * Custom extend of json for jqCron settings.
+ * We don't use jQuery.extend because simple extend does not fit our needs, and deep extend has a bad
+ * feature for us : it replaces keys of "Arrays" instead of replacing the full array.
+ */
+(function($){
+ var extend = function(dst, src) {
+ for(var i in src) {
+ if($.isPlainObject(src[i])) {
+ dst[i] = extend(dst[i] && $.isPlainObject(dst[i]) ? dst[i] : {}, src[i]);
+ }
+ else if($.isArray(src[i])) {
+ dst[i] = src[i].slice(0);
+ }
+ else if(src[i] !== undefined) {
+ dst[i] = src[i];
+ }
+ }
+ return dst;
+ };
+ this.jqCronMergeSettings = function(obj) {
+ return extend(extend({}, jqCronDefaultSettings), obj || {});
+ };
+}).call(window, $);
+
+/**
+ * Shortcut to get the instance of jqCron instance from one jquery object
+ */
+(function($){
+ $.fn.jqCronGetInstance = function() {
+ return this.data('jqCron');
+ };
+}).call(window, $);
+
+/**
+ * Main plugin
+ */
+(function($){
+ $.fn.jqCron = function(settings) {
+ var saved_settings = settings;
+ return this.each(function() {
+ var cron, saved;
+ var $this = $(this);
+ var settings = jqCronMergeSettings(saved_settings); // clone settings
+ var translations = settings.texts[settings.lang];
+
+ if (typeof(translations) !== 'object' || $.isEmptyObject(translations)) {
+ console && console.error(
+ 'Missing translations for language "' + settings.lang + '". ' +
+ 'Please include jqCron.' + settings.lang + '.js or manually provide ' +
+ 'the necessary translations when calling $.fn.jqCron().'
+ );
+ return;
+ }
+
+ if(!settings.jquery_container) {
+ if($this.is(':container')) {
+ settings.jquery_element = $this.uniqueId('jqCron');
+ }
+ else if($this.is(':autoclose')) {
+ // delete already generated dom if exists
+ if($this.next('.jqCron').length == 1) {
+ $this.next('.jqCron').remove();
+ }
+ // generate new
+ settings.jquery_element = $(' ').uniqueId('jqCron').insertAfter($this);
+ }
+ else {
+ console && console.error(settings.texts[settings.lang].error1.replace('%s', this.tagName));
+ return;
+ }
+ }
+
+ // autoset bind_to if it is an input
+ if($this.is(':input')) {
+ settings.bind_to = settings.bind_to || $this;
+ }
+
+ // init cron object
+ if(settings.bind_to){
+ if(settings.bind_to.is(':input')) {
+ // auto bind from input to object if an input, textarea ...
+ settings.bind_to.blur(function(){
+ var value = settings.bind_method.get(settings.bind_to);
+ $this.jqCronGetInstance().setCron(value);
+ });
+ }
+ saved = settings.bind_method.get(settings.bind_to);
+ cron = new jqCron(settings);
+ cron.setCron(saved);
+ }
+ else {
+ cron = new jqCron(settings);
+ }
+ $(this).data('jqCron', cron);
+ });
+ };
+}).call(window, $);
+
+/**
+ * jqCron class
+ */
+(function($){
+ var jqCronInstances = [];
+
+ function jqCron(settings) {
+ var _initialized = false;
+ var _self = this;
+ var _$elt = this;
+ var _$obj = $(' ');
+ var _$blocks = $(' ');
+ var _$blockPERIOD = $(' ');
+ var _$blockDOM = $(' ');
+ var _$blockMONTH = $(' ');
+ var _$blockMINS = $(' ');
+ var _$blockDOW = $(' ');
+ var _$blockTIME = $(' ');
+ var _$cross = $('✘ ');
+ var _selectors = [];
+ var _selectorPeriod, _selectorMins, _selectorTimeH, _selectorTimeM, _selectorDow, _selectorDom, _selectorMonth;
+
+ // instanciate a new selector
+ function newSelector($block, multiple, type){
+ var selector = new jqCronSelector(_self, $block, multiple, type);
+ selector.$.bind('selector:open', function(){
+ // we close all opened selectors of all other jqCron
+ for(var n = jqCronInstances.length; n--; ){
+ if(jqCronInstances[n] != _self) {
+ jqCronInstances[n].closeSelectors();
+ }
+ else {
+ // we close all other opened selectors of this jqCron
+ for(var o = _selectors.length; o--; ){
+ if(_selectors[o] != selector) {
+ _selectors[o].close();
+ }
+ }
+ }
+ }
+ });
+ selector.$.bind('selector:change', function(){
+ var boundChanged = false;
+ // don't propagate if not initialized
+ if(!_initialized) return;
+ // bind data between two minute selectors (only if they have the same multiple settings)
+ if(settings.multiple_mins == settings.multiple_time_minutes) {
+ if(selector == _selectorMins) {
+ boundChanged = _selectorTimeM.setValue(_selectorMins.getValue());
+ }
+ else if(selector == _selectorTimeM) {
+ boundChanged = _selectorMins.setValue(_selectorTimeM.getValue());
+ }
+ }
+ // we propagate the change event to the main object
+ boundChanged || _$obj.trigger('cron:change', _self.getCron());
+ });
+ _selectors.push(selector);
+ return selector;
+ }
+
+ // disable the selector
+ this.disable = function(){
+ _$obj.addClass('disable');
+ settings.disable = true;
+ _self.closeSelectors();
+ };
+
+ // return if the selector is disabled
+ this.isDisabled = function() {
+ return settings.disable == true;
+ };
+
+ // enable the selector
+ this.enable = function(){
+ _$obj.removeClass('disable');
+ settings.disable = false;
+ };
+
+ // get cron value
+ this.getCron = function(){
+ var period = _selectorPeriod.getValue();
+ var items = ['*', '*', '*', '*', '*'];
+ if(period == 'hour') {
+ items[0] = _selectorMins.getCronValue();
+ }
+ if(period == 'day' || period == 'week' || period == 'month' || period == 'year') {
+ items[0] = _selectorTimeM.getCronValue();
+ items[1] = _selectorTimeH.getCronValue();
+ }
+ if(period == 'month' || period == 'year') {
+ items[2] = _selectorDom.getCronValue();
+ }
+ if(period == 'year') {
+ items[3] = _selectorMonth.getCronValue();
+ }
+ if(period == 'week') {
+ items[4] = _selectorDow.getCronValue();
+ }
+ return items.join(' ');
+ };
+
+ // set cron (string like * * * * *)
+ this.setCron = function(str) {
+ if(!str) return;
+ try {
+ str = str.replace(/\s+/g, ' ').replace(/^ +/, '').replace(/ +$/, ''); // sanitize
+ var mask = str.replace(/[^\* ]/g, '-').replace(/-+/g, '-').replace(/ +/g, '');
+ var items = str.split(' ');
+ if (items.length != 5) _self.error(_self.getText('error2'));
+ if(mask == '*****') { // 1 possibility
+ _selectorPeriod.setValue('minute');
+ }
+ else if(mask == '-****') { // 1 possibility
+ _selectorPeriod.setValue('hour');
+ _selectorMins.setCronValue(items[0]);
+ _selectorTimeM.setCronValue(items[0]);
+ }
+ else if(mask.substring(2, mask.length) == '***') { // 4 possibilities
+ _selectorPeriod.setValue('day');
+ _selectorMins.setCronValue(items[0]);
+ _selectorTimeM.setCronValue(items[0]);
+ _selectorTimeH.setCronValue(items[1]);
+ }
+ else if(mask.substring(2, mask.length) == '-**') { // 4 possibilities
+ _selectorPeriod.setValue('month');
+ _selectorMins.setCronValue(items[0]);
+ _selectorTimeM.setCronValue(items[0]);
+ _selectorTimeH.setCronValue(items[1]);
+ _selectorDom.setCronValue(items[2]);
+ }
+ else if(mask.substring(2, mask.length) == '**-') { // 4 possibilities
+ _selectorPeriod.setValue('week');
+ _selectorMins.setCronValue(items[0]);
+ _selectorTimeM.setCronValue(items[0]);
+ _selectorTimeH.setCronValue(items[1]);
+ _selectorDow.setCronValue(items[4]);
+ }
+ else if (mask.substring(3, mask.length) == '-*') { // 8 possibilities
+ _selectorPeriod.setValue('year');
+ _selectorMins.setCronValue(items[0]);
+ _selectorTimeM.setCronValue(items[0]);
+ _selectorTimeH.setCronValue(items[1]);
+ _selectorDom.setCronValue(items[2]);
+ _selectorMonth.setCronValue(items[3]);
+ }
+ else {
+ _self.error(_self.getText('error4'));
+ }
+ _self.clearError();
+ } catch(e) {}
+ };
+
+ // close all child selectors
+ this.closeSelectors = function(){
+ for(var n = _selectors.length; n--; ){
+ _selectors[n].close();
+ }
+ };
+
+ // get the main element id
+ this.getId = function(){
+ return _$elt.attr('id');
+ }
+
+ // get the translated text
+ this.getText = function(key) {
+ var text = settings.texts[settings.lang][key] || null;
+ if(typeof(text) == "string" && text.match(' )/gi, '');
+ text = '' + text + ' ';
+ }
+ return text;
+ };
+
+ // get the human readable text
+ this.getHumanText = function() {
+ var texts=[];
+ _$obj
+ .find('> span > span:visible')
+ .find('.jqCron-text, .jqCron-selector > span')
+ .each(function() {
+ var text = $(this).text().replace(/\s+$/g, '').replace(/^\s+/g, '');
+ text && texts.push(text);
+ });
+ return texts.join(' ').replace(/\s:\s/g, ':');
+ }
+
+ // get settings
+ this.getSettings = function(){
+ return settings;
+ };
+
+ // display an error
+ this.error = function(msg) {
+ console && console.error('[jqCron Error] ' + msg);
+ _$obj.addClass('jqCron-error').attr('title', msg);
+ throw msg;
+ };
+
+ // clear error
+ this.clearError = function(){
+ _$obj.attr('title', '').removeClass('jqCron-error');
+ };
+
+ // clear
+ this.clear = function() {
+ _selectorDom.setValue([]);
+ _selectorDow.setValue([]);
+ _selectorMins.setValue([]);
+ _selectorMonth.setValue([]);
+ _selectorTimeH.setValue([]);
+ _selectorTimeM.setValue([]);
+ _self.triggerChange();
+ };
+
+ // init (called in constructor)
+ this.init = function(){
+ var n,i,labelsList,list;
+ if(_initialized) return;
+
+ settings = jqCronMergeSettings(settings);
+ settings.jquery_element || _self.error(_self.getText('error3'));
+ _$elt = settings.jquery_element;
+ _$elt.append(_$obj);
+ _$obj.data('id', settings.id);
+ _$obj.data('jqCron', _self);
+ _$obj.append(_$blocks);
+ settings.no_reset_button || _$obj.append(_$cross);
+ (!settings.disable) || _$obj.addClass('disable');
+ _$blocks.append(_$blockPERIOD);
+
+ if ( /^(ko)$/i.test(settings.lang) )
+ {
+ _$blocks.append(_$blockMONTH, _$blockDOM);
+ }
+ else
+ {
+ _$blocks.append(_$blockDOM, _$blockMONTH);
+ }
+
+ _$blocks.append(_$blockMINS);
+ _$blocks.append(_$blockDOW);
+ _$blocks.append(_$blockTIME);
+
+ // various binding
+ _$cross.click(function(){
+ _self.isDisabled() || _self.clear();
+ });
+
+ // binding from cron to target
+ _$obj.bind('cron:change', function(evt, value){
+ if(!settings.bind_to) return;
+ settings.bind_method.set && settings.bind_method.set(settings.bind_to, value);
+ _self.clearError();
+ });
+
+ // PERIOD
+ _$blockPERIOD.append(_self.getText('text_period'));
+ _selectorPeriod = newSelector(_$blockPERIOD, false, 'period');
+ settings.enabled_minute && _selectorPeriod.add('minute', _self.getText('name_minute'));
+ settings.enabled_hour && _selectorPeriod.add('hour', _self.getText('name_hour'));
+ settings.enabled_day && _selectorPeriod.add('day', _self.getText('name_day'));
+ settings.enabled_week && _selectorPeriod.add('week', _self.getText('name_week'));
+ settings.enabled_month && _selectorPeriod.add('month', _self.getText('name_month'));
+ settings.enabled_year && _selectorPeriod.add('year', _self.getText('name_year'));
+ _selectorPeriod.$.bind('selector:change', function(e, value){
+ _$blockDOM.hide();
+ _$blockMONTH.hide();
+ _$blockMINS.hide();
+ _$blockDOW.hide();
+ _$blockTIME.hide();
+ if(value == 'hour') {
+ _$blockMINS.show();
+ }
+ else if(value == 'day') {
+ _$blockTIME.show();
+ }
+ else if(value == 'week') {
+ _$blockDOW.show();
+ _$blockTIME.show();
+ }
+ else if(value == 'month') {
+ _$blockDOM.show();
+ _$blockTIME.show();
+ }
+ else if(value == 'year') {
+ _$blockDOM.show();
+ _$blockMONTH.show();
+ _$blockTIME.show();
+ }
+ });
+ _selectorPeriod.setValue(settings.default_period);
+
+ // MINS (minutes)
+ _$blockMINS.append(_self.getText('text_mins'));
+ _selectorMins = newSelector(_$blockMINS, settings.multiple_mins, 'minutes');
+ for(i=0, list=settings.minutes; i');
+ var _$title = $(' ');
+ var _$selector = $(' ');
+ var _values = {};
+ var _value = [];
+ var _hasNumericTexts = true;
+ var _numeric_zero_pad = _cron.getSettings().numeric_zero_pad;
+
+ // return an array without doublon
+ function array_unique(l){
+ var i=0,n=l.length,k={},a=[];
+ while(i' + value + '');
+ _$list.append($item);
+ _values[key] = $item;
+ $item.click(function(){
+ if(_multiple && $(this).hasClass('selected')) {
+ _self.removeValue(key);
+ }
+ else {
+ _self.addValue(key);
+ if(!_multiple) _self.close();
+ }
+ });
+ };
+
+ // expose main jquery object
+ this.$ = _$selector;
+
+ // constructor
+ _$block.find('b:eq(0)').after(_$selector).remove();
+ _$selector
+ .addClass('jqCron-selector-' + _$block.find('.jqCron-selector').length)
+ .append(_$title)
+ .append(_$list)
+ .bind('selector:open', function(){
+ if(_hasNumericTexts) {
+ var nbcols = 1, n = _$list.find('li').length;
+ if(n > 5 && n <= 16) nbcols = 2;
+ else if(n > 16 && n <= 23) nbcols = 3;
+ else if(n > 23 && n <= 40) nbcols = 4;
+ else if(n > 40) nbcols = 5;
+ _$list.addClass('cols'+nbcols);
+ }
+ _$list.show();
+ })
+ .bind('selector:close', function(){
+ _$list.hide();
+ })
+ .bind('selector:change', function(){
+ _$title.html(_self.getTitleText());
+ })
+ .click(function(e){
+ e.stopPropagation();
+ })
+ .trigger('selector:change')
+ ;
+ $.fn.disableSelection && _$selector.disableSelection(); // only work with jQuery UI
+ _$title.click(function(e){
+ (_self.isOpened() || _cron.isDisabled()) ? _self.close() : _self.open();
+ });
+ _self.close();
+ _self.clear();
+ }
+ this.jqCronSelector = jqCronSelector;
+}).call(window, $);
+
+/**
+ * Generate unique id for each element.
+ * Skip elements which have already an id.
+ */
+(function($){
+ var jqUID = 0;
+ var jqGetUID = function(prefix){
+ var id;
+ while(1) {
+ jqUID++;
+ id = ((prefix || 'JQUID')+'') + jqUID;
+ if(!document.getElementById(id)) return id;
+ }
+ };
+ $.fn.uniqueId = function(prefix) {
+ return this.each(function(){
+ if($(this).attr('id')) return;
+ var id = jqGetUID(prefix);
+ $(this).attr('id', id);
+ });
+ };
+}).call(window, $);
+
+
+/**
+ * Extends jQuery selectors with new block selector
+ */
+(function($){
+ $.extend($.expr[':'], {
+ container: function(a) {
+ return (a.tagName+'').toLowerCase() in {
+ a:1,
+ abbr:1,
+ acronym:1,
+ address:1,
+ b:1,
+ big:1,
+ blockquote:1,
+ button:1,
+ cite:1,
+ code:1,
+ dd: 1,
+ del:1,
+ dfn:1,
+ div:1,
+ dt:1,
+ em:1,
+ fieldset:1,
+ form:1,
+ h1:1,
+ h2:1,
+ h3:1,
+ h4:1,
+ h5:1,
+ h6: 1,
+ i:1,
+ ins:1,
+ kbd:1,
+ label:1,
+ li:1,
+ p:1,
+ pre:1,
+ q:1,
+ samp:1,
+ small:1,
+ span:1,
+ strong:1,
+ sub: 1,
+ sup:1,
+ td:1,
+ tt:1
+ };
+ },
+ autoclose: function(a) {
+ return (a.tagName+'').toLowerCase() in {
+ area:1,
+ base:1,
+ basefont:1,
+ br:1,
+ col:1,
+ frame:1,
+ hr:1,
+ img:1,
+ input:1,
+ link:1,
+ meta:1,
+ param:1
+ };
+ }
+ });
+}).call(window, $);
diff --git a/user/plugins/admin/themes/grav/app/utils/formatbytes.js b/user/plugins/admin/themes/grav/app/utils/formatbytes.js
new file mode 100644
index 0000000..55cf34c
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/formatbytes.js
@@ -0,0 +1,11 @@
+const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
+
+export default function formatBytes(bytes, decimals) {
+ if (bytes === 0) return '0 Byte';
+
+ let k = 1000;
+ let value = Math.floor(Math.log(bytes) / Math.log(k));
+ let decimal = decimals + 1 || 3;
+
+ return (bytes / Math.pow(k, value)).toPrecision(decimal) + ' ' + sizes[value];
+}
diff --git a/user/plugins/admin/themes/grav/app/utils/gpm.js b/user/plugins/admin/themes/grav/app/utils/gpm.js
new file mode 100644
index 0000000..cedd9f4
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/gpm.js
@@ -0,0 +1,57 @@
+import { parseJSON, parseStatus, userFeedbackError } from './response';
+import { config } from 'grav-config';
+import { EventEmitter } from 'events';
+
+export default class GPM extends EventEmitter {
+ constructor(action = 'getUpdates') {
+ super();
+ this.payload = {};
+ this.raw = {};
+ this.action = action;
+ }
+
+ setPayload(payload = {}) {
+ this.payload = payload;
+ this.emit('payload', payload);
+
+ return this;
+ }
+
+ setAction(action = 'getUpdates') {
+ this.action = action;
+ this.emit('action', action);
+
+ return this;
+ }
+
+ fetch(callback = () => true, flush = false) {
+ let data = new FormData();
+ data.append('admin-nonce', config.admin_nonce);
+
+ if (flush) {
+ data.append('flush', true);
+ }
+
+ this.emit('fetching', this);
+
+ fetch(`${config.base_url_relative}/update.json/task${config.param_sep}getUpdates`, {
+ credentials: 'same-origin',
+ method: 'post',
+ body: data
+ }).then((response) => { this.raw = response; return response; })
+ .then(parseStatus)
+ .then(parseJSON)
+ .then((response) => this.response(response))
+ .then((response) => callback(response, this.raw))
+ .then((response) => this.emit('fetched', this.payload, this.raw, this))
+ .catch(userFeedbackError);
+ }
+
+ response(response) {
+ this.payload = response;
+
+ return response;
+ }
+}
+
+export let Instance = new GPM();
diff --git a/user/plugins/admin/themes/grav/app/utils/jquery-utils.js b/user/plugins/admin/themes/grav/app/utils/jquery-utils.js
new file mode 100644
index 0000000..9ab3f3f
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/jquery-utils.js
@@ -0,0 +1,50 @@
+import $ from 'jquery';
+import getSlug from 'speakingurl';
+
+// jQuery no parents filter
+$.expr[':']['noparents'] = $.expr.createPseudo((text) => (element) => $(element).parents(text).length < 1);
+
+// Slugify
+// CommonJS and ES6 version of https://github.com/madflow/jquery-slugify
+$.fn.slugify = (source, options) => {
+ return this.each((element) => {
+ let target = $(element);
+ let source = $(source);
+
+ target.on('keyup change', () => {
+ target.data('locked', target.val() !== '' && target.val() !== undefined);
+ });
+
+ source.on('keyup change', () => {
+ if (target.data('locked') === true) { return true; }
+
+ let isInput = target.is('input') || target.is('textarea');
+ target[isInput ? 'val' : 'text']($.slugify(source.val(), options));
+ });
+ });
+};
+
+// Static method.
+$.slugify = (sourceString, options) => {
+ options = $.extend({}, $.slugify.options, options);
+ options.lang = options.lang || $('html').prop('lang');
+
+ if (typeof options.preSlug === 'function') {
+ sourceString = options.preSlug(sourceString);
+ }
+
+ sourceString = options.slugFunc(sourceString, options);
+
+ if (typeof options.postSlug === 'function') {
+ sourceString = options.postSlug(sourceString);
+ }
+
+ return sourceString;
+};
+
+// Default plugin options
+$.slugify.options = {
+ preSlug: null,
+ postSlug: null,
+ slugFunc: (input, opts) => getSlug(input, opts)
+};
diff --git a/user/plugins/admin/themes/grav/app/utils/keepalive.js b/user/plugins/admin/themes/grav/app/utils/keepalive.js
new file mode 100644
index 0000000..b99d9e1
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/keepalive.js
@@ -0,0 +1,32 @@
+import { config } from 'grav-config';
+import { userFeedbackError } from './response';
+
+class KeepAlive {
+ constructor() {
+ this.active = false;
+ }
+
+ start() {
+ let timeout = config.admin_timeout / 1.5 * 1000;
+ this.timer = setInterval(() => this.fetch(), timeout);
+ this.active = true;
+ }
+
+ stop() {
+ clearInterval(this.timer);
+ this.active = false;
+ }
+
+ fetch() {
+ let data = new FormData();
+ data.append('admin-nonce', config.admin_nonce);
+
+ fetch(`${config.base_url_relative}/task${config.param_sep}keepAlive`, {
+ credentials: 'same-origin',
+ method: 'post',
+ body: data
+ }).catch(userFeedbackError);
+ }
+}
+
+export default new KeepAlive();
diff --git a/user/plugins/admin/themes/grav/app/utils/offline.js b/user/plugins/admin/themes/grav/app/utils/offline.js
new file mode 100644
index 0000000..e2f7c02
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/offline.js
@@ -0,0 +1,21 @@
+import $ from 'jquery';
+import isOnline from '../utils/offline';
+
+const offlineElement = $('#offline-status');
+
+$(window).on('offline', () => {
+ offlineElement.slideDown();
+});
+
+$(window).on('online', () => {
+ offlineElement.slideUp();
+});
+
+$(document).ready(() => {
+ if (!isOnline) {
+ offlineElement.slideDown();
+ }
+});
+
+// assume online if can't check
+export default typeof global.navigator.onLine !== 'undefined' ? global.navigator.onLine : true;
diff --git a/user/plugins/admin/themes/grav/app/utils/packages.js b/user/plugins/admin/themes/grav/app/utils/packages.js
new file mode 100644
index 0000000..5a46d52
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/packages.js
@@ -0,0 +1,470 @@
+import $ from 'jquery';
+import { config, translations } from 'grav-config';
+import request from '../utils/request';
+import { Instance as gpm } from '../utils/gpm';
+import { Promise } from 'es6-promise';
+
+class Sorter {
+ getElements(elements, container) {
+ this.elements = elements || $('[data-gpm-plugin], [data-gpm-theme]');
+ this.container = container || $('.gpm-plugins > table > tbody, .gpm-themes > .themes.card-row');
+ return this.elements;
+ }
+
+ static sort(A, B, direction = 'asc') {
+ if (A > B) { return (direction === 'asc') ? 1 : -1; }
+ if (A < B) { return (direction === 'asc') ? -1 : 1; }
+
+ return 0;
+ }
+
+ byCommon(direction = 'asc', data = '') {
+ let elements = this.getElements().sort((a, b) => {
+ let A = $(a).data(data).toString().toLowerCase();
+ let B = $(b).data(data).toString().toLowerCase();
+
+ return Sorter.sort(A, B, direction);
+ });
+
+ return elements.appendTo(this.container);
+ }
+
+ byName(direction = 'asc', data = 'gpm-name') {
+ return this.byCommon(direction, data);
+ }
+
+ byAuthor(direction = 'asc', data = 'gpm-author') {
+ return this.byCommon(direction, data);
+ }
+
+ byOfficial(direction = 'asc', data = 'gpm-official') {
+ return this.byCommon(direction, data);
+ }
+
+ byReleaseDate(direction = 'asc', data = 'gpm-release-date') {
+ let elements = this.getElements().sort((a, b) => {
+ let A = new Date($(a).data(data)).getTime();
+ let B = new Date($(b).data(data)).getTime();
+
+ return Sorter.sort(A, B, direction === 'asc' ? 'desc' : 'asc');
+ });
+
+ elements.appendTo(this.container);
+ }
+
+ byUpdatable(direction = 'asc', data = 'gpm-updatable') {
+ return this.byCommon(direction, data);
+ }
+
+ byEnabled(direction = 'asc', data = 'gpm-enabled') {
+ return this.byCommon(direction, data);
+ }
+
+ byTesting(direction = 'asc', data = 'gpm-testing') {
+ return this.byCommon(direction, data);
+ }
+}
+
+class Packages {
+ constructor() {
+ this.Sort = new Sorter();
+ }
+
+ static getBackToList(type) {
+ global.location.href = `${config.base_url_relative}/${type}s`;
+ }
+
+ static addDependencyToList(type, dependency, slug = '') {
+ if (['admin', 'form', 'login', 'email', 'grav'].indexOf(dependency) !== -1) { return; }
+ let container = $('.package-dependencies-container');
+ let text = `${dependency} Remove `;
+
+ if (slug) {
+ text += ` (was needed by ${slug})`;
+ }
+
+ container.append(`${text} `);
+ }
+
+ addDependenciesToList(dependencies, slug = '') {
+ dependencies.forEach((dependency) => {
+ Packages.addDependencyToList('plugin', dependency.name || dependency, slug);
+ });
+ }
+
+ static getTaskUrl(type, task) {
+ let url = `${config.base_url_relative}`;
+ url += `/${type}s.json`;
+ url += `/task${config.param_sep}${task}`;
+ return url;
+ }
+
+ static getRemovePackageUrl(type) {
+ return `${Packages.getTaskUrl(type, 'removePackage')}`;
+ }
+
+ static getReinstallPackageUrl(type) {
+ return `${Packages.getTaskUrl(type, 'reinstallPackage')}`;
+ }
+
+ static getGetPackagesDependenciesUrl(type) {
+ return `${Packages.getTaskUrl(type, 'getPackagesDependencies')}`;
+ }
+
+ static getInstallDependenciesOfPackagesUrl(type) {
+ return `${Packages.getTaskUrl(type, 'installDependenciesOfPackages')}`;
+ }
+
+ static getInstallPackageUrl(type) {
+ return `${Packages.getTaskUrl(type, 'installPackage')}`;
+ }
+
+ removePackage(type, slug) {
+ let url = Packages.getRemovePackageUrl(type);
+
+ request(url, {
+ method: 'post',
+ body: {
+ package: slug
+ }
+ }, (response) => {
+ if (response.status === 'success') {
+ $('.remove-package-confirm').addClass('hidden');
+
+ if (response.dependencies && response.dependencies.length > 0) {
+ this.addDependenciesToList(response.dependencies);
+ $('.remove-package-dependencies').removeClass('hidden');
+ } else {
+ $('.remove-package-done').removeClass('hidden');
+ }
+
+ // The package was removed. When the modal closes, move to the packages list
+ $(document).on('closing', '[data-remodal-id="remove-package"]', () => {
+ Packages.getBackToList(type);
+ });
+ } else {
+ $('.remove-package-confirm').addClass('hidden');
+ $('.remove-package-error').removeClass('hidden');
+ }
+ });
+ }
+
+ reinstallPackage(type, slug, package_name, current_version) {
+ $('.button-bar button').addClass('hidden');
+ $('.button-bar .spinning-wheel').removeClass('hidden');
+
+ let url = Packages.getReinstallPackageUrl(type);
+
+ request(url, {
+ method: 'post',
+ body: {
+ slug: slug,
+ type: type,
+ package_name: package_name,
+ current_version: current_version
+ }
+ }, (response) => {
+ if (response.status === 'success') {
+ $('.reinstall-package-confirm').addClass('hidden');
+ $('.reinstall-package-done').removeClass('hidden');
+ } else {
+ $('.reinstall-package-confirm').addClass('hidden');
+ $('.reinstall-package-error').removeClass('hidden');
+ }
+
+ window.location.reload();
+ });
+ }
+
+ removeDependency(type, slug, button) {
+ let url = Packages.getRemovePackageUrl(type);
+
+ request(url, {
+ method: 'post',
+ body: {
+ package: slug
+ }
+ }, (response) => {
+ if (response.status === 'success') {
+ button.removeClass('button');
+ button.replaceWith($('Removed successfully '));
+
+ if (response.dependencies && response.dependencies.length > 0) {
+ this.addDependenciesToList(response.dependencies, slug);
+ }
+ }
+ });
+ }
+
+ static addNeededDependencyToList(action, slug) {
+ $('.install-dependencies-package-container .type-' + action).removeClass('hidden');
+ let list = $('.install-dependencies-package-container .type-' + action + ' ul');
+
+ if (action !== 'install') {
+ let current_version = '';
+ let available_version = '';
+ let name = '';
+
+ let resources = gpm.payload.payload.resources;
+
+ if (resources.plugins[slug]) {
+ available_version = resources.plugins[slug].available;
+ current_version = resources.plugins[slug].version;
+ name = resources.plugins[slug].name;
+ } else if (resources.themes[slug]) {
+ available_version = resources.themes[slug].available;
+ current_version = resources.themes[slug].version;
+ name = resources.themes[slug].name;
+ }
+
+ list.append(`${name ? name : slug}, ${translations.PLUGIN_ADMIN.FROM} v${current_version} ${translations.PLUGIN_ADMIN.TO} v${available_version} `);
+ } else {
+ list.append(`${name ? name : slug} `);
+ }
+ }
+
+ getPackagesDependencies(type, slugs, finishedLoadingCallback) {
+ let url = Packages.getGetPackagesDependenciesUrl(type);
+
+ request(url, {
+ method: 'post',
+ body: {
+ packages: slugs
+ }
+ }, (response) => {
+
+ finishedLoadingCallback();
+
+ if (response.status === 'success') {
+ if (response.dependencies) {
+ let hasDependencies = false;
+ for (var dependency in response.dependencies) {
+ if (response.dependencies.hasOwnProperty(dependency)) {
+ if (dependency === 'grav') {
+ continue;
+ }
+ hasDependencies = true;
+ let dependencyName = dependency;
+ let action = response.dependencies[dependency];
+
+ Packages.addNeededDependencyToList(action, dependencyName);
+ }
+ }
+
+ if (hasDependencies) {
+ $('[data-packages-modal] .install-dependencies-package-container').removeClass('hidden');
+ } else {
+ $('[data-packages-modal] .install-package-container').removeClass('hidden');
+ }
+ } else {
+ $('[data-packages-modal] .install-package-container').removeClass('hidden');
+ }
+ } else {
+ $('[data-packages-modal] .install-package-error').removeClass('hidden');
+ }
+ });
+ }
+
+ installDependenciesOfPackages(type, slugs, callbackSuccess, callbackError) {
+ let url = Packages.getInstallDependenciesOfPackagesUrl(type);
+
+ request(url, {
+ method: 'post',
+ body: {
+ packages: slugs
+ }
+ }, callbackSuccess);
+ }
+
+ installPackages(type, slugs, callbackSuccess) {
+ let url = Packages.getInstallPackageUrl(type);
+
+ Promise.all(slugs.map((slug) => {
+ return new Promise((resolve, reject) => {
+ request(url, {
+ method: 'post',
+ body: {
+ package: slug,
+ type: type
+ }
+ }, (response) => {
+ resolve(response);
+ });
+ });
+ })).then(callbackSuccess);
+
+ }
+
+ static getSlugsFromEvent(event) {
+ let slugs = '';
+ if ($(event.target).is('[data-packages-slugs]')) {
+ slugs = $(event.target).attr('data-packages-slugs');
+ } else {
+ slugs = $(event.target).parent('[data-packages-slugs]').attr('data-packages-slugs');
+ }
+
+ if (typeof slugs === 'undefined') {
+ return null;
+ }
+
+ slugs = slugs.split(',');
+ return typeof slugs === 'string' ? [slugs] : slugs;
+ }
+
+ handleGettingPackageDependencies(type, event, action = 'update') {
+ let slugs = Packages.getSlugsFromEvent(event);
+
+ if (!slugs) {
+ alert('No slug set');
+ return;
+ }
+
+ // Cleanup
+ $('.packages-names-list').html('');
+ $('.install-dependencies-package-container li').remove();
+
+ slugs.forEach((slug) => {
+ if (action === 'update') {
+ let current_version = '';
+ let available_version = '';
+ let name = '';
+
+ let resources = gpm.payload.payload.resources;
+
+ if (resources.plugins[slug]) {
+ available_version = resources.plugins[slug].available;
+ current_version = resources.plugins[slug].version;
+ name = resources.plugins[slug].name;
+ } else if (resources.themes[slug]) {
+ available_version = resources.themes[slug].available;
+ current_version = resources.themes[slug].version;
+ name = resources.themes[slug].name;
+ }
+
+ $('.packages-names-list').append(`${name ? name : slug}, ${translations.PLUGIN_ADMIN.FROM} v${current_version} ${translations.PLUGIN_ADMIN.TO} v${available_version} `);
+ } else {
+ $('.packages-names-list').append(`${name ? name : slug} `);
+ }
+ });
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ // fix mismatching types when sharing install modal between plugins/themes
+ const query = '[data-packages-modal] [data-theme-action], [data-packages-modal] [data-plugin-action]';
+ const data = $(query).data('themeAction') || $(query).data('pluginAction');
+ $(query).removeAttr('data-theme-action').removeAttr('data-plugin-action').attr(`data-${type}-action`, data);
+
+ // Restore original state
+ $('[data-packages-modal] .loading').removeClass('hidden');
+ $('[data-packages-modal] .install-dependencies-package-container').addClass('hidden');
+ $('[data-packages-modal] .install-package-container').addClass('hidden');
+ $('[data-packages-modal] .installing-dependencies').addClass('hidden');
+ $('[data-packages-modal] .installing-package').addClass('hidden');
+ $('[data-packages-modal] .installation-complete').addClass('hidden');
+ $('[data-packages-modal] .install-package-error').addClass('hidden');
+
+ this.getPackagesDependencies(type, slugs, () => {
+ let slugs_string = slugs.join();
+ $(`[data-packages-modal] [data-${type}-action="install-dependencies-and-package"]`).attr('data-packages-slugs', slugs_string);
+ $(`[data-packages-modal] [data-${type}-action="install-package"]`).attr('data-packages-slugs', slugs_string);
+ $('[data-packages-modal] .loading').addClass('hidden');
+ });
+ }
+
+ handleInstallingDependenciesAndPackage(type, event) {
+ let slugs = Packages.getSlugsFromEvent(event);
+ event.preventDefault();
+ event.stopPropagation();
+
+ $('[data-packages-modal] .install-dependencies-package-container').addClass('hidden');
+ $('[data-packages-modal] .install-package-container').addClass('hidden');
+ $('[data-packages-modal] .installing-dependencies').removeClass('hidden');
+
+ this.installDependenciesOfPackages(type, slugs, (response) => {
+ $('[data-packages-modal] .installing-dependencies').addClass('hidden');
+ $('[data-packages-modal] .installing-package').removeClass('hidden');
+ this.installPackages(type, slugs, () => {
+ $('[data-packages-modal] .installing-package').addClass('hidden');
+ $('[data-packages-modal] .installation-complete').removeClass('hidden');
+
+ if (response.status === 'error') {
+ let remodal = $.remodal.lookup[$('[data-packages-modal]').data('remodal')];
+ remodal.close();
+
+ return;
+ }
+
+ setTimeout(() => {
+ if (slugs.length === 1) {
+ global.location.href = `${config.base_url_relative}/${type}s/${slugs[0]}`;
+ } else {
+ global.location.href = `${config.base_url_relative}/${type}s`;
+ }
+ }, 1000);
+
+ });
+ });
+ }
+
+ handleInstallingPackage(type, event) {
+ let slugs = Packages.getSlugsFromEvent(event);
+ event.preventDefault();
+ event.stopPropagation();
+
+ $('[data-packages-modal] .install-package-container').addClass('hidden');
+ $('[data-packages-modal] .installing-package').removeClass('hidden');
+
+ this.installPackages(type, slugs, (response) => {
+ $('[data-packages-modal] .installing-package').addClass('hidden');
+ $('[data-packages-modal] .installation-complete').removeClass('hidden');
+
+ const errors = Array.from(response).filter((r) => r.status === 'error');
+
+ if (errors && errors.length) {
+ let remodal = $.remodal.lookup[$('[data-packages-modal].remodal-is-opened').data('remodal')];
+ remodal.close();
+
+ return;
+ }
+
+ if (slugs.length === 1) {
+ global.location.href = `${config.base_url_relative}/${type}s/${slugs[0]}`;
+ } else {
+ global.location.href = `${config.base_url_relative}/${type}s`;
+ }
+ });
+ }
+
+ handleRemovingPackage(type, event) {
+ let slug = $(event.target).attr('data-packages-slugs');
+ event.preventDefault();
+ event.stopPropagation();
+
+ this.removePackage(type, slug);
+ }
+
+ handleReinstallPackage(type, event) {
+ let target = $(event.target);
+ let slug = target.attr('data-package-slug');
+ let package_name = target.attr('data-package-name');
+ let current_version = target.attr('data-package-current-version');
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ this.reinstallPackage(type, slug, package_name, current_version);
+ }
+
+ handleRemovingDependency(type, event) {
+ let slug = $(event.target).attr('data-dependency-slug');
+ let button = $(event.target);
+ event.preventDefault();
+ event.stopPropagation();
+
+ this.removeDependency(type, slug, button);
+ }
+
+}
+
+export default new Packages();
diff --git a/user/plugins/admin/themes/grav/app/utils/request.js b/user/plugins/admin/themes/grav/app/utils/request.js
new file mode 100644
index 0000000..2b7911f
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/request.js
@@ -0,0 +1,38 @@
+import { parseStatus, parseJSON, userFeedback, userFeedbackError } from './response';
+import { config } from 'grav-config';
+
+let raw;
+let request = function(url, options = {}, callback = () => true) {
+ if (typeof options === 'function') {
+ callback = options;
+ options = {};
+ }
+
+ if (options.method && options.method === 'post') {
+ let data = new FormData();
+
+ options.body = Object.assign({ 'admin-nonce': config.admin_nonce }, options.body || {});
+ Object.keys(options.body).map((key) => data.append(key, options.body[key]));
+ options.body = data;
+ }
+
+ options = Object.assign({
+ credentials: 'same-origin',
+ headers: {
+ 'Accept': 'application/json'
+ }
+ }, options);
+
+ return fetch(url, options)
+ .then((response) => {
+ raw = response;
+ return response;
+ })
+ .then(parseStatus)
+ .then(parseJSON)
+ .then(userFeedback)
+ .then((response) => callback(response, raw))
+ .catch(userFeedbackError);
+};
+
+export default request;
diff --git a/user/plugins/admin/themes/grav/app/utils/response.js b/user/plugins/admin/themes/grav/app/utils/response.js
new file mode 100644
index 0000000..9cb8d05
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/response.js
@@ -0,0 +1,101 @@
+import $ from 'jquery';
+import toastr from './toastr';
+import isOnline from './offline';
+import { config } from 'grav-config';
+import trim from 'mout/string/trim';
+
+let UNLOADING = false;
+let error = function(response) {
+ let error = new Error(response.statusText || response || '');
+ error.response = response;
+
+ return error;
+};
+
+export function parseStatus(response) {
+ return response;
+
+ /* Whoops can handle JSON responses so we don't need this for now.
+ if (response.status >= 200 && response.status < 300) {
+ return response;
+ } else {
+ throw error(response);
+ }
+ */
+}
+
+export function parseJSON(response) {
+ return response.text().then((text) => {
+ let parsed = text;
+ try {
+ parsed = JSON.parse(text);
+ } catch (error) {
+ let content = document.createElement('div');
+ content.innerHTML = text;
+
+ let the_error = new Error();
+ the_error.stack = trim(content.innerText);
+
+ throw the_error;
+ }
+
+ return parsed;
+ });
+}
+
+export function userFeedback(response) {
+ if (UNLOADING) { return true; }
+
+ let status = response.status || (response.error ? 'error' : '');
+ let message = response.message || (response.error ? response.error.message : null);
+ let settings = response.toastr || null;
+ let backup;
+
+ switch (status) {
+ case 'unauthenticated':
+ document.location.href = config.base_url_relative;
+ throw error('Logged out');
+ case 'unauthorized':
+ status = 'error';
+ message = message || 'Unauthorized.';
+ break;
+ case 'error':
+ status = 'error';
+ message = message || 'Unknown error.';
+ break;
+ case 'success':
+ status = 'success';
+ message = message || '';
+ break;
+ default:
+ status = 'error';
+ message = message || 'Invalid AJAX response.';
+ break;
+ }
+
+ if (settings) {
+ backup = Object.assign({}, toastr.options);
+ Object.keys(settings).forEach((key) => { toastr.options[key] = settings[key]; });
+ }
+
+ if (message && (isOnline || (!isOnline && status !== 'error'))) {
+ toastr[status === 'success' ? 'success' : 'error'](message);
+ }
+
+ if (settings) {
+ toastr.options = backup;
+ }
+
+ return response;
+}
+
+export function userFeedbackError(error) {
+ if (UNLOADING) { return true; }
+ let stack = error.stack ? `${error.stack} ` : '';
+ toastr.error(`Fetch Failed: ${error.message} ${stack}`);
+ console.error(`${error.message} at ${error.stack}`);
+}
+
+$(global).on('beforeunload._ajax', () => {
+ UNLOADING = true;
+});
diff --git a/user/plugins/admin/themes/grav/app/utils/scrollbar.js b/user/plugins/admin/themes/grav/app/utils/scrollbar.js
new file mode 100644
index 0000000..774f624
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/scrollbar.js
@@ -0,0 +1,40 @@
+import $ from 'jquery';
+import GeminiScrollbar from 'gemini-scrollbar';
+
+const defaults = {
+ autoshow: false,
+ createElements: true,
+ forceGemini: false
+};
+
+export default class Scrollbar {
+ constructor(element, options) {
+ this.element = $(element);
+ this.created = false;
+ if (!this.element.length) { return; }
+
+ this.options = Object.assign({}, defaults, options, { element: this.element[0] });
+
+ this.element.css('overflow', 'auto');
+ this.instance = new GeminiScrollbar(this.options);
+ this.create();
+ this.element.data('scrollbar', this.instance);
+ }
+
+ create() {
+ this.instance.create();
+ this.created = true;
+ }
+
+ update() {
+ if (!this.created) { return false; }
+ this.instance.update();
+ }
+
+ destroy() {
+ if (!this.created) { return false; }
+ this.instance.destroy();
+ }
+}
+
+export let Instance = new Scrollbar('#admin-main .content-wrapper');
diff --git a/user/plugins/admin/themes/grav/app/utils/selectize-option-click.js b/user/plugins/admin/themes/grav/app/utils/selectize-option-click.js
new file mode 100644
index 0000000..e2d6ade
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/selectize-option-click.js
@@ -0,0 +1,42 @@
+import $ from 'jquery';
+import Selectize from 'selectize';
+
+Selectize.define('option_click', function(options) {
+ const self = this;
+ const setup = self.setup;
+ this.setup = function() {
+ setup.apply(self, arguments);
+ let clicking = false;
+
+ // Detect click on a .clickable
+ self.$dropdown_content.on('mousedown click', function(e) {
+ const target = $(e.target);
+ if (target.hasClass('clickable') || target.closest('.clickable').length) {
+ if (e.type === 'mousedown') {
+ clicking = true;
+ self.isFocused = false; // awful hack to defuse the document mousedown listener
+ } else {
+ self.isFocused = true;
+ setTimeout(function() {
+ clicking = false; // wait until blur has been preempted
+ });
+ }
+ } else { // cleanup in case user right-clicked or dragged off the element
+ clicking = false;
+ self.isFocused = true;
+ }
+ });
+
+ // Intercept default handlers
+ self.$dropdown.off('mousedown click', '[data-selectable]').on('mousedown click', '[data-selectable]', function() {
+ if (!clicking) {
+ return self.onOptionSelect.apply(self, arguments);
+ }
+ });
+ self.$control_input.off('blur').on('blur', function() {
+ if (!clicking) {
+ return self.onBlur.apply(self, arguments);
+ }
+ });
+ };
+});
diff --git a/user/plugins/admin/themes/grav/app/utils/selectize-required-fix.js b/user/plugins/admin/themes/grav/app/utils/selectize-required-fix.js
new file mode 100644
index 0000000..c0a5bed
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/selectize-required-fix.js
@@ -0,0 +1,28 @@
+/**
+ * This is a plugin to override the `.refreshValidityState` method of
+ * the Selectize library (https://selectize.github.io/selectize.js/).
+ * The library is not maintained anymore (as of 2017-09-13) and contains
+ * a bug which causes Microsoft Edge to not work with selectized [required]
+ * form fields. This plugin should be removed if
+ * https://github.com/selectize/selectize.js/pull/1320 is ever merged
+ * and a new version of Selectize gets released.
+ */
+
+import Selectize from 'selectize';
+
+Selectize.define('required-fix', function(options) {
+ this.refreshValidityState = () => {
+ if (!this.isRequired) return false;
+
+ let invalid = !this.items.length;
+ this.isInvalid = invalid;
+
+ if (invalid) {
+ this.$control_input.attr('required', '');
+ this.$input.removeAttr('required');
+ } else {
+ this.$control_input.removeAttr('required');
+ this.$input.attr('required');
+ }
+ };
+});
diff --git a/user/plugins/admin/themes/grav/app/utils/sidebar.js b/user/plugins/admin/themes/grav/app/utils/sidebar.js
new file mode 100644
index 0000000..4e7c3f8
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/sidebar.js
@@ -0,0 +1,179 @@
+import $ from 'jquery';
+import Scrollbar from './scrollbar';
+import Map from 'es6-map';
+
+const MOBILE_BREAKPOINT = 48 - 0.062;
+const DESKTOP_BREAKPOINT = 75 + 0.063;
+const EVENTS = 'touchstart._grav click._grav';
+const TARGETS = '[data-sidebar-mobile-toggle], #overlay';
+const MOBILE_QUERY = `(max-width: ${MOBILE_BREAKPOINT}em)`;
+const DESKTOP_QUERY = `(min-width: ${DESKTOP_BREAKPOINT}em)`;
+
+let map = new Map();
+
+export default class Sidebar {
+ constructor() {
+ this.timeout = null;
+ this.isOpen = false;
+ this.body = $('body');
+ this.matchMedia = global.matchMedia(MOBILE_QUERY);
+ this.scroller = new Scrollbar('.admin-menu-wrapper', { autoshow: true });
+ this.enable();
+ }
+
+ enable() {
+ const sidebar = $('#admin-sidebar');
+
+ this.matchMedia.addListener(this._getBound('checkMatch'));
+ this.checkMatch(this.matchMedia);
+ this.body.on(EVENTS, '[data-sidebar-toggle]', this._getBound('toggleSidebarState'));
+
+ if (sidebar.data('quickopen')) {
+ sidebar.hover(this._getBound('quickOpenIn'), this._getBound('quickOpenOut'));
+ }
+ }
+
+ disable() {
+ const sidebar = $('#admin-sidebar');
+
+ this.close();
+ this.matchMedia.removeListener(this._getBound('checkMatch'));
+ this.body.off(EVENTS, '[data-sidebar-toggle]', this._getBound('toggleSidebarState'));
+ if (sidebar.data('quickopen')) {
+ sidebar.off('mouseenter mouseleave');
+ }
+ }
+
+ attach() {
+ this.body.on(EVENTS, TARGETS, this._getBound('toggle'));
+ }
+
+ detach() {
+ this.body.off(EVENTS, TARGETS, this._getBound('toggle'));
+ }
+
+ quickOpenIn(/* event */) {
+ let isDesktop = global.matchMedia(DESKTOP_QUERY).matches;
+ let delay = $('#admin-sidebar').data('quickopen-delay') || 500;
+ if (this.body.hasClass('sidebar-mobile-open')) { return; }
+
+ let shouldQuickOpen = isDesktop ? this.body.hasClass('sidebar-closed') : !this.body.hasClass('sidebar-open');
+ if (!shouldQuickOpen && !this.body.hasClass('sidebar-quickopen')) { return this.quickOpenOut(); }
+
+ this.timeout = setTimeout(() => {
+ this.body.addClass('sidebar-open sidebar-quickopen');
+ $(global).trigger('sidebar_state._grav', isDesktop);
+ }, delay);
+ }
+
+ quickOpenOut(/* event */) {
+ clearTimeout(this.timeout);
+ if (this.body.hasClass('sidebar-quickopen')) {
+ this.body.removeClass('sidebar-open sidebar-quickopen');
+ }
+
+ return true;
+ }
+
+ open(event, quick = false) {
+ if (event) { event.preventDefault(); }
+ let overlay = $('#overlay');
+ let sidebar = $('#admin-sidebar');
+ let scrollbar = $('#admin-menu').data('scrollbar');
+
+ this.body.addClass('sidebar-mobile-open');
+ overlay.css('display', 'block');
+
+ if (!quick) {
+ sidebar.css('display', 'block').animate({
+ opacity: 1
+ }, 200, () => {
+ this.isOpen = true;
+ });
+ } else {
+ sidebar.css({ display: 'block', opacity: 1 });
+ this.isOpen = true;
+ }
+
+ if (scrollbar) { scrollbar.update(); }
+ }
+
+ close(event, quick = false) {
+ if (event) { event.preventDefault(); }
+ let overlay = $('#overlay');
+ let sidebar = $('#admin-sidebar');
+ let scrollbar = $('#admin-menu').data('scrollbar');
+
+ this.body.removeClass('sidebar-mobile-open');
+ overlay.css('display', 'none');
+
+ if (!quick) {
+ sidebar.animate({
+ opacity: 0
+ }, 200, () => {
+ sidebar.css('display', 'none');
+ this.isOpen = false;
+ });
+ } else {
+ sidebar.css({ opacity: 0, display: 'none' });
+ this.isOpen = false;
+ }
+
+ if (scrollbar) { scrollbar.update(); }
+ }
+
+ toggle(event) {
+ if (event) { event.preventDefault(); }
+ return this[this.isOpen ? 'close' : 'open'](event);
+ }
+
+ toggleSidebarState(event) {
+ if (event) { event.preventDefault(); }
+ clearTimeout(this.timeout);
+ let isDesktop = global.matchMedia(DESKTOP_QUERY).matches;
+
+ if (isDesktop) {
+ this.body.removeClass('sidebar-open');
+ }
+
+ if (!isDesktop) {
+ this.body.removeClass('sidebar-closed');
+ this.body.removeClass('sidebar-mobile-open');
+ }
+
+ this.body.toggleClass(`sidebar-${isDesktop ? 'closed' : 'open'}`);
+ $(global).trigger('sidebar_state._grav', isDesktop);
+ }
+
+ checkMatch(data) {
+ let sidebar = $('#admin-sidebar');
+ let overlay = $('#overlay');
+ this.isOpen = false;
+
+ overlay.css('display', 'none');
+ sidebar.css({
+ display: data.matches ? 'none' : 'inherit',
+ opacity: data.matches ? 0 : 1
+ });
+
+ if (data.matches) {
+ this.body.removeClass('sidebar-open sidebar-closed');
+ }
+
+ this[data.matches ? 'attach' : 'detach']();
+ }
+
+ _resetMap() {
+ return map.clear();
+ }
+
+ _getBound(fn) {
+ if (map.has(fn)) {
+ return map.get(fn);
+ }
+
+ return map.set(fn, this[fn].bind(this)).get(fn);
+ }
+}
+
+export let Instance = new Sidebar();
diff --git a/user/plugins/admin/themes/grav/app/utils/storage.js b/user/plugins/admin/themes/grav/app/utils/storage.js
new file mode 100644
index 0000000..03b95d3
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/storage.js
@@ -0,0 +1,41 @@
+// localStorage
+(function() {
+ function isSupported() {
+ var item = 'localStoragePollyfill';
+ try {
+ localStorage.setItem(item, item);
+ localStorage.removeItem(item);
+ sessionStorage.setItem(item, item);
+ sessionStorage.removeItem(item);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
+ if (!isSupported()) {
+ try {
+ Storage.prototype._data = {};
+
+ Storage.prototype.setItem = function(id, val) {
+ this._data[id] = String(val);
+ return this._data[id];
+ };
+
+ Storage.prototype.getItem = function(id) {
+ return this._data.hasOwnProperty(id) ? this._data[id] : undefined;
+ };
+
+ Storage.prototype.removeItem = function(id) {
+ return delete this._data[id];
+ };
+
+ Storage.prototype.clear = function() {
+ this._data = {};
+ return this._data;
+ };
+ } catch (e) {
+ console.error('localStorage pollyfill error: ', e);
+ }
+ }
+}());
diff --git a/user/plugins/admin/themes/grav/app/utils/tabs-memory.js b/user/plugins/admin/themes/grav/app/utils/tabs-memory.js
new file mode 100644
index 0000000..2206cdf
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/tabs-memory.js
@@ -0,0 +1,29 @@
+import $ from 'jquery';
+import Cookies from 'cookies-js';
+import { Instance as Editors } from '../forms/fields/editor';
+
+let Data = JSON.parse(Cookies.get('grav-tabs-state') || '{}');
+
+$('body').on('touchstart click', '[data-tabid]', (event) => {
+ event && event.stopPropagation();
+ let target = $(event.currentTarget);
+
+ Data[target.data('tabkey')] = target.data('scope');
+ Cookies.set('grav-tabs-state', JSON.stringify(Data), { expires: Infinity });
+
+ const panel = $(`[id="${target.data('tabid')}"]`);
+
+ target.siblings('[data-tabid]').removeClass('active');
+ target.addClass('active');
+
+ panel.siblings('[id]').removeClass('active');
+ panel.addClass('active');
+
+ Editors.editors.each((index, editor) => {
+ let codemirror = $(editor).data('codemirror');
+ if (!codemirror) { return; }
+ if (codemirror.display.lastWrapWidth === 0) {
+ codemirror.refresh();
+ }
+ });
+});
diff --git a/user/plugins/admin/themes/grav/app/utils/toastr.js b/user/plugins/admin/themes/grav/app/utils/toastr.js
new file mode 100644
index 0000000..8010965
--- /dev/null
+++ b/user/plugins/admin/themes/grav/app/utils/toastr.js
@@ -0,0 +1,6 @@
+import toastr from 'toastr';
+
+toastr.options.positionClass = 'toast-top-right';
+toastr.options.preventDuplicates = true;
+
+export default toastr;
diff --git a/user/plugins/admin/themes/grav/css-compiled/fonts.css b/user/plugins/admin/themes/grav/css-compiled/fonts.css
new file mode 100644
index 0000000..f444cc9
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css-compiled/fonts.css
@@ -0,0 +1,3 @@
+@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500|Inconsolata:400,700|&subset=latin-ext");body,h5,h6,.badge,.note,.grav-mdeditor-preview,input,select,textarea,button,.selectize-input{font-family:"Roboto","Helvetica","Tahoma","Geneva","Arial",sans-serif}h1,h2,h3,h4,.form-tabs>label,.label{font-family:"Roboto","Helvetica","Tahoma","Geneva","Arial",sans-serif}code,kbd,pre,samp,body .CodeMirror{font-family:"Inconsolata","Monaco","Consolas","Lucida Console",monospace !important}
+
+/*# sourceMappingURL=fonts.css.map */
diff --git a/user/plugins/admin/themes/grav/css-compiled/fonts.css.map b/user/plugins/admin/themes/grav/css-compiled/fonts.css.map
new file mode 100644
index 0000000..fa7887c
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css-compiled/fonts.css.map
@@ -0,0 +1 @@
+{"version":3,"file":"fonts.css","sources":["fonts.scss","configuration/fonts/_support.scss"],"sourcesContent":["$fonts-header: 'Roboto' !default;\n$fonts-default: 'Roboto' !default;\n$fonts-mono: 'Inconsolata' !default;\n\n$font-definitions: (\n Roboto: '300,400,500',\n Inconsolata: '400,700'\n);\n\n@import \"configuration/fonts/support\";\n","@function str-replace($string, $search, $replace: '') {\n $index: str-index($string, $search);\n\n @if $index {\n @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);\n }\n\n @return $string;\n}\n\n@function admin-font-faces($fonts) {\n $url: \"https://fonts.googleapis.com/css?family=\";\n $nb: 0;\n\n @each $fontname, $weights in $fonts {\n\n @if $fontname == $fonts-default or\n $fontname == $fonts-header or\n $fontname == $fonts-mono {\n\n $nb: $nb + 1;\n $nb-word: 0;\n\n $fontname: str-replace(\"#{$fontname}\", \" \", \"+\");\n\n $url: $url + $fontname;\n\n @if $weights != null {\n $url: $url + \":\" + $weights;\n }\n\n @if $nb < 3 {\n $url: $url + \"|\";\n }\n }\n }\n\n @return $url + \"&subset=latin-ext\";\n}\n\n@mixin body-fonts($font) {\n body, h5, h6,\n .badge, .note, .grav-mdeditor-preview,\n input, select, textarea, button, .selectize-input {\n font-family: \"#{$font}\", \"Helvetica\", \"Tahoma\", \"Geneva\", \"Arial\", sans-serif;\n }\n}\n\n@mixin header-fonts($font) {\n h1, h2, h3, h4,\n .form-tabs > label, .label {\n font-family: \"#{$font}\", \"Helvetica\", \"Tahoma\", \"Geneva\", \"Arial\", sans-serif;\n }\n}\n\n@mixin mono-fonts($font) {\n code, kbd, pre, samp,\n body .CodeMirror {\n font-family: \"#{$font}\", \"Monaco\", \"Consolas\", \"Lucida Console\", monospace !important;\n }\n}\n$font-url: admin-font-faces($font-definitions);\n\n@import url(\"#{$font-url}\");\n\n@include body-fonts($fonts-default);\n\n@include header-fonts($fonts-header);\n\n@include mono-fonts($fonts-mono);\n\n\n\n\n\n"],"names":[],"mappings":"AC+DA,OAAO,CAAC,uGAAI,CAtBR,AAAA,IAAI,CAAE,EAAE,CAAE,EAAE,CACZ,MAAM,CAAE,KAAK,CAAE,sBAAsB,CACrC,KAAK,CAAE,MAAM,CAAE,QAAQ,CAAE,MAAM,CAAE,gBAAgB,AAAC,CAC9C,WAAW,CAAE,QAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,EAAE,CAAE,EAAE,CAAE,EAAE,CAAE,EAAE,CACd,UAAU,CAAG,KAAK,CAAE,MAAM,AAAC,CACvB,WAAW,CAAE,QAAU,CAAE,WAAW,CAAE,QAAQ,CAAE,QAAQ,CAAE,OAAO,CAAE,UAAU,CAChF,AAID,AAAA,IAAI,CAAE,GAAG,CAAE,GAAG,CAAE,IAAI,CACpB,IAAI,CAAC,WAAW,AAAC,CACb,WAAW,CAAE,aAAU,CAAE,QAAQ,CAAE,UAAU,CAAE,gBAAgB,CAAE,SAAS,CAAC,UAAU,CACxF"}
\ No newline at end of file
diff --git a/user/plugins/admin/themes/grav/css-compiled/nucleus.css b/user/plugins/admin/themes/grav/css-compiled/nucleus.css
new file mode 100644
index 0000000..a21a5d1
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css-compiled/nucleus.css
@@ -0,0 +1,3 @@
+*,*::before,*::after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-webkit-viewport{width:device-width}@-moz-viewport{width:device-width}@-ms-viewport{width:device-width}@-o-viewport{width:device-width}@viewport{width:device-width}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent;text-decoration:none}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}mark{background:#ff0;color:#000}sub,sup{font-size:.65rem;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0;max-width:100%}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0}pre{overflow:auto}code,kbd,pre,samp{font-size:.9rem}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0;table-layout:fixed;width:100%}tr,td,th{vertical-align:middle}th,td{padding:.375rem 0}th{text-align:left}.container{width:75em;margin:0 auto;padding:0}@media only all and (min-width: 60em) and (max-width: 74.938em){.container{width:60em}}@media only all and (min-width: 48em) and (max-width: 59.938em){.container{width:48em}}@media only all and (min-width: 30.063em) and (max-width: 47.938em){.container{width:30em}}@media only all and (max-width: 30em){.container{width:100%}}.grid{display:-webkit-box;display:-moz-box;display:box;display:-webkit-flex;display:-moz-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row;-moz-flex-flow:row;flex-flow:row;list-style:none;margin:0;padding:0}@media only all and (max-width: 47.938em){.grid{-webkit-flex-flow:row wrap;-moz-flex-flow:row wrap;flex-flow:row wrap}}.block{-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;-webkit-flex:1;-moz-flex:1;-ms-flex:1;flex:1}@media only all and (max-width: 47.938em){.block{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 100%;-moz-flex:0 100%;-ms-flex:0 100%;flex:0 100%}}.content{margin:.625rem;padding:.938rem}@media only all and (max-width: 47.938em){body [class*="size-"]{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 100%;-moz-flex:0 100%;-ms-flex:0 100%;flex:0 100%}}.size-1-2{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 50%;-moz-flex:0 50%;-ms-flex:0 50%;flex:0 50%}.size-1-3{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 33.33333%;-moz-flex:0 33.33333%;-ms-flex:0 33.33333%;flex:0 33.33333%}.size-1-4{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 25%;-moz-flex:0 25%;-ms-flex:0 25%;flex:0 25%}.size-1-5{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 20%;-moz-flex:0 20%;-ms-flex:0 20%;flex:0 20%}.size-1-6{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 16.66667%;-moz-flex:0 16.66667%;-ms-flex:0 16.66667%;flex:0 16.66667%}.size-1-7{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 14.28571%;-moz-flex:0 14.28571%;-ms-flex:0 14.28571%;flex:0 14.28571%}.size-1-8{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 12.5%;-moz-flex:0 12.5%;-ms-flex:0 12.5%;flex:0 12.5%}.size-1-9{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 11.11111%;-moz-flex:0 11.11111%;-ms-flex:0 11.11111%;flex:0 11.11111%}.size-1-10{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 10%;-moz-flex:0 10%;-ms-flex:0 10%;flex:0 10%}.size-1-11{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 9.09091%;-moz-flex:0 9.09091%;-ms-flex:0 9.09091%;flex:0 9.09091%}.size-1-12{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 8.33333%;-moz-flex:0 8.33333%;-ms-flex:0 8.33333%;flex:0 8.33333%}@media only all and (min-width: 48em) and (max-width: 59.938em){.size-tablet-1-2{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 50%;-moz-flex:0 50%;-ms-flex:0 50%;flex:0 50%}.size-tablet-1-3{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 33.33333%;-moz-flex:0 33.33333%;-ms-flex:0 33.33333%;flex:0 33.33333%}.size-tablet-1-4{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 25%;-moz-flex:0 25%;-ms-flex:0 25%;flex:0 25%}.size-tablet-1-5{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 20%;-moz-flex:0 20%;-ms-flex:0 20%;flex:0 20%}.size-tablet-1-6{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 16.66667%;-moz-flex:0 16.66667%;-ms-flex:0 16.66667%;flex:0 16.66667%}.size-tablet-1-7{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 14.28571%;-moz-flex:0 14.28571%;-ms-flex:0 14.28571%;flex:0 14.28571%}.size-tablet-1-8{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 12.5%;-moz-flex:0 12.5%;-ms-flex:0 12.5%;flex:0 12.5%}.size-tablet-1-9{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 11.11111%;-moz-flex:0 11.11111%;-ms-flex:0 11.11111%;flex:0 11.11111%}.size-tablet-1-10{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 10%;-moz-flex:0 10%;-ms-flex:0 10%;flex:0 10%}.size-tablet-1-11{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 9.09091%;-moz-flex:0 9.09091%;-ms-flex:0 9.09091%;flex:0 9.09091%}.size-tablet-1-12{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0 8.33333%;-moz-flex:0 8.33333%;-ms-flex:0 8.33333%;flex:0 8.33333%}}@media only all and (max-width: 47.938em){@supports not (flex-wrap: wrap){.grid{display:block;-webkit-box-lines:inherit;-moz-box-lines:inherit;box-lines:inherit;-webkit-flex-wrap:inherit;-moz-flex-wrap:inherit;-ms-flex-wrap:inherit;flex-wrap:inherit}.block{display:block;-webkit-box-flex:inherit;-moz-box-flex:inherit;box-flex:inherit;-webkit-flex:inherit;-moz-flex:inherit;-ms-flex:inherit;flex:inherit}}}.first-block{-webkit-box-ordinal-group:0;-webkit-order:-1;-ms-flex-order:-1;order:-1}.last-block{-webkit-box-ordinal-group:2;-webkit-order:1;-ms-flex-order:1;order:1}.fixed-blocks{-webkit-flex-flow:row wrap;-moz-flex-flow:row wrap;flex-flow:row wrap}.fixed-blocks .block{-webkit-box-flex:inherit;-moz-box-flex:inherit;box-flex:inherit;-webkit-flex:inherit;-moz-flex:inherit;-ms-flex:inherit;flex:inherit;width:25%}@media only all and (min-width: 60em) and (max-width: 74.938em){.fixed-blocks .block{width:33.33333%}}@media only all and (min-width: 48em) and (max-width: 59.938em){.fixed-blocks .block{width:50%}}@media only all and (max-width: 47.938em){.fixed-blocks .block{width:100%}}@supports not (flex-wrap: wrap){.fixed-blocks{display:block;-webkit-flex-flow:inherit;-moz-flex-flow:inherit;flex-flow:inherit}}body{font-size:.9rem;line-height:1.5}h1,h2,h3,h4,h5,h6{margin:1.5rem 0 .75rem 0;text-rendering:optimizeLegibility}h1{font-size:3.1rem}h2{font-size:2.4rem}h3{font-size:2rem}h4{font-size:1.65rem}h5{font-size:1.25rem}h6{font-size:.75rem}p{margin:.75rem 0 1.5rem}ul,ol{margin-top:1.5rem;margin-bottom:1.5rem}ul ul,ul ol,ol ul,ol ol{margin-top:0;margin-bottom:0}blockquote{margin:1.5rem 0;padding-left:.75rem}cite{display:block;font-size:.775rem}cite:before{content:"\2014 \0020"}pre{margin:1.5rem 0;padding:.938rem}code{vertical-align:bottom}small{font-size:.775rem}hr{border-left:none;border-right:none;border-top:none;margin:1.5rem 0}fieldset{border:0;padding:.938rem;margin:0 0 1.5rem 0}input,label,select{display:block}label{margin-bottom:.375rem}label.required:after{content:"*"}label abbr{display:none}textarea,input[type="email"],input[type="number"],input[type="password"],input[type="search"],input[type="tel"],input[type="text"],input[type="url"],input[type="color"],input[type="date"],input[type="datetime"],input[type="datetime-local"],input[type="month"],input[type="time"],input[type="week"],select[multiple=multiple]{-webkit-transition:border-color;-moz-transition:border-color;transition:border-color;border-radius:.1875rem;padding:.375rem .375rem;width:100%}textarea:focus,input[type="email"]:focus,input[type="number"]:focus,input[type="password"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="text"]:focus,input[type="url"]:focus,input[type="color"]:focus,input[type="date"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,select[multiple=multiple]:focus{outline:none}textarea{resize:vertical}input[type="checkbox"],input[type="radio"]{display:inline;margin-right:.375rem}input[type="file"]{width:100%}select{width:auto;max-width:100%;margin-bottom:1.5rem}button,input[type="submit"]{cursor:pointer;user-select:none;vertical-align:middle;white-space:nowrap;border:inherit}
+
+/*# sourceMappingURL=nucleus.css.map */
diff --git a/user/plugins/admin/themes/grav/css-compiled/nucleus.css.map b/user/plugins/admin/themes/grav/css-compiled/nucleus.css.map
new file mode 100644
index 0000000..50fbd74
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css-compiled/nucleus.css.map
@@ -0,0 +1 @@
+{"version":3,"file":"nucleus.css","sources":["nucleus.scss","vendor/bourbon/_bourbon.scss","vendor/bourbon/settings/_prefixer.scss","vendor/bourbon/settings/_px-to-em.scss","vendor/bourbon/helpers/_convert-units.scss","vendor/bourbon/helpers/_gradient-positions-parser.scss","vendor/bourbon/helpers/_is-num.scss","vendor/bourbon/helpers/_linear-angle-parser.scss","vendor/bourbon/helpers/_linear-gradient-parser.scss","vendor/bourbon/helpers/_linear-positions-parser.scss","vendor/bourbon/helpers/_linear-side-corner-parser.scss","vendor/bourbon/helpers/_radial-arg-parser.scss","vendor/bourbon/helpers/_radial-positions-parser.scss","vendor/bourbon/helpers/_radial-gradient-parser.scss","vendor/bourbon/helpers/_render-gradients.scss","vendor/bourbon/helpers/_shape-size-stripper.scss","vendor/bourbon/helpers/_str-to-num.scss","vendor/bourbon/functions/_assign.scss","vendor/bourbon/functions/_color-lightness.scss","vendor/bourbon/functions/_flex-grid.scss","vendor/bourbon/functions/_golden-ratio.scss","vendor/bourbon/functions/_grid-width.scss","vendor/bourbon/functions/_modular-scale.scss","vendor/bourbon/functions/_px-to-em.scss","vendor/bourbon/functions/_px-to-rem.scss","vendor/bourbon/functions/_strip-units.scss","vendor/bourbon/functions/_tint-shade.scss","vendor/bourbon/functions/_transition-property-name.scss","vendor/bourbon/functions/_unpack.scss","vendor/bourbon/css3/_animation.scss","vendor/bourbon/css3/_appearance.scss","vendor/bourbon/css3/_backface-visibility.scss","vendor/bourbon/css3/_background.scss","vendor/bourbon/css3/_background-image.scss","vendor/bourbon/css3/_border-image.scss","vendor/bourbon/css3/_border-radius.scss","vendor/bourbon/css3/_box-sizing.scss","vendor/bourbon/css3/_calc.scss","vendor/bourbon/css3/_columns.scss","vendor/bourbon/css3/_filter.scss","vendor/bourbon/css3/_flex-box.scss","vendor/bourbon/css3/_font-face.scss","vendor/bourbon/css3/_hyphens.scss","vendor/bourbon/css3/_hidpi-media-query.scss","vendor/bourbon/css3/_image-rendering.scss","vendor/bourbon/css3/_keyframes.scss","vendor/bourbon/css3/_linear-gradient.scss","vendor/bourbon/css3/_perspective.scss","vendor/bourbon/css3/_radial-gradient.scss","vendor/bourbon/css3/_transform.scss","vendor/bourbon/css3/_transition.scss","vendor/bourbon/css3/_user-select.scss","vendor/bourbon/css3/_placeholder.scss","vendor/bourbon/addons/_button.scss","vendor/bourbon/addons/_clearfix.scss","vendor/bourbon/addons/_directional-values.scss","vendor/bourbon/addons/_ellipsis.scss","vendor/bourbon/addons/_font-family.scss","vendor/bourbon/addons/_hide-text.scss","vendor/bourbon/addons/_html5-input-types.scss","vendor/bourbon/addons/_position.scss","vendor/bourbon/addons/_prefixer.scss","vendor/bourbon/addons/_retina-image.scss","vendor/bourbon/addons/_size.scss","vendor/bourbon/addons/_timing-functions.scss","vendor/bourbon/addons/_triangle.scss","vendor/bourbon/addons/_word-wrap.scss","vendor/bourbon/_bourbon-deprecated-upcoming.scss","configuration/nucleus/_base.scss","configuration/nucleus/_core.scss","configuration/nucleus/_breakpoints.scss","configuration/nucleus/_layout.scss","configuration/nucleus/_typography.scss","configuration/nucleus/_nav.scss","nucleus/functions/_base.scss","nucleus/functions/_direction.scss","nucleus/functions/_range.scss","nucleus/mixins/_base.scss","nucleus/mixins/_breakpoints.scss","nucleus/mixins/_utilities.scss","nucleus/_core.scss","nucleus/_flex.scss","nucleus/_typography.scss","nucleus/_forms.scss"],"sourcesContent":["// REQUIRED DEPENDENCIES - DO NOT CHANGE\n\n// Load Third Party Libraries\n@import \"vendor/bourbon/bourbon\";\n\n// Load Nucleus Configuration\n@import \"configuration/nucleus/base\";\n\n// Load Nucleus Mixins and Functions\n@import \"nucleus/functions/base\";\n@import \"nucleus/mixins/base\";\n\n//-------------------------------------------\n\n// LOAD NUCLEUS COMPONENTS\n\n// Core\n@import \"nucleus/core\";\n\n// Flex\n@import \"nucleus/flex\";\n\n// Typography\n@import \"nucleus/typography\";\n\n// Forms\n@import \"nucleus/forms\";\n","// Settings\n@import \"settings/prefixer\";\n@import \"settings/px-to-em\";\n\n// Custom Helpers\n@import \"helpers/convert-units\";\n@import \"helpers/gradient-positions-parser\";\n@import \"helpers/is-num\";\n@import \"helpers/linear-angle-parser\";\n@import \"helpers/linear-gradient-parser\";\n@import \"helpers/linear-positions-parser\";\n@import \"helpers/linear-side-corner-parser\";\n@import \"helpers/radial-arg-parser\";\n@import \"helpers/radial-positions-parser\";\n@import \"helpers/radial-gradient-parser\";\n@import \"helpers/render-gradients\";\n@import \"helpers/shape-size-stripper\";\n@import \"helpers/str-to-num\";\n\n// Custom Functions\n@import \"functions/assign\";\n@import \"functions/color-lightness\";\n@import \"functions/flex-grid\";\n@import \"functions/golden-ratio\";\n@import \"functions/grid-width\";\n@import \"functions/modular-scale\";\n@import \"functions/px-to-em\";\n@import \"functions/px-to-rem\";\n@import \"functions/strip-units\";\n@import \"functions/tint-shade\";\n@import \"functions/transition-property-name\";\n@import \"functions/unpack\";\n\n// CSS3 Mixins\n@import \"css3/animation\";\n@import \"css3/appearance\";\n@import \"css3/backface-visibility\";\n@import \"css3/background\";\n@import \"css3/background-image\";\n@import \"css3/border-image\";\n@import \"css3/border-radius\";\n@import \"css3/box-sizing\";\n@import \"css3/calc\";\n@import \"css3/columns\";\n@import \"css3/filter\";\n@import \"css3/flex-box\";\n@import \"css3/font-face\";\n@import \"css3/hyphens\";\n@import \"css3/hidpi-media-query\";\n@import \"css3/image-rendering\";\n@import \"css3/keyframes\";\n@import \"css3/linear-gradient\";\n@import \"css3/perspective\";\n@import \"css3/radial-gradient\";\n@import \"css3/transform\";\n@import \"css3/transition\";\n@import \"css3/user-select\";\n@import \"css3/placeholder\";\n\n// Addons & other mixins\n@import \"addons/button\";\n@import \"addons/clearfix\";\n@import \"addons/directional-values\";\n@import \"addons/ellipsis\";\n@import \"addons/font-family\";\n@import \"addons/hide-text\";\n@import \"addons/html5-input-types\";\n@import \"addons/position\";\n@import \"addons/prefixer\";\n@import \"addons/retina-image\";\n@import \"addons/size\";\n@import \"addons/timing-functions\";\n@import \"addons/triangle\";\n@import \"addons/word-wrap\";\n\n// Soon to be deprecated Mixins\n@import \"bourbon-deprecated-upcoming\";\n","// Variable settings for /addons/prefixer.scss\n$prefix-for-webkit: true !default;\n$prefix-for-mozilla: true !default;\n$prefix-for-microsoft: true !default;\n$prefix-for-opera: true !default;\n$prefix-for-spec: true !default; // required for keyframe mixin\n","$em-base: 16px !default;\n","//************************************************************************//\n// Helper function for str-to-num fn.\n// Source: http://sassmeister.com/gist/9647408\n//************************************************************************//\n@function _convert-units($number, $unit) {\n $strings: 'px' 'cm' 'mm' '%' 'ch' 'pica' 'in' 'em' 'rem' 'pt' 'pc' 'ex' 'vw' 'vh' 'vmin' 'vmax', 'deg', 'rad', 'grad', 'turn';\n $units: 1px 1cm 1mm 1% 1ch 1pica 1in 1em 1rem 1pt 1pc 1ex 1vw 1vh 1vmin 1vmax, 1deg, 1rad, 1grad, 1turn;\n $index: index($strings, $unit);\n\n @if not $index {\n @warn \"Unknown unit `#{$unit}`.\";\n @return false;\n }\n @return $number * nth($units, $index);\n}\n","@function _gradient-positions-parser($gradient-type, $gradient-positions) {\n @if $gradient-positions\n and ($gradient-type == linear)\n and (type-of($gradient-positions) != color) {\n $gradient-positions: _linear-positions-parser($gradient-positions);\n }\n @else if $gradient-positions\n and ($gradient-type == radial)\n and (type-of($gradient-positions) != color) {\n $gradient-positions: _radial-positions-parser($gradient-positions);\n }\n @return $gradient-positions;\n}\n","//************************************************************************//\n// Helper for linear-gradient-parser\n//************************************************************************//\n@function _is-num($char) {\n $values: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' 0 1 2 3 4 5 6 7 8 9;\n $index: index($values, $char);\n @return if($index, true, false);\n}\n","// Private function for linear-gradient-parser\n@function _linear-angle-parser($image, $first-val, $prefix, $suffix) {\n $offset: null;\n $unit-short: str-slice($first-val, str-length($first-val) - 2, str-length($first-val));\n $unit-long: str-slice($first-val, str-length($first-val) - 3, str-length($first-val));\n\n @if ($unit-long == \"grad\") or\n ($unit-long == \"turn\") {\n $offset: if($unit-long == \"grad\", -100grad * 3, -0.75turn);\n }\n\n @else if ($unit-short == \"deg\") or\n ($unit-short == \"rad\") {\n $offset: if($unit-short == \"deg\", -90 * 3, 1.6rad);\n }\n\n @if $offset {\n $num: _str-to-num($first-val);\n\n @return (\n webkit-image: -webkit- + $prefix + ($offset - $num) + $suffix,\n spec-image: $image\n );\n }\n}\n","@function _linear-gradient-parser($image) {\n $image: unquote($image);\n $gradients: ();\n $start: str-index($image, \"(\");\n $end: str-index($image, \",\");\n $first-val: str-slice($image, $start + 1, $end - 1);\n\n $prefix: str-slice($image, 0, $start);\n $suffix: str-slice($image, $end, str-length($image));\n\n $has-multiple-vals: str-index($first-val, \" \");\n $has-single-position: unquote(_position-flipper($first-val) + \"\");\n $has-angle: _is-num(str-slice($first-val, 0, 0));\n\n @if $has-multiple-vals {\n $gradients: _linear-side-corner-parser($image, $first-val, $prefix, $suffix, $has-multiple-vals);\n }\n\n @else if $has-single-position != \"\" {\n $pos: unquote($has-single-position + \"\");\n\n $gradients: (\n webkit-image: -webkit- + $image,\n spec-image: $prefix + \"to \" + $pos + $suffix\n );\n }\n\n @else if $has-angle {\n // Rotate degree for webkit\n $gradients: _linear-angle-parser($image, $first-val, $prefix, $suffix);\n }\n\n @else {\n $gradients: (\n webkit-image: -webkit- + $image,\n spec-image: $image\n );\n }\n\n @return $gradients;\n}\n","@function _linear-positions-parser($pos) {\n $type: type-of(nth($pos, 1));\n $spec: null;\n $degree: null;\n $side: null;\n $corner: null;\n $length: length($pos);\n // Parse Side and corner positions\n @if ($length > 1) {\n @if nth($pos, 1) == \"to\" { // Newer syntax\n $side: nth($pos, 2);\n\n @if $length == 2 { // eg. to top\n // Swap for backwards compatability\n $degree: _position-flipper(nth($pos, 2));\n }\n @else if $length == 3 { // eg. to top left\n $corner: nth($pos, 3);\n }\n }\n @else if $length == 2 { // Older syntax (\"top left\")\n $side: _position-flipper(nth($pos, 1));\n $corner: _position-flipper(nth($pos, 2));\n }\n\n @if (\"#{$side} #{$corner}\" == \"left top\") or (\"#{$side} #{$corner}\" == \"top left\") {\n $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});\n }\n @else if (\"#{$side} #{$corner}\" == \"right top\") or (\"#{$side} #{$corner}\" == \"top right\") {\n $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});\n }\n @else if (\"#{$side} #{$corner}\" == \"right bottom\") or (\"#{$side} #{$corner}\" == \"bottom right\") {\n $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});\n }\n @else if (\"#{$side} #{$corner}\" == \"left bottom\") or (\"#{$side} #{$corner}\" == \"bottom left\") {\n $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});\n }\n $spec: to $side $corner;\n }\n @else if $length == 1 {\n // Swap for backwards compatability\n @if $type == string {\n $degree: $pos;\n $spec: to _position-flipper($pos);\n }\n @else {\n $degree: -270 - $pos; //rotate the gradient opposite from spec\n $spec: $pos;\n }\n }\n $degree: unquote($degree + \",\");\n $spec: unquote($spec + \",\");\n @return $degree $spec;\n}\n\n@function _position-flipper($pos) {\n @return if($pos == left, right, null)\n if($pos == right, left, null)\n if($pos == top, bottom, null)\n if($pos == bottom, top, null);\n}\n","// Private function for linear-gradient-parser\n@function _linear-side-corner-parser($image, $first-val, $prefix, $suffix, $has-multiple-vals) {\n $val-1: str-slice($first-val, 0, $has-multiple-vals - 1 );\n $val-2: str-slice($first-val, $has-multiple-vals + 1, str-length($first-val));\n $val-3: null;\n $has-val-3: str-index($val-2, \" \");\n\n @if $has-val-3 {\n $val-3: str-slice($val-2, $has-val-3 + 1, str-length($val-2));\n $val-2: str-slice($val-2, 0, $has-val-3 - 1);\n }\n\n $pos: _position-flipper($val-1) _position-flipper($val-2) _position-flipper($val-3);\n $pos: unquote($pos + \"\");\n\n // Use old spec for webkit\n @if $val-1 == \"to\" {\n @return (\n webkit-image: -webkit- + $prefix + $pos + $suffix,\n spec-image: $image\n );\n }\n\n // Bring the code up to spec\n @else {\n @return (\n webkit-image: -webkit- + $image,\n spec-image: $prefix + \"to \" + $pos + $suffix\n );\n }\n}\n","@function _radial-arg-parser($G1, $G2, $pos, $shape-size) {\n @each $value in $G1, $G2 {\n $first-val: nth($value, 1);\n $pos-type: type-of($first-val);\n $spec-at-index: null;\n\n // Determine if spec was passed to mixin\n @if type-of($value) == list {\n $spec-at-index: if(index($value, at), index($value, at), false);\n }\n @if $spec-at-index {\n @if $spec-at-index > 1 {\n @for $i from 1 through ($spec-at-index - 1) {\n $shape-size: $shape-size nth($value, $i);\n }\n @for $i from ($spec-at-index + 1) through length($value) {\n $pos: $pos nth($value, $i);\n }\n }\n @else if $spec-at-index == 1 {\n @for $i from ($spec-at-index + 1) through length($value) {\n $pos: $pos nth($value, $i);\n }\n }\n $G1: null;\n }\n\n // If not spec calculate correct values\n @else {\n @if ($pos-type != color) or ($first-val != \"transparent\") {\n @if ($pos-type == number)\n or ($first-val == \"center\")\n or ($first-val == \"top\")\n or ($first-val == \"right\")\n or ($first-val == \"bottom\")\n or ($first-val == \"left\") {\n\n $pos: $value;\n\n @if $pos == $G1 {\n $G1: null;\n }\n }\n\n @else if\n ($first-val == \"ellipse\")\n or ($first-val == \"circle\")\n or ($first-val == \"closest-side\")\n or ($first-val == \"closest-corner\")\n or ($first-val == \"farthest-side\")\n or ($first-val == \"farthest-corner\")\n or ($first-val == \"contain\")\n or ($first-val == \"cover\") {\n\n $shape-size: $value;\n\n @if $value == $G1 {\n $G1: null;\n }\n\n @else if $value == $G2 {\n $G2: null;\n }\n }\n }\n }\n }\n @return $G1, $G2, $pos, $shape-size;\n}\n","@function _radial-positions-parser($gradient-pos) {\n $shape-size: nth($gradient-pos, 1);\n $pos: nth($gradient-pos, 2);\n $shape-size-spec: _shape-size-stripper($shape-size);\n\n $pre-spec: unquote(if($pos, \"#{$pos}, \", null))\n unquote(if($shape-size, \"#{$shape-size},\", null));\n $pos-spec: if($pos, \"at #{$pos}\", null);\n\n $spec: \"#{$shape-size-spec} #{$pos-spec}\";\n\n // Add comma\n @if ($spec != ' ') {\n $spec: \"#{$spec},\"\n }\n\n @return $pre-spec $spec;\n}\n","@function _radial-gradient-parser($image) {\n $image: unquote($image);\n $gradients: ();\n $start: str-index($image, \"(\");\n $end: str-index($image, \",\");\n $first-val: str-slice($image, $start + 1, $end - 1);\n\n $prefix: str-slice($image, 0, $start);\n $suffix: str-slice($image, $end, str-length($image));\n\n $is-spec-syntax: str-index($first-val, \"at\");\n\n @if $is-spec-syntax and $is-spec-syntax > 1 {\n $keyword: str-slice($first-val, 1, $is-spec-syntax - 2);\n $pos: str-slice($first-val, $is-spec-syntax + 3, str-length($first-val));\n $pos: append($pos, $keyword, comma);\n\n $gradients: (\n webkit-image: -webkit- + $prefix + $pos + $suffix,\n spec-image: $image\n )\n }\n\n @else if $is-spec-syntax == 1 {\n $pos: str-slice($first-val, $is-spec-syntax + 3, str-length($first-val));\n\n $gradients: (\n webkit-image: -webkit- + $prefix + $pos + $suffix,\n spec-image: $image\n )\n }\n\n @else if str-index($image, \"cover\") or str-index($image, \"contain\") {\n @warn \"Radial-gradient needs to be updated to conform to latest spec.\";\n\n $gradients: (\n webkit-image: null,\n spec-image: $image\n )\n }\n\n @else {\n $gradients: (\n webkit-image: -webkit- + $image,\n spec-image: $image\n )\n }\n\n @return $gradients;\n}\n","// User for linear and radial gradients within background-image or border-image properties\n\n@function _render-gradients($gradient-positions, $gradients, $gradient-type, $vendor: false) {\n $pre-spec: null;\n $spec: null;\n $vendor-gradients: null;\n @if $gradient-type == linear {\n @if $gradient-positions {\n $pre-spec: nth($gradient-positions, 1);\n $spec: nth($gradient-positions, 2);\n }\n }\n @else if $gradient-type == radial {\n $pre-spec: nth($gradient-positions, 1);\n $spec: nth($gradient-positions, 2);\n }\n\n @if $vendor {\n $vendor-gradients: -#{$vendor}-#{$gradient-type}-gradient(#{$pre-spec} $gradients);\n }\n @else if $vendor == false {\n $vendor-gradients: \"#{$gradient-type}-gradient(#{$spec} #{$gradients})\";\n $vendor-gradients: unquote($vendor-gradients);\n }\n @return $vendor-gradients;\n}\n","@function _shape-size-stripper($shape-size) {\n $shape-size-spec: null;\n @each $value in $shape-size {\n @if ($value == \"cover\") or ($value == \"contain\") {\n $value: null;\n }\n $shape-size-spec: \"#{$shape-size-spec} #{$value}\";\n }\n @return $shape-size-spec;\n}\n","//************************************************************************//\n// Helper function for linear/radial-gradient-parsers.\n// Source: http://sassmeister.com/gist/9647408\n//************************************************************************//\n@function _str-to-num($string) {\n // Matrices\n $strings: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9';\n $numbers: 0 1 2 3 4 5 6 7 8 9;\n\n // Result\n $result: 0;\n $divider: 0;\n $minus: false;\n\n // Looping through all characters\n @for $i from 1 through str-length($string) {\n $character: str-slice($string, $i, $i);\n $index: index($strings, $character);\n\n @if $character == '-' {\n $minus: true;\n }\n\n @else if $character == '.' {\n $divider: 1;\n }\n\n @else {\n @if not $index {\n $result: if($minus, $result * -1, $result);\n @return _convert-units($result, str-slice($string, $i));\n }\n\n $number: nth($numbers, $index);\n\n @if $divider == 0 {\n $result: $result * 10;\n }\n\n @else {\n // Move the decimal dot to the left\n $divider: $divider * 10;\n $number: $number / $divider;\n }\n\n $result: $result + $number;\n }\n }\n @return if($minus, $result * -1, $result);\n}\n","@function assign-inputs($inputs, $pseudo: null) {\n $list : ();\n\n @each $input in $inputs {\n $input: unquote($input);\n $input: if($pseudo, $input + \":\" + $pseudo, $input);\n $list: append($list, $input, comma);\n }\n\n @return $list;\n}","// Programatically determines whether a color is light or dark\n// Returns a boolean\n// More details here http://robots.thoughtbot.com/closer-look-color-lightness\n\n@function is-light($hex-color) {\n $-local-red: red(rgba($hex-color, 1.0));\n $-local-green: green(rgba($hex-color, 1.0));\n $-local-blue: blue(rgba($hex-color, 1.0));\n\n $-local-lightness: ($-local-red * 0.2126 + $-local-green * 0.7152 + $-local-blue * 0.0722) / 255;\n\n @return $-local-lightness > .6;\n}\n","// Flexible grid\n@function flex-grid($columns, $container-columns: $fg-max-columns) {\n $width: $columns * $fg-column + ($columns - 1) * $fg-gutter;\n $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;\n @return percentage($width / $container-width);\n}\n\n// Flexible gutter\n@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) {\n $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;\n @return percentage($gutter / $container-width);\n}\n\n// The $fg-column, $fg-gutter and $fg-max-columns variables must be defined in your base stylesheet to properly use the flex-grid function.\n// This function takes the fluid grid equation (target / context = result) and uses columns to help define each.\n//\n// The calculation presumes that your column structure will be missing the last gutter:\n//\n// -- column -- gutter -- column -- gutter -- column\n//\n// $fg-column: 60px; // Column Width\n// $fg-gutter: 25px; // Gutter Width\n// $fg-max-columns: 12; // Total Columns For Main Container\n//\n// div {\n// width: flex-grid(4); // returns (315px / 995px) = 31.65829%;\n// margin-left: flex-gutter(); // returns (25px / 995px) = 2.51256%;\n//\n// p {\n// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%;\n// float: left;\n// margin: flex-gutter(4); // returns (25px / 315px) = 7.936508%;\n// }\n//\n// blockquote {\n// float: left;\n// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%;\n// }\n// }","@function golden-ratio($value, $increment) {\n @return modular-scale($value, $increment, $golden)\n}\n","@function grid-width($n) {\n @return $n * $gw-column + ($n - 1) * $gw-gutter;\n}\n\n// The $gw-column and $gw-gutter variables must be defined in your base stylesheet to properly use the grid-width function.\n//\n// $gw-column: 100px; // Column Width\n// $gw-gutter: 40px; // Gutter Width\n//\n// div {\n// width: grid-width(4); // returns 520px;\n// margin-left: $gw-gutter; // returns 40px;\n// }\n","// Scaling Variables\n$golden: 1.618;\n$minor-second: 1.067;\n$major-second: 1.125;\n$minor-third: 1.2;\n$major-third: 1.25;\n$perfect-fourth: 1.333;\n$augmented-fourth: 1.414;\n$perfect-fifth: 1.5;\n$minor-sixth: 1.6;\n$major-sixth: 1.667;\n$minor-seventh: 1.778;\n$major-seventh: 1.875;\n$octave: 2;\n$major-tenth: 2.5;\n$major-eleventh: 2.667;\n$major-twelfth: 3;\n$double-octave: 4;\n\n@function modular-scale($value, $increment, $ratio) {\n $v1: nth($value, 1);\n $v2: nth($value, length($value));\n $value: $v1;\n\n // scale $v2 to just above $v1\n @while $v2 > $v1 {\n $v2: ($v2 / $ratio); // will be off-by-1\n }\n @while $v2 < $v1 {\n $v2: ($v2 * $ratio); // will fix off-by-1\n }\n\n // check AFTER scaling $v2 to prevent double-counting corner-case\n $double-stranded: $v2 > $v1;\n\n @if $increment > 0 {\n @for $i from 1 through $increment {\n @if $double-stranded and ($v1 * $ratio) > $v2 {\n $value: $v2;\n $v2: ($v2 * $ratio);\n } @else {\n $v1: ($v1 * $ratio);\n $value: $v1;\n }\n }\n }\n\n @if $increment < 0 {\n // adjust $v2 to just below $v1\n @if $double-stranded {\n $v2: ($v2 / $ratio);\n }\n\n @for $i from $increment through -1 {\n @if $double-stranded and ($v1 / $ratio) < $v2 {\n $value: $v2;\n $v2: ($v2 / $ratio);\n } @else {\n $v1: ($v1 / $ratio);\n $value: $v1;\n }\n }\n }\n\n @return $value;\n}\n","// Convert pixels to ems\n// eg. for a relational value of 12px write em(12) when the parent is 16px\n// if the parent is another value say 24px write em(12, 24)\n\n@function em($pxval, $base: $em-base) {\n @if not unitless($pxval) {\n $pxval: strip-units($pxval);\n }\n @if not unitless($base) {\n $base: strip-units($base);\n }\n @return ($pxval / $base) * 1em;\n}\n","// Convert pixels to rems\n// eg. for a relational value of 12px write rem(12)\n// Assumes $em-base is the font-size of \n\n@function rem($pxval) {\n @if not unitless($pxval) {\n $pxval: strip-units($pxval);\n }\n\n $base: $em-base;\n @if not unitless($base) {\n $base: strip-units($base);\n }\n @return ($pxval / $base) * 1rem;\n}\n","// Srtips the units from a value. e.g. 12px -> 12\n\n@function strip-units($val) {\n @return ($val / ($val * 0 + 1));\n}\n","// Add percentage of white to a color\n@function tint($color, $percent){\n @return mix(white, $color, $percent);\n}\n\n// Add percentage of black to a color\n@function shade($color, $percent){\n @return mix(black, $color, $percent);\n}\n","// Return vendor-prefixed property names if appropriate\n// Example: transition-property-names((transform, color, background), moz) -> -moz-transform, color, background\n//************************************************************************//\n@function transition-property-names($props, $vendor: false) {\n\t$new-props: ();\n\t\n\t@each $prop in $props {\n\t\t$new-props: append($new-props, transition-property-name($prop, $vendor), comma);\n\t}\n\n\t@return $new-props;\n}\n\n@function transition-property-name($prop, $vendor: false) {\n\t// put other properties that need to be prefixed here aswell\n\t@if $vendor and $prop == transform {\n\t\t@return unquote('-'+$vendor+'-'+$prop);\n\t}\n\t@else {\n\t\t@return $prop;\n\t}\n}","// Convert shorthand to the 4-value syntax\n\n@function unpack($shorthand) {\n @if length($shorthand) == 1 {\n @return nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1);\n }\n @else if length($shorthand) == 2 {\n @return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 1) nth($shorthand, 2);\n }\n @else if length($shorthand) == 3 {\n @return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 3) nth($shorthand, 2);\n }\n @else {\n @return $shorthand;\n }\n}\n\n","// http://www.w3.org/TR/css3-animations/#the-animation-name-property-\n// Each of these mixins support comma separated lists of values, which allows different transitions for individual properties to be described in a single style rule. Each value in the list corresponds to the value at that same position in the other properties.\n\n// Official animation shorthand property.\n@mixin animation ($animations...) {\n @include prefixer(animation, $animations, webkit moz spec);\n}\n\n// Individual Animation Properties\n@mixin animation-name ($names...) {\n @include prefixer(animation-name, $names, webkit moz spec);\n}\n\n\n@mixin animation-duration ($times...) {\n @include prefixer(animation-duration, $times, webkit moz spec);\n}\n\n\n@mixin animation-timing-function ($motions...) {\n// ease | linear | ease-in | ease-out | ease-in-out\n @include prefixer(animation-timing-function, $motions, webkit moz spec);\n}\n\n\n@mixin animation-iteration-count ($values...) {\n// infinite | \n @include prefixer(animation-iteration-count, $values, webkit moz spec);\n}\n\n\n@mixin animation-direction ($directions...) {\n// normal | alternate\n @include prefixer(animation-direction, $directions, webkit moz spec);\n}\n\n\n@mixin animation-play-state ($states...) {\n// running | paused\n @include prefixer(animation-play-state, $states, webkit moz spec);\n}\n\n\n@mixin animation-delay ($times...) {\n @include prefixer(animation-delay, $times, webkit moz spec);\n}\n\n\n@mixin animation-fill-mode ($modes...) {\n// none | forwards | backwards | both\n @include prefixer(animation-fill-mode, $modes, webkit moz spec);\n}\n","@mixin appearance ($value) {\n @include prefixer(appearance, $value, webkit moz ms o spec);\n}\n","//************************************************************************//\n// Backface-visibility mixin\n//************************************************************************//\n@mixin backface-visibility($visibility) {\n @include prefixer(backface-visibility, $visibility, webkit spec);\n}\n","//************************************************************************//\n// Background property for adding multiple backgrounds using shorthand\n// notation.\n//************************************************************************//\n\n@mixin background($backgrounds...) {\n $webkit-backgrounds: ();\n $spec-backgrounds: ();\n\n @each $background in $backgrounds {\n $webkit-background: ();\n $spec-background: ();\n $background-type: type-of($background);\n\n @if $background-type == string or list {\n $background-str: if($background-type == list, nth($background, 1), $background);\n\n $url-str: str-slice($background-str, 0, 3);\n $gradient-type: str-slice($background-str, 0, 6);\n\n @if $url-str == \"url\" {\n $webkit-background: $background;\n $spec-background: $background;\n }\n\n @else if $gradient-type == \"linear\" {\n $gradients: _linear-gradient-parser(\"#{$background}\");\n $webkit-background: map-get($gradients, webkit-image);\n $spec-background: map-get($gradients, spec-image);\n }\n\n @else if $gradient-type == \"radial\" {\n $gradients: _radial-gradient-parser(\"#{$background}\");\n $webkit-background: map-get($gradients, webkit-image);\n $spec-background: map-get($gradients, spec-image);\n }\n\n @else {\n $webkit-background: $background;\n $spec-background: $background;\n }\n }\n\n @else {\n $webkit-background: $background;\n $spec-background: $background;\n }\n\n $webkit-backgrounds: append($webkit-backgrounds, $webkit-background, comma);\n $spec-backgrounds: append($spec-backgrounds, $spec-background, comma);\n }\n\n background: $webkit-backgrounds;\n background: $spec-backgrounds;\n}\n","//************************************************************************//\n// Background-image property for adding multiple background images with\n// gradients, or for stringing multiple gradients together.\n//************************************************************************//\n\n@mixin background-image($images...) {\n $webkit-images: ();\n $spec-images: ();\n\n @each $image in $images {\n $webkit-image: ();\n $spec-image: ();\n\n @if (type-of($image) == string) {\n $url-str: str-slice($image, 0, 3);\n $gradient-type: str-slice($image, 0, 6);\n\n @if $url-str == \"url\" {\n $webkit-image: $image;\n $spec-image: $image;\n }\n\n @else if $gradient-type == \"linear\" {\n $gradients: _linear-gradient-parser($image);\n $webkit-image: map-get($gradients, webkit-image);\n $spec-image: map-get($gradients, spec-image);\n }\n\n @else if $gradient-type == \"radial\" {\n $gradients: _radial-gradient-parser($image);\n $webkit-image: map-get($gradients, webkit-image);\n $spec-image: map-get($gradients, spec-image);\n }\n }\n\n $webkit-images: append($webkit-images, $webkit-image, comma);\n $spec-images: append($spec-images, $spec-image, comma);\n }\n\n background-image: $webkit-images;\n background-image: $spec-images;\n}\n","@mixin border-image($borders...) {\n $webkit-borders: ();\n $spec-borders: ();\n\n @each $border in $borders {\n $webkit-border: ();\n $spec-border: ();\n $border-type: type-of($border);\n\n @if $border-type == string or list {\n $border-str: if($border-type == list, nth($border, 1), $border);\n\n $url-str: str-slice($border-str, 0, 3);\n $gradient-type: str-slice($border-str, 0, 6);\n\n @if $url-str == \"url\" {\n $webkit-border: $border;\n $spec-border: $border;\n }\n\n @else if $gradient-type == \"linear\" {\n $gradients: _linear-gradient-parser(\"#{$border}\");\n $webkit-border: map-get($gradients, webkit-image);\n $spec-border: map-get($gradients, spec-image);\n }\n\n @else if $gradient-type == \"radial\" {\n $gradients: _radial-gradient-parser(\"#{$border}\");\n $webkit-border: map-get($gradients, webkit-image);\n $spec-border: map-get($gradients, spec-image);\n }\n\n @else {\n $webkit-border: $border;\n $spec-border: $border;\n }\n }\n\n @else {\n $webkit-border: $border;\n $spec-border: $border;\n }\n\n $webkit-borders: append($webkit-borders, $webkit-border, comma);\n $spec-borders: append($spec-borders, $spec-border, comma);\n }\n\n -webkit-border-image: $webkit-borders;\n border-image: $spec-borders;\n border-style: solid;\n}\n\n//Examples:\n// @include border-image(url(\"image.png\"));\n// @include border-image(url(\"image.png\") 20 stretch);\n// @include border-image(linear-gradient(45deg, orange, yellow));\n// @include border-image(linear-gradient(45deg, orange, yellow) stretch);\n// @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round);\n// @include border-image(radial-gradient(top, cover, orange, yellow, orange));\n","//************************************************************************//\n// Shorthand Border-radius mixins\n//************************************************************************//\n@mixin border-top-radius($radii) {\n @include prefixer(border-top-left-radius, $radii, spec);\n @include prefixer(border-top-right-radius, $radii, spec);\n}\n\n@mixin border-bottom-radius($radii) {\n @include prefixer(border-bottom-left-radius, $radii, spec);\n @include prefixer(border-bottom-right-radius, $radii, spec);\n}\n\n@mixin border-left-radius($radii) {\n @include prefixer(border-top-left-radius, $radii, spec);\n @include prefixer(border-bottom-left-radius, $radii, spec);\n}\n\n@mixin border-right-radius($radii) {\n @include prefixer(border-top-right-radius, $radii, spec);\n @include prefixer(border-bottom-right-radius, $radii, spec);\n}\n","@mixin box-sizing ($box) {\n// content-box | border-box | inherit\n @include prefixer(box-sizing, $box, webkit moz spec);\n}\n","@mixin calc($property, $value) {\n #{$property}: -webkit-calc(#{$value});\n #{$property}: calc(#{$value});\n}\n","@mixin columns($arg: auto) {\n// || \n @include prefixer(columns, $arg, webkit moz spec);\n}\n\n@mixin column-count($int: auto) {\n// auto || integer\n @include prefixer(column-count, $int, webkit moz spec);\n}\n\n@mixin column-gap($length: normal) {\n// normal || length\n @include prefixer(column-gap, $length, webkit moz spec);\n}\n\n@mixin column-fill($arg: auto) {\n// auto || length\n @include prefixer(column-fill, $arg, webkit moz spec);\n}\n\n@mixin column-rule($arg) {\n// || || \n @include prefixer(column-rule, $arg, webkit moz spec);\n}\n\n@mixin column-rule-color($color) {\n @include prefixer(column-rule-color, $color, webkit moz spec);\n}\n\n@mixin column-rule-style($style: none) {\n// none | hidden | dashed | dotted | double | groove | inset | inset | outset | ridge | solid\n @include prefixer(column-rule-style, $style, webkit moz spec);\n}\n\n@mixin column-rule-width ($width: none) {\n @include prefixer(column-rule-width, $width, webkit moz spec);\n}\n\n@mixin column-span($arg: none) {\n// none || all\n @include prefixer(column-span, $arg, webkit moz spec);\n}\n\n@mixin column-width($length: auto) {\n// auto || length\n @include prefixer(column-width, $length, webkit moz spec);\n}\n","@mixin filter($function: none) {\n // [\n @include prefixer(perspective, $depth, webkit moz spec);\n}\n\n@mixin perspective-origin($value: 50% 50%) {\n @include prefixer(perspective-origin, $value, webkit moz spec);\n}\n","// Requires Sass 3.1+\n@mixin radial-gradient($G1, $G2,\n $G3: null, $G4: null,\n $G5: null, $G6: null,\n $G7: null, $G8: null,\n $G9: null, $G10: null,\n $pos: null,\n $shape-size: null,\n $fallback: null) {\n\n $data: _radial-arg-parser($G1, $G2, $pos, $shape-size);\n $G1: nth($data, 1);\n $G2: nth($data, 2);\n $pos: nth($data, 3);\n $shape-size: nth($data, 4);\n\n $full: $G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10;\n\n // Strip deprecated cover/contain for spec\n $shape-size-spec: _shape-size-stripper($shape-size);\n\n // Set $G1 as the default fallback color\n $first-color: nth($full, 1);\n $fallback-color: nth($first-color, 1);\n\n @if (type-of($fallback) == color) or ($fallback == \"transparent\") {\n $fallback-color: $fallback;\n }\n\n // Add Commas and spaces\n $shape-size: if($shape-size, '#{$shape-size}, ', null);\n $pos: if($pos, '#{$pos}, ', null);\n $pos-spec: if($pos, 'at #{$pos}', null);\n $shape-size-spec: if(($shape-size-spec != ' ') and ($pos == null), '#{$shape-size-spec}, ', '#{$shape-size-spec} ');\n\n background-color: $fallback-color;\n background-image: -webkit-radial-gradient(unquote(#{$pos}#{$shape-size}#{$full}));\n background-image: unquote(\"radial-gradient(#{$shape-size-spec}#{$pos-spec}#{$full})\");\n}\n","@mixin transform($property: none) {\n// none | \n @include prefixer(transform, $property, webkit moz ms o spec);\n}\n\n@mixin transform-origin($axes: 50%) {\n// x-axis - left | center | right | length | %\n// y-axis - top | center | bottom | length | %\n// z-axis - length\n @include prefixer(transform-origin, $axes, webkit moz ms o spec);\n}\n\n@mixin transform-style ($style: flat) {\n @include prefixer(transform-style, $style, webkit moz ms o spec);\n}\n","// Shorthand mixin. Supports multiple parentheses-deliminated values for each variable.\n// Example: @include transition (all 2s ease-in-out);\n// @include transition (opacity 1s ease-in 2s, width 2s ease-out);\n// @include transition-property (transform, opacity);\n\n@mixin transition ($properties...) {\n // Fix for vendor-prefix transform property\n $needs-prefixes: false;\n $webkit: ();\n $moz: ();\n $spec: ();\n\n // Create lists for vendor-prefixed transform\n @each $list in $properties {\n @if nth($list, 1) == \"transform\" {\n $needs-prefixes: true;\n $list1: -webkit-transform;\n $list2: -moz-transform;\n $list3: ();\n\n @each $var in $list {\n $list3: join($list3, $var);\n\n @if $var != \"transform\" {\n $list1: join($list1, $var);\n $list2: join($list2, $var);\n }\n }\n\n $webkit: append($webkit, $list1);\n $moz: append($moz, $list2);\n $spec: append($spec, $list3);\n }\n\n // Create lists for non-prefixed transition properties\n @else {\n $webkit: append($webkit, $list, comma);\n $moz: append($moz, $list, comma);\n $spec: append($spec, $list, comma);\n }\n }\n\n @if $needs-prefixes {\n -webkit-transition: $webkit;\n -moz-transition: $moz;\n transition: $spec;\n }\n @else {\n @if length($properties) >= 1 {\n @include prefixer(transition, $properties, webkit moz spec);\n }\n\n @else {\n $properties: all 0.15s ease-out 0s;\n @include prefixer(transition, $properties, webkit moz spec);\n }\n }\n}\n\n@mixin transition-property ($properties...) {\n -webkit-transition-property: transition-property-names($properties, 'webkit');\n -moz-transition-property: transition-property-names($properties, 'moz');\n transition-property: transition-property-names($properties, false);\n}\n\n@mixin transition-duration ($times...) {\n @include prefixer(transition-duration, $times, webkit moz spec);\n}\n\n@mixin transition-timing-function ($motions...) {\n// ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier()\n @include prefixer(transition-timing-function, $motions, webkit moz spec);\n}\n\n@mixin transition-delay ($times...) {\n @include prefixer(transition-delay, $times, webkit moz spec);\n}\n","@mixin user-select($arg: none) {\n @include prefixer(user-select, $arg, webkit moz ms spec);\n}\n","@mixin placeholder {\n $placeholders: \":-webkit-input\" \":-moz\" \"-moz\" \"-ms-input\";\n @each $placeholder in $placeholders {\n &:#{$placeholder}-placeholder {\n @content;\n }\n }\n}\n","@mixin button ($style: simple, $base-color: #4294f0, $text-size: inherit, $padding: 7px 18px) {\n\n @if type-of($style) == string and type-of($base-color) == color {\n @include buttonstyle($style, $base-color, $text-size, $padding);\n }\n\n @if type-of($style) == string and type-of($base-color) == number {\n $padding: $text-size;\n $text-size: $base-color;\n $base-color: #4294f0;\n\n @if $padding == inherit {\n $padding: 7px 18px;\n }\n\n @include buttonstyle($style, $base-color, $text-size, $padding);\n }\n\n @if type-of($style) == color and type-of($base-color) == color {\n $base-color: $style;\n $style: simple;\n @include buttonstyle($style, $base-color, $text-size, $padding);\n }\n\n @if type-of($style) == color and type-of($base-color) == number {\n $padding: $text-size;\n $text-size: $base-color;\n $base-color: $style;\n $style: simple;\n\n @if $padding == inherit {\n $padding: 7px 18px;\n }\n\n @include buttonstyle($style, $base-color, $text-size, $padding);\n }\n\n @if type-of($style) == number {\n $padding: $base-color;\n $text-size: $style;\n $base-color: #4294f0;\n $style: simple;\n\n @if $padding == #4294f0 {\n $padding: 7px 18px;\n }\n\n @include buttonstyle($style, $base-color, $text-size, $padding);\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n}\n\n\n// Selector Style Button\n//************************************************************************//\n@mixin buttonstyle($type, $b-color, $t-size, $pad) {\n // Grayscale button\n @if $type == simple and $b-color == grayscale($b-color) {\n @include simple($b-color, true, $t-size, $pad);\n }\n\n @if $type == shiny and $b-color == grayscale($b-color) {\n @include shiny($b-color, true, $t-size, $pad);\n }\n\n @if $type == pill and $b-color == grayscale($b-color) {\n @include pill($b-color, true, $t-size, $pad);\n }\n\n @if $type == flat and $b-color == grayscale($b-color) {\n @include flat($b-color, true, $t-size, $pad);\n }\n\n // Colored button\n @if $type == simple {\n @include simple($b-color, false, $t-size, $pad);\n }\n\n @else if $type == shiny {\n @include shiny($b-color, false, $t-size, $pad);\n }\n\n @else if $type == pill {\n @include pill($b-color, false, $t-size, $pad);\n }\n\n @else if $type == flat {\n @include flat($b-color, false, $t-size, $pad);\n }\n}\n\n\n// Simple Button\n//************************************************************************//\n@mixin simple($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {\n $color: hsl(0, 0, 100%);\n $border: adjust-color($base-color, $saturation: 9%, $lightness: -14%);\n $inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%);\n $stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%);\n $text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%);\n\n @if is-light($base-color) {\n $color: hsl(0, 0, 20%);\n $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);\n }\n\n @if $grayscale == true {\n $border: grayscale($border);\n $inset-shadow: grayscale($inset-shadow);\n $stop-gradient: grayscale($stop-gradient);\n $text-shadow: grayscale($text-shadow);\n }\n\n border: 1px solid $border;\n border-radius: 3px;\n box-shadow: inset 0 1px 0 0 $inset-shadow;\n color: $color;\n display: inline-block;\n font-size: $textsize;\n font-weight: bold;\n @include linear-gradient ($base-color, $stop-gradient);\n padding: $padding;\n text-decoration: none;\n text-shadow: 0 1px 0 $text-shadow;\n background-clip: padding-box;\n\n &:hover:not(:disabled) {\n $base-color-hover: adjust-color($base-color, $saturation: -4%, $lightness: -5%);\n $inset-shadow-hover: adjust-color($base-color, $saturation: -7%, $lightness: 5%);\n $stop-gradient-hover: adjust-color($base-color, $saturation: 8%, $lightness: -14%);\n\n @if $grayscale == true {\n $base-color-hover: grayscale($base-color-hover);\n $inset-shadow-hover: grayscale($inset-shadow-hover);\n $stop-gradient-hover: grayscale($stop-gradient-hover);\n }\n\n box-shadow: inset 0 1px 0 0 $inset-shadow-hover;\n cursor: pointer;\n @include linear-gradient ($base-color-hover, $stop-gradient-hover);\n }\n\n &:active:not(:disabled),\n &:focus:not(:disabled) {\n $border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%);\n $inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%);\n\n @if $grayscale == true {\n $border-active: grayscale($border-active);\n $inset-shadow-active: grayscale($inset-shadow-active);\n }\n\n border: 1px solid $border-active;\n box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active;\n }\n}\n\n\n// Shiny Button\n//************************************************************************//\n@mixin shiny($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {\n $color: hsl(0, 0, 100%);\n $border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81);\n $border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122);\n $fourth-stop: adjust-color($base-color, $red: -79, $green: -70, $blue: -46);\n $inset-shadow: adjust-color($base-color, $red: 37, $green: 29, $blue: 12);\n $second-stop: adjust-color($base-color, $red: -56, $green: -50, $blue: -33);\n $text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114);\n $third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48);\n\n @if is-light($base-color) {\n $color: hsl(0, 0, 20%);\n $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);\n }\n\n @if $grayscale == true {\n $border: grayscale($border);\n $border-bottom: grayscale($border-bottom);\n $fourth-stop: grayscale($fourth-stop);\n $inset-shadow: grayscale($inset-shadow);\n $second-stop: grayscale($second-stop);\n $text-shadow: grayscale($text-shadow);\n $third-stop: grayscale($third-stop);\n }\n\n border: 1px solid $border;\n border-bottom: 1px solid $border-bottom;\n border-radius: 5px;\n box-shadow: inset 0 1px 0 0 $inset-shadow;\n color: $color;\n display: inline-block;\n font-size: $textsize;\n font-weight: bold;\n @include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%);\n padding: $padding;\n text-align: center;\n text-decoration: none;\n text-shadow: 0 -1px 1px $text-shadow;\n\n &:hover:not(:disabled) {\n $first-stop-hover: adjust-color($base-color, $red: -13, $green: -15, $blue: -18);\n $second-stop-hover: adjust-color($base-color, $red: -66, $green: -62, $blue: -51);\n $third-stop-hover: adjust-color($base-color, $red: -93, $green: -85, $blue: -66);\n $fourth-stop-hover: adjust-color($base-color, $red: -86, $green: -80, $blue: -63);\n\n @if $grayscale == true {\n $first-stop-hover: grayscale($first-stop-hover);\n $second-stop-hover: grayscale($second-stop-hover);\n $third-stop-hover: grayscale($third-stop-hover);\n $fourth-stop-hover: grayscale($fourth-stop-hover);\n }\n\n cursor: pointer;\n @include linear-gradient(top, $first-stop-hover 0%,\n $second-stop-hover 50%,\n $third-stop-hover 50%,\n $fourth-stop-hover 100%);\n }\n\n &:active:not(:disabled),\n &:focus:not(:disabled) {\n $inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122);\n\n @if $grayscale == true {\n $inset-shadow-active: grayscale($inset-shadow-active);\n }\n\n box-shadow: inset 0 0 20px 0 $inset-shadow-active;\n }\n}\n\n\n// Pill Button\n//************************************************************************//\n@mixin pill($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {\n $color: hsl(0, 0, 100%);\n $border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%);\n $border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%);\n $border-top: adjust-color($base-color, $hue: -1, $saturation: -30%, $lightness: -15%);\n $inset-shadow: adjust-color($base-color, $hue: -1, $saturation: -1%, $lightness: 7%);\n $stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%);\n $text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%);\n\n @if is-light($base-color) {\n $color: hsl(0, 0, 20%);\n $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);\n }\n\n @if $grayscale == true {\n $border-bottom: grayscale($border-bottom);\n $border-sides: grayscale($border-sides);\n $border-top: grayscale($border-top);\n $inset-shadow: grayscale($inset-shadow);\n $stop-gradient: grayscale($stop-gradient);\n $text-shadow: grayscale($text-shadow);\n }\n\n border: 1px solid $border-top;\n border-color: $border-top $border-sides $border-bottom;\n border-radius: 16px;\n box-shadow: inset 0 1px 0 0 $inset-shadow;\n color: $color;\n display: inline-block;\n font-size: $textsize;\n font-weight: normal;\n line-height: 1;\n @include linear-gradient ($base-color, $stop-gradient);\n padding: $padding;\n text-align: center;\n text-decoration: none;\n text-shadow: 0 -1px 1px $text-shadow;\n background-clip: padding-box;\n\n &:hover:not(:disabled) {\n $base-color-hover: adjust-color($base-color, $lightness: -4.5%);\n $border-bottom: adjust-color($base-color, $hue: 8, $saturation: 13.5%, $lightness: -32%);\n $border-sides: adjust-color($base-color, $hue: 4, $saturation: -2%, $lightness: -27%);\n $border-top: adjust-color($base-color, $hue: -1, $saturation: -17%, $lightness: -21%);\n $inset-shadow-hover: adjust-color($base-color, $saturation: -1%, $lightness: 3%);\n $stop-gradient-hover: adjust-color($base-color, $hue: 8, $saturation: -4%, $lightness: -15.5%);\n $text-shadow-hover: adjust-color($base-color, $hue: 5, $saturation: -5%, $lightness: -22%);\n\n @if $grayscale == true {\n $base-color-hover: grayscale($base-color-hover);\n $border-bottom: grayscale($border-bottom);\n $border-sides: grayscale($border-sides);\n $border-top: grayscale($border-top);\n $inset-shadow-hover: grayscale($inset-shadow-hover);\n $stop-gradient-hover: grayscale($stop-gradient-hover);\n $text-shadow-hover: grayscale($text-shadow-hover);\n }\n\n border: 1px solid $border-top;\n border-color: $border-top $border-sides $border-bottom;\n box-shadow: inset 0 1px 0 0 $inset-shadow-hover;\n cursor: pointer;\n @include linear-gradient ($base-color-hover, $stop-gradient-hover);\n text-shadow: 0 -1px 1px $text-shadow-hover;\n background-clip: padding-box;\n }\n\n &:active:not(:disabled),\n &:focus:not(:disabled) {\n $active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%);\n $border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%);\n $border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%);\n $inset-shadow-active: adjust-color($base-color, $hue: 9, $saturation: 2%, $lightness: -21.5%);\n $text-shadow-active: adjust-color($base-color, $hue: 5, $saturation: -12%, $lightness: -21.5%);\n\n @if $grayscale == true {\n $active-color: grayscale($active-color);\n $border-active: grayscale($border-active);\n $border-bottom-active: grayscale($border-bottom-active);\n $inset-shadow-active: grayscale($inset-shadow-active);\n $text-shadow-active: grayscale($text-shadow-active);\n }\n\n background: $active-color;\n border: 1px solid $border-active;\n border-bottom: 1px solid $border-bottom-active;\n box-shadow: inset 0 0 6px 3px $inset-shadow-active;\n text-shadow: 0 -1px 1px $text-shadow-active;\n }\n}\n\n\n\n// Flat Button\n//************************************************************************//\n@mixin flat($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {\n $color: hsl(0, 0, 100%);\n\n @if is-light($base-color) {\n $color: hsl(0, 0, 20%);\n }\n\n background-color: $base-color;\n border-radius: 3px;\n border: none;\n color: $color;\n display: inline-block;\n font-size: inherit;\n font-weight: bold;\n padding: 7px 18px;\n text-decoration: none;\n background-clip: padding-box;\n\n &:hover:not(:disabled){\n $base-color-hover: adjust-color($base-color, $saturation: 4%, $lightness: 5%);\n\n @if $grayscale == true {\n $base-color-hover: grayscale($base-color-hover);\n }\n\n background-color: $base-color-hover;\n cursor: pointer;\n }\n\n &:active:not(:disabled),\n &:focus:not(:disabled) {\n $base-color-active: adjust-color($base-color, $saturation: -4%, $lightness: -5%);\n\n @if $grayscale == true {\n $base-color-active: grayscale($base-color-active);\n }\n\n background-color: $base-color-active;\n cursor: pointer;\n }\n}\n","// Modern micro clearfix provides an easy way to contain floats without adding additional markup.\n//\n// Example usage:\n//\n// // Contain all floats within .wrapper\n// .wrapper {\n// @include clearfix;\n// .content,\n// .sidebar {\n// float : left;\n// }\n// }\n\n@mixin clearfix {\n &:after {\n content:\"\";\n display:table;\n clear:both;\n }\n}\n\n// Acknowledgements\n// Beat *that* clearfix: [Thierry Koblentz](http://www.css-101.org/articles/clearfix/latest-new-clearfix-so-far.php)\n","// directional-property mixins are shorthands\n// for writing properties like the following\n//\n// @include margin(null 0 10px);\n// ------\n// margin-right: 0;\n// margin-bottom: 10px;\n// margin-left: 0;\n//\n// - or -\n//\n// @include border-style(dotted null);\n// ------\n// border-top-style: dotted;\n// border-bottom-style: dotted;\n//\n// ------\n//\n// Note: You can also use false instead of null\n\n@function collapse-directionals($vals) {\n $output: null;\n\n $A: nth( $vals, 1 );\n $B: if( length($vals) < 2, $A, nth($vals, 2));\n $C: if( length($vals) < 3, $A, nth($vals, 3));\n $D: if( length($vals) < 2, $A, nth($vals, if( length($vals) < 4, 2, 4) ));\n\n @if $A == 0 { $A: 0 }\n @if $B == 0 { $B: 0 }\n @if $C == 0 { $C: 0 }\n @if $D == 0 { $D: 0 }\n\n @if $A == $B and $A == $C and $A == $D { $output: $A }\n @else if $A == $C and $B == $D { $output: $A $B }\n @else if $B == $D { $output: $A $B $C }\n @else { $output: $A $B $C $D }\n\n @return $output;\n}\n\n@function contains-falsy($list) {\n @each $item in $list {\n @if not $item {\n @return true;\n }\n }\n\n @return false;\n}\n\n@mixin directional-property($pre, $suf, $vals) {\n // Property Names\n $top: $pre + \"-top\" + if($suf, \"-#{$suf}\", \"\");\n $bottom: $pre + \"-bottom\" + if($suf, \"-#{$suf}\", \"\");\n $left: $pre + \"-left\" + if($suf, \"-#{$suf}\", \"\");\n $right: $pre + \"-right\" + if($suf, \"-#{$suf}\", \"\");\n $all: $pre + if($suf, \"-#{$suf}\", \"\");\n\n $vals: collapse-directionals($vals);\n\n @if contains-falsy($vals) {\n @if nth($vals, 1) { #{$top}: nth($vals, 1); }\n\n @if length($vals) == 1 {\n @if nth($vals, 1) { #{$right}: nth($vals, 1); }\n } @else {\n @if nth($vals, 2) { #{$right}: nth($vals, 2); }\n }\n\n // prop: top/bottom right/left\n @if length($vals) == 2 {\n @if nth($vals, 1) { #{$bottom}: nth($vals, 1); }\n @if nth($vals, 2) { #{$left}: nth($vals, 2); }\n\n // prop: top right/left bottom\n } @else if length($vals) == 3 {\n @if nth($vals, 3) { #{$bottom}: nth($vals, 3); }\n @if nth($vals, 2) { #{$left}: nth($vals, 2); }\n\n // prop: top right bottom left\n } @else if length($vals) == 4 {\n @if nth($vals, 3) { #{$bottom}: nth($vals, 3); }\n @if nth($vals, 4) { #{$left}: nth($vals, 4); }\n }\n\n // prop: top/right/bottom/left\n } @else {\n #{$all}: $vals;\n }\n}\n\n@mixin margin($vals...) {\n @include directional-property(margin, false, $vals...);\n}\n\n@mixin padding($vals...) {\n @include directional-property(padding, false, $vals...);\n}\n\n@mixin border-style($vals...) {\n @include directional-property(border, style, $vals...);\n}\n\n@mixin border-color($vals...) {\n @include directional-property(border, color, $vals...);\n}\n\n@mixin border-width($vals...) {\n @include directional-property(border, width, $vals...);\n}\n","@mixin ellipsis($width: 100%) {\n display: inline-block;\n max-width: $width;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n","$georgia: Georgia, Cambria, \"Times New Roman\", Times, serif;\n$helvetica: \"Helvetica Neue\", Helvetica, Roboto, Arial, sans-serif;\n$lucida-grande: \"Lucida Grande\", Tahoma, Verdana, Arial, sans-serif;\n$monospace: \"Bitstream Vera Sans Mono\", Consolas, Courier, monospace;\n$verdana: Verdana, Geneva, sans-serif;\n","@mixin hide-text {\n overflow: hidden;\n\n &:before {\n content: \"\";\n display: block;\n width: 0;\n height: 100%;\n }\n}\n","//************************************************************************//\n// Generate a variable ($all-text-inputs) with a list of all html5\n// input types that have a text-based input, excluding textarea.\n// http://diveintohtml5.org/forms.html\n//************************************************************************//\n$inputs-list: 'input[type=\"email\"]',\n 'input[type=\"number\"]',\n 'input[type=\"password\"]',\n 'input[type=\"search\"]',\n 'input[type=\"tel\"]',\n 'input[type=\"text\"]',\n 'input[type=\"url\"]',\n\n // Webkit & Gecko may change the display of these in the future\n 'input[type=\"color\"]',\n 'input[type=\"date\"]',\n 'input[type=\"datetime\"]',\n 'input[type=\"datetime-local\"]',\n 'input[type=\"month\"]',\n 'input[type=\"time\"]',\n 'input[type=\"week\"]';\n\n// Bare inputs\n//************************************************************************//\n$all-text-inputs: assign-inputs($inputs-list);\n\n// Hover Pseudo-class\n//************************************************************************//\n$all-text-inputs-hover: assign-inputs($inputs-list, hover);\n\n// Focus Pseudo-class\n//************************************************************************//\n$all-text-inputs-focus: assign-inputs($inputs-list, focus);\n\n\n\n// You must use interpolation on the variable:\n// #{$all-text-inputs}\n// #{$all-text-inputs-hover}\n// #{$all-text-inputs-focus}\n\n// Example\n//************************************************************************//\n// #{$all-text-inputs}, textarea {\n// border: 1px solid red;\n// }\n\n\n\n//************************************************************************//\n// Generate a variable ($all-button-inputs) with a list of all html5\n// input types that have a button-based input, excluding button.\n//************************************************************************//\n$inputs-button-list: 'input[type=\"button\"]',\n 'input[type=\"reset\"]',\n 'input[type=\"submit\"]';\n\n// Bare inputs\n//************************************************************************//\n$all-button-inputs: assign-inputs($inputs-button-list);\n\n// Hover Pseudo-class\n//************************************************************************//\n$all-button-inputs-hover: assign-inputs($inputs-button-list, hover);\n\n// Focus Pseudo-class\n//************************************************************************//\n$all-button-inputs-focus: assign-inputs($inputs-button-list, focus);\n\n// Active Pseudo-class\n//************************************************************************//\n$all-button-inputs-active: assign-inputs($inputs-button-list, active);\n\n\n\n// You must use interpolation on the variable:\n// #{$all-button-inputs}\n// #{$all-button-inputs-hover}\n// #{$all-button-inputs-focus}\n// #{$all-button-inputs-active}\n\n// Example\n//************************************************************************//\n// #{$all-button-inputs}, button {\n// border: 1px solid red;\n// }\n","@mixin position ($position: relative, $coordinates: null null null null) {\n\n @if type-of($position) == list {\n $coordinates: $position;\n $position: relative;\n }\n\n $coordinates: unpack($coordinates);\n\n $top: nth($coordinates, 1);\n $right: nth($coordinates, 2);\n $bottom: nth($coordinates, 3);\n $left: nth($coordinates, 4);\n\n position: $position;\n\n @if ($top and $top == auto) or (type-of($top) == number) {\n top: $top;\n }\n\n @if ($right and $right == auto) or (type-of($right) == number) {\n right: $right;\n }\n\n @if ($bottom and $bottom == auto) or (type-of($bottom) == number) {\n bottom: $bottom;\n }\n\n @if ($left and $left == auto) or (type-of($left) == number) {\n left: $left;\n }\n}\n","//************************************************************************//\n// Example: @include prefixer(border-radius, $radii, webkit ms spec);\n//************************************************************************//\n// Variables located in /settings/_prefixer.scss\n\n@mixin prefixer ($property, $value, $prefixes) {\n @each $prefix in $prefixes {\n @if $prefix == webkit {\n @if $prefix-for-webkit {\n -webkit-#{$property}: $value;\n }\n }\n @else if $prefix == moz {\n @if $prefix-for-mozilla {\n -moz-#{$property}: $value;\n }\n }\n @else if $prefix == ms {\n @if $prefix-for-microsoft {\n -ms-#{$property}: $value;\n }\n }\n @else if $prefix == o {\n @if $prefix-for-opera {\n -o-#{$property}: $value;\n }\n }\n @else if $prefix == spec {\n @if $prefix-for-spec {\n #{$property}: $value;\n }\n }\n @else {\n @warn \"Unrecognized prefix: #{$prefix}\";\n }\n }\n}\n\n@mixin disable-prefix-for-all() {\n $prefix-for-webkit: false !global;\n $prefix-for-mozilla: false !global;\n $prefix-for-microsoft: false !global;\n $prefix-for-opera: false !global;\n $prefix-for-spec: false !global;\n}\n","@mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $retina-suffix: _2x, $asset-pipeline: false) {\n @if $asset-pipeline {\n background-image: image-url(\"#{$filename}.#{$extension}\");\n }\n @else {\n background-image: url(\"#{$filename}.#{$extension}\");\n }\n\n @include hidpi {\n @if $asset-pipeline {\n @if $retina-filename {\n background-image: image-url(\"#{$retina-filename}.#{$extension}\");\n }\n @else {\n background-image: image-url(\"#{$filename}#{$retina-suffix}.#{$extension}\");\n }\n }\n\n @else {\n @if $retina-filename {\n background-image: url(\"#{$retina-filename}.#{$extension}\");\n }\n @else {\n background-image: url(\"#{$filename}#{$retina-suffix}.#{$extension}\");\n }\n }\n\n background-size: $background-size;\n\n }\n}\n","@mixin size($size) {\n $height: nth($size, 1);\n $width: $height;\n\n @if length($size) > 1 {\n $height: nth($size, 2);\n }\n\n @if $height == auto or (type-of($height) == number and not unitless($height)) {\n height: $height;\n }\n\n @if $width == auto or (type-of($height) == number and not unitless($width)) {\n width: $width;\n }\n}\n","// CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie)\n// Timing functions are the same as demo'ed here: http://jqueryui.com/demos/effect/easing.html\n\n// EASE IN\n$ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530);\n$ease-in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190);\n$ease-in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220);\n$ease-in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060);\n$ease-in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715);\n$ease-in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035);\n$ease-in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335);\n$ease-in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045);\n\n// EASE OUT\n$ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940);\n$ease-out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000);\n$ease-out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000);\n$ease-out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000);\n$ease-out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000);\n$ease-out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000);\n$ease-out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000);\n$ease-out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275);\n\n// EASE IN OUT\n$ease-in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955);\n$ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000);\n$ease-in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000);\n$ease-in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000);\n$ease-in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950);\n$ease-in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000);\n$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860);\n$ease-in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550);\n","@mixin triangle ($size, $color, $direction) {\n height: 0;\n width: 0;\n\n $width: nth($size, 1);\n $height: nth($size, length($size));\n\n $foreground-color: nth($color, 1);\n $background-color: if(length($color) == 2, nth($color, 2), transparent);\n\n @if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) {\n\n $width: $width / 2;\n $height: if(length($size) > 1, $height, $height/2);\n\n @if $direction == up {\n border-left: $width solid $background-color;\n border-right: $width solid $background-color;\n border-bottom: $height solid $foreground-color;\n\n } @else if $direction == right {\n border-top: $width solid $background-color;\n border-bottom: $width solid $background-color;\n border-left: $height solid $foreground-color;\n\n } @else if $direction == down {\n border-left: $width solid $background-color;\n border-right: $width solid $background-color;\n border-top: $height solid $foreground-color;\n\n } @else if $direction == left {\n border-top: $width solid $background-color;\n border-bottom: $width solid $background-color;\n border-right: $height solid $foreground-color;\n }\n }\n\n @else if ($direction == up-right) or ($direction == up-left) {\n border-top: $height solid $foreground-color;\n\n @if $direction == up-right {\n border-left: $width solid $background-color;\n\n } @else if $direction == up-left {\n border-right: $width solid $background-color;\n }\n }\n\n @else if ($direction == down-right) or ($direction == down-left) {\n border-bottom: $height solid $foreground-color;\n\n @if $direction == down-right {\n border-left: $width solid $background-color;\n\n } @else if $direction == down-left {\n border-right: $width solid $background-color;\n }\n }\n\n @else if ($direction == inset-up) {\n border-width: $height $width;\n border-style: solid;\n border-color: $background-color $background-color $foreground-color;\n }\n\n @else if ($direction == inset-down) {\n border-width: $height $width;\n border-style: solid;\n border-color: $foreground-color $background-color $background-color;\n }\n\n @else if ($direction == inset-right) {\n border-width: $width $height;\n border-style: solid;\n border-color: $background-color $background-color $background-color $foreground-color;\n }\n\n @else if ($direction == inset-left) {\n border-width: $width $height;\n border-style: solid;\n border-color: $background-color $foreground-color $background-color $background-color;\n }\n}\n","@mixin word-wrap($wrap: break-word) {\n word-wrap: $wrap;\n\n @if $wrap == break-word {\n overflow-wrap: break-word;\n word-break: break-all;\n }\n}\n","//************************************************************************//\n// These mixins/functions are deprecated\n// They will be removed in the next MAJOR version release\n//************************************************************************//\n@mixin inline-block {\n display: inline-block;\n @warn \"inline-block mixin is deprecated and will be removed in the next major version release\";\n}\n","// Core\n@import \"core\";\n\n// Breakpoints\n@import \"breakpoints\";\n\n// Layout\n@import \"layout\";\n\n// Typography\n@import \"typography\";\n\n// Nav\n@import \"nav\";","// Border Radius\n$core-border-radius:\t\t\trem(3) !default;","// Media Device Breakpoints\n$large-desktop-container:\t\t75.000em !default;\n$desktop-container:\t\t\t\t60.000em !default;\n$tablet-container:\t\t\t\t48.000em !default;\n$large-mobile-container:\t\t30.000em !default;\n$mobile-container:\t\t\t\t100% !default;\n\n// Breakpoint Variables For Particles\n$media: \"all\" !default;\n$mobile-only: \"#{$media} and (max-width:#{$tablet-container - 0.062})\" !default;\n$no-mobile: \"#{$media} and (min-width:#{$tablet-container})\" !default;\n$small-mobile-range: \"#{$media} and (max-width:#{$large-mobile-container})\" !default;\n$large-mobile-range: \"#{$media} and (min-width:#{$large-mobile-container + 0.063}) and (max-width:#{$tablet-container - 0.062})\" !default;\n$tablet-range: \"#{$media} and (min-width:#{$tablet-container}) and (max-width:#{$desktop-container - 0.062})\" !default;\n$desktop-range: \"#{$media} and (min-width:#{$desktop-container}) and (max-width:#{$large-desktop-container - 0.062})\" !default;\n$large-desktop-range: \"#{$media} and (min-width:#{$large-desktop-container})\" !default;\n","// Content Block Spacing Variables\n$content-margin:\t\t\t\t0.625rem !default;\n$content-padding:\t\t\t\t0.938rem !default;\n\n// Fixed Block Variables\n$fixed-block-full:\t\t\t\tpercentage(1/4) !default;\n$fixed-block-desktop:\t\t\tpercentage(1/3) !default;\n$fixed-block-tablet:\t\t\tpercentage(1/2) !default;","// Base Sizes\n$core-font-size:\t\t\t\t.9rem !default;\n$core-line-height:\t\t\t\t1.5 !default;\n\n// Heading Sizes\n$h1-font-size:\t\t\t\t\t$core-font-size + 2.20 !default;\n$h2-font-size:\t\t\t\t\t$core-font-size + 1.50 !default;\n$h3-font-size:\t\t\t\t\t$core-font-size + 1.10 !default;\n$h4-font-size:\t\t\t\t\t$core-font-size + 0.75 !default;\n$h5-font-size:\t\t\t\t\t$core-font-size + 0.35 !default;\n$h6-font-size:\t\t\t\t\t$core-font-size - 0.15 !default;\n\n// Spacing\n$leading-margin:\t\t\t\t$core-line-height * 1rem !default;\n","// Dropdowns\n$dropdown-width:\t\t\t\t140px !default;\n$flyout-width:\t\t\t\t\t140px !default;","@import \"direction\";\n@import \"range\";","@function opposite-direction($dir) {\n\t@if $dir == 'left' {\n\t\t@return right;\n\t}\n\t@else if $dir == 'right' {\n\t\t@return left;\n\t}\n\t@else if $dir == 'ltr' {\n\t\t@return rtl;\n\t}\n\t@else if $dir == 'rtl' {\n\t\t@return ltr;\n\t}\n\t@else if $dir == 'top' {\n\t\t@return bottom;\n\t}\n\t@else if $dir == 'bottom' {\n\t\t@return top;\n\t}\n\t@else {\n\t\t@warn \"#{$dir} is not a direction! Make sure your direction is all lowercase!\";\n\t\t@return false;\n\t}\n}\n\n@function named-direction($dir) {\n\t@if $dir == 'ltr' {\n\t\t@return left;\n\t}\n\t@else if $dir == 'rtl' {\n\t\t@return right;\n\t}\n\t@else {\n\t\t@warn \"#{$dir} is not a valid HTML direction! Make sure you are using a valid HTML direction\";\n\t\t@return false;\n\t}\n}","@function lower-bound($range){\n\t@if length($range) <= 0 {\n\t\t@return 0;\n\t}\n\t@return nth($range,1);\n}\n\n@function upper-bound($range) {\n\t@if length($range) < 2 {\n\t\t@return 999999999999;\n\t}\n\t@return nth($range, 2);\n}","@import \"breakpoints\";\n@import \"utilities\";","// Breakpoints\n@mixin breakpoint($breakpoints, $media: all) {\n @each $breakpoint in $breakpoints {\n @if $breakpoint == large-desktop-range {\n @media only #{$media} and (min-width: $large-desktop-container) { @content; }\n }\n @else if $breakpoint == desktop-range {\n @media only #{$media} and (min-width: $desktop-container) and (max-width: $large-desktop-container - 0.062) { @content; }\n }\n @else if $breakpoint == tablet-range {\n @media only #{$media} and (min-width: $tablet-container) and (max-width: $desktop-container - 0.062) { @content; }\n }\n @else if $breakpoint == large-mobile-range {\n @media only #{$media} and (min-width: $large-mobile-container + 0.063) and (max-width: $tablet-container - 0.062) { @content; }\n }\n @else if $breakpoint == mini-nav-range {\n @media only #{$media} and (min-width: $tablet-container - 0.062) and (max-width: $large-desktop-container - 0.062) { @content; }\n }\n @else if $breakpoint == small-mobile-range {\n @media only #{$media} and (max-width: $large-mobile-container) { @content; }\n }\n @else if $breakpoint == no-mobile {\n @media only #{$media} and (min-width: $tablet-container) { @content; }\n }\n @else if $breakpoint == mobile-only {\n @media only #{$media} and (max-width: $tablet-container - 0.062) { @content; }\n }\n @else if $breakpoint == desktop-only {\n @media only #{$media} and (max-width: $desktop-container - 0.062) { @content; }\n }\n @else if $breakpoint == mobile-plus {\n @media only #{$media} and (min-width: $mobile-container - 0.062) { @content; }\n }\n @else if $breakpoint == tablet-plus {\n @media only #{$media} and (min-width: $tablet-container - 0.062) { @content; }\n }\n @else if $breakpoint == desktop-plus {\n @media only #{$media} and (min-width: $desktop-container - 0.062) { @content; }\n }\n @else if $breakpoint == large-desktop-plus {\n @media only #{$media} and (min-width: $large-desktop-container - 0.062) { @content; }\n }\n }\n}\n","\n\n// List Reset\n%list-reset {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n// Vertical Centering\n%vertical-align {\n\tposition: relative;\n\ttop: 50%;\n\t-webkit-transform: translateY(-50%);\n\t-moz-transform: translateY(-50%);\n\t-o-transform: translateY(-50%);\n\t-ms-transform: translateY(-50%);\n\ttransform: translateY(-50%);\n}\n\n// Columns\n@mixin columns($columns) {\n\twidth: percentage(1/$columns);\n}\n\n// Float with margin variable\n@mixin float($direction, $margin: 0) {\n\tfloat: $direction;\n\tmargin-#{opposite-direction($direction)}: $margin;\n}\n","*, *::before, *::after {\n\t@include box-sizing(border-box);\n}\n\n@-webkit-viewport{width:device-width}\n@-moz-viewport{width:device-width}\n@-ms-viewport{width:device-width}\n@-o-viewport{width:device-width}\n@viewport{width:device-width}\n\nhtml {\n\tfont-size: 100%;\n\t-ms-text-size-adjust: 100%;\n\t-webkit-text-size-adjust: 100%;\n}\n\nbody {\n\tmargin: 0;\n}\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nnav,\nsection,\nsummary {\n\tdisplay: block;\n}\n\naudio,\ncanvas,\nprogress,\nvideo {\n\tdisplay: inline-block;\n\tvertical-align: baseline;\n}\n\naudio:not([controls]) {\n\tdisplay: none;\n\theight: 0;\n}\n\n[hidden],\ntemplate {\n\tdisplay: none;\n}\n\na {\n\tbackground: transparent;\n\ttext-decoration: none;\n}\n\na:active,\na:hover {\n\toutline: 0;\n}\n\nabbr[title] {\n\tborder-bottom: 1px dotted;\n}\n\nb,\nstrong {\n\tfont-weight: bold;\n}\n\ndfn {\n\tfont-style: italic;\n}\n\nmark {\n\tbackground: #ff0;\n\tcolor: #000;\n}\n\nsub,\nsup {\n\tfont-size: $core-font-size - 0.250;\n\tline-height: 0;\n\tposition: relative;\n\tvertical-align: baseline;\n}\n\nsup {\n\ttop: -0.5em;\n}\n\nsub {\n\tbottom: -0.25em;\n}\n\nimg {\n\tborder: 0;\n\tmax-width: 100%;\n}\n\nsvg:not(:root) {\n\toverflow: hidden;\n}\n\nfigure {\n\tmargin: 1em 40px;\n}\n\nhr {\n\theight: 0;\n}\n\npre {\n\toverflow: auto;\n}\n\ncode,\nkbd,\npre,\nsamp {\n\tfont-size: $core-font-size;\n}\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n\tcolor: inherit;\n\tfont: inherit;\n\tmargin: 0;\n}\n\nbutton {\n\toverflow: visible;\n}\n\nbutton,\nselect {\n\ttext-transform: none;\n}\n\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n\t-webkit-appearance: button;\n\tcursor: pointer;\n}\n\nbutton[disabled],\nhtml input[disabled] {\n\tcursor: default;\n}\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n\tborder: 0;\n\tpadding: 0;\n}\n\ninput {\n\tline-height: normal;\n}\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n\tpadding: 0;\n}\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n\theight: auto;\n}\n\ninput[type=\"search\"] {\n\t-webkit-appearance: textfield;\n}\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n\t-webkit-appearance: none;\n}\n\nlegend {\n\tborder: 0;\n\tpadding: 0;\n}\n\ntextarea {\n\toverflow: auto;\n}\n\noptgroup {\n\tfont-weight: bold;\n}\n\ntable {\n\tborder-collapse: collapse;\n\tborder-spacing: 0;\n\ttable-layout: fixed;\n\twidth: 100%;\n}\n\ntr, td, th {\n\tvertical-align: middle;\n}\n\nth, td {\n\tpadding: ($leading-margin / 4) 0;\n}\n\nth {\n\ttext-align: left;\n}\n","// Page Container\n.container {\n\twidth: $large-desktop-container;\n\tmargin: 0 auto;\n\tpadding: 0;\n\t@include breakpoint(desktop-range) {\n\t\twidth: $desktop-container;\n\t}\n\t@include breakpoint(tablet-range) {\n\t\twidth: $tablet-container;\n\t}\n\t@include breakpoint(large-mobile-range) {\n\t\twidth: $large-mobile-container;\n\t}\n\t@include breakpoint(small-mobile-range) {\n\t\twidth: $mobile-container;\n\t}\n}\n\n// Grid Row and Column Setup\n.grid {\n\t@include display(flex);\n\t@include flex-flow(row);\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\t@include breakpoint(mobile-only) {\n\t\t@include flex-flow(row wrap);\n\t}\n}\n\n.block {\n\t@include flex(1);\n\t@include breakpoint(mobile-only) {\n\t\t@include flex(0 100%);\n\t}\n}\n\n// Content Block Spacing\n.content {\n\tmargin: $content-margin;\n\tpadding: $content-padding;\n}\n\nbody [class*=\"size-\"] {\n\t@include breakpoint(mobile-only) {\n\t\t@include flex(0 100%);\n\t}\n}\n\n// Custom Size Modifiers\n.size-1-2 {\n\t@include flex(0 percentage(1/2));\n}\n\n.size-1-3 {\n\t@include flex(0 percentage(1/3));\n}\n\n.size-1-4 {\n\t@include flex(0 percentage(1/4));\n}\n\n.size-1-5 {\n\t@include flex(0 percentage(1/5));\n}\n\n.size-1-6 {\n\t@include flex(0 percentage(1/6));\n}\n\n.size-1-7 {\n\t@include flex(0 percentage(1/7));\n}\n\n.size-1-8 {\n\t@include flex(0 percentage(1/8));\n}\n\n.size-1-9 {\n\t@include flex(0 percentage(1/9));\n}\n\n.size-1-10 {\n\t@include flex(0 percentage(1/10));\n}\n\n.size-1-11 {\n\t@include flex(0 percentage(1/11));\n}\n\n.size-1-12 {\n\t@include flex(0 percentage(1/12));\n}\n\n@include breakpoint(tablet-range) {\n\t.size-tablet-1-2 {\n\t\t@include flex(0 percentage(1/2));\n\t}\n\n\t.size-tablet-1-3 {\n\t\t@include flex(0 percentage(1/3));\n\t}\n\n\t.size-tablet-1-4 {\n\t\t@include flex(0 percentage(1/4));\n\t}\n\n\t.size-tablet-1-5 {\n\t\t@include flex(0 percentage(1/5));\n\t}\n\n\t.size-tablet-1-6 {\n\t\t@include flex(0 percentage(1/6));\n\t}\n\n\t.size-tablet-1-7 {\n\t\t@include flex(0 percentage(1/7));\n\t}\n\n\t.size-tablet-1-8 {\n\t\t@include flex(0 percentage(1/8));\n\t}\n\n\t.size-tablet-1-9 {\n\t\t@include flex(0 percentage(1/9));\n\t}\n\n\t.size-tablet-1-10 {\n\t\t@include flex(0 percentage(1/10));\n\t}\n\n\t.size-tablet-1-11 {\n\t\t@include flex(0 percentage(1/11));\n\t}\n\n\t.size-tablet-1-12 {\n\t\t@include flex(0 percentage(1/12));\n\t}\n}\n\n// Fix for Firefox versions 27 and below\n@include breakpoint(mobile-only) {\n\t@supports not (flex-wrap: wrap) {\n\t\t.grid {\n\t\t\tdisplay: block;\n\t\t\t@include flex-wrap(inherit);\n\t\t}\n\t\t.block {\n\t\t\tdisplay: block;\n\t\t\t@include flex(inherit);\n\t\t}\n\t}\n}\n\n// Reordering\n.first-block {\n -webkit-box-ordinal-group: 0;\n -webkit-order: -1;\n -ms-flex-order: -1;\n order: -1;\n}\n\n.last-block {\n -webkit-box-ordinal-group: 2;\n -webkit-order: 1;\n -ms-flex-order: 1;\n order: 1;\n}\n\n// Fixed Grid Style\n.fixed-blocks {\n\t@include flex-flow(row wrap);\n\t.block {\n\t\t@include flex(inherit);\n\t\twidth: $fixed-block-full;\n\t\t@include breakpoint(desktop-range) {\n\t\t\twidth: $fixed-block-desktop;\n\t\t}\n\t\t@include breakpoint(tablet-range) {\n\t\t\twidth: $fixed-block-tablet;\n\t\t}\n\t\t@include breakpoint(mobile-only) {\n\t\t\twidth: 100%;\n\t\t}\n\t}\n}\n\n// Fix for browsers that don't support flex-wrap\n@supports not (flex-wrap: wrap) {\n\t.fixed-blocks {\n\t\tdisplay: block;\n\t\t@include flex-flow(inherit);\n\t}\n}","// Body Base\nbody {\n\tfont-size: $core-font-size;\n\tline-height: $core-line-height;\n}\n\n// Headings\nh1, h2, h3, h4, h5, h6 {\n\tmargin: $leading-margin 0 $leading-margin / 2 0;\n\ttext-rendering: optimizeLegibility;\n}\n\nh1 {\n\tfont-size: $h1-font-size;\n}\n\nh2 {\n\tfont-size: $h2-font-size;\n}\n\nh3 {\n\tfont-size: $h3-font-size;\n}\n\nh4 {\n\tfont-size: $h4-font-size;\n}\n\nh5 {\n\tfont-size: $h5-font-size;\n}\n\nh6 {\n\tfont-size: $h6-font-size;\n}\n\n// Paragraph\np {\n\tmargin: $leading-margin / 2 0 $leading-margin ;\n}\n\n// Lists\nul, ol {\n\tmargin-top: $leading-margin;\n\tmargin-bottom: $leading-margin;\n\tul, ol {\n\t\tmargin-top: 0;\n\t\tmargin-bottom: 0;\n\t}\n}\n\n// Blockquote\nblockquote {\n\tmargin: $leading-margin 0;\n\tpadding-left: $leading-margin / 2;\n}\n\ncite {\n\tdisplay: block;\n\tfont-size: $core-font-size - 0.125;\n\t&:before {\n content: \"\\2014 \\0020\";\n }\n}\n\n// Inline and Code\npre {\n\tmargin: $leading-margin 0;\n \tpadding: $content-padding;\n}\n\ncode {\n\tvertical-align: bottom;\n}\n\n// Extras\nsmall {\n\tfont-size: $core-font-size - 0.125;\n}\n\nhr {\n\tborder-left: none;\n\tborder-right: none;\n\tborder-top: none;\n\tmargin: $leading-margin 0;\n}\n","fieldset {\n\tborder: 0;\n\tpadding: $content-padding;\n\tmargin: 0 0 $leading-margin 0;\n}\n\ninput,\nlabel,\nselect {\n\tdisplay: block;\n}\n\nlabel {\n\tmargin-bottom: $leading-margin / 4;\n\n\t&.required:after {\n\t\tcontent: \"*\";\n\t}\n\n\tabbr {\n\t\tdisplay: none;\n\t}\n}\n\ntextarea, #{$all-text-inputs}, select[multiple=multiple] {\n\t@include transition(border-color);\n\tborder-radius: $core-border-radius;\n\tpadding: ($leading-margin / 4) ($leading-margin / 4);\n\twidth: 100%;\n\n\t&:focus {\n\t\toutline: none;\n\t}\n}\n\ntextarea {\n\tresize: vertical;\n}\n\ninput[type=\"checkbox\"], input[type=\"radio\"] {\n\tdisplay: inline;\n\tmargin-right: $leading-margin / 4;\n}\n\ninput[type=\"file\"] {\n\twidth: 100%;\n}\n\nselect {\n\twidth: auto;\n\tmax-width: 100%;\n\tmargin-bottom: $leading-margin;\n}\n\nbutton,\ninput[type=\"submit\"] {\n\tcursor: pointer;\n\tuser-select: none;\n\tvertical-align: middle;\n\twhite-space: nowrap;\n\tborder: inherit;\n}\n"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/user/plugins/admin/themes/grav/css-compiled/preset.css b/user/plugins/admin/themes/grav/css-compiled/preset.css
new file mode 100644
index 0000000..3863ec0
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css-compiled/preset.css
@@ -0,0 +1,3 @@
+body{background:#F6F6F6;color:#6f7b8a}a{color:#0090D9}a:hover{color:#007fc0}.text-gray,.text-grey{color:#B7B9BD}.text-update{color:#77559D}blockquote{border-left:10px solid #e6e6e6;background:#fafafa}blockquote p{color:#646e7c}blockquote cite{color:#6f7b8a}code{background:#f9f2f4;color:#9c1d3d}pre{background:#f6f6f6;border:1px solid #ddd}pre code{color:#237794}hr{border-bottom:2px solid #f7f7f7}.label{background:#0090D9;color:#fff}.badge{background:#77559D;color:#fff}.light-border{border-color:#f7f7f7 !important}.subtle-text{color:#8b95a1 !important}td,thead th{border-bottom:1px solid #f7f7f7}tr td.gpm-details{background-color:#fafafa}.tab-bar{background:#e3e3e3;color:gray}.tab-bar li.active span,.tab-bar li.active a{background:#fff;color:#6f7b8a}.tab-bar span,.tab-bar a{color:gray}.tab-bar span:hover,.tab-bar a:hover{color:#9a9a9a;background:#fdfdfd}.button{color:rgba(255,255,255,0.85);border-radius:4px;background:#0090D9}.button:hover{background:#26a1df;color:#fff}.button.dropdown-toggle{border-left:1px solid #007fc0}.button.dropdown-toggle{border-left:1px solid #007fc0}.button.secondary{color:rgba(255,255,255,0.85);border-radius:4px;background:#0073ae}.button.secondary:hover{background:#2688ba;color:#fff}.button.secondary.dropdown-toggle{border-left:1px solid #006295}.button.secondary.dropdown-toggle{border-left:1px solid #006295}.button.danger{color:rgba(255,255,255,0.85);border-radius:4px;background:#F45857}.button.danger:hover{background:#cf4b4a;color:#fff}.button.danger.dropdown-toggle{border-left:1px solid #f6706f}.button-group .dropdown-menu{background-color:#0090D9;box-shadow:0 6px 12px rgba(0,0,0,0.175)}.button-group .dropdown-menu .divider{background-color:#0090D9}.button-group .dropdown-menu li>a{color:#fff}.button-group .dropdown-menu li>a:focus,.button-group .dropdown-menu li>a:hover{color:#fff}.button-group .dropdown-menu li>a:focus:hover,.button-group .dropdown-menu li>a:hover:hover{background:#26a1df}.button-group .dropdown-menu.language-switcher a.active{background-color:#0daeff}#admin-main .admin-block a.gumroad-button{background:#F45857 !important;color:#fff !important}#admin-login-wrapper{background:radial-gradient(circle at center, #535a6b 40%, #272a31 100%)}#admin-login{background:#3D424E}#admin-login.wide form label{color:#B7B9BD}#admin-login.wide form input::-webkit-input-placeholder{color:rgba(183,185,189,0.6)}#admin-login.wide form input::-moz-placeholder{color:rgba(183,185,189,0.6)}#admin-login.wide form input:-moz-placeholder{color:rgba(183,185,189,0.6)}#admin-login.wide form input:-ms-input-placeholder{color:rgba(183,185,189,0.6)}#admin-login .wrapper-spacer h3{color:rgba(255,255,255,0.4);border-bottom:3px solid rgba(255,255,255,0.1)}#admin-login .instructions,#admin-login p{color:rgba(183,185,189,0.8)}#admin-login h1{background-color:#323640}#admin-login h1.text-logo{color:#fff}#admin-login h1 svg path:first-child{fill:#2b2f37}#admin-login h1 svg path:last-child{fill:rgba(255,255,255,0.9) !important}#admin-login h5{color:#B7B9BD}#admin-login form input{background:#444957;color:#fff;border:1px solid #31353e}#admin-login form input::-webkit-input-placeholder{color:#9c9da1}#admin-login form input::-moz-placeholder{color:#9c9da1}#admin-login form input:-moz-placeholder{color:#9c9da1}#admin-login form input:-ms-input-placeholder{color:#9c9da1}#admin-sidebar{background:#3D424E}#admin-sidebar a{color:rgba(255,255,255,0.9)}#admin-sidebar a:hover{color:#fff}#admin-logo{background-color:#323640}#admin-logo .grav-logo path{fill:rgba(255,255,255,0.9) !important}#admin-logo .grav-logo:hover path{fill:#fff !important}#admin-logo a,#admin-logo i{color:#e6e6e6}#admin-logo a:hover,#admin-logo i:hover{color:#fff}#open-handle{background-color:#272a32;border-left:1px solid #3d424e}#open-handle i{color:rgba(255,255,255,0.6)}#open-handle i:hover{color:rgba(255,255,255,0.8)}#admin-nav-quick-tray{background:#393d48;border-bottom:1px solid #363b45;color:#fff}#admin-nav-quick-tray i:hover{color:#fff}.block-userinfo img{border:4px solid #e6e6e6}#offline-status{background-color:#77559D;color:#fff}#admin-user-details,.admin-user-details{border-bottom:1px solid #484e5c}#admin-user-details:hover img,.admin-user-details:hover img{box-shadow:0px 0px 0 50px #444957}#admin-user-details .admin-user-names h4,#admin-user-details .admin-user-names h5,.admin-user-details .admin-user-names h4,.admin-user-details .admin-user-names h5{color:#fff}#admin-menu li .badges .count{color:#fff;background-color:#323640}#admin-menu li .badges .updates{color:#fff;background-color:#06A599}#admin-menu li a:hover{background:#434753;color:#fff}#admin-menu li a:hover .fa{color:#fff}#admin-menu li.selected a{background:#323640;color:#fff;border-left:9px solid #007ab8}#admin-menu li.selected a .fa{color:#fff}#admin-main .grav-update{background:#77559D;color:#fff}#admin-main .grav-update a.button,#admin-main .grav-update span.button,#admin-main .grav-update button.button{color:rgba(255,255,255,0.85);border-radius:4px;background:#5f447e}#admin-main .grav-update a.button:hover,#admin-main .grav-update span.button:hover,#admin-main .grav-update button.button:hover{background:#513a6b;color:#fff}#admin-main .grav-update a.button.dropdown-toggle,#admin-main .grav-update span.button.dropdown-toggle,#admin-main .grav-update button.button.dropdown-toggle{border-left:1px solid #6b4d8f}#admin-main .titlebar{background:#fff;color:#3D424E}#admin-main .titlebar .button-bar .button{color:rgba(255,255,255,0.85);border-radius:4px;background:#0090D9}#admin-main .titlebar .button-bar .button:hover{background:#26a1df;color:#fff}#admin-main .titlebar .button-bar .button.dropdown-toggle{border-left:1px solid #007fc0}#admin-main .lines-button{background:rgba(0,0,0,0.1)}#admin-main .lines-button .lines,#admin-main .lines-button .lines:before,#admin-main .lines-button .lines:after{background:#3D424E}#admin-main .admin-block{background:#fff;color:#6f7b8a}#admin-main .admin-block a:not(.button){color:#0090D9}#admin-main .admin-block a:not(.button):hover{color:#007fc0}#admin-main .admin-block h1{color:#414147;border-bottom:1px solid #f7f7f7}#admin-main .danger.button-bar,#admin-main .success.button-bar{background:#f7f7f7}#admin-main .danger .button{color:rgba(255,255,255,0.85);border-radius:4px;background:#F45857}#admin-main .danger .button:hover{background:#cf4b4a;color:#fff}#admin-main .danger .button.dropdown-toggle{border-left:1px solid #f6706f}#admin-main .content-padding div[class*=notifications-container] .alert.note{background:#fff;color:#6f7b8a}#admin-main .content-padding div[class*=notifications-container] .alert.note.alert{border-bottom:2px solid #f2f2f2}#admin-main .content-padding div[class*=notifications-container] .alert.note a:not(.button){color:#0090D9}#admin-main .content-padding div[class*=notifications-container] .alert.note a:not(.button):hover{color:#007fc0}#admin-main .content-padding div[class*=notifications-container] .alert.warning{background:#F45857;color:#fff}#admin-main .content-padding div[class*=notifications-container] .alert.warning.alert{border-bottom:2px solid #f2403f}#admin-main .content-padding div[class*=notifications-container] .alert.warning a{color:#fff}#admin-main .content-padding div[class*=notifications-container] .alert.warning a:hover{color:#fff}#admin-main #notifications .badge.alert.note{background:#0090D9;color:#fff}#admin-main #notifications .badge.alert.warning{background:#F45857;color:#fff}.sidebar-mobile-open #admin-main .lines-button .lines,.sidebar-mobile-open #admin-main .lines-button .lines:before,.sidebar-mobile-open #admin-main .lines-button .lines:after{background:#fff}#admin-dashboard .admin-block li{border-bottom:1px solid #f7f7f7}#admin-dashboard .primary-accent{background-color:#0078b5;background-image:-webkit-linear-gradient(#0078b5,#0090D9);background-image:linear-gradient(#0078b5,#0090D9)}#admin-dashboard .primary-accent h1{color:#fff;border-bottom:0}#admin-dashboard .primary-accent p{color:rgba(255,255,255,0.95)}#admin-dashboard .primary-accent .stat i{color:rgba(255,255,255,0.75)}#admin-dashboard .primary-accent .numeric em{color:rgba(255,255,255,0.85)}#admin-dashboard .primary-accent .chart-loader{color:#0073ae}#admin-dashboard .secondary-accent{background-color:#058379;background-image:-webkit-linear-gradient(#058379,#06A599);background-image:linear-gradient(#058379,#06A599)}#admin-dashboard .secondary-accent h1{color:#fff;border-bottom:0}#admin-dashboard .secondary-accent p{color:rgba(255,255,255,0.95)}#admin-dashboard .secondary-accent .stat i{color:rgba(255,255,255,0.75)}#admin-dashboard .secondary-accent .numeric em{color:rgba(255,255,255,0.85)}#admin-dashboard .secondary-accent .chart-loader{color:#05847a}.sidebar-mobile-open #admin-main .lines-button .lines,.sidebar-mobile-open #admin-main .lines-button .lines:before,.sidebar-mobile-open #admin-main .lines-button .lines:after{background:#fff}form h1,form h3{border-bottom:3px solid #f7f7f7}form h1,form h2,form h3,form h4,form h5{color:#414147}form .note{color:#F45857}form .required{color:#da4b46}form .overlay{background:#fafafa}form .overlay pre{background:#fefefe}form .form-border{border:1px solid #e6e6e6}form .form-field-colors{background-color:#fcfcfc;color:#646e7c}form .form-select-wrapper:after,form .selectize-control.single .selectize-input:after{color:#99a2ad}form .selectize-control.single .selectize-input,form .selectize-control.single .selectize-input.full,form .selectize-control.single .selectize-input.items,form .selectize-control.single .selectize-input.active,form .selectize-control.multi .selectize-input,form .selectize-control.multi .selectize-input.full,form .selectize-control.multi .selectize-input.items,form .selectize-control.multi .selectize-input.active{color:#646e7c;border-color:#e6e6e6;background-color:#fcfcfc}form .selectize-control.single .selectize-input input,form .selectize-control.multi .selectize-input input{color:#646e7c}form .selectize-control.single .selectize-input a.remove,form .selectize-control.multi .selectize-input a.remove{color:#F6F6F6 !important}form .form-input-wrapper .form-input-addon{border:1px solid #e6e6e6;background:#fff}form .form-input-wrapper .form-input-addon.copy-to-clipboard{background:#0090D9;color:#fff}form .form-input-wrapper .form-input-addon.copy-to-clipboard:hover{background:#007fc0}form .selectize-control.single.plugin-remove_button .selectize-input .item,form .selectize-control.single.plugin-remove_button .selectize-input .remove-single,form .selectize-control.multi .selectize-input .item,form .selectize-control.multi .selectize-input .remove-single{color:#fff !important;background:#6f7b8a}form .selectize-control.single.plugin-remove_button .selectize-input .item.active,form .selectize-control.single.plugin-remove_button .selectize-input .remove-single.active,form .selectize-control.multi .selectize-input .item.active,form .selectize-control.multi .selectize-input .remove-single.active{background:#06A599;color:#fff}form select{border:1px solid #e6e6e6;background:#fcfcfc;color:#646e7c}form input[type=text],form input[type=password],form input[type=email],form input[type=date],form input[type=tel],form input[type=time],form input[type=week],form input[type=month],form input[type=number],form input[type=color],form input[type=url]{border:1px solid #e6e6e6;background:#fcfcfc;color:#646e7c}form input[readonly=readonly]{background:#fff}form textarea{color:#646e7c;border:1px solid #e6e6e6;background:#fcfcfc}form .form-frontmatter-wrapper{border:1px solid #e6e6e6}form .button-bar{background:#f7f7f7}form .checkboxes label:before{background:#fcfcfc;border:1px solid #e6e6e6}form input::-webkit-input-placeholder,form textarea::-webkit-input-placeholder,form select::-webkit-input-placeholder{color:#a7afb8}form input::-moz-placeholder,form textarea::-moz-placeholder,form select::-moz-placeholder{color:#a7afb8}form input:-moz-placeholder,form textarea:-moz-placeholder,form select:-moz-placeholder{color:#a7afb8}form input:-ms-input-placeholder,form textarea:-ms-input-placeholder,form select:-ms-input-placeholder{color:#a7afb8}form .g-colorpicker{width:230px}form .g-colorpicker .g-colorpicker-preview-wrap{border-color:#e6e6e6;height:43px;width:43px}form .g-colorpicker input{height:43px}form .form-input-file{border:2px dashed #e6e6e6}form .form-input-file p{color:#646e7c}form .form-input-file ul{color:#fff;background:#0090D9}form .file-thumbnail-remove{background:#0090D9}form .file-thumbnail-remove .fa{color:#fff}form .file-thumbnail-remove:hover{background:#fff}form .file-thumbnail-remove:hover .fa{color:#0090D9}form input[type=range]::-webkit-slider-runnable-track{background:#fafafa;border:1px solid #e6e6e6}form input[type=range]::-webkit-slider-thumb{border:1px solid #e6e6e6;background:#0090D9}form input[type=range]:focus::-webkit-slider-runnable-track{background:#fafafa}form input[type=range]::-moz-range-track{background:#fafafa;border:1px solid #e6e6e6}form input[type=range]::-moz-range-progress{background:#fafafa}form input[type=range]::-moz-range-thumb{border:1px solid #e6e6e6;background:#0090D9}form input[type=range]::-ms-fill-lower,form input[type=range]::-ms-fill-upper{background:#fafafa;border:1px solid #e6e6e6}form input[type=range]::-ms-thumb{border:1px solid #e6e6e6;background:#0090D9}form input[type=range]:focus::-ms-fill-lower,form input[type=range]:focus::-ms-fill-upper{background:#fafafa}form .form-display-wrapper table{border:1px solid #e6e6e6}form .form-display-wrapper table th{background-color:#f5f5f5;border-bottom:3px solid #e6e6e6;border-right:1px solid #e6e6e6}form .form-display-wrapper table td{border-right:1px solid #e6e6e6}form .form-display-wrapper table tr{border-bottom:1px solid #e6e6e6}.grav-editor-resizer{background-color:#d5d5d5;background-color:#d5d5d5;background-image:-webkit-linear-gradient(top, #d5d5d5 0%,#d5d5d5 20%,#ededed 20%,#ededed 40%,#d5d5d5 40%,#d5d5d5 60%,#ededed 60%,#ededed 80%,#d5d5d5 80%,#d5d5d5 100%);background-image:linear-gradient(to bottom,#d5d5d5 0%,#d5d5d5 20%,#ededed 20%,#ededed 40%,#d5d5d5 40%,#d5d5d5 60%,#ededed 60%,#ededed 80%,#d5d5d5 80%,#d5d5d5 100%)}.form-order-wrapper ul.orderable li{border:1px solid #e6e6e6;background:#fff;color:#6f7b8a}.form-order-wrapper ul.orderable li.drag-handle{background:#fcfcfc;color:#646e7c}.form-order-wrapper ul.orderable li[data-active-id]{border-color:#0090D9}.form-order-wrapper ul.orderable.disabled li{opacity:0.7}.form-list-wrapper ul[data-collection-holder]>li{border:1px solid #e6e6e6;background:#fff;color:#8b95a1}.form-list-wrapper ul[data-collection-holder]>li .item-actions{color:#58626e}.form-list-wrapper .collection-sort{background:#fafafa;border-right:1px solid #e6e6e6}.form-fieldset{background-color:#fcfcfc;border:1px solid #e6e6e6}.form-fieldset--label{background-color:#f5f5f5}.form-fieldset--label:hover,.form-fieldset input:checked+.form-fieldset--label{background-color:#f0f0f0}#admin-main #admin-topbar{background:#e3e3e3}#admin-main .form-tabs{background:#e3e3e3}#admin-main .form-tabs.side-tabs>.tabs-nav{background:#fff}#admin-main .form-tabs.side-tabs>.tabs-nav a{color:#0090D9;cursor:pointer}#admin-main .form-tabs.side-tabs>.tabs-nav a:hover{background:#F6F6F6}#admin-main .form-tabs.side-tabs>.tabs-nav a.active{background:#f0f0f0;color:gray}#admin-main .form-tabs.side-tabs>.tabs-content{box-shadow:-20px 0 50px -40px rgba(0,0,0,0.3)}#admin-main .form-tabs .tabs-nav a{color:gray}#admin-main .form-tabs .tabs-nav a:hover{color:#737373;background:#d6d6d6}#admin-main .form-tabs .tabs-nav a.active{background:#fff;color:#6f7b8a}#admin-main .form-tabs.subtle{background:#fff}#admin-main .form-tabs.subtle .tabs-nav{background:linear-gradient(to bottom, rgba(255,255,255,0), #f7f7f7)}#admin-main .form-tabs.subtle a{color:#646f7c;border:0 solid transparent;border-bottom:1px solid #e6e6e6}#admin-main .form-tabs.subtle a:hover{color:#59626e;background:linear-gradient(to bottom, rgba(255,255,255,0), #ededed)}#admin-main .form-tabs.subtle a.active{border-top:1px solid #e6e6e6;border-left:1px solid #e6e6e6;border-right:1px solid #e6e6e6;border-bottom:0}#admin-main .form-tabs.subtle a.active:hover{background:#fff}#admin-main .form-tabs.subtle a.active:first-child{border-left:0}#admin-main .form-tabs.subtle a.active:last-child{border-right:0}#admin-main .form-tabs .tabs-content{background:#fff}.switch-grav{background-color:#fcfcfc;border:1px solid #e6e6e6}.switch-grav label{color:#6f7b8a;transition:background-color 0.5s ease}.switch-grav.switch-toggle input.highlight:checked+label{background:#0090D9;color:#fff}.switch-grav.switch-toggle input:checked+label{color:#fff;background:#6f7b8a}.toast{background-color:#030303}.toast-success{background-color:#77559D}.toast-success .button{background:#8462aa;color:rgba(255,255,255,0.85);border-radius:4px;background:#9172b3}.toast-success .button:hover{background:#7b6198;color:#fff}.toast-success .button.dropdown-toggle{border-left:1px solid #9e83bc}.toast-error{background-color:#F45857}.toast-error .button{background-color:#f12827;color:rgba(255,255,255,0.85);border-radius:4px;background:#d70f0e}.toast-error .button:hover{background:#dd3332;color:#fff}.toast-error .button.dropdown-toggle{border-left:1px solid #bf0e0d}.toast-info{background-color:#06A599}.toast-info .button{background-color:#04746b;color:rgba(255,255,255,0.85);border-radius:4px;background:#02433e}.toast-info .button:hover{background:#285f5b;color:#fff}.toast-info .button.dropdown-toggle{border-left:1px solid #022a27}.info{background:#77559D;color:#fff}.info a{color:#e6e6e6}.info a:hover{color:#fff}.info a.button,.info span.button,.info button.button{color:rgba(255,255,255,0.85);border-radius:4px;background:#5f447e}.info a.button:hover,.info span.button:hover,.info button.button:hover{background:#513a6b;color:#fff}.info a.button.dropdown-toggle,.info span.button.dropdown-toggle,.info button.button.dropdown-toggle{border-left:1px solid #6b4d8f}.info-reverse{color:#77559D}.notice,.note{background:#06A599;color:#fff}.notice a,.note a{color:#e6e6e6}.notice a:hover,.note a:hover{color:#fff}.notice a.button,.notice span.button,.notice button.button,.note a.button,.note span.button,.note button.button{color:rgba(255,255,255,0.85);border-radius:4px;background:#05847a}.notice a.button:hover,.notice span.button:hover,.notice button.button:hover,.note a.button:hover,.note span.button:hover,.note button.button:hover{background:#047068;color:#fff}.notice a.button.dropdown-toggle,.notice span.button.dropdown-toggle,.notice button.button.dropdown-toggle,.note a.button.dropdown-toggle,.note span.button.dropdown-toggle,.note button.button.dropdown-toggle{border-left:1px solid #069d91}.notice-reverse,.note-reverse{color:#06A599}.error,.warning{background:#F45857;color:#fff}.error a,.warning a{color:#e6e6e6}.error a:hover,.warning a:hover{color:#fff}.error a.button,.error span.button,.error button.button,.warning a.button,.warning span.button,.warning button.button{color:rgba(255,255,255,0.85);border-radius:4px;background:#c34646}.error a.button:hover,.error span.button:hover,.error button.button:hover,.warning a.button:hover,.warning span.button:hover,.warning button.button:hover{background:#a63c3c;color:#fff}.error a.button.dropdown-toggle,.error span.button.dropdown-toggle,.error button.button.dropdown-toggle,.warning a.button.dropdown-toggle,.warning span.button.dropdown-toggle,.warning button.button.dropdown-toggle{border-left:1px solid #c95959}.error-reverse,.warning-reverse{color:#F45857}.primary-accent{background:#007fc0;color:#fff}.primary-accent .button-bar{background:#0078b5}.primary-accent .button{color:rgba(255,255,255,0.85);border-radius:4px;background:#0090D9}.primary-accent .button:hover{background:#26a1df;color:#fff}.primary-accent .button.dropdown-toggle{border-left:1px solid #007fc0}.secondary-accent{background:#058c82;color:#fff}.secondary-accent .button-bar{background:#058379}.secondary-accent .button{color:rgba(255,255,255,0.85);border-radius:4px;background:#06A599}.secondary-accent .button:hover{background:#2bb3a8;color:#fff}.secondary-accent .button.dropdown-toggle{border-left:1px solid #058c82}.secondary-accent .button-group .dropdown-menu{background:#06A599}.secondary-accent .button-group .dropdown-menu li a:hover{background:#2bb3a8}.card-item{border:1px solid #f2f2f2;background:#fff}.card-item .gpm-actions{background:#fafafa}.card-item.active-theme{border:1px solid #06A599}.card-item.active-theme .gpm-actions{background:#06A599;color:#fff}.user-details h2{color:#414147}.user-details h2 span{color:#6f7b8a}.user-details .gravatar{color:#a7afb8}.user-details .gravatar a{color:#6f7b8a !important}#admin-main .grav-editor-toolbar .dropdown-menu{box-shadow:0 3px 6px rgba(0,0,0,0.075);border:1px solid #e6e6e6;background:#fff}#admin-main .grav-editor-toolbar{border:1px solid #e6e6e6;border-top-right-radius:4px;border-top-left-radius:4px;background:#fff}#admin-main .grav-editor-toolbar ul a{color:#6f7b8a}#admin-main .grav-editor-toolbar ul a:hover,#admin-main .grav-editor-toolbar ul a:focus{background:#f7f7f7;color:#646e7c;border-top:1px solid #e6e6e6}#admin-main .grav-editor-toolbar ul .editor-active a{cursor:auto;background:#fff;border-top:1px solid #e6e6e6;border-left:1px solid #e6e6e6;border-right:1px solid #e6e6e6}#admin-main .grav-editor-toolbar ul .editor-active a:hover{background:#f7f7f7;color:#646e7c;border-top:0;border-bottom:1px solid #e6e6e6}#admin-main .grav-editor-toolbar ul .editor-active:last-child a,#admin-main .grav-editor-toolbar ul .editor-active:last-child a:hover{border-right:1px solid transparent}#admin-main .grav-editor-toolbar ul .button-disabled a{color:rgba(111,123,138,0.5)}#admin-main .grav-editor-toolbar ul .button-disabled a:hover,#admin-main .grav-editor-toolbar ul .button-disabled a:focus{background:#fff;color:#6f7b8a;border-left:1px solid transparent;border-right:1px solid transparent}#admin-main .grav-editor-hide-toolbar+div{border:1px solid #e6e6e6}.grav-editor-content,.grav-editor-preview{border:1px solid #e6e6e6;border-top:0}.grav-mdeditor-content{border:1px solid #e6e6e6}[data-mode=split] .grav-mdeditor-code{border-right:1px solid #e6e6e6}#admin-main .grav-editor-preview h1,#admin-main .grav-editor-preview h2,#admin-main .grav-editor-preview h3,#admin-main .grav-editor-preview h4,#admin-main .grav-editor-preview h5,#admin-main .grav-editor-preview h6{color:#58626e}.primary-accent .ct-chart .ct-series.ct-series-a .ct-bar{stroke:rgba(255,255,255,0.85) !important}.primary-accent .ct-chart .ct-series.ct-series-a .ct-slice-donut{stroke:#fff !important}.primary-accent .ct-chart .ct-series.ct-series-b .ct-slice-donut{stroke:rgba(255,255,255,0.2) !important}.secondary-accent .ct-chart .ct-series.ct-series-a .ct-bar{stroke:rgba(255,255,255,0.85) !important}.secondary-accent .ct-chart .ct-series.ct-series-a .ct-slice-donut{stroke:#fff !important}.secondary-accent .ct-chart .ct-series.ct-series-b .ct-slice-donut{stroke:rgba(255,255,255,0.2) !important}.dropzone{background:#fcfcfc;border:1px #e6e6e6 solid}.dropzone.dz-drag-hover{border-color:#ccc;background:rgba(0,0,0,0.04)}.dropzone .dz-preview{border:1px solid #f2f2f2}.dropzone .dz-preview .dz-success-mark,.dropzone .dz-preview .dz-error-mark{color:#fff}.dropzone .dz-preview .dz-success-mark{background-color:#0daeff}.dropzone .dz-preview .dz-error-mark{background-color:#F45857}.dropzone .dz-preview .dz-progress{background:#d7d7d7}.dropzone .dz-preview .dz-progress .dz-upload{background-color:#0daeff}.dropzone .dz-preview .dz-error-message{background:#fff;color:#F45857}.dropzone .dz-preview:hover .dz-details{box-shadow:0px 0px 20px -5px rgba(0,0,0,0.4)}.dropzone .dz-preview .dz-remove,.dropzone .dz-preview .dz-unset,.dropzone .dz-preview .dz-view,.dropzone .dz-preview .dz-insert,.dropzone .dz-preview .dz-metadata{background:#ededed}.dropzone .dz-preview .dz-remove:hover:after,.dropzone .dz-preview .dz-unset:hover:after,.dropzone .dz-preview .dz-view:hover:after,.dropzone .dz-preview .dz-insert:hover:after,.dropzone .dz-preview .dz-metadata:hover:after{color:#0090D9}.dropzone .dz-preview .dz-remove:after,.dropzone .dz-preview .dz-unset:after,.dropzone .dz-preview .dz-view:after,.dropzone .dz-preview .dz-insert:after,.dropzone .dz-preview .dz-metadata:after{color:#6f7b8a}.dropzone .dz-preview .dz-remove:hover:after{color:#F45857}.dropzone.dz-clickable .dz-message,.dropzone.dz-clickable .dz-message span{color:#c3c8cf}.gpm>table>tbody>tr{border-bottom:1px solid #f7f7f7}.gpm>table>tbody>tr:hover{background:#fff}.gpm .gpm-name{color:#99a2ad}.gpm .gpm-version{color:#8b95a1}.gpm .gpm-ribbon,.gpm .badge.update{background:#77559D}.gpm .gpm-ribbon a,.gpm .gpm-ribbon,.gpm .badge.update a,.gpm .badge.update{color:#fff !important}.gpm .gpm-ribbon a:hover,.gpm .gpm-ribbon:hover,.gpm .badge.update a:hover,.gpm .badge.update:hover{background:#654885}.gpm .gpm-update .gpm-name{color:#0090D9}.gpm .gpm-item-info{border-bottom:3px solid #f7f7f7}.gpm .gpm-item-info .gpm-item-icon{color:#fafafa}.gpm .gpm-actions .disabled{color:#8b95a1}.gpm .gpm-testing{background-color:#F45857;color:#fff}.pages-list{border-top:1px solid #f7f7f7}.pages-list .row{border-bottom:1px solid #f7f7f7}.pages-list .row:hover{background:#fff}.pages-list .row p.page-route{color:#6f7b8a}.pages-list .row p.page-route .spacer{color:#6f7b8a}.pages-list .row .badge.lang{background-color:#e3e3e3;color:gray}.pages-list .row .badge.lang.info{background-color:#77559D;color:#fff}.pages-list .page-home{color:#8b95a1}.pages-list .page-info{color:#8b95a1}#admin-topbar #admin-lang-toggle{z-index:3}#admin-topbar #admin-lang-toggle button{color:rgba(255,255,255,0.85);border-radius:4px;background:#77559D}#admin-topbar #admin-lang-toggle button:hover{background:#8b6fac;color:#fff}#admin-topbar #admin-lang-toggle button.dropdown-toggle{border-left:1px solid #6a4c8c}#admin-topbar #admin-lang-toggle .dropdown-menu{background:#77559D}#admin-topbar .switch-grav{background-color:#fdfdfd}#admin-topbar .switch-toggle input:checked+label{color:#fff}#admin-topbar .switch-toggle input+label{color:gray}#admin-topbar .switch-toggle input.highlight:checked+label{background:#0090D9}body .selectize-dropdown .optgroup-header{color:#000;border-bottom:1px solid #eee;background-color:#fafafa}body .bootstrap-datetimepicker-widget.dropdown-menu{border-color:#e6e6e6}body .bootstrap-datetimepicker-widget table td span:hover,body .bootstrap-datetimepicker-widget table th:hover,body .bootstrap-datetimepicker-widget table td.day:hover{background:#f2f2f2 !important}body .bootstrap-datetimepicker-widget table .active{color:#0090D9 !important}body .bootstrap-datetimepicker-widget table .active:hover{color:#006ea6}#phpinfo th{background:#fcfcfc}#phpinfo td{word-wrap:break-word}#phpinfo td:first-child{color:#0090D9}.remodal{background:#fff;color:#6f7b8a}.remodal-close{color:#6f7b8a}.remodal-close:hover,.remodal-close:focus{color:#424951}.gm-scrollbar .thumb{background-color:rgba(0,0,0,0.2)}.gm-scrollbar .thumb:hover,.gm-scrollbar .thumb:active{background-color:rgba(0,0,0,0.3)}#page-filtering .page-shortcuts .button{color:rgba(255,255,255,0.85);border-radius:4px;background:rgba(100,110,124,0.5)}#page-filtering .page-shortcuts .button:hover{background:rgba(153,160,169,0.575);color:#fff}#page-filtering .page-shortcuts .button.dropdown-toggle{border-left:1px solid rgba(88,98,110,0.5)}#admin-sidebar .gm-scrollbar .thumb{background-color:rgba(255,255,255,0.2)}#admin-sidebar .gm-scrollbar .thumb:hover,#admin-sidebar .gm-scrollbar .thumb:active{background-color:rgba(255,255,255,0.3)}.preview-wrapper iframe{border:1px solid #eee}#backups-stats .backups-usage-wrapper>div{background:linear-gradient(-90deg, #006ea6 0, #005d8d 100%)}#backups-stats .backups-usage-wrapper>div.full{background:linear-gradient(-90deg, #f2403f 0, #8f0a09 100%)}.logs-output table.noflex td.level .badge.error,.logs-output table.noflex td.level .badge.critical,.logs-output table.noflex td.level .badge.alert{background-color:#DC3023;color:#fff}.logs-output table.noflex td.level .badge.notice,.logs-output table.noflex td.level .badge.warning{background-color:#E08A1E;color:#fff}.logs-output table.noflex td.level .badge.debug{background-color:#26A65B;color:#fff}.logs-output table.noflex td.level .badge.info{background-color:#22A7F0;color:#fff}.logs-output table.noflex td.level .badge.emergency{background-color:#8E44AD;color:#fff}.permission-container{overflow:hidden}input[type=range].rangefield{display:inline-block;vertical-align:middle;-webkit-appearance:none;margin:10px 0;width:230px;background:none}input[type=range].rangefield:focus{outline:none}input[type=range].rangefield::-webkit-slider-runnable-track{width:230px;height:20px;cursor:pointer;animate:0.2s;border-radius:25px}input[type=range].rangefield::-webkit-slider-thumb{height:24px;width:35px;border-radius:6px;cursor:pointer;-webkit-appearance:none;margin-top:-3px}input[type=range].rangefield::-moz-focus-outer{border:0}input[type=range].rangefield::-moz-range-track{width:230px;height:20px;cursor:pointer;animate:0.2s;border-radius:25px}input[type=range].rangefield::-moz-range-progress{height:20px;border-radius:25px}input[type=range].rangefield::-moz-range-thumb{height:24px;width:35px;border-radius:6px;cursor:pointer}input[type=range].rangefield::-ms-track{width:230px;height:20px;cursor:pointer;animate:0.2s;background:transparent;border-color:transparent;color:transparent}input[type=range].rangefield::-ms-fill-lower,input[type=range].rangefield input[type=range]::-ms-fill-upper{border-radius:50px}input[type=range].rangefield::-ms-thumb{height:24px;width:35px;border-radius:6px;cursor:pointer}input[type=range].rangefield ~ input[type=number].rangefield{background:none;display:inline-block;width:60px;text-align:right;border:0;line-height:16px;vertical-align:middle;padding:0 0 0 5px}span.range-append{display:inline-block;line-height:20px;vertical-align:middle;margin-left:-3px}
+
+/*# sourceMappingURL=preset.css.map */
diff --git a/user/plugins/admin/themes/grav/css-compiled/preset.css.map b/user/plugins/admin/themes/grav/css-compiled/preset.css.map
new file mode 100644
index 0000000..bb6d3c3
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css-compiled/preset.css.map
@@ -0,0 +1 @@
+{"version":3,"file":"preset.css","sources":["preset.scss","configuration/template/_base.scss","configuration/template/_colors.scss","configuration/template/_variables.scss","template/modules/_bourbon_essentials.scss","template/modules/_buttons.scss","template/modules/_input-range.scss"],"sourcesContent":["// Load Third Party Libraries\n@import \"configuration/template/base\";\n@import \"template/modules/bourbon_essentials\";\n@import \"template/modules/buttons\";\n\n// Logo\n$logo-bg: #323640 !default;\n$logo-link: #FFFFFF !default;\n\n// Nav\n$nav-bg: #3D424E !default;\n$nav-text: #B7B9BD !default;\n$nav-link: #ffffff !default;\n$nav-selected-bg: #323640 !default;\n$nav-selected-link: #ffffff !default;\n$nav-hover-bg: #434753 !default;\n$nav-hover-link: #ffffff !default;\n\n// Toolbar\n$toolbar-bg: #ffffff !default;\n$toolbar-text: #3D424E !default;\n\n// Page\n$page-bg: #F6F6F6 !default;\n$page-text: #6f7b8a !default;\n$page-link: #0090D9 !default;\n\n// Content\n$content-bg: #ffffff !default;\n$content-text: #6f7b8a !default;\n$content-link: #0090D9 !default;\n$content-link2: #da4b46 !default;\n$content-header: #414147 !default;\n$content-tabs-bg: #e3e3e3 !default;\n$content-tabs-text: #808080 !default;\n\n// Buttons\n$button-bg: #0090D9 !default;\n$button-text: #ffffff !default;\n\n// Notice\n$notice-bg: #06A599 !default;\n$notice-text: #ffffff !default;\n\n// Updates\n$update-bg: #77559D !default;\n$update-text: #ffffff !default;\n\n// Critical\n$critical-bg: #F45857 !default;\n$critical-text: #ffffff !default;\n\n// Calculated values\n$primary-accent: button !default;\n$secondary-accent: notice !default;\n$tertiary-accent: critical !default;\n\n// Initial values\n$primary-accent-bg: $notice-bg;\n$primary-accent-fg: $notice-text;\n$secondary-accent-bg: $notice-bg;\n$secondary-accent-fg: $notice-text;\n$tertiary-accent-bg: $notice-bg;\n$tertiary-accent-fg: $notice-text;\n\n$element-map: (\n button: $button-bg $button-text,\n notice: $notice-bg $notice-text,\n content: $content-bg $content-text,\n tabs: $content-tabs-bg $content-tabs-text,\n update: $update-bg $update-text,\n critical: $critical-bg $critical-text,\n page: $page-bg $page-text,\n nav: $nav-bg $nav-text,\n toolbar: $toolbar-bg $toolbar-text,\n logo: $logo-bg $logo-link\n);\n\n// Set based on configuration\n@each $element, $element-parts in $element-map {\n @if $primary-accent == $element {\n $primary-accent-bg: nth($element-parts, 1);\n $primary-accent-fg: nth($element-parts, 2);\n }\n}\n\n@each $element, $element-parts in $element-map {\n @if $secondary-accent == $element {\n $secondary-accent-bg: nth($element-parts, 1);\n $secondary-accent-fg: nth($element-parts, 2);\n }\n}\n\n@each $element, $element-parts in $element-map {\n @if $tertiary-accent == $element {\n $tertiary-accent-bg: nth($element-parts, 1);\n $tertiary-accent-fg: nth($element-parts, 2);\n }\n}\n\n$form-border: #f00;\n$form-field-bg: #00f;\n$form-field-text: #0f0;\n$underline: #ff0;\n$subtle-text: #0ff;\n$ir-slider-bg: #f0f;\n\n@if (lightness($content-bg) < 50) {\n $form-border: lighten($content-bg, 15%);\n $form-field-bg: lighten($content-bg, 1%);\n $ir-slider-bg: lighten($content-bg, 2%);\n $form-field-text: lighten($content-text, 5%);\n $underline: lighten($content-bg, 3%);\n $subtle-text: darken($content-text, 10%);\n} @else {\n $form-border: darken($content-bg, 10%);\n $form-field-bg: darken($content-bg, 1%);\n $ir-slider-bg: darken($content-bg, 2%);\n $form-field-text: darken($content-text, 5%);\n $underline: darken($content-bg, 3%);\n $subtle-text: lighten($content-text, 10%);\n}\n\n$form-border-width: 1px;\n\n// Core\nbody {\n background: $page-bg;\n color: $page-text;\n}\n\na {\n color: $page-link;\n &:hover {\n color: darken($page-link, 5%);\n }\n}\n\n.text-gray, .text-grey {\n color: $nav-text;\n}\n\n.text-update {\n color: $update-bg;\n}\n\n// Typography\nblockquote {\n border-left: 10px solid darken($content-bg, 10%);\n background: darken($content-bg, 2%);\n p {\n color: darken($page-text, 5%);\n }\n cite {\n color: $page-text;\n }\n}\n\ncode {\n background: $code-bg;\n color: darken($code-text,10%);\n}\n\npre {\n background: $pre-bg;\n border: 1px solid $core-border-color;\n code {\n color: $pre-text;\n }\n}\n\nhr {\n border-bottom: 2px solid $underline;\n}\n\n.label {\n background: $page-link;\n color: $white;\n}\n\n.badge {\n background: $update-bg;\n color: $update-text;\n}\n\n.light-border {\n border-color: $underline !important;\n}\n\n.subtle-text {\n color: $subtle-text !important;\n}\n\n// Tables\ntd, thead th {\n border-bottom: 1px solid $underline;\n}\n\ntr {\n td {\n &.gpm-details {\n background-color: darken($content-bg,2%);\n }\n }\n}\n\n// Tabs\n.tab-bar {\n background: $content-tabs-bg;\n color: $content-tabs-text;\n\n li {\n &.active {\n span, a {\n background: $content-bg;\n color: $content-text;\n }\n }\n }\n\n span, a {\n color: $content-tabs-text;\n &:hover {\n color: lighten($content-tabs-text, 10%);\n background: lighten($content-tabs-bg,10%);\n }\n }\n}\n\n\n// Buttons\n.button {\n @include button-color($button-bg);\n\n &.dropdown-toggle {\n border-left: 1px solid darken($button-bg, 5%);\n }\n\n &.secondary {\n @include button-color(shade($button-bg, 20%));\n\n &.dropdown-toggle {\n border-left: 1px solid darken(shade($button-bg, 20%), 5%);\n }\n }\n\n &.danger {\n @include button-color($critical-bg);\n }\n}\n\n.button-group {\n .dropdown-menu {\n background-color: $button-bg;\n box-shadow: 0 6px 12px rgba(0, 0, 0, .175);\n\n .divider {\n background-color: $button-bg;\n }\n\n li > a {\n color: $button-text;\n\n &:focus, &:hover {\n color: $button-text;\n &:hover {\n background: tint($button-bg,15%);\n }\n }\n }\n\n &.language-switcher {\n a.active {\n background-color: lighten($button-bg, 10%);\n }\n }\n }\n}\n\n#admin-main .admin-block a.gumroad-button {\n background: $critical-bg !important;\n color: $critical-text !important;\n}\n\n\n#admin-login-wrapper {\n background: radial-gradient(circle at center, lighten($nav-bg, 10%) 40%, darken($nav-bg, 10%) 100%);\n}\n\n#admin-login {\n background: $nav-bg;\n\n &.wide form {\n\n label {\n color: $nav-text;\n }\n\n input {\n\n @include placeholder {\n color: rgba($nav-text, 0.6);\n }\n\n }\n }\n\n .wrapper-spacer {\n h3 {\n color: rgba(white, 0.4);\n border-bottom: 3px solid rgba($nav-link, 0.1);\n }\n }\n\n .instructions, p {\n color: rgba($nav-text,0.8);\n }\n\n h1 {\n background-color: $logo-bg;\n\n &.text-logo {\n color: $logo-link;\n }\n\n svg {\n path:first-child {\n fill: darken($logo-bg, 3%);\n }\n path:last-child {\n fill: rgba($logo-link, 0.9) !important;\n }\n }\n }\n\n h5 {\n color: $nav-text;\n }\n\n form {\n input {\n background: lighten($nav-bg, 3%);\n color: $nav-link;\n border: 1px solid shade($nav-bg, 20%);\n @include placeholder {\n @if (lightness($nav-bg) < 50) {\n color: shade($nav-text, 15%);\n } @else {\n color: tint($nav-text, 15%);\n }\n }\n }\n }\n}\n\n#admin-sidebar {\n background: $nav-bg;\n a {\n color: rgba($nav-link, 0.9);\n &:hover {\n color: $nav-link;\n }\n }\n}\n\n#admin-logo {\n background-color: $logo-bg;\n\n .grav-logo {\n path {\n fill: rgba($logo-link, 0.9) !important;\n }\n\n &:hover {\n path {\n fill: $logo-link !important;\n }\n }\n }\n a, i {\n color: shade($logo-link, 10%);\n &:hover {\n color: $logo-link;\n }\n }\n}\n\n#open-handle {\n background-color: darken($logo-bg, 5%);\n border-left: 1px solid lighten($logo-bg, 5%);\n\n i {\n color: rgba($logo-link, 0.6);\n &:hover {\n color: rgba($logo-link, 0.8);\n }\n }\n}\n\n#admin-nav-quick-tray {\n background: darken($nav-bg, 2%);\n border-bottom: 1px solid darken($nav-bg, 3%);\n color: $nav-link;\n\n i:hover {\n @if (lightness($nav-bg) < 50) {\n color: lighten($nav-link, 10%);\n } @else {\n color: darken($nav-link, 10%);\n }\n }\n}\n\n.block-userinfo {\n img {\n border: 4px solid $form-border;\n }\n}\n#offline-status {\n background-color: $update-bg;\n color: $update-text;\n}\n\n#admin-user-details, .admin-user-details {\n border-bottom: 1px solid lighten($nav-bg, 5%);\n\n &:hover img {\n box-shadow: 0px 0px 0 50px lighten($nav-bg, 3%);\n }\n\n .admin-user-names {\n h4, h5 {\n color: $nav-link;\n }\n }\n}\n\n#admin-menu {\n li {\n .badges {\n .count {\n color: $nav-link;\n background-color: darken($nav-bg, 5%);\n\n }\n\n .updates {\n color: $notice-text;\n background-color: $notice-bg;\n }\n }\n\n a {\n &:hover {\n background: $nav-hover-bg;\n color: $nav-hover-link;\n\n .fa {\n color: $nav-hover-link;\n }\n }\n }\n\n &.selected {\n a {\n background: $nav-selected-bg;\n color: $nav-selected-link;\n border-left: 9px solid shade($button-bg, 15%);\n\n .fa {\n color: $nav-selected-link;\n }\n }\n }\n }\n}\n\n#admin-main {\n\n .grav-update {\n background: $update-bg;\n color: $update-text;\n a.button, span.button, button.button {\n @include button-color(shade($update-bg, 20%), $update-text, true);\n }\n }\n\n .titlebar {\n background: $toolbar-bg;\n color: $toolbar-text;\n .button-bar {\n .button {\n @include button-color($button-bg, $button-text, false);\n }\n }\n }\n\n .lines-button {\n background: rgba($black, 0.1); // move this\n .lines, .lines:before, .lines:after {\n background: $toolbar-text;\n }\n }\n\n .admin-block {\n background: $content-bg;\n color: $content-text;\n\n a:not(.button) {\n color: $content-link;\n &:hover {\n color: darken($content-link, 5%);\n }\n }\n\n h1 {\n color: $content-header;\n border-bottom: 1px solid $underline;\n }\n\n //.button {\n // @include button-color($button-bg);\n //}\n }\n\n .danger, .success {\n &.button-bar {\n background: darken($content-bg, 3%);\n }\n }\n\n .danger {\n .button {\n @include button-color($critical-bg);\n }\n }\n\n // Notifications\n .content-padding {\n div[class*=notifications-container] {\n\n .alert.note {\n background: $content-bg;\n color: $content-text;\n\n &.alert {\n border-bottom: 2px solid darken($content-bg, 5%);\n }\n\n a:not(.button) {\n color: $content-link;\n &:hover {\n color: darken($content-link, 5%);\n }\n }\n }\n\n .alert.warning {\n background: $critical_bg;\n color: $critical_text;\n\n &.alert {\n border-bottom: 2px solid darken($critical_bg, 5%);\n }\n\n a {\n color: $critical_text;\n &:hover {\n color: lighten($critical_text, 10%);\n }\n }\n }\n }\n }\n\n #notifications {\n .badge.alert {\n &.note {\n background: $content_link;\n color: $content_bg;\n }\n &.warning {\n background: $critical_bg;\n color: $critical_text;\n }\n }\n\n }\n}\n\n.sidebar-mobile-open {\n #admin-main {\n .lines-button {\n .lines, .lines:before, .lines:after {\n background: $logo-link;\n }\n }\n }\n}\n\n#admin-dashboard {\n\n .admin-block {\n li {\n border-bottom: 1px solid $underline;\n }\n }\n\n .dashboard-item {\n\n }\n\n .primary-accent {\n @include linear-gradient(darken($primary-accent-bg, 7%), $primary-accent-bg);\n\n h1 {\n color: $white;\n border-bottom: 0;\n }\n\n p {\n color: rgba($primary-accent-fg, 0.95);\n }\n .stat {\n i {\n color: rgba($primary-accent-fg, 0.75);\n }\n }\n .numeric {\n em {\n color: rgba($primary-accent-fg, .85);\n }\n }\n\n .chart-loader {\n color: shade($primary-accent-bg, 20%);\n }\n }\n\n .secondary-accent {\n @include linear-gradient(darken($secondary-accent-bg, 7%), $secondary-accent-bg);\n\n h1 {\n color: $white;\n border-bottom: 0;\n }\n\n p {\n color: rgba($secondary-accent-fg, 0.95);\n }\n .stat {\n i {\n color: rgba($secondary-accent-fg, 0.75);\n }\n }\n .numeric {\n em {\n color: rgba($secondary-accent-fg, .85);\n }\n }\n\n .chart-loader {\n color: shade($secondary-accent-bg, 20%);\n }\n }\n\n\n}\n\n.sidebar-mobile-open {\n #admin-main {\n .lines-button {\n .lines, .lines:before, .lines:after {\n background: $logo-link;\n }\n }\n }\n}\n\n// Forms\nform {\n h1, h3 {\n border-bottom: 3px solid $underline;\n }\n\n h1, h2, h3, h4, h5 {\n color: $content-header;\n }\n\n .note {\n color: $critical-bg;\n }\n\n .required {\n color: $content-link2;\n }\n\n .overlay {\n background: darken($content-bg, 2%);\n\n pre {\n background: lighten($pre-bg, 3%);\n }\n }\n\n .form-border {\n border: 1px solid $form-border;\n }\n\n .form-field-colors {\n background-color: $form-field-bg;\n color: $form-field-text;\n }\n\n .form-select-wrapper, .selectize-control.single .selectize-input {\n &:after {\n color: lighten($content-text, 15%);\n }\n }\n\n .selectize-control {\n &.single, &.multi {\n .selectize-input {\n &, &.full, &.items, &.active {\n color: $form-field-text;\n border-color: $form-border;\n background-color: $form-field-bg;\n }\n input {\n color: $form-field-text;\n }\n\n a.remove {\n color: $page-bg !important;\n }\n }\n }\n }\n\n .form-input-wrapper {\n .form-input-addon {\n border: 1px solid $form-border;\n background: $content-bg;\n\n &.copy-to-clipboard {\n background: $button-bg;\n color: $button-text;\n\n &:hover {\n background: darken($button-bg, 5%);\n }\n }\n }\n }\n\n\n .selectize-control.single.plugin-remove_button .selectize-input,\n .selectize-control.multi .selectize-input {\n .item, .remove-single {\n color: $content-bg !important;\n background: $content-text;\n &.active {\n background: $notice-bg;\n color: $notice-text;\n }\n }\n }\n\n select {\n border: $form-border-width solid $form-border;\n background: $form-field-bg;\n color: $form-field-text;\n }\n\n input[type=text],\n input[type=password],\n input[type=email],\n input[type=date],\n input[type=tel],\n input[type=time],\n input[type=week],\n input[type=month],\n input[type=number],\n input[type=color],\n input[type=url] {\n border: $form-border-width solid $form-border;\n background: $form-field-bg;\n color: $form-field-text;\n }\n\n input[readonly=readonly] {\n background: $content-bg;\n }\n\n textarea {\n color: $form-field-text;\n border: $form-border-width solid $form-border;\n background: $form-field-bg;\n }\n\n .form-frontmatter-wrapper {\n border: $form-border-width solid $form-border;\n }\n\n .button-bar {\n background: lighten($form-border,7%);\n }\n\n .checkboxes {\n label:before {\n background: $form-field-bg;\n border: $form-border-width solid $form-border;\n }\n }\n\n input, textarea, select {\n @include placeholder {\n @if (lightness($form-field-bg) < 50) {\n color: darken($form-field-text, 25%);\n } @else {\n color: lighten($form-field-text, 25%);\n }\n }\n }\n\n .g-colorpicker {\n width: 230px;\n .g-colorpicker-preview-wrap {\n border-color: $form-border;\n height: 43px;\n width: 43px;\n }\n input {\n height: 43px;\n }\n\n }\n\n .form-input-file {\n border: 2px dashed $form-border;\n\n p {\n color: $form-field-text;\n }\n\n ul {\n color: $primary-accent-fg;\n background: $primary-accent-bg;\n }\n }\n\n .file-thumbnail-remove {\n background: $button-bg;\n\n .fa {\n color: $button-text;\n }\n\n &:hover {\n background: $button-text;\n .fa {\n color: $button-bg;\n }\n }\n }\n\n // Range\n input[type=range] {\n &::-webkit-slider-runnable-track {\n background: $ir-slider-bg;\n border: 1px solid $form-border;\n }\n &::-webkit-slider-thumb {\n border: 1px solid $form-border;\n background: $button-bg;\n }\n &:focus::-webkit-slider-runnable-track {\n background: $ir-slider-bg;\n }\n &::-moz-range-track {\n background: $ir-slider-bg;\n border: 1px solid $form-border;\n }\n &::-moz-range-progress {\n background: $ir-slider-bg;\n }\n &::-moz-range-thumb {\n border: 1px solid $form-border;\n background: $button-bg;\n }\n &::-ms-fill-lower, &::-ms-fill-upper {\n background: $ir-slider-bg;\n border: 1px solid $form-border;\n }\n &::-ms-thumb {\n border: 1px solid $form-border;\n background: $button-bg;\n }\n &:focus::-ms-fill-lower, &:focus::-ms-fill-upper {\n background: $ir-slider-bg;\n }\n }\n}\n\n// Display RESET\nform {\n .form-display-wrapper {\n\n table {\n border: 1px solid $form-border;\n\n th {\n background-color: lighten($form-border, 6%);\n border-bottom: 3px solid $form-border;\n border-right: 1px solid $form-border;\n }\n\n td {\n border-right: 1px solid $form-border;\n }\n\n tr {\n border-bottom: 1px solid $form-border;\n }\n }\n }\n\n}\n\n\n.grav-editor-resizer {\n background-color: rgba(213,213,213,1);\n @include linear-gradient(top, rgba(213,213,213,1) 0%, rgba(213,213,213,1) 20%, rgba(237,237,237,1) 20%, rgba(237,237,237,1) 40%, rgba(213,213,213,1) 40%, rgba(213,213,213,1) 60%, rgba(237,237,237,1) 60%, rgba(237,237,237,1) 80%, rgba(213,213,213,1) 80%, rgba(213,213,213,1) 100%);\n}\n\n.form-order-wrapper {\n ul.orderable {\n\n li {\n border: 1px solid $form-border;\n background: lighten($content-bg, 2%);\n color: $content-text;\n\n &.drag-handle {\n background: $form-field-bg;\n color: $form-field-text;\n }\n\n &[data-active-id] {\n border-color: $content-link;\n }\n\n }\n\n &.disabled li {\n opacity: 0.7;\n }\n }\n}\n\n.form-list-wrapper {\n ul[data-collection-holder] {\n > li {\n border: 1px solid $form-border;\n background: lighten($content-bg, 4%);\n color: lighten($content-text, 10%);\n\n .item-actions {\n color: darken($content-text, 10%);\n }\n }\n }\n\n .collection-sort {\n background: darken($content-bg, 2%);\n border-right: 1px solid $form-border;\n }\n}\n\n.form-fieldset {\n background-color: darken($content-bg, 1%);\n border: 1px solid $form-border;\n}\n\n.form-fieldset--label {\n background-color: darken($content-bg, 4%);\n\n &:hover,\n .form-fieldset input:checked + & {\n background-color: darken($content-bg, 6%);\n }\n}\n#admin-main {\n\n #admin-topbar {\n background: $content-tabs-bg;\n }\n\n .form-tabs {\n background: $content-tabs-bg;\n\n &.side-tabs {\n > .tabs-nav {\n background: $content-bg;\n\n a {\n color: $content-link;\n cursor: pointer;\n\n &:hover {\n background: $page-bg;\n }\n &.active {\n background: lighten($content-tabs-bg, 5%);\n color: $content-tabs-text;\n }\n }\n }\n\n > .tabs-content {\n box-shadow: -20px 0 50px -40px rgba(#000, 0.3);\n }\n }\n\n .tabs-nav {\n a {\n color: $content-tabs-text;\n &:hover {\n color: darken($content-tabs-text, 5%);\n background: darken($content-tabs-bg,5%);\n }\n\n &.active {\n background: $content-bg;\n color: $content-text;\n }\n }\n }\n\n &.subtle {\n background: $content-bg;\n\n .tabs-nav {\n background: linear-gradient(to bottom, rgba($content-bg,0), shade($content-bg,3%));\n }\n\n a {\n color: shade($content-text, 10%);\n border: 0 solid transparent;\n\n &:hover {\n color: shade($content-text, 20%);\n background: linear-gradient(to bottom, rgba($content-bg,0), shade($content-bg,7%));\n }\n\n border-bottom: 1px solid $form-border;\n\n &.active {\n border-top: 1px solid $form-border;\n border-left: 1px solid $form-border;\n border-right: 1px solid $form-border;\n border-bottom: 0;\n\n &:hover {\n background: $content-bg;\n }\n\n &:first-child {\n border-left: 0;\n }\n\n &:last-child {\n border-right: 0;\n }\n }\n\n }\n\n\n }\n\n .tabs-content {\n background: $content-bg;\n }\n }\n}\n\n\n\n\n.switch-grav {\n background-color: $form-field-bg;\n border: $form-border-width solid $form-border;\n\n label {\n color: $content-text;\n transition: background-color 0.5s ease;\n }\n\n &.switch-toggle input.highlight:checked + label {\n background: $button-bg;\n color: $button-text;\n }\n\n &.switch-toggle input:checked + label {\n color: $content-bg;\n background: $content-text;\n }\n\n}\n\n// Toaster\n.toast {\n background-color: #030303;\n}\n.toast-success {\n background-color: $update-bg;\n\n .button {\n background: lighten($update-bg, 5%);\n @include button-color(lighten($update-bg, 10%));\n }\n\n}\n.toast-error {\n background-color: $critical-bg;\n\n .button {\n background-color: darken($critical-bg, 10);\n @include button-color(darken($critical-bg, 20));\n }\n}\n.toast-info {\n background-color: $notice-bg;\n\n .button {\n background-color: darken($notice-bg, 10);\n @include button-color(darken($notice-bg, 20));\n }\n}\n\n// Info\n.info {\n background: $update-bg;\n color: $update-text;\n\n a {\n color: darken($update-text, 10%);\n &:hover {\n color: $update-text;\n }\n }\n\n a.button, span.button, button.button {\n @include button-color(shade($update-bg, 20%), $update-text, true);\n }\n}\n\n.info-reverse {\n color: $update-bg;\n}\n\n.notice, .note {\n background: $notice-bg;\n color: $notice-text;\n\n a {\n color: darken($notice-text, 10%);\n &:hover {\n color: $notice-text;\n }\n }\n\n a.button, span.button, button.button {\n @include button-color(shade($notice-bg, 20%), $notice-text, true);\n }\n}\n\n.notice-reverse, .note-reverse {\n color: $notice-bg;\n}\n\n.error, .warning {\n background: $critical-bg;\n color: $critical-text;\n\n a {\n color: darken($critical-text, 10%);\n &:hover {\n color: $critical-text;\n }\n }\n\n a.button, span.button, button.button {\n @include button-color(shade($critical-bg, 20%), $critical-text, true);\n }\n}\n\n.error-reverse, .warning-reverse {\n color: $critical-bg;\n}\n\n.primary-accent {\n background: darken($primary-accent-bg, 5%);\n color: $primary-accent-fg;\n\n .button-bar {\n background: darken($primary-accent-bg, 7%);\n }\n\n .button {\n @include button-color($primary-accent-bg, $primary-accent-fg);\n }\n}\n\n.secondary-accent {\n background: darken($secondary-accent-bg, 5%);\n color: $secondary-accent-fg;\n\n .button-bar {\n background: darken($secondary-accent-bg, 7%);\n }\n\n .button {\n @include button-color($secondary-accent-bg, $secondary-accent-fg);\n }\n\n .button-group {\n .dropdown-menu {\n background: $secondary-accent-bg;\n li {\n a {\n &:hover {\n background: tint($secondary-accent-bg,15%);\n }\n }\n }\n }\n }\n}\n\n.card-item {\n border: 1px solid darken($content-bg, 5%);\n background: $white;\n\n .gpm-actions {\n background: darken($content-bg, 2%);\n }\n\n\n &.active-theme {\n border: 1px solid $notice-bg;\n\n .gpm-actions {\n background: $notice-bg;\n color: $notice-text;\n }\n }\n}\n\n.user-details {\n h2 {\n color: $content-header;\n\n span {\n color: $content-text;\n }\n }\n\n .gravatar {\n color: lighten($content-text,20%);\n a {\n color: $content-text !important;\n }\n }\n}\n\n#admin-main .grav-editor-toolbar .dropdown-menu {\n box-shadow: 0 3px 6px rgba(0, 0, 0, .075);\n border: 1px solid $form-border;\n background: lighten($content-bg, 5%);\n}\n\n#admin-main .grav-editor-toolbar {\n border: 1px solid $form-border;\n border-top-right-radius: $border-radius;\n border-top-left-radius: $border-radius;\n background: lighten($content-bg, 5%);\n\n ul {\n a {\n color: $content-text;\n\n &:hover, &:focus {\n background: darken($content-bg, 3%);\n color: $form-field-text;\n border-top: 1px solid $form-border;\n }\n }\n\n .editor-active {\n a {\n cursor: auto;\n background: lighten($content-bg, 8%);\n border-top: 1px solid $form-border;\n border-left: 1px solid $form-border;\n border-right: 1px solid $form-border;\n\n &:hover {\n background: darken($content-bg, 3%);\n color: $form-field-text;\n border-top: 0;\n border-bottom: 1px solid $form-border;\n }\n }\n\n &:last-child a, &:last-child a:hover {\n border-right: 1px solid transparent;\n }\n }\n\n .button-disabled a {\n color: rgba($content-text, 0.5);\n\n &:hover, &:focus {\n background: lighten($content-bg, 5%);\n color: $content-text;\n border-left: 1px solid transparent;\n border-right: 1px solid transparent;\n }\n }\n }\n}\n\n#admin-main .grav-editor-hide-toolbar + div {\n border: 1px solid $form-border;\n\n}\n\n.grav-editor-content, .grav-editor-preview {\n border: 1px solid $form-border;\n border-top: 0;\n\n}\n\n\n\n.grav-mdeditor-content {\n border: 1px solid $form-border;\n}\n\n[data-mode=split] {\n .grav-mdeditor-code {\n border-right: 1px solid $form-border;\n }\n}\n\n#admin-main {\n .grav-editor-preview {\n h1, h2, h3, h4, h5, h6 {\n color: darken($content-text, 10%);\n }\n }\n}\n\n.primary-accent {\n .ct-chart {\n .ct-series {\n &.ct-series-a {\n .ct-bar {\n stroke: rgba($primary-accent-fg, 0.85) !important;\n }\n .ct-slice-donut {\n stroke: $primary-accent-fg !important;\n }\n }\n &.ct-series-b {\n .ct-slice-donut {\n stroke: rgba($primary-accent-fg, 0.2) !important;\n }\n }\n }\n }\n}\n\n.secondary-accent {\n .ct-chart {\n .ct-series {\n &.ct-series-a {\n .ct-bar {\n stroke: rgba($secondary-accent-fg, 0.85) !important;\n }\n .ct-slice-donut {\n stroke: $secondary-accent-fg !important;\n }\n }\n &.ct-series-b {\n .ct-slice-donut {\n stroke: rgba($secondary-accent-fg, 0.2) !important;\n }\n }\n }\n }\n}\n\n\n.dropzone {\n background: $form-field-bg;\n border: 1px $form-border solid;\n\n &.dz-drag-hover {\n border-color: darken($form-border, 10%);\n background: rgba(0,0,0,0.04);\n }\n\n .dz-preview {\n border:1px solid darken($content-bg,5%);\n\n .dz-success-mark, .dz-error-mark {\n color: $white;\n }\n\n .dz-success-mark {\n background-color: lighten($button-bg,10%);\n }\n\n .dz-error-mark {\n background-color: $critical-bg;\n }\n\n .dz-progress {\n background: #d7d7d7;\n\n .dz-upload {\n background-color: lighten($button-bg,10%);\n }\n }\n\n .dz-error-message {\n background: lighten($content-bg,3%);\n color: $critical-bg;\n }\n\n &:hover {\n .dz-details {\n box-shadow: 0px 0px 20px -5px rgba(0,0,0,0.4);\n }\n }\n\n .dz-remove, .dz-unset, .dz-view, .dz-insert, .dz-metadata {\n\n background: darken($content-bg,7%);\n\n &:hover {\n &:after {\n color: $content-link;\n }\n }\n\n &:after {\n color: $content-text;\n }\n }\n\n .dz-remove {\n &:hover {\n &:after {\n color: $critical-bg;\n }\n }\n }\n\n\n }\n\n &.dz-clickable {\n .dz-message, .dz-message span {\n color: lighten($content-text,30%);\n }\n }\n}\n\n.gpm {\n\n > table > tbody {\n > tr {\n border-bottom: 1px solid $underline;\n\n &:hover {\n background: lighten($content-bg, 2%);\n }\n }\n }\n\n .gpm-name {\n color: lighten($content-text, 15%);\n }\n\n .gpm-version {\n color: lighten($content-text, 10%);\n }\n\n .gpm-ribbon, .badge.update {\n background: $update-bg;\n a, & {\n color: $update-text !important;\n &:hover {\n background: shade($update-bg, 15%);\n }\n }\n }\n\n .gpm-update {\n .gpm-name {\n color: $button-bg;\n }\n }\n\n .gpm-item-info {\n border-bottom: 3px solid $underline;\n .gpm-item-icon {\n color: darken($content-bg, 2%);\n }\n }\n\n .gpm-actions {\n .disabled {\n color: lighten($content-text, 10%);\n }\n }\n\n .gpm-testing {\n background-color: $critical-bg;\n color: $critical-text;\n }\n}\n\n.pages-list {\n border-top: 1px solid $underline;\n\n .row {\n border-bottom: 1px solid $underline;\n\n &:hover {\n background: lighten($content-bg, 2%);\n }\n\n p.page-route {\n color: $content-text;\n\n .spacer {\n color: $content-text;\n }\n }\n\n .badge.lang {\n background-color: $content-tabs-bg;\n color: $content-tabs-text;\n }\n\n .badge.lang.info {\n background-color: $update-bg;\n color: $update-text;\n }\n }\n\n .page-home {\n color: $subtle-text;\n }\n\n .page-info {\n color: $subtle-text;\n }\n}\n\n#admin-topbar {\n #admin-lang-toggle {\n z-index: 3;\n button {\n @include button-color($update-bg);\n }\n .dropdown-menu {\n background: $update-bg;\n }\n }\n\n .switch-grav {\n background-color: lighten($content-tabs-bg,10%);\n }\n\n .switch-toggle {\n input:checked + label {\n color: $white;\n }\n\n input + label {\n color: $content-tabs-text;\n }\n\n input.highlight:checked + label {\n background: $button-bg;\n }\n }\n}\n\nbody .selectize-dropdown .optgroup-header {\n color: $black;\n border-bottom: 1px solid #eee;\n background-color: #fafafa;\n}\n\nbody .bootstrap-datetimepicker-widget {\n &.dropdown-menu {\n border-color: $form-border;\n }\n table {\n td span:hover, th:hover, td.day:hover {\n background: darken($content-bg, 5%) !important;\n }\n .active {\n color: $content-link !important;\n &:hover {\n color: darken($content-link, 10%);\n }\n }\n }\n}\n\n\n#phpinfo {\n th {\n background: $form-field-bg;\n }\n\n td {\n word-wrap: break-word;\n &:first-child {\n color: $button-bg;\n }\n }\n}\n\n.remodal {\n background: $content-bg;\n color: $content-text;\n // box-shadow: 0 10px 20px rgba(0,0,0,0.5);\n}\n\n.remodal-close {\n color: $content-text;\n}\n\n.remodal-close:hover,\n.remodal-close:focus {\n color: darken($content-text, 20%);\n}\n\n.gm-scrollbar .thumb {\n @if (lightness($page-bg) < 50) {\n background-color: rgba($white, 0.2);\n &:hover, &:active {\n background-color: rgba($white, 0.3);\n }\n } @else {\n background-color: rgba($black, 0.2);\n &:hover, &:active {\n background-color: rgba($black, 0.3);\n }\n }\n}\n\n#page-filtering {\n .page-shortcuts {\n .button {\n @include button-color(rgba(darken($content-text,5%), 0.5));\n }\n }\n}\n\n#admin-sidebar .gm-scrollbar .thumb {\n @if (lightness($nav-bg) < 50) {\n background-color: rgba($white, 0.2);\n &:hover, &:active {\n background-color: rgba($white, 0.3);\n }\n } @else {\n background-color: rgba($black, 0.2);\n &:hover, &:active {\n background-color: rgba($black, 0.3);\n }\n }\n}\n\n.preview-wrapper {\n iframe {\n @if (lightness($page-bg) < 50) {\n border: 1px solid lighten($page-bg, 3%)\n } @else {\n border: 1px solid darken($page-bg, 3%)\n }\n }\n}\n\n#backups-stats {\n .backups-usage-wrapper {\n > div {\n background: linear-gradient(-90deg, darken($primary-accent-bg, 10%) 0, darken($primary-accent-bg, 15%) 100%);\n\n &.full {\n background: linear-gradient(-90deg, darken($critical-bg, 5%) 0, darken($critical-bg, 35%) 100%);\n }\n }\n }\n}\n\n// Log Viewer\n.logs-output {\n table.noflex {\n\n td.level .badge {\n &.error, &.critical, &.alert {\n background-color: #DC3023;\n color: #fff;\n }\n &.notice, &.warning {\n background-color: #E08A1E;\n color: #fff;\n }\n &.debug {\n background-color: #26A65B;\n color: #fff;\n }\n &.info {\n background-color: #22A7F0;\n color: #fff;\n }\n &.emergency {\n background-color: #8E44AD;\n color: #fff;\n }\n }\n }\n}\n\n.permission-container {\n overflow: hidden;\n}\n\n// Range-field\n@import \"template/modules/input-range\";\n","// Colors\n@import \"colors\";\n\n// Variables\n@import \"variables\";\n","// Core\n$white:\t\t\t\t\t\t\t#ffffff;\n$black:\t\t\t\t\t\t\t#000000;\n$light-gray:\t\t\t\t\t#dddddd;\n\n$core-border-color:\t\t\t\t$light-gray;\n$core-border-color-hover: \t\tdarken($core-border-color, 10);\n\n$code-text:\t\t\t\t\t\t#c7254e;\n$code-bg:\t\t\t\t\t\t#f9f2f4;\n$pre-text:\t\t\t\t\t\t#237794;\n$pre-bg:\t\t\t\t\t\t#f6f6f6;\n\n// Shadows\n$core-box-shadow: \t\t\t\tinset 0 1px 3px hsla(0, 0%, 0%, 0.06);\n","// Sizes\n$header-height: 5rem;\n$footer-height: 6rem;\n$border-radius: 4px;\n\n// Font Weights\n$font-weight-bold: 600;\n$font-weight-regular: 400;\n$font-weight-light:300;\n\n// Global Paddings\n$padding-default: 1.5rem;\n$padding-horiz: 7rem;\n$padding-vert: 3rem;\n\n//Forms\n$form-label-width: 30%;\n$form-border-radius: 4px;\n$form-border-width: 1px;\n$form-label-pad: 5px 0 5px 3rem;\n$form-input-pad: 5px 10px;\n$form-select-pad: 5px 30px 5px 10px;\n","// Add percentage of white to a color\n@function tint($color, $percent) {\n @return mix(white, $color, $percent);\n}\n\n// Add percentage of black to a color\n@function shade($color, $percent) {\n @return mix(black, $color, $percent);\n}\n\n@function _linear-positions-parser($pos) {\n $type: type-of(nth($pos, 1));\n $spec: null;\n $degree: null;\n $side: null;\n $corner: null;\n $length: length($pos);\n // Parse Side and corner positions\n @if ($length > 1) {\n @if nth($pos, 1) == \"to\" { // Newer syntax\n $side: nth($pos, 2);\n\n @if $length == 2 { // eg. to top\n // Swap for backwards compatability\n $degree: _position-flipper(nth($pos, 2));\n }\n @else if $length == 3 { // eg. to top left\n $corner: nth($pos, 3);\n }\n }\n @else if $length == 2 { // Older syntax (\"top left\")\n $side: _position-flipper(nth($pos, 1));\n $corner: _position-flipper(nth($pos, 2));\n }\n\n @if (\"#{$side} #{$corner}\" == \"left top\") or (\"#{$side} #{$corner}\" == \"top left\") {\n $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});\n }\n @else if (\"#{$side} #{$corner}\" == \"right top\") or (\"#{$side} #{$corner}\" == \"top right\") {\n $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});\n }\n @else if (\"#{$side} #{$corner}\" == \"right bottom\") or (\"#{$side} #{$corner}\" == \"bottom right\") {\n $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});\n }\n @else if (\"#{$side} #{$corner}\" == \"left bottom\") or (\"#{$side} #{$corner}\" == \"bottom left\") {\n $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});\n }\n $spec: to $side $corner;\n }\n @else if $length == 1 {\n // Swap for backwards compatability\n @if $type == string {\n $degree: $pos;\n $spec: to _position-flipper($pos);\n }\n @else {\n $degree: -270 - $pos; //rotate the gradient opposite from spec\n $spec: $pos;\n }\n }\n $degree: unquote($degree + \",\");\n $spec: unquote($spec + \",\");\n @return $degree $spec;\n}\n\n@function _position-flipper($pos) {\n @return if($pos == left, right, null)\n if($pos == right, left, null)\n if($pos == top, bottom, null)\n if($pos == bottom, top, null);\n}\n\n\n@mixin placeholder {\n $placeholders: \":-webkit-input\" \":-moz\" \"-moz\" \"-ms-input\";\n @each $placeholder in $placeholders {\n &:#{$placeholder}-placeholder {\n @content;\n }\n }\n}\n\n@mixin linear-gradient($pos, $G1, $G2: null,\n $G3: null, $G4: null,\n $G5: null, $G6: null,\n $G7: null, $G8: null,\n $G9: null, $G10: null,\n $fallback: null) {\n // Detect what type of value exists in $pos\n $pos-type: type-of(nth($pos, 1));\n $pos-spec: null;\n $pos-degree: null;\n\n // If $pos is missing from mixin, reassign vars and add default position\n @if ($pos-type == color) or (nth($pos, 1) == \"transparent\") {\n $G10: $G9; $G9: $G8; $G8: $G7; $G7: $G6; $G6: $G5;\n $G5: $G4; $G4: $G3; $G3: $G2; $G2: $G1; $G1: $pos;\n $pos: null;\n }\n\n @if $pos {\n $positions: _linear-positions-parser($pos);\n $pos-degree: nth($positions, 1);\n $pos-spec: nth($positions, 2);\n }\n\n $full: $G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10;\n\n // Set $G1 as the default fallback color\n $fallback-color: nth($G1, 1);\n\n // If $fallback is a color use that color as the fallback color\n @if (type-of($fallback) == color) or ($fallback == \"transparent\") {\n $fallback-color: $fallback;\n }\n\n background-color: $fallback-color;\n background-image: -webkit-linear-gradient($pos-degree $full); // Safari 5.1+, Chrome\n background-image: unquote(\"linear-gradient(#{$pos-spec}#{$full})\");\n}\n","%button {\n\tdisplay: inline-block;\n\tpadding: 0.5rem 1rem;\n font-weight: 400;\n cursor: pointer;\n vertical-align: middle;\n outline: none;\n\n &:active {\n margin: 1px 0 -1px 0;\n }\n\n i {\n //margin-right: 5px;\n }\n\n &.button-small {\n padding: 4px 8px;\n font-size: 95%;\n }\n\n &.button-x-small {\n padding: 2px 5px;\n font-size: 90%;\n }\n\n &:disabled {\n opacity: .6;\n cursor: no-drop;\n }\n\n}\n\n@mixin button-color($color, $text:$white, $lighter:null) {\n\tcolor: rgba($text, 0.85);\n\tborder-radius: $border-radius;\n background: $color;\n\n @if ($lighter == null) {\n $lighter: lightness($color) > 50;\n }\n\n @if ($lighter) {\n &:hover {\n background: shade($color,15%);\n color: $text;\n }\n &.dropdown-toggle {\n border-left: 1px solid lighten($color, 5%);\n }\n } @else {\n &:hover {\n background: tint($color,15%);\n color: $text;\n }\n &.dropdown-toggle {\n border-left: 1px solid darken($color, 5%);\n }\n }\n}\n","$ir_slider_width: 230px;\n$ir_slider_height: 20px;\n$ir_counter_width: 60px;\n$ir_margin: 10px 0;\n\ninput[type=range].rangefield {\n display: inline-block;\n vertical-align: middle;\n -webkit-appearance: none;\n margin: $ir_margin;\n width: $ir_slider_width;\n background: none;\n\n &:focus {\n outline: none;\n }\n &::-webkit-slider-runnable-track {\n width: $ir_slider_width;\n height: $ir_slider_height;\n cursor: pointer;\n animate: 0.2s;\n border-radius: 25px;\n }\n &::-webkit-slider-thumb {\n height: 24px;\n width: 35px;\n border-radius: 6px;\n cursor: pointer;\n -webkit-appearance: none;\n margin-top: -3px;\n }\n &::-moz-focus-outer {\n border: 0;\n }\n &::-moz-range-track {\n width: $ir_slider_width;\n height: $ir_slider_height;\n cursor: pointer;\n animate: 0.2s;\n border-radius: 25px;\n }\n &::-moz-range-progress {\n height: $ir_slider_height;\n border-radius: 25px;\n }\n &::-moz-range-thumb {\n height: 24px;\n width: 35px;\n border-radius: 6px;\n cursor: pointer;\n }\n &::-ms-track {\n width: $ir_slider_width;\n height: $ir_slider_height;\n cursor: pointer;\n animate: 0.2s;\n background: transparent;\n border-color: transparent;\n color: transparent;\n }\n &::-ms-fill-lower, input[type=range]::-ms-fill-upper {\n border-radius: 50px;\n }\n &::-ms-thumb {\n height: 24px;\n width: 35px;\n border-radius: 6px;\n cursor: pointer;\n }\n & ~ input[type=number].rangefield {\n background: none;\n display: inline-block;\n width: $ir_counter_width;\n text-align: right;\n border: 0;\n line-height: 16px;\n vertical-align: middle;\n padding: 0 0 0 5px;\n }\n }\n\nspan.range-append {\n display: inline-block;\n line-height: 20px;\n vertical-align: middle;\n margin-left: -3px;\n}\n"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/user/plugins/admin/themes/grav/css-compiled/simple-fonts.css b/user/plugins/admin/themes/grav/css-compiled/simple-fonts.css
new file mode 100644
index 0000000..2526cf9
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css-compiled/simple-fonts.css
@@ -0,0 +1,3 @@
+body,h5,h6,.badge,.note,.grav-mdeditor-preview,input,select,textarea,button,.selectize-input,h1,h2,h3,h4,#admin-menu li,.form-tabs>label,.label,body .CodeMirror pre{font-family:"Helvetica Neue", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif}code,kbd,pre,samp,.mono{font-family:"Monaco", "Consolas", "Lucida Console", monospace}
+
+/*# sourceMappingURL=simple-fonts.css.map */
diff --git a/user/plugins/admin/themes/grav/css-compiled/simple-fonts.css.map b/user/plugins/admin/themes/grav/css-compiled/simple-fonts.css.map
new file mode 100644
index 0000000..a573da9
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css-compiled/simple-fonts.css.map
@@ -0,0 +1 @@
+{"version":3,"file":"simple-fonts.css","sources":["simple-fonts.scss"],"sourcesContent":["body, h5, h6,\n.badge, .note, .grav-mdeditor-preview,\ninput, select, textarea, button, .selectize-input,\nh1, h2, h3, h4,\n#admin-menu li, .form-tabs > label, .label,\nbody .CodeMirror pre {\n font-family: \"Helvetica Neue\", \"Helvetica\", \"Tahoma\", \"Geneva\", \"Arial\", sans-serif;\n}\ncode, kbd, pre, samp, .mono {\n font-family: \"Monaco\", \"Consolas\", \"Lucida Console\", monospace;\n}\n"],"names":[],"mappings":"AAAA,AAAA,IAAI,CAAE,EAAE,CAAE,EAAE,CACZ,MAAM,CAAE,KAAK,CAAE,sBAAsB,CACrC,KAAK,CAAE,MAAM,CAAE,QAAQ,CAAE,MAAM,CAAE,gBAAgB,CACjD,EAAE,CAAE,EAAE,CAAE,EAAE,CAAE,EAAE,CACd,WAAW,CAAC,EAAE,CAAE,UAAU,CAAG,KAAK,CAAE,MAAM,CAC1C,IAAI,CAAC,WAAW,CAAC,GAAG,AAAC,CACjB,WAAW,CAAE,sEAAsE,CACtF,AACD,AAAA,IAAI,CAAE,GAAG,CAAE,GAAG,CAAE,IAAI,CAAE,KAAK,AAAE,CACzB,WAAW,CAAE,iDAAiD,CACjE"}
\ No newline at end of file
diff --git a/user/plugins/admin/themes/grav/css-compiled/template.css b/user/plugins/admin/themes/grav/css-compiled/template.css
new file mode 100644
index 0000000..17dc855
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css-compiled/template.css
@@ -0,0 +1,7 @@
+#admin-login,#admin-main .titlebar h1,#admin-main .titlebar .button-bar,#admin-main .danger.button-bar .button,#admin-main .success.button-bar .button,#admin-dashboard .chart-loader .fa,#admin-dashboard #updates .numeric,#admin-topbar #admin-mode-toggle,#admin-topbar #admin-lang-toggle{position:relative;top:50%;-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%);-o-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.button{display:inline-block;padding:0.5rem 1rem;font-weight:400;cursor:pointer;vertical-align:middle;outline:none}.button:active{margin:1px 0 -1px 0}.button-small.button{padding:4px 8px;font-size:95%}.button-x-small.button{padding:2px 5px;font-size:90%}.button:disabled{opacity:.6;cursor:no-drop}.switch-toggle{display:inline-block;display:inline-flex;overflow:hidden;border-radius:4px;line-height:35px}.switch-toggle input[type=radio]{position:absolute;visibility:hidden;display:none}.switch-toggle label{display:inline-block;cursor:pointer;padding:0 15px;white-space:nowrap}.datetime-picker-wrapper{position:relative}.datetime-picker-wrapper input{padding-right:2.5rem}.datetime-picker-wrapper .field-icons{position:absolute;top:0;right:10px;bottom:0;line-height:2.5rem}.collapse{display:none}.collapse.in{display:block}tr .collapse.in{display:table-row}tbody .collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease,visibility 0.35s ease;-moz-transition:height 0.35s ease,visibility 0.35s ease;transition:height 0.35s ease,visibility 0.35s ease}.sr-only,.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after,.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after,.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after,.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after,.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after,.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after,.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after,.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after,.bootstrap-datetimepicker-widget .btn[data-action="today"]::after,.bootstrap-datetimepicker-widget .picker-switch::after,.bootstrap-datetimepicker-widget table th.prev::after,.bootstrap-datetimepicker-widget table th.next::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.bootstrap-datetimepicker-widget.dropdown-menu{position:absolute;z-index:1000;display:none;float:left;min-width:160px;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid rgba(0,0,0,0.15);border-radius:4px;box-shadow:0 6px 12px rgba(0,0,0,0.175)}.bootstrap-datetimepicker-widget .list-unstyled{padding-left:0;list-style:none}.bootstrap-datetimepicker-widget .collapse{display:none;visibility:hidden}.bootstrap-datetimepicker-widget .collapse.in{display:block;visibility:visible}.bootstrap-datetimepicker-widget .datepicker-years .picker-switch{cursor:default !important;background:inherit !important}.bootstrap-datetimepicker-widget .table-condensed>thead>tr>th{padding:5px}.bootstrap-datetimepicker-widget table{display:table}.bootstrap-datetimepicker-widget table thead{display:table-header-group}.bootstrap-datetimepicker-widget table tbody{display:table-row-group}.bootstrap-datetimepicker-widget table tr{display:table-row}.bootstrap-datetimepicker-widget table tr:hover{background:inherit}.bootstrap-datetimepicker-widget table tr th,.bootstrap-datetimepicker-widget table tr td{border:0;display:table-cell}.bootstrap-datetimepicker-widget table tr th:first-child,.bootstrap-datetimepicker-widget table tr td:first-child{padding-left:inherit}.bootstrap-datetimepicker-widget table tr th:last-child,.bootstrap-datetimepicker-widget table tr td:last-child{padding-right:inherit}/*!
+ * Datetimepicker for Bootstrap 3
+ * ! version : 4.7.14
+ * https://github.com/Eonasdan/bootstrap-datetimepicker/
+ */.bootstrap-datetimepicker-widget{list-style:none}.bootstrap-datetimepicker-widget.dropdown-menu{margin:2px 0;padding:4px;width:19em}@media (min-width: 768px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width: 992px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width: 1200px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}.bootstrap-datetimepicker-widget.dropdown-menu:before,.bootstrap-datetimepicker-widget.dropdown-menu:after{content:'';display:inline-block;position:absolute}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before{border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,0.2);top:-7px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;top:-6px;left:8px}.bootstrap-datetimepicker-widget.dropdown-menu.top:before{border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #ccc;border-top-color:rgba(0,0,0,0.2);bottom:-7px;left:6px}.bootstrap-datetimepicker-widget.dropdown-menu.top:after{border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #fff;bottom:-6px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after{left:auto;right:7px}.bootstrap-datetimepicker-widget .list-unstyled{margin:0}.bootstrap-datetimepicker-widget a[data-action]{padding:6px 0}.bootstrap-datetimepicker-widget a[data-action]:active{box-shadow:none}.bootstrap-datetimepicker-widget .timepicker-hour,.bootstrap-datetimepicker-widget .timepicker-minute,.bootstrap-datetimepicker-widget .timepicker-second{width:54px;font-weight:bold;font-size:1.2em;margin:0}.bootstrap-datetimepicker-widget button[data-action]{padding:6px}.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after{content:"Increment Hours"}.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after{content:"Increment Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after{content:"Decrement Hours"}.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after{content:"Decrement Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after{content:"Show Hours"}.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after{content:"Show Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after{content:"Toggle AM/PM"}.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after{content:"Clear the picker"}.bootstrap-datetimepicker-widget .btn[data-action="today"]::after{content:"Set the date to today"}.bootstrap-datetimepicker-widget .picker-switch{text-align:center}.bootstrap-datetimepicker-widget .picker-switch::after{content:"Toggle Date and Time Screens"}.bootstrap-datetimepicker-widget .picker-switch td{padding:0;margin:0;height:auto;width:auto;line-height:inherit}.bootstrap-datetimepicker-widget .picker-switch td span{line-height:2.5;height:2.5em;width:100%}.bootstrap-datetimepicker-widget table{width:100%;margin:0}.bootstrap-datetimepicker-widget table td,.bootstrap-datetimepicker-widget table th{text-align:center;border-radius:3px}.bootstrap-datetimepicker-widget table th{height:29px;line-height:29px;width:29px}.bootstrap-datetimepicker-widget table th.picker-switch{width:145px}.bootstrap-datetimepicker-widget table th.disabled,.bootstrap-datetimepicker-widget table th.disabled:hover{background:none;color:#ccc;cursor:not-allowed}.bootstrap-datetimepicker-widget table th.prev::after{content:"Previous Month"}.bootstrap-datetimepicker-widget table th.next::after{content:"Next Month"}.bootstrap-datetimepicker-widget table thead tr:first-child th{cursor:pointer}.bootstrap-datetimepicker-widget table thead tr:first-child th:hover{background:#ddd}.bootstrap-datetimepicker-widget table td{height:54px;line-height:54px;width:54px}.bootstrap-datetimepicker-widget table td.cw{font-size:.8em;height:20px;line-height:20px;color:#ccc}.bootstrap-datetimepicker-widget table td.day{height:29px;line-height:29px;width:29px}.bootstrap-datetimepicker-widget table td.day:hover,.bootstrap-datetimepicker-widget table td.hour:hover,.bootstrap-datetimepicker-widget table td.minute:hover,.bootstrap-datetimepicker-widget table td.second:hover{background:#ddd;cursor:pointer}.bootstrap-datetimepicker-widget table td.old,.bootstrap-datetimepicker-widget table td.new{color:#ccc}.bootstrap-datetimepicker-widget table td.today{position:relative}.bootstrap-datetimepicker-widget table td.today:before{content:'';display:inline-block;border:0 0 7px 7px solid transparent;border-bottom-color:#fff;border-top-color:rgba(0,0,0,0.2);position:absolute;bottom:4px;right:4px}.bootstrap-datetimepicker-widget table td.active,.bootstrap-datetimepicker-widget table td.active:hover{background-color:#fff;color:blue;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget table td.active.today:before{border-bottom-color:#fff}.bootstrap-datetimepicker-widget table td.disabled,.bootstrap-datetimepicker-widget table td.disabled:hover{background:none;color:#ccc;cursor:not-allowed}.bootstrap-datetimepicker-widget table td span{display:inline-block;width:54px;height:54px;line-height:54px;margin:2px 1.5px;cursor:pointer;border-radius:3px}.bootstrap-datetimepicker-widget table td span:hover{background:#ddd}.bootstrap-datetimepicker-widget table td span.active{background-color:#fff;color:blue;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget table td span.old{color:#ccc}.bootstrap-datetimepicker-widget table td span.disabled,.bootstrap-datetimepicker-widget table td span.disabled:hover{background:none;color:#ccc;cursor:not-allowed}.bootstrap-datetimepicker-widget.usetwentyfour td.hour{height:27px;line-height:27px}.input-group.date .input-group-addon{cursor:pointer}.gm-scrollbar-disable-selection{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.gm-prevented{-webkit-overflow-scrolling:touch}.gm-prevented .gm-scrollbar{display:none}.gm-scrollbar-container{position:relative;overflow:hidden !important;width:100%;height:100%}.gm-scrollbar{position:absolute;right:2px;bottom:2px;z-index:2;border-radius:3px}.gm-scrollbar.-vertical{width:6px;top:2px}.gm-scrollbar.-horizontal{height:6px;left:2px}.gm-scrollbar .thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit}.gm-scrollbar.-vertical .thumb{width:100%}.gm-scrollbar.-horizontal .thumb{height:100%}.gm-scrollbar-container .gm-scroll-view{width:100%;height:100%;overflow:scroll;-webkit-overflow-scrolling:touch}.gm-scrollbar-container.gm-autoshow .gm-scrollbar{opacity:0;transition:opacity 120ms ease-out}.gm-scrollbar-container.gm-autoshow:hover .gm-scrollbar,.gm-scrollbar-container.gm-autoshow:focus .gm-scrollbar{opacity:1;transition:opacity 340ms ease-out}.gm-resize-trigger{position:absolute;display:block;top:0;left:0;height:100%;width:100%;overflow:hidden;pointer-events:none;z-index:-1;visibility:hidden}.g-colorpicker{position:relative;border-radius:.1875rem;max-width:100%}.g-colorpicker input{width:100% !important}.g-colorpicker i{position:absolute;top:15px;right:10px;z-index:2}.g-colorpicker .g-colorpicker-preview-wrap{cursor:default;position:absolute;top:0;right:0;width:43px;height:43px;border-radius:0 3px 3px 0;border:1px solid rgba(0,0,0,0.2)}.g-colorpicker .g-colorpicker-preview{position:absolute;top:0px;right:0px;bottom:0px;left:0px;border-radius:0 3px 3px 0}.cp-sprite{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA2YAAACWCAYAAAC1r5t6AAEuWklEQVR42uz9a8xt25YVhrU+1ner7qseLiEjhERwfkDFeWAEl6dCQcAUCBDCwUSJwg+jRPIzgGVZMcZ2DCKyIycxiSOi2JbMr8hBgFNVGKNAHgKCTBnbUYCYEsHYIoiKKuYW9zzu2XvP0fNjjUfrbfQx5/r23ufWPnX2PvrOWmvOueYc87HmHG201luzv/GzvstvVmG4/3N39H8GAwzAnASHw8zgDpjRdAcOFPz0v/J1mvrm/374h3+48Oevfe1rOh/PnF/xdv+5TvgLf+EvLAv9vJ/38/ATsdzP/bk/l9tZ6c/l/XEyr8/3B9ZT3X07r/1hM/04+U62XW1X2ka/X9Rn63l0e33fHmnLbtvhONOxqiffw9m+9HW4+9h+X87dR5vbv4M+11prHW/mP3/16lU9jqO+fPnSP/nkk/rxxx/XDz74oP7Yj/2Y/8iP/Ej9F/7l/8lLfAXAVwB8mV75L5v26LwvAh8X4EMAHwH40O9//P5Dm58/wn3ZD/pnu7//AMA3APw4gB9ty8GSX++Y9iXAfyqA7wbsOwH/jtYg/vvquiP+ZcC+StO+dJ+GrwDHF+4N+tCBj+3+NxrdduJjzJ3t0z+k6R+01w8B/B0AXwfwX2R3H6AA+J7291UAX4Xjq7DldH0Fjq/A8GV425v7+/s00PRxSnDDJ9TQj0ejDB/D23RrO+Ft+n3+R+F17tQ32s58HUCFHzWen7d9p7Zv0cre6rZ+QnbwJ6AZ9MVnrGMu2t+tX7bvKOnPNnz+0sl96er+9kWEX8ZH9P7Di/f9l6D3q/9ve3/+7zsB/FQA39Xef0f71ev9Sm/U8U4Qpr26xR3Iduijzfv++QO6Z32j3av+Nj3N6N+3Afi72x58B7X4q9JCPkVfkcOfff42AMCLTcO1wWdn7IPkfvW3743/o2/xB/cE4MmAL2D+PXl7tfv78NrmP9F3nxy4GQ5zvALwCoYDwCsAB7y9WpvnOML87LUv4+174/NT+/xLDthX27LffwD/JV0n/+n65zbw1w7Yn2yfv3HA/lzb5qtX67bHfvB613Va2O/dsXA8wfAExxOAG9A+zwP7BThusPYKfAEWTxIcX2jffUuXwk/HJ4DX/S3PLZ9mhMh6z8YNZvZWnwx//s//+bf9pHkHnlzfun+1VrRr8VFAspvn1Ol/k/U8GwwlgITbA26btNN3856zzBusiwYunHsOBsDatPQzvS9t/8PASfbq7n1Zb5/HX1/mOI7Spo1lGhDDcRx49eoVXr165S9fvsSLFy/w4sUL//jjj/HBBx/gx3/8x/G3/tbf8h/5kR95rLeU/HkG7elMO51Zr3rhbQ6uzRejASNr/7PWHitJG4v27qwt2E6LtVcvbXppG7f1z6gxTt+1Ns/ae8fcsOkdSXbGbV3Ozu9i/aKZLbOweAm7baMza2NJH9+6z3VaJ+9zRLVlLD2/c35hrONbDofXdujaOeFu9iP99dNlfF3Q274/H2P4g0N2vj56rnbkdcCNt2vmbQKr1wJZ/bo9+/JunofB3kfPtS/fr3Qtzp/uuJD1D8uPJv6Q9Admj/UoXL6S/Yz7342ac3u4m9c7j7dkB3jndjvzGsPPdvEH2oki72u+B9miu9XuDr8/66J+ZGcgF8kNsNs8O3Z8nrqSX76PVuL77jjafmMjb34RYF+6vy/hmVPGrzBekbW93h/5Tsv572xn5EMAf76dgz8K4McA/F/akORHn4eD/XQfV5VfS+/ZKC0We5qzwzGuewPwN98q8Pna175mb8iQfa6BGTOgz1yWAUJpAxHt8rC3ts0z4IJ9l9Toe/UChNtVm2jesm1337alzSsEVvV54SfgqzSGq7ehgypdDjTNGtgO66O/oy/XAJe5u7XXDsxqm4fjOFBrtfbeXr16Za9evSovX770Fy9e+CeffGLf/OY38eGHH9o3vvEN+/rXv24/+qM/ih/7sR8zz35JHVBhgiG+XVwCNY8Ard7HelB9351Huw110BZm2WwPdn1Wz3p5Gb52mZ5darxTm1uNKyponVjfdfapk+s21+2vdxuzDn7aJ0sOgtOrJ03vc9bT760rzHN17CTrLIn0wufjxNu+ejsvxnvRgLC5w3UPze64tnfPra+HwG77yfK6nbv5xmOTNpFCmN1b5APOTqjHx7kddeNz5+OaXLbL63I0lYrPdVGb5jctXHtm/Vje97t42HRsedj8fVvG5JVbU8vMTYz9Nx6c9fBrsAC6+8CHj9/tvP9mR65dTeZ0PzEB0u1Y+Bxc6Oc4rL8kIxY7sGXJz1e/43t87gkgQ7Jq7bDqwMrTQ7/mpw2oKEmDffcYze9VdoJfrnYo25myh5ZFxsjKCVQ6G5/yizvfeWOxOStlDtZZaeDsJ3038osAfjaA7wfwXwHs1wL2RYN9l4VBuzscm09GC5KhOI9BmY/391cf593hXynwX9GA269og3xftzsp/e8C+MsA/k8A/l+NEv3JCMy+C7B6/sMcd2JbAVlY9u0Ds0/hF/B5ZMweAUV6p/LnAK8N8HkEZIHATxhT6+vsQFAAFOi7fTmTZXwDNHcADFfATJfj7XFb5HvhcwNObmaF2KxKoCoFZg2QIQNpDYDd7pPqYMRqrf3vrmM8Dj+Ow2ut3hiy2l7tOA57+fIl2l/55JNP8PHHH/sHH3yAv/N3/g5+/Md/HF//+tf9gw8+CEM5jgmsLMMw9NkSMLaAMwJmFe2VcElt/TCvE7ghYdX4SnbIIL7vrhJPAFRNgJogSdR7Q8YOtmnmQOWdcfoqIcoOzsJ7BmXc+b1mRjJQtVLMVR6a1s7rBBQV3qZ7W+ZoU/qjtT+OK33LCbx56JjPLncEgsbAFkYsr7ULAksXv19vlad1YC1gbZDZnowYeNjyipEds9PvK4BFwMtzG3RnAN8exzbGaTUaW54jCR0c3XcnwuJ5Mce23MHs/cfhPNDQLruJeH2AngD4x2/Hm5CmL9v2k7oK7tbOu9GPOIP30pfwDjh9gfV92GACQKdDwmebAKj7OMbekLShtvtCO07KkFny2RJEgAQ1IQcndgF7rv60OSck04aWKgnytM10CPjwPclkZ0OeJ0RdETrwtoeWJVnMNntjD+DB65254jIZiLH6oRBr9uonW3fxSwD+mwB+PYBfDdjPLiioA3yZ3NXX1yqMGT8huYNnBNBW9iy+lvuT5rsNjgL/h+rc4n8C4E8A+CEAfxZ3bf1PEmBm38nDZ3l3vJjchHyzrH0WgNR7YLYCsvPBpmsQtrtX+gMMmm9A2hlQ8k27+Dm2kwyeMmEbIHYGzFy27y49DmLTOnM11snAirY/ANYdazqfS+/va63eARsDtVpr6V9qrBg6GOt/r1696sAMx3F4B2QvXryoL168wMuXL8vLly/x0Ucf+QcffIBvfOMb+MY3voEPPvjAP/roI0LPiKUhZ4jAG4hSfFMnGGNpY/UJyjrBUQnP9PkO6m9b7P+5EmGgJ0NKUFnojId7njPwYtAm83ln7ADqrTW2s2QdpNUVhDnp91xqbnB2711/UFcAbf3z8YD0AMYqFTs6jXdmpagd3jHn4QKpnDrWHrvZdc67E1Se7KqFNclNIDkez1ANnM7ziy9Zun09Ab5dIBvwum6pL8v7+Q65zs9Y2mQFvrK+ft7ITTv8ep927dqdFd+dKT8HD0qOnNE02yfcvnUZaDhTTKqU8RyYMZR5RL6oSNOxlfj5BRjDBshmgIx3Kvl3S1b1iKr0SmH6WBcF+ZZNQJkpWHt79UQ/wf++DcAvBPDfAezXGexn3ve0DPjTQdmUJzJL1sGYEdiyFJA5saGRQWP2LANnE6D5+OwowPdW1O8F8NsN/tcA/2MA/g8A/n0ALz/jwOyr8ZdoOx1u6GoDKmH47ACpt7q+d8noI1vuww8/3B6HM5DzpuxaIovc3R3LlRxRwNCWMRO2LZM92hVoOwNmm/cdBBmAgxiwsH7+LBLIgODa50qAC8SIjScJAbPBijUTDzQvjw7SrNZaGJQdxxGAGdeUvXz5Ep988ol/85vfrC9evLAXL17Yhx9+iP738ccf+4sXL6b6zqNsyXFJ06wyRtU6tPoyL+0VAtCYFevLYYK1paNqcewpkDPZVRoka77pyPKONGYMjR1j1sylWK4StbesypNiOpbe9fvu479aXawiShl9/FeI50JjyjLwVsNaLIV3SN531ikyXwtzlgIr2yADEh/aZIOss2BlldY1jiVI5Dy5DuL0uyzQCfXPzTk86AMn6zXWYSt5bwIhWPjY98PhKE3COOZ7Gyjtpd4ygGBc3hVFjunl7jyeOrZTSUcqkkUdw7V+zgpxXjlJYR7PAYg9DW02D4TwfT8jRF94D4vnK4COMzbsTerJNmVyV+Vn9uDfifqPAMXTBZQ52xHbt/xsv0sCZIFznablwOwm+M1OYKTCqOd16Naa2P2ZS+qCTWuPP/PA7O8B8NsB/BrAfrahNCBUiB3jv1mPXNoxqu39TsroWKWMJFcMIE2kjAGU9fkdwFmDg6UByPv0+l8uwD9RUf+JxqT9uwB+P4D//LMJzPAVqSPzeLfTIT7LLnRQjRnetitjWN9bcGX83NeYPQrImAzCXmF/xogtrNIDbVTQ5AlQc3lMVGH/kGyTvzeAUqvdGCDVzALLmEkK5b2Cq/A9BlZmZg04mZkNRqtJNcc8RMnjaB/Vinlr45je5+n74zisyxYbc1ZqrUO+2P7w8uVL60DsxYsX+Pjjj+2jjz6yFy9e+De/+U3rfw28WaV+TyWABsIkdlJDBsItOm1IGQmbBFxjMv2I8kVWBzKZtQU0JqArW9aUDpSdcmq4yhm5SK5mO+OJlJGli1V2Jlzpyy1XuqULZzUfnj64r7tEsT9YPcXLtQGzLmOcnFo8FixzNGLY4pq3IzoJsDxnWMJdwn0eqjqPoYvMjhR+6/PMV04quxX5jqEiBOJB/+crozMesQpqGkvuKzNoXdrosTbNWK64YdVCK8KF4qMd8zqjWj73nKwdk+vmfM4foidSx1G6N/alBnDpY7/8nDtz5VY9NrAkjM4ZUCs4N9zxcyLPHhyVzMimGx41APlCQlGdcU72jJ262AE8uDN8rG/rfZXLz3a+LHYC0kyua7sci39AFFmsbZiZM2phueU789n49/0Afitgv6GgfOcd7qBBISMDpxyYObFl+uoC0KqwY7HGLK0tWySMfZDQhDkrYyDIx+f7q6EA31tQv/eA/zbAfxDAHwTwpz5jjNlXhClrd0JQPRlffLb7CfjnkjF71/+plPFRYw4BOsH840FW7AyQGfZ1XX5iQmJYDT14B5l9S7fBJiMNIAV2q9WpqUlHPQFmvM7Ong3mi4EZyxW77LGfo2Zrv8gc24oK1Yvxd5xYsd6OWwNh3pm04ziGlPHVq1fHcRzWppXEhbEzZvjkk0/w4YcferPMxze/+U28ePHiDvIyXwthyHrJFTyZX3OWbPSlapQy9lqyGvt6iTUmqQGlP+w7m/yAYoQuGexZAsIyCnAsWyc4qzVT/LWdqrNgrsscO02o6DLrFW86B+fWG56aqXRGjBWlnO1QxzipD7FjZt5qtKOeyhiHrcPS9uJ+RkZgsVRHNAnO+pcuRiX500vZO0tHoyLTZcsajKwEPT0DlvxobJYN2vned7BmDAJ1t7PNJJd6IOhS1aDnYwHPHx7cn8WkdvARNWZs+IT8tvtGVo51pp87Q1TAtrjJkjP9CDTKJI2dNTsdV1+0gmfVbRmUOWHQrurLzgCtHtfbHpjdTr5q+0O9Zc4svVAcl1V/1kAZvw6mrESAZp85YParAfunDPb33yWJpd3NI0PGssVu7JHXmOV1ZqusMZc07pwZy6g5W6WMNcgYfXyuAULOPSjw7y6ov/WA/1bA/z0A/3MAf/IzAsy+eg5hgtEH2WWF9++B2WcAmPmGcUqPUQMOx4PATQZ7PXssVuTySce5MYera6LIFzOQZiplTEBVYLS6cUhntjrjVErBcRxWSkGt1XochDgldpnhIWxZqClz91H7lQCxwZi5+43BYJMm9m24uxeWLrLBR8sh6+sqDMxIwuivXr3qWWV2HId1UMbArAOxjz76qH7yySel1aH5y5cv76ALOYnDSj3bIQBmshSwHRNgdSKpNsliNzHobFlkHbA6dVcZb1p+IBmVIA31jdVkeOg3tiwAuP56TIBVM8MPp7bUiCC1/ox/duZSXOfSDVkL3Z1g2XycRQljtOxAUiVWlxoxPqC+HNy5M0ZCSm7j8ET0XSVXNOy4g7FuImHDyy+4J7aLYTCptMXq3VTIA8DzzGLP+jZ7WbsPfsgaOBikU5M2GuZrl9MxhLBFxCkAyWvb3uzAhFPeZJOsujWqMHAFWEZbdumqGqhVzeWyNcTNmjcYc3qWYmTmxYzRstEP2eQ69JaLOtq/gYByg7HmvBkB5J2XNcT1DF/hgnMDw3KCY4CHLQDtBCRcGYIohjwHZjeBNVcwcAfWtiMaj6Cex0Fad/Z/EfcgA2daxmcXOPn53T4x/xh0XQdmBMR6P3jEp3S7/PMKwHcHkOGfMdgvt8YnRSBWgAC+CgGtEhiyCNQQXlfDD9vWmJ2BMn2dIC2TMjKLVgNoK+0+bYNJq7/GUH8N4H8SwL/0rjNoTyhfiUXmqsNV0bjRxHCXiYr198Ds3fiXyeweAFu5M/nKZJ2ZezDQqifrGnc3XQ/Vbu3YNCfWiwFXb9eI1esmG02q2GWL1hmoBNChyQSHu+HGwr4AcF6PAjN67yR1LA2chfqzxnwNEKuSxQa2uvNisMTnurLOmjUpY7fE7+6LvbbMXr58aQ2sBSkjv+8SxlevXpVXr17VWqu5jmyLJ8ZigpdJFp1wTDK9lgbI+tdJFUiGcdHcEBO8YWOjv1BKi6RLUKQx2rz483p3uWUnk278EXSYmAjTFbCJEUgCTKKUMed2qgA1p2ynWVvGn7sI0ZHHzfWHY8U0+dibgOTHiC37l65+vF+d9c1rQDFY6tkI4HQAE1wXfQPCBAFVI9Nin0ctdPp5XR6h1oDAnngWbnLaVA5ZEyZvsm2rX4wtoxPRjdKVIwxmHr5KQxfHEqbFJwCrmGb2oQSCt+3MlsZj5zwQYSuTOL9r0XQkXkBTeskDNWdZZVks35XFIvaEiV10Oq6cGdk34+mUE39KYE2m2TyzxbjwNXxEf3n1WdnKhPMzrBYmWenfI+SlP+voNzBWmtFHlzCmUkZizsbrO/vv+wH7Jw32q0uDLROQFbK5LwvP1M0/dkxZEVOQgsyhESJltADE1Dqfa80mOJtM2Wz5lDJGpsxEfGkE0ipsQNL6qwz1VwH444D/L95VBu0J+BKNCGykELscSEtHmN92jlm4+t9Cjtlb5Z7fJaOPbLmf+TN/pjJLZzb4Z46H6SPppD7syjkxq9EyYcUCaOsyQ0zZYXH3w/uoq7gyErCDvA+DcSwzFEbOxMSjgylm77iubLgyErgKIK4DOAZlCs6ojoyBWVEb/OM4nNi0wiCySRdxHEcl6aJ1R8b2B2LB6nEcpYdKdyOQzpB9/PHH9eXLl3j16tWdhduwYZ5YABr3tTYh0+6IurnuMu9kmV8jCGMHele2zpJ2GXJNV5V5UIt6sr73BEX2HejzOzDrr0PKSH7/AcNYRJwBy1g0AFksMFfgNmOe14QyJ0ARxYZs62HD/EP/Vs/GrMaMoQRb64MsH5C+M2/jr078ls2TVjsbZTZc9I1gRjeKGEBg+s038DLjBmKG2MqUWlvWMZWmDCDv22Mj927VzkxSq91qpiQ1jGFOBqu2Hwrve8g5s3lNkkm9mHKQnb+RlSmxYib1ib5oCi068Te2zQbgkZjTxvC6cbs8wHBjhtOBap6w2BZjU+/2R3c21Jpb58iiq0AAbNbaNY/n/bDX1nYssVRbm/wzaSMuGDWVgCA1YN9ucleWlUtXdtVZZ6LJgtylMev0nYz7ZMjoEXmoADPDuYwx++pVAtu55Db5Vq8nKwBuvYZMZIxql9+ljP5OGoD8PQD+OUP5h6azYkmki4WcFudnFQUym1YDCMtkjcyinWWYxfoydWZUUKaujBZqy7TGrI7PnVlj0FaGSNN/LVB/LYB/HcDvA/CfvVvAzL4cLY2MmbKTgGmeHwvj3zNm79C/Z9SY2QVoKyfM184eP3M/VDt7BUoLOMJqBKL5YUAcXDYNZRagZhvXxPCeAVObXsXWfqyzyQ+HlFGAVmmvNZM50nwnaSRb6aNLFLPg6A7AiDHrLoxgS/wG1soGmOHly5f11atX5cWLF+zS6I1dQ5dB1lpn+VPiuOYEyAJ7tguVrjlz5uQsP9wZyXlxIZ8Q5YzBQ0OxDT/B2T6/GharSQjqWyzzJQ/AfAVmXCTHhXLV84K54PuPyUA4We4bdbyRktkLy7KKEI1U+pHR8QWcNXOGUImWGX9AODggqznLbEpKjUyajxNXhW3y4UpYOXC6ChO2s4Zn4wwjRotzwtXt0GMJIrs0pmwYnw+vi7zQ6buTlPUwxtmBH2pinNGBYaVlnbdP13KN28zMTgJoFmtTF4bOwL8vNg5ZTTgiq8iB4EaB0nX8Jrw5PTr9mJ3zzFyPs5M81RcDlPCEup3QMQXnQckP+rPbA6+6yZ3LfBcrrsDshuuiuUfYss2Y9XNK1XYOl1kGAFGABXf7kiyJDc/YC1yelqSBnYy4dXAmdWWFbfPJLt/ajrx7wOzbAPxjcPsX4eU7ipUFhOUAbfJLuRujETNmQ4RdBuSBhE1HN8Yql8SjUkaWMM5pHurMatpaBmF1QM/SFB4diHaQ5sD/sMJ+C4B/DsAfwDsSTvcE+9LU0Ya7tK3Twkgt1nyzeyfhbfO7bxtIvdP886cFzNRt8EFQlppsMChqTFUAZMRseRIS3X+HnkgXFeA5rYvrv1xZPq4N659l/xRIPReYQQ08ZFkk75kBUzDGn5k9c9zt8J2zypK6MhcgBgJjB08nYFa7C2ObXhoL1oFYB2gcND0A3CeffOKNpQsSxZATRrePusEuUEMQWaZjmlom2ZEK4/L+ZV5rlolzzz4PNk2rrZDoMzEpPjYBqYREfbcDSNgvJCwZyOWiJiDMaIpvhQG2GH9kDo0xoW3ubW3LHGIAklvlS/XUyc3cloEjX4AbwgBiAEc2qVSTGIeBixwbyhSD0VrOCX3ZLV7vwyY+tac34uEGl7ZeZm2bBkc1C5aKRmxbtJPPcWYoHAPXe8XwZ5MA7DBW0am+ujKwca9myLVReQMlfYSRGv5e8J/sTpA0KOxtBIaH9kzdIulqGldYZ9MoygDtmBp8BWRallUexC+WjCnILD/BdI9EpLG7fJf6IQVmTw+CMrtAVifdrKsStTNdYcZKCjC7bdiw8sCxe8TSZHuD70zZjRgzBmGFasqMQFp/9e7O+E78+37A/hV4+a+hltHmYoXkiUWkiwXRfbEkph+lAaQi7FiUMuZh0wzAbMkte46UkUFYXk8Wa8tKqKCrKAQ9p6zRxzEosO+qsP9VBf4HDvyTeAfqz+6ujCYCa0NODi99AK1He8+YvWv/2L79pBbsTL64mzaAV2LOsQVdZGoBRne97ktAZLnPqsuyVFeGVkjmtdZhnS+gzgVgMZC0zPpezT1onwJIo/U71ZQpEHPNMZNA6LGNnlXWjUDo1YUt6+Ct2+GzRX7peWW9xoxcGAfQauDMGjizxpbVly9f+nEcw0q/G4RwXVs9wzRdzefRcMNX7/VocqhlWUdTNyaOjFyGxaVaofsmtWeLoayyZoH6YyYIyKGhFsb1nAA2AhEp49h3tpuU+YttvglrBmx89kJLI6CyRb6IsAdqAsJeLNMc/35GJozb15lVccjTLXuKlmcWO6SWji4g70xSUj/liTff8iYLgd45B7rQrcziZFQstWW3LbqX0ihU3C47Dj5iibj1bZAIAIuFbQE41yjNhOyNY/VtcrbV54EBx8xfU9OckBOoO71Kdd186Y6EIzzMo31ky3HYd2DMdvpBnACKM4CSHPNHQVm5IJkS9Z+MLz/KlNkDO+Pn4CzrOT2KA7mpT3M9Gd93BSfLCTbc/xw8MmVjw8SYWUlqy9jwQ+vLDLCnd6GL978G7B9Bvd1GLZwXwK0Bs0KQJpMyFnFeLKlD47siZazUshLyzGpodf88TUBMuMHaLoPJqxnsv3EA/54D/xqA3/kTC8zKF9vJfADKcCKcLmB9xPit55iF+9JbyDH7zAVPvA3GbAe0TqYHwMZAqPeIhIXLTDyWmjPK7GIzDmd3xA4+GigzMvHoZh0DJPRssLkrk/nq3xVwOMDYBUu2LLcBXtm8fgy6MQgaumRgdrufnmF1z2YhLEvsDNpikd8BGwEvdmLswAwM1F69elVJmjjAWpMzllevXvmLFy/A+Wcd3L18+RLNVMSqb/pUwl7VBtKChBEx5ssoAmzUnB335wvXjw3cws6MZKW/GB2qY1xmJKh3K5YyUj3SliXj4DUjIMbzQo2ZIh8CaBo6rQqF9ReadqoyS3dLzOo5bJq5ryopZd34wwf3U2Xqmn/AAkkPIM2R2E+Ee9EEPDwGeH/GdAOIYQTBxnyDLqOiQTMJhG41SUO+aIv4jscmK9HBo8zLWqSBTUYMWEq1ePPj/jlPjlEdGFXJUYmAu4fAbWcKmOSXo+ZrOC5q6HbMS7eRy9bbOPfB6fp3R3J0JGG6H4t2BAzRGbG6C90nUd+LcUprCw/+pvar8QA7HWsNvr+sgboCGbhAWsmtxE9IJj9hgFTSd8Nd7rf++7YTaHPDuT7zTI94sq87kGa4rtvT+chVpWc5ZnYhedoDNQZlLF9EZMsYoAUARiBtcTP5Cfv3vQD+APz2y1Fbm0ppZjzTUbIYV2N1j0JLGDRDJnFcJY3RnfFKyvhcq/xcyuijbnq1y+8mIBbm9c+VZIsTgvW9tPZkmn8Ge6qw31Fh/3UA/zCAv/oTxJh9+d6okg2eWOwUFAFnOlBY3j4we9fX9y4ZfWTL/Y2/8TfOANjClnUExC6DZuaUk4UWjjymGT3Za60dfJUOMGi9gRnrjFGXIXYQQ2HMxd0rM2icE9amj2WScGfOKuuSQGXJdhLFDJgVrRPjZboRCS3rwpCVLkUU+WLpx5zAZK8z8437Iup95q0Bs9qAVKVlbsdx1JcvX9YuaWzThl3+ixcvagNyt2YUguM4/MWLF/XVq1d9WWusWT2OY+IXJZxcJI3c31KWzKeaqSbDne7RkbHSYPvO1Z7nszGbmsTl1vhyi2CHxjM3xmDNrrQg7UxIxLaYns37wRJG7tS6Wkyqa2PNJA2LE+PkOSzUBKkIEQTK+vSjPTQRYkRzjWrcEsisYuWj+Hv9tmOtZixk0bnLbtvAR73Wqn9vmFVU4oTMUCtgVuG1GVY0IDhMQvoYo0jU7peB3dmYyjJDD8fXQl0jsTa97dVmG6svlYCDGO0mH0OMQsoEYofYo6bXV1kDj1573pmpZ+XAP/fl+j161ox1y/vaK/gofqAD3TubVxdHxHm8WCxLMtyRNeghdWf8lMwD7o3lyTblmG05tONe23L9uN7Pb7/GSk+lvG+3nFBXu97+A3b5Vy77VzlmGUh74lHP8a2nE9YsA1sXdN+O/vMHG7sDdBnSfALwKko1d5wf8EZCzNh3HWV5dgdoIbeMN9J/dAlr1hkpuw4z+BT//SNNuvjlu3Sx/Q2AZujT7VaoziqCtDzHbNrnr5JGBWq4kDLas6zyVynjCsYcM0szt8d3AmIsZzR6X3AD2lKdNQNqe23s2a+ssP+oAr/DgH/zJwCYfZGoWhkRA/Y2stmv4n2N2Tv378ouP5EcZt8L5hsETDzbRgMW9WRZVyZNJIQd0LBrImidXMtViRnkGjMQc5a1YamDk5oyBVUQu3sGW5WW5ZoyF3aNrfd7cHWXKQZpYwdwAG6UTWYaKk1yxm6Jz3b5Y33EkFkHaR1wdSasSReN5oEZsw7E3b2oC6JtFGF+4pw+pI51lTN6yy1zAAcp/tjIsJuBOGGpkEklNWjmiQJgqw64CGBT4KWSRsukjIj0XhVNZgBnyM1AlDFY3UlCfpmJRJEZsg7cMvmiGt9zwLQPKxAn+OYLY7bajOwERzZrl5wgZGd/XAoJh5xNA4nb91suliohezBzNKyQCzeJV6hONhXi7KFyRZcE58VlXkw/+BpyKSPwtj8WDebX2sRRv8ubcYLrvv4mQ/gZr9aJqPLIBNLyMZrAw4CGJ0Ky/MBOt30nl8qllFN+e3z+xlXtzBN7aMu9avepIYB207F6H0jO6Jgr58WNN/surtkvNnEmaxT1H63hCtIoZbXjlB6QL/pJj+wR8w+K/uporBt/aDT2I06MWcbZvtPYGLKb5yHjxVZWrE8b4KyDMa07+5Z3Jb8M4J8Gyr8QAJkX5ABtlTRmtWaZ+UdupW8JQIugrI5BBAuALJcyxvqyWFMGAmMuEsc7lNJgaXZltMGUkVBx7CkGW5axZnSlfRWwf8OBnwbgXwHwzW8dMCtfphGBROLjUkUNMv7wtHr+XQdSnysp4xkwe4RBI7Cj5h/qwmjJOpZA6c4OKXBDdF4MdvmcedYZM/pu4TBmWq4KGFMmbLxm7NnZcgn4sgyY9XUmDotGNWlcb9bnFbLB7wCtyxkr1Z3daq1HB2QNjA3jkMaQlWaRrzlm1mvQ+rxeS8YgrbFyDATv+7Az8UC8E/smj9lJxhhAGc0/nNR/Hj0znGSNmmPGpFOlEiHzhC3LQJqptMrFfvwBKOMuwEyQpNvKpOmfajMD2sgaHvPLJgzY2+TXjeFHTf+mkb4t38yOwwRpnJyGNMJ6gic1tHDyaDfnzjmfn/6pIXhbD5f75Ld8SBynPbxhSggnM3Nn1hwWrOeHa2IHv2IB740GMq0d67wY6+w6w0cB2VH6OBksbv/gPAcrSNPIgKO7Vlrw8W/HkG7PPXDbg+GIzXDpdm5skTn29dN5GcYm87fnYcK8pscaeBDFVvdMo2tYBZZ9eXeL1H3HuuULDYh5Y83K/ebTQVpn0a6YoRNv9rIZyy649sjIXgnHiGX+mfFH5m14LvRbIM1VLRmw97YvF+iq7VQo73Lx36Bb8G6TO55gMYVipmwYfZjIF7M/zi1Lssy+9TlmXwTsj8LLr47SxXJ3iDSL4GwANIM9FZRQa1aSmjI1/yipnJEljVHKON0aHUiDph+pL+OaMk8Cpvf2+C6AzIIByJQ6TlGkhf9Ags44vcJ+b4X9IgC/CcDLbyFjRjVmhXQ/Zmvxdag3MxkmfPtSxq997WthfW8hx+xzZf7RpGdZhlkmY4QabXR5oSxjTc64A2n9dt6ljF1OaE12yOCLm7Y14aCFAoMl4BEJI2ZSG4ekLiyVMipAI9mhkxSRs8ucgFnpbezghuWNmPVl0M/t/eLCSKYfA7Q1KWOlejIA6BLEo4G1W6856w6MXb7YAFp98eJFbXJGa5b41iSQN2Lh7NWrV/dtWuIWx07yZ46MVaz1sfbtGYdAyKbK7IBNK/1ALvE2LGkXq6NOh25c7nHA3l5yYw7i5LXuyopJgdw6VJK3bxkwA7FkylnEsDZP+D89BSuIm+HTIDN9CzHViszzkbApbbQVWnZZHSwYecArpnGhRRt+sgB1cic0i46KdLuA0/lyH4btE8V38WXXUvbtu6XXg1OxFwcy97GmUP9EEssO7ypdpIaOLV3KDrmmykOGGQb/yZlqE7TctectaYyy3txYLjuvuTokgQyCuy19dFVkdmuYhzhdFR5ZSiPwN65YlXY619LRdONQbYs3AnUCdQJ2pSMCAmIDlB3tz5/nzW7724ZdkFA7FiiDWR2GvVyA2RkiOgNjV0YgmxsDTr6SoaRNcJs93XHyE/beK6GFCTewkJm+MpNwAE9+Z8tu4sI4ECCDT2bOINllQDQCsW8lY/bzAfxLqOVX3kFX4xfrbbaPwZjWmtUCK5NTipLFQnJFE6MQO6k3u3JljCL3sn3v29csYPrMHn81+eAcs/v0G26BJVNoNl9BnwHAfl2F/SCA3wXgP/wWMGZfwtBf48E7S/rePy0p43vG7C0wZkkQ9ILLuK4M0YnQmf3pQERYLgZR1pgulQUOEJSAxNF7VGv7zXY6+Dk2mWaLhT2iXb5fGH7wMbEzeSJviwOihSEbbetW9iTDHOtimSNJFwvlmHmttTQXxl5X1k0/vIM0ssuv3YmRHRtbJlmXKZbGktUudezs2atXr0CZaXMf6NdEg9PBIt+wL89i58aMaOlSxurRmRHKktmq/KtJ+VXFGi2V15nRmthcYyttFCmfH+27tHOLlBHR/MOji1yOkizea5c4k9Uy3ynamR+WEGgFWkY5wNnU6cTowoJhMdrP/yJzFnt8HNxMnE8AoAysKh2Gu9xvhow5AQgj7d5gWlqn3pklC515yeZsaKcy4Jvc/pDfuVUChK3GiZnAjrPNwN6RA2Txb8bqGJFWGWNIeqM6t+hYGJm2vmxtoMx7PVhleWKd7PICtWvr0GDESBtdtxyrZ+SaOEw5FiaJrV+IGWX7VGLrxhkM7qm0z+zmyeMQfYHydGfMCu6d1cKSxt757gDtAVnjA2rAK/bsjDnT90/SbYuavBv2scxnlF+5Bp56LM5w3SYlm3FDj//6wtM+WDud5hFLmc/LJPi4qOJLLfEL7sDFkDNlwMaV8YbFmfFbA8x+Gdx+CPX2HfNgdobsdiJlbNd0Y9XMCszWWrNZZcX2GnnA9L7GLANjlmaYecKcqayRWbO6tcXfMWdGe1bps7VfSfzPBZZFeSNGLV5b5vsP4Bc58GsB/LlPnzHrF5ixTXH2np6GkCHvmWP2uTL/eJeMPrLlvvu7v3snW1ymkXEHAxvOIBufWaLI4C8x4dBtmsodhTFTtswTyaPvgBczcB1IdPOPRJaodWSeMGqWgMoBsBJp4+79aCcZqRixZgPEkfNkB3BOWWXdJMQbGKudzaIcM3ZjRA+FJmljPY6jW+ZbA3ZduggGZn2e1JjlakB1RLsgk+rqsB4+e42GhZWkjUMdWFdn+dG/rgi1ZyUUHCG3zXeTnqSLtDFrtDJk5NDY883Gvqkzo0gcF3zDiJR73TWxaV/dCaJa0wO/xUzYrALzU4A2YVpt3z6EY6sngGyBx9RhH7+p0PZl91xkbL4GHXcq10OVVI0YnK3UQ/ZV+6wlUUQNh6gPn8lwg6zTejUtD3Snh75Y7IcEBE9j1aJTaN+GR8rYZB/FIWcp0wIoSNvuIFO/thi7MKMcWfDAhvkaHLBMMWGDez5djYMMcQUurLfnVLD5nTHrPfthANJvCk3WeLsB9qqBs3oOQB4Y+S0nmMZOoBRLGffAjAumdhpCPCAC3BTc2sX7CwyokWDlFkuinr7QWu8TOwVJIwg7YUNmekx6ckXCUFDWLfE9AWStoc7SRQmkhrozfuo5Zr8csB9ELV+NVvhllS1mAM0JpJWS1JqVxPRjdWUszfKpL7UCNQZjbJe/ZphZkCxG1mxXY1aDVUk0/1CmbEoYI1NWRh2zBclivbCbqWOAagC176rAHwfw6wD82U+fMeMR24I4+pQFSiPhkvGeMXvX/vWOzkV9WWaMEcAUMVbOwIa+E1wYQbVftD4eyxwsmTBrLFfMpIuBQRNghqQ2jA1ElD0zAYE7S3wos9bBFM7NP7JwabCNf/tcWwYbyxbZZn+AJQZjZPzR7fAHG0fgzGqtpTFkfhxH6c6LzWVxLNvAm3cgRyCw0DEIxvHsqhi6mZkujjptnjnPi/v8YvCB+Vn7YZ7Vusm40WWgdF9Sd8IvEqqdNJx9Q1Xrzfq+nUgaPcmM5HokF92O57c0D5lSCKALQ7Sn0i8Ek5D4WTPQ6pjawZotoMsFDLnILTngmqqj3FNj9azf3dc0pW4TlbuttWqGjTNncijXTl7Sqh6IjOw7FlwWzx5FtjuVfiEvyxEuVqONuKO+43RG3VxdHv3Pfshy3R72cedh29HSktiwuIbleGUndqklLdLL73+dPTukU/4Ko6rrmUO59uD7Mw+NTMp479CybYadwL7dVgoe4/fk+NoFq8ZRYPTXD2dhVeDTHQPvuD4wI4bIkvHt0abadX+KAnvXFuz92LFBouIKKxMElAW27FOXMn4fqv0Aavnqfbu3SDvaBqCF2rOVRSslZpuZSBhLYJHKhimbtvkqAiwhfPrK+AOSV4YkVBqBHavEktVQXzYN/CfEZKHmDQg2/9r2/TQA8g7fVWF/zD9FcPYEfPt9OMNt78ZYENmzba7Op5JjFtb3FnLMPlc1Zg8AM5U1MkCafdoVZLmwYEGGmAAuXb9mnGVBzxwY3T8fOyljAtI4HJpr1VIjj0eBWa+Vo3WXTY1ZJSCm0kUnJirMo8wyY4kizSttvU6ArNveFwqdPjoQ4xyzzqQ1IMbyRbScM6P5fhwH75e7Ow4e7BZMsozE7ySM5Mo45IvMqNlU/h2IIdMV2IvnEkdGNj5c9EXhaq7J6LF4/Af4QpJFa6nYs8WTMdPGpQ0m0BZSopNstaBciDszK51MYn8ZaNXAlu2dGGOMNJ+BOtwZIWtZDUBUtuj0HZbNHdXJMKLnlBntbqznAurM88Ls3HNm2TwKkqHVD+cw+2BGywjQ9XqsVmvWt1M5FU4Apq3Yesonc+bIuqSSc9eoHS6awJF+ZkZ1bdZvoON6scG+GV0JEJUMBL6T9NV8MQlh634+/gr6zFimGQOjQ4xbYx/J+3/uU+ubGNUD8vfmOEOSWm19/wt10p2kjE46OY9siVlee3YiY7QL0AVcG39kf19oIKYGA32Fb1dej1mCmt4UbQVkLp8Tk5RCqkFrbvSlROKJiZzb7dyJsQggMyRu9n5uEh7AVzHamDowItJ8ULasrNM+zRwzw/fB7Yfg5aur0UfGlN2aRvk2QdgAbrEGzUJNGQO03AxkDZi2jStjXluWWeYX0k+UDTizhTFzMftnMOZDtjgN/jGSytjS47y2DOnAhqfvOzjzTwWc3Rmz8esRuaI9OBQ0RrjeM2afUcZs9zkz9RiW92bWgY1LAHVg4RgkMeumjJ18N4A9coZktozr3LCztQ9Mj9SJyfJ2YnNvCvTUBl9YMGbNwmuTQIKki/dhjenKCMkuqxIqPcCUu3fZYZc8VmLAynEc9dWrVyNgun3m2rUOvlQqWZi9U2DmdO+qiBnMTCCFX1zPYSa1X83t/wZjlqn9mHCC1Jvdr/moFAzgUO9nVQEaXaYMyHYuJQw8erB0R559R2uNNWUHRNroecHcsBasdBAqjeL6jjZYbDUiG8agzANzdSA27/6dSpzY3F8jWSM2eWZRgBa3agtvdDf/6IYUk5qtA9SMvRmAy+J6SDbX66dqcjzudVEe3RUrj0+yVLAGRYkLNcsW/TZGHCzEaBt9p0o1wNicTUuUmdhTm9kJxA9/XjKdMRwBCAHDt+NcWzusW8hP18lZVxbdO9XZY8DPagScbZZIJlQLj+E5yTODPNEVTLr8cI1OB11PNSmzEObYKlCt1ZjdfGrnRpbZMfs7XeLG1TFWALzC4hJk66DOmZ3Gzkk+s4TXZdlp/iVuMDzBZygXcguRnc7whPLCBuXsdgiTGXtqXchbmQwZM2bWdqS/Pt1W1/ri58aPWmu2NCdjzgozZEYAnFEeyxoTdix1Y/zUGLPvQ7UfhHX5YgdbxJL5jUw+qOasTw/AzRZmrRuBIMAcBmjqyFgaoCpDH1E2wGxKGVdAxmzZapevDozqxohmkd/ZsGidP+GmB4BmsGb3sa8tA8kc75WmkS1DsAQxHv78rgr8sfopMGfTlbE7z6gkIou2Zx2BsYTk81dj9q7/E7C0AKYMiCUgzZltGv0eMgvh4OhkXQtrJo6Kah7iiRQx+x5b5ENqwaCgLTP7SGrAsnkM2kabEkniaHuvP2MWLKkrgxiNjJwxZtY0t6wzbWTyMRiv/plkjE5sGJrFfm3vQcuGzDNuW68zC3JMlROKQVyQGgkoqr4Y0wWWzVwUfjV29xi7VEjotOdjRruxpPWziR5LF8gBSEj6DVZ0VcCWx1oxtuobAMwTidlO47bKLG2BQ5llicteeHKqfBjkO5BkloFsKKpY4HMnmlk7xcQWmD6HUzDxVGk4MYo2zB5ATFZ77YYYZiELrSujLfBbGHpcc45aMDIT8XHRutk9nNrLffniwfLTauv/4B7uDHIorIFC64YlfMndGbgByti63vxuHkKRAB3kWO3xCJ2JsnlsGzBlS3t4Z+2auYhHB0/v7h3gEGm55oh98xZNMErSjMjcfu+1eU0PQNbG2azJQbtT5Ki+a+fTKKnF1dwmuDJS9EGHt60mFV6pUw5hy56iSyOk7mjYpb+axiAP1pjthIV+AdqUk2Hb/C8M7my1QZgdTFwIKHc1ZnY+bi0lVzeLoNFsVl2VhNhhzFjKdLAfOKlM8NWJLRCZmam5Lv0l00I+ofogLFqQMUrhHBuuvH27/O8D7Afh9h0DTJ0xZTs2LQCxWwRobjC/oVgEYlUAmQn/dAdKE6D5hnfqdWUrILPBgmXW+Vmo9GTO6sgem0yYD9MPriOblvl17BtCDMB9zkEXszUwBgKaO+7b19fvKrAfqnfm7P/+1oBZtW9HsSLi3QehTRyqRLXPH2P2Lhl9ZMv9xb/4F5/Flp1IHKHMEz+DEnZMpYxBP6HOi7Q+zSDLctF6O2/ufog8EfJ5YbuQ1Jdlhh/0uTCoam0cAKmtTy3xKwGZ0izzS2cxOw3V68jMrPTarnt/pzrLFRPjjw6qnOrUQMzXqCnrZh/dOr/P786MfX5rp27Tj+OoLJ909+GKxwaGjD3S2jJizIbDneSYOWWbDcYMksuMxB7fVyPDMd0TwmzXB2GdZSrFyyzzSYsZ6TAs5h8HMWGVZIzKnlVjxw0JoVZdprJjlgKtaMfhW4gJSirrHb06HpRz3ywIHaNDoxGbOC3164ZdI6Fkr1FlVlZC8qoC7aTAcLlvsbWJi+qt9m1RxhembJK3443BG3w+OQpao489eOXbErA83SFF7wj9Lr0NodVGgxkEkInZ0nFVU8uOvqytAyNZiDSLNb0hWNPvyu8dcn64jWNWB7BOuW3j/ORR1RlrOZhNNzmOhBQGLUPMWTcA6cjSpGDKlH16dR64/ECXKTM6xIkwkeWMji8AeKJKnCjuqoHlMOlkZo1dcxCvhrNDVrTUkxVVgpJBoBHSvN2ip+TNiMwELiwZkm6qyziaZ18gq/wMjC0gTICZgrO3C8x+8d19MWHKUFq5ETNkDNBue4CWGoUYzKKUsQSL/Chn3LsznoVNv76Uka3y7+/LaGWlbDJ+P1taydqk4iZ1ZTUMBEYHxv7+JnVmwFJjRq9AhX23A3/cgV8F4IffCjD7Jt0E9AbLkvZ4Q1x/09Qhep9j9o5JGZ9RX3YmaYSAr3ZvM3ZrXCzkZT4o18zVHbFPVFfGJO8MmfmHgLZl/SKD1PeB7ZL1q5yRpYoj6y1hz1TWyFJGD4HNbXn6rIwZqL7sIDMQZxv8O8aa71mSSDLHLm08GHCR6Ycfx1F7fRsde/YqXNgP86STJvVlSiSlMkYnYGb3oOmDlH8MwpyVgIhlWV4T7KXSRksYs9SNwR8DZ2Ck2Vq92OX7qoRElpK9YfTC8HHdjnxbMi223FKj+1lbBhxN3tghWEkCpjOw5Sn4WnFxlJJ4yFEMJI2JAfCGefBNnzJlcpEg+EdH83zdXjQcyeV1cX3+rGFCG2HL9fERR78Yjcx+IJvj4JYAxt3xT2u+ztq4cVQ8Qzr+jHHXchPP9U7T1OnYd+tmIJVs9GtEGYZmDLI/xlcc1VkG85xexC7fGjCb9WU+Rvk5OHiah9fW0azUeT5nzk7MPzBrx77QJIu3IkYfJGPcGhu2HerALHXb94mhiyX4KjmN2591QMEkfS0ZAEMOxhnoaJ3Zx2+jF25fws1+P6x8FeUm4EvqxnrewCJlPGHQmEVrGWd3+/wi9vkTlJXEobFIzVkGyN5UyqhW+XZqk1+pZVW4Ph9g0xMp4978I0oXLcnJ2AC076zAv+p4+hWO24s3B2bed0QeAA/eIPv1XKc72/sas3dTyujPYMgsW47AE8sZU2ljN7/oQEzBizBtRt/3JMfM1bCDvy82+OOWnDBfDPjAWWYEpNQeH4lF/gCC9LnUWg/6TnH3g5YDZ5V1ZrCDrc6S9eWIFesujFzz1d9Xmu/EiB1ijV/6+poT4wB5nQ3roG1XB9eDqxsTWJiVclX+CW7xiqD/P4sEcyagQHVlBLaCmM4jsRQ6jdKZt6xPu0NuS6B0oFjmipbluNfuK2XX2TAFWy6o1m3Ffqe3r9X1ECEtzJM+7ypnrCHrTO30Z8B0lVQ0u2QX4+vOINNrjUJNI3d4Jykfh+ch+lN4iD7wluM191F9rSoFVEc3i/t5KeM0TUOKYY5hCEycibR38SUcxh09o2s+3J1s8I22yetnrN1NP8zFEbJLEX1Wjblkg/E2gtGIMGtT1NiNNOaIbZdOYgzAYRqf9H3vcSu+AWgNwBml143Q654DR+fP6PcVTDd72zrQNx/rjTo5ljKyXk6zC5UxKfL+1eVQtJ3KG2Pnr1DX8EaSLRZi3VmqJ+ry3kTGuNb7eJNt9arQIwiOH/D/bw3uUsr+N+rCmC2j32xhnEM5yLir6UaUmJqcBJt834Cykg9alMzUaaknwww8C5iU6L1xAVtiAnKLiO/Ne5PfCccfhpevTXt7AlevI2XswdNFTEHMwnfu9vm5O2O00FcgVjaujDspo22t8lXKqO/vy9dtuDSHSM/6MmstRBtKnHx0HiC9M/uAyBtBdyB+wo4n4S85UP4dwH4LgA/eCJh97I4bae+1o5T9bsczkX4I3Qn6U2Ck3jYwq+8ZMz97rwYd7MpoFJ68ADuRObJLo4v5hy7rmlMmtWnKYHkiQXRpn+3YtBMHxstpCsxEuljIIKOyW2PPIwNZ4gNgsHV0wEXLsxGHqxNjB3CUPeYiRez1ZR2MdaYs1J61dXWpY5AxAuiAz/m81AwX1IRMYqDU8MzoSKqJX2L+4XteKvNH3Jra190zdBkk3i3pG+kPo8/c13AwZgexZgc2FpOWMy0MNqqfSs4zv5Mzri8Cs2jr4UGwGKWM7MQYhY+etkA5O2trCUd/uVdlR30tZuRDZh4ZKV/cIOdJbyMpMe/M48YrS+cMwYgCvsppx5qy8+hxH2yK8LAYjLRtMvEU6jiXZWO7Q53hGOOqW3KKoJGcralPjEHcs0HBxHe0mfItzwZvJLKBLf85W27HeHpoGzORXEd4o+Ill55+naDM1AhEa87Ype9F6E6cO8rPTqAPY3HuzkK6uRgyLJb8PbWpTlAmethx/tJ8f1C6UwdqB1a7IG24GfDkzRWyM2UdkIHYMRNikerKBjgr06PidiM1KcgsUVkyj0ANgqX0d2UZc6CgbDgv8jLCILKkNQPp/e/VG3TO7vVuvwe1/KoUdAXw1aWMtwekjBRAvQA3C+u92+eb5JvZImX0E/v8KGFUBu3RgGmtL8NJiLQTCGMDEA+DGJbUlk1JIys19tJFS5iyCMwWgPZrK+yfd5R/+o2A2SdCB4cHnNxE2S3fRP1h9qkxZu860PvJBswWkCbMVVZXNuzjsa8NCyIfMsdwAXDdZt5ovWemHmypr/b6ocZM6ssCkNuBtc74neSTOdeSiUNjYMX6MZrRZMMEhGu4+ufOXFVxZhwyR5Y3aj0Y1ZiBcstATBtLJhcXRq6Do2M0ATHfK3ZoaFPExFJGU0zDwKxI7rIl0V9IjAxp/mQXNoST7YbxLQlc29VGaRZAYj1ZaYSVacBqMQeAqYB6gizrKghkdi+PFp71YhvCkpiyKGms9ODE4NUmuqynNWSRWVPuzqjNtdZA/JCKWaRypknG4ZyxfUU396gbJq6S//zMU9vD3cFAeQR8JrI/9xVTQ1g/Y4rPGkx1ogGBod/t2w3GIcngwrT3V5MdclMgMw4gxogZeyYGa36bph6urAuGy6OHaAcPB9xMQJe4UPbrwU96AZZtg82HjBhU3rkQNuwxaLgQY1Yo48w4LP6YerzRmf8EGl4+u/eF0p1KMFSwYOJdiCGbLBkGILMGygzePjmJHyNrNmWOlRiA2wBrXaI8Qdqho9VtR25Aq2i7uy7e2iV505wyBWSIrvOBaCSsUPA8h8qrurNF3JAuaHmN2RIiXc4Z07cBzAr+YVj5xy+ZMduYeyxM2SZouu5qzrgaK0oaLbXQz+zzo0tjbddxBGQmUsZoANIDpvuV7ImUMYopK0Vf1xAwnRl+cB2ZXkUVbPbBYC2CMGHGzoAZHPidjvJXAPs334AxW40/PQ5SpSMTi3bdxwPgrQKfH/7hHw7rews5Zm9VyvguGX1kyz0IzE5rzbiejGtAtPaL82241iwBZmDZowAvT2SY2AReg4BbAHH6PZmWyRXBoK0Dw+6CyLI+YcwYlNVdsPTOPp9cFbucceSa9XPH2WVSF9ZryI7u5kiyxA7ImIUbksgeLq1mH622rLsx+ob+mIyZP04qjcFsrTsDgTSWNR4zx4x8FoITY7WVLBgyxooQ+3WpABReZfqO72R5NWovDdP/n3fMXTSZBMCEaVhQZXYGFlOM1egiAjQkIsasKsxG6CfXoMWH00GiRn4sOdhS/Ty7zJa2jLXUKZI0I7aF1IbWr8Aul+NM2EbF1m4H3002nDPG5sqMHB8NbSC5d/6DVNIo84wz0ppaqLFE04aepHycLGfzOqtO5vgeawKHDf6QPUocgE9Wa/x/ANDmXFnvYGkQfc3l0KmbUo2gEYMlbx0jM5KHdit+J8/GmTHgVc4tOzZXTGt9vpf3Npn0qAPmpmNobVRiSB1t5tAJPg965lHU5FPOCDIAMXk/nBoR0Ucz1L6vt2/oBdhk28d4/S0wY2id312NSxF3uwkBmDVj641pAALqwmLIyWxMr8Rk1NYyh+PVAGho7xtLBuALzJQ5SRfbMe7vGYyF2jJIBBgZgXRXxoK1XuwUoFnCoqnENwNigR1zLIYfCyMqYMw2QQfHa9MNPwcof2AwYrVxorsas/CqeWVqk3+7MP/gjLMbSjHc5BryNOMsC5qOgdMzYHoFZFPKmBuA2Ka2rG7qywrJGQs5M97GkNNjtWUQWWb2+aS2LKgN2+/vCcC/UWF/DrC//NrArDB1raMOu06NjNK5fTrmH++ljG/2T9gjKHOlgIdlgvKewZQlcsbSC8oyKSNiNhmE8eIaNbsAYqe5ZGw8koRUd/CExmb1mrDxnow/TC3iVcrIDo3kzsgZZIFVo7yy4fRIrJkRUwVh0IbrYmfD2vub1JkNi/xuf+/u3QykunvpwKvXl7m7dTaNgGJvF+9XqPOrGeNkp2TJUPyZ1pPVSTaxd0Ylw0IgD5NmcqNi7w7nZ3y8n1B97ok4sFIHkqaF5GzekbrKFY8MqHmkBSHzzXN6RmgFIyGaXShNJ1GpXopTxniQWNE2zJjTY9jTqrWzmjOVMrqcu1ij5Doi6Ht4auJcGAFtYnZ0RN7RR51YDZRIGwJq7owWJHguF2Go6QuFUSI3ZK1WjTI+iZUMvVBX6R9tx1stWm2gn9PlJvm10t0ql2SsVRdp4SzwG1CeXSv5xqDuDC1PzioWJnUd83EYZ7f5IoIMDJoHcrCQTBGrrLFrrPkHtnxOXPpGd/PlkBRiVIkxxOJ6FjUdiEKpDsRuoZqMgVmBGoAgGHyUYPah8rHb+D1be3//e9X+Cu5ui18w2ja9Z6zDqk/NaWYPDRN3k86YcTSA2bULo1E/ld/jpKsaLfE9MmbBjTwJ6g21Z1i5vZevLWH8g/fRJgZmze3KbAmGXqzvF83ojm2TerNRdxbZtLsRCDNlhZiwWGv2OkHTZfte2TJsAdmEi9EinxkzZsvq8rsD8gDpXf3Y5Lszxszp11yESbsDUfu3HPYLXgcS3YGZieEH6XkXiYbUlvH0T4Mx+wys7zMlZXxdtgyzvmsAHQV0O9OOjDGTZYO9fQM7rZ/mXQLYwVRNZIqFlu1tpVXP2q8OyEhqyOtY7PEZzG2kjKA/ljJ2R0Pr78kuP2PIhpwQsw6NGTOo1LCZdZSktmxIGfu2GdC15fk7at+fbTdwNpVGJz0vKVqwjteuY52gjKWMLs6MrPALJVhOTJrPsquxKVuNDU0Lwx1rwLRTz8IvdJlOtSmDYaNaFa/TPr8Wki0i9/1XKeMZlmF9WUDHHKXsK1hdxZdJbhkEnM05Rxt7zzwdJwtTAyCMDF5G/UkNGpl/TCljzfG0YSlaCtlgNRI3Z8ypoTuJ+WqsCHaFNJxlPoXtuzxLjZi3NgZlFmupgjuyYUowSY5oBMaWyzrkCbIziq/7u8gNsUbkZcdET59J2OByNJhFW7d3L4Mwwqsuwwx0zTSjxBD3B/5Q6fduqNa6UKydMzb/YNmiNUdGqTczz63Uu5NF8FPsY/aQ6V3aqIAsCh5NOntaY7ZjyyCgDNJxZvfGSiYNPkDaNPgwb1vxaGdvmKYdHAemNWYQR0aTvOYuZexZaAGzubhTEpum3epsbLAst0aPoMuyWChqvOtO9HNMIIdbfLxWD/R/Ays/JzBdnSXrdWF2S+SKdsKiscTxJjlmVG9WS9y3tu1yK8JTTV9QJ+EgAhN7DcoqON8sgjG1y58yRrXNn1duXRiz/t7IkgRBKtxh2/GQC2POju0kjHyf4ifb7f75awfsXwXsdzwbmH0ThiJBqiNjUhQzKuvXrsunBMze15i9fWC2BWM7uSLb3ieMWpAzkhNjBzdVzD8W+WJiZ8/zWDK5kzsOF0QGbZ3lEzYtZKVlmWe0XSeWjOvJXGrMVMrYmTAGdiFwGkBnuPp2CtWYBTdHDpLmeW3acRxHB3f8vrNpB7FpnT2zkEs2HTG7PFTBbH9/399CSj3VQCNSWCaSxS5THPglsc3vVvdjErNndsdAGgUW8EvC6FsW/bWANSMc4OsOBE1mgqycHU7ad2uNQWzDMt+k5iwLntbhYAsyvvygr7dQlyoeZiXqA6+UNDa6czVklEURpKeJaRB5IzNZDdZVj3JD6njP+JZWvF0jm3n/ChdFhcKkCWycQpwHeBK/RpOY7krHXh7Hg7FaUBmBmwG2ars0DFlmgxlImucLeLoDqulAqFflLGwnQBRq5xqzZdawlNb0ESLsPyxyHOmApx8/azLFXFQzeduwXwRGB5uHiJ6Nr5IR1G0jkmDWCBo936oAd7qGhg2gsmYeA7QgI06abzbon2OROCqsMuokqieckfyLeTVmyWZg7qwzY6bMQn3ZaqF/35NCvzY2F59HrAxhcutQ9uDoMgFYYLQoDoyZsSy7jDEAI8xyW2vLuPxvMHEZ0eUrybUEUCOh1xYgJp+BGD7dz3nJwg3ajrx6dm/21wPlH93WkmXmHyxL9JMasyu3xtNQamsujWdSxpLa5tfBKmmNmV3Y5fvCmlXhfSNzZiEC2wJ7ZkllWTT6yNmzrMZsBWH7GjOMp2JJnm4O/PYK++OA/YnnMWbVUfiqpzqPHQAzz9U0n4b5x9e+9rWwvreQY/Z5tcs/Y8NYqheADaZDo1HHHAS6mE1jkFNpfQsoZGljUg+mEkjf1Ix1pmwBXyQ3VAmkujIyg+aZAyO9hu+KMYhtsstYijncEPv7LiHEdGg0CqQecsW+/pZd1s07rNvhdzDXZYsdFLbvdsljCKbuckUAXdbY96OI/BIiMbUFN5zY/3GpVlZfpvVnHcvUMsEYG3xwuRYzdp453WPDlPhCl+RfBk4yzfQ9OZpUBmvsNEcADWIKAnExYYAWdg6XkkBsxGkqNlRw1pdaQVmHWB5kjPy+LhHWNZhzZy4xaiBRvSYaVJU2enoifcnD8vQcuo44eiKek3DjYFm/kdudWAYmysuzfaDvaCBz0rZchBiPxSIT9KTGb2nkqhGNx9hPJcL5s8bDJRyNTDw7IouT5f5awHKcZqeliOMiYg1cv0F190Yj63xrAixDdD8bpiBcpW8JU5aHPJcgYHSCcZp5NlkzD66MloIxEIPBHc8oLcvsFJxG/CcYy3LXmHgKSkFbSSj21RiMGbs6elQaopzIGHcybjtRdy8qNjX6gHzenMOOOp2A2/MYsy+i2P/2EpSlEsasxux2XmO2rUHLAJrBSmnB01mt2d6VcQI1UI2ZLYBsrTGL4dImsek1aYEtjow2hiluD9aV5QzZKinefeKho/4buwlQk9/b/85hPwt3p6AHgVkboeBngO14KicpI6Z6oP9gPo81Zu+S0Ue23J/+03/6UWCmYGwBS8jrzxT4ZIAqnSeSxi5dTCWQ7HLY69KI9SosWSRZ48HL0ntn2WGXNDZwxK/eJYcsVWwgrbAlfs8mwwypHutv6wjL+r34q0sbvW2vEgs2WCqpKxs5Zl2qSBJEZwasuzCSI6PLeoZlfjf70PUIGB946Nj1hjbyO08yzTwzN6xTBVilvmyR4NlqcOh+bhS55eSDZm2HND2FMSFA2ZMMgFpjQjZrMQfyNAmXtqnTVAbNH9mhxew83Zto/MhQatac1QDU4r5xXZnL59hxzuhJXw5/CJhOTxBwki4MSd5als9IrXWo/USieLrt69mnmhB/QAqYyTkRZZM+cszEuRFXhy05zhwQ99CuMtN4cRaW3bPckfHRg2u7cOxe1OSJXs6TmjNiykPOWX/fXYWMWL6QZEz7uXb+ovsiQp1MfI+w3JQyzqozl5qnKGcsAsoiA1ADa25L3lTHr8OBEYkLoyVM2c5Hg8ifW4ns241Ph+nRlAg66nOqIQgyIcHiwuirfLEIBAzSR/GPHEHPz3RlNPungPLTJ0DiVO4LV8bLP7tm0nzj2NgBms9ss7XWTAHaZGPPrPPP7fJjuPQqZawjx2wNlTayx3eUxjXXbU2Zui4qQ8YGJjvGLDJjCNMjGLX4+WdU2G932P/sYWD2oc+Ae76/MCum7ozO7H539J0Pgfc1Zu++lDGArUS+GEARYt1YkAGyXHGMgJqxkYe5+9iGmQW7fJYuJkYhyoxp+9gUxGV5ZyCWMGepM+NFbtm23kwAHIT1UtYMtdYOGrsTIog1q219IGki2+uPdZBU0dhAhECYcYbZq1evDqo700y1LmEMwKzLQTtYBZlhXXaZfNaVqbP8cJffSBmrzbIsxipVLPGdw4XpgeykdktNjbJGm9IDF24mC6oU8w9rNWbVYkHcAGIK1DCTtD2OeK1MnyUaUk+xcsZX5ZlwTnvkYS9njRlzaLz/kG/sECTXqU3rENsAs7Okg8ey6X4C/lnMHPtWbvfTsLd6/UN6fiCes94UOz66tsGYIUoXs5ozdmm0SuYgVBQ3Ppt0KyyMsBuxCWs2EgJYU67mRkzZbO7qDKjW+Wpo4MGx0Sh6l7OmfHSaB1NHwdEDRDHG6dMyZ/kSMQ3HgZVWBnXLgFck1sLv/5RBczLJPGPOMqZsC8zESlJbaM+qMfteFPsXT6WLS7A0uTQy2NqGT99Ocs2k5sw5fHqakJjF4GmuLXO5ltQ23xcLfVxIGdmZ0UOWWTfyiAYfXFN233JnzJ7akylGS9/ryu7CYzvNKcu8FtXoozNjJqwYxqCJLT0Fig74fQ77IwD+6kPA7JMx8vOAKkZrhP0zCaQ+V8DsESnjzpa+AakF+PB3yCI/GHnUWs3MmJGqUsu1fK/XWmXtfMACf2H3eFt93bp9/f7ZtN0fgb4A7joo1mU6K0YgDQLMBpAkMFYI0IGmOdnrO8kZ3d0P2u8h3ezgj7PcuqzRfVsxZnxd1Fvs7avCzpUVo0EeYwbNV8Bm7BrPBiCJrJGXy8qzTLGN0keL0Z0iN13e94DNiBL0Siwa7VDdNJytvQOaOrNhdDx24z6NlQvrj5ViSGzzVwhniSujukLueEwPDJ9K7HzU4niSRefpIZmSj3DU0kPoUofGIQOWXCjxa6ZLS5RBJaN8HsZfmiKDCbrkGAkRHM6B0GEHNiMQdyv/eCmLQ70IckzOkw+XR54WoYYeYY/1P9KBdjrP8AnQZwTC/IJjGgiB690t7n/aQQ+0Dw8hszkEs2I0vt/NQULtIjMvehwyawHQ1cBAzRczEK5UY5ByA0Z31xcgaKGWbAVnwGrYMA9DJjmz5P3CmiECtYB5Co0fiZlhB3nFyXYfdHq0rix7GLEnS2ZKl9WaQZk0QpvASvmFATDJOPPyqCtjgdnvxVMpC8VYGihqcsJYW0bujB1g+W1KGM9qyYzcF5klKwLIRmi1Ua3ZjUBWWYCY5pqdSRlLEjTN9WVXUsbSdBhlkTRWiZmY74/kd8AMtsuv0lPmLMK0GAxjgQXEqK3TZyZ4sOPJYb/HYf/9R4bOnj7yXBEwi3OTPLNdv+XTyTELW3kLOWbvGbMEmO3mJ4HOZ+DIlHFLllfHRk+YMFeTEg6e5to02fayTWHEQjA1m4WQg6JLPIARqHLNMaPjawLQvJtwkJlGJRBkxKwNySDb4Lf13xpg4qBqELjqNWfDiXFjrc8ujRX3ujKWNA62TM6NkyRzPM+r571/l3oyJ8zCpoXBhVH9M7opCNWYLVbvtgmWpuk8kprWzKbsgscnvwuwCEFrNTozeuLWWBMnE2bNAl2VJWhX2pErDMaxyty9942cMdp2VGiGWbTRP4Z4pblNtqVq8HaLVWx+4QOp0K/We0bUHQA0cBMwK+V9OSKqYFcIisOeN8PZe/PWezewEYWPbTOa8Jbd1cGQwUZ5oKlcNMj9nXwyJy9hhM0VD3TAN/LQGCo5tdsZ+HjLA/ORN2YM6zpS8XoPqJ4yh5Etxs/8iGYjMBsGHAFNzu2uHGePWWitaseW7aY5Fa3XkRn/duV36E2uOTNZJowP2InRZ+8EF84xAwVLYyYoG/3QTAZeGMiNwRSuklv5sNX9Lb6PEkcPNWWFKspKYp4PsUpYJY0lgLNYA2NDfAzKn1okhJRdZhuQBjUBKXGaEn2F68wyuWIDdmwGol3OwiIH26ghQq0gQbwFRWZsmQAxCCX4uCvjb8bNfnNqgV+TAOnUIj/Rje5qzcpJrlndWOtr8LSVwZSt4dN5rtnrShmruDDaYj8yKyEL2ZFM5mzCNLbInz7BNpgu/n8NSWTxXUW0xC9JkDR/LsuzdWXPDPjvOuzfBvB/vAZmYfMXA7GnRRtjhOZdZ7g+V+YfjwCzjXxRp9kGREFYrp4J5grGkjo129WwEbPVpZOZo2IhX/xeo8X1XAflpDHI4GXqxoUxzG/TeZ84fLpSzVkw/6DtGlnhd3fEe73WcYAll72OrIMhYs3AjBq9Z2ki15g517RxXRmxec6W+X0Zmeczos5Ht3fnheEJRdOxxsKWqSKQpYxYc5m1giutmaJO5lLyZids2SnHhFT4N3vY1HBGnpbkmLFtfsgvq1JTRu8rMkrl5Ca3VuucWeQrWKsEqRi03UUhdYx3GlWjVapKw4Xpx/5+xTlmBMP4+rIYCxD4Gnfqj/vKBrHjTBWxyMbwYswzJ9HXZGItYdWc6FufFoLhaARHRxfzLQpzjqt3YeNm22LzNWesL1wjHOr3szoBUwx3xhp9MMCnb56qWQIZsWcWp8/XGkD5BNzZb7a2WjqIt6dF05TuIHm7zQKmTtN0IFYIqA0pI8Q2n8FZ7/JUqVvCIl7cAbCVIbNFxmckX4x2+RHtrJb5FgCayzx+tYUNIGBGBGMh9iuAMTX4wGqPb2XRaM6870S+uEAhjxlnBTEjOlwXRW+L1Dil9VyljSWXMnZmyVRg+RAw+wLMftelhDGrOwugjI0/bteOjj27zAm4ZSDNiD3DrdWaRYfGFaBxRSSCpNG2UkYTMIZQX7aGSM/astWV8dzwQ4dEtMYTdN0XrBb4vgxk2KLwEMFQAG5n4MyB3+2wH8LFlfP0kY4k2wWMsc3A2ByX/VyZf7xLRh/ZcjspowIsrGYezFhlhh2egDdPMsY6AAs+01lYdRYeLbLDDBwuUkLMrDUOvWZJpYsDpEvtmYujoisr1kFZPzbiwtilipXkiK52+Y01q8y6UZ1YB2YH56KxsyJb5tN6BlAjeSO3O4BKtd/vwFVkjWkM8/ZekZAivV+65J8JSDMinjrRFOCQxb6zAg3D6jRvyA3z9gVKu4U9VtiF96LfZPTZAVeaSebiyOjJ/ZWDsB4BlWvHdz1FLg8PtenwDbxaPR1d8s4M0fTDwrwod9TjXr3eWZlN2HCoeW5Tj0UyWe4Pc3EYrkue1n1lhmnD3lUiJtli7sTMMDvlbDXiSx2UkX6P78PWnsyjbGljmLECoxkdUJnicpbTrec++mL0Xm2NZQluDXSr/f7swrOkhpTn59clbTyWQGSmLHysHOxYONg0MEHqIc/MNU/NOhhkS3RqYCGKn6k6Q2TLOhCD0pxVXhFkUxBft8ilMXvGXomWApUyukMlBWAIAA0PvG5H9gMQ6kaWGWOGJFjaLSnnKudSxhvl6WrQNM6MPdohKSrNXTSPiRbSLNEUI2fNQsEcVcddm3/8JtzKzwluKaUbcdgqYbQdSCPGrFyYfFjCwu2MQbJaszavmOaaxaDpaf5RAsh5rpTRwJll0/SjpBLGKWW8EVumYRSHCGB9Mf+414vVhQXLLfHFzGP5HH47IaOQ6+oMBfj5B+zXX7FmTx/Js/8SVV0zaO9rzD4DjNkzmbMAxDbr6wDsEACm29AaM2XPPAmfVpOQbrRxKCAjeeKuboyBl8oXNZtMl2PpIkiOmMkcPas3Y2DG4JXNSQhYoTk3cnB0ZbfGmQtde61YOY6DpYyB+eqySmLGKkMAcX7Umt0pZTy7B5CVopN0sdYN5UXLuZh/HNjnap3wWOkNMx1gMpz4/gNL8ZtaTJoETjubgHTGzCizDFHCuEgZfbWchK/azQsCynJYKQDLyHFxfl7jow1Hex+NO2pzaVwdGnnnfAMUPTk7flTUZCdm2LSsxVhu2ImnYygX95nQCiosoLgKJAaFRvlnLvUHPsHf1BlS9rIJWGhHa2R0LQKUthqVoq7Ih/NFXd27sOaog8Fj4sRovV3O0h4aP27yR664cELMsT1AtIP3ZT/nybcBtCLAbWlEradfnc+Bx4EitxUgO+vimCUTxkxdG4N0ka3zGagpUihBFhWZR0u6kBB3RoT6M7XLB3k0ukA3Bmseas8UrPlGTGyBMQvyRWXJkmlBzigmhhAjkHIT7xWbAHCpOWPmDLJ9KFCnAQhLWLOl1kyRY8KWITEG8bZjr057zDcU/DPRwrIAh9paZiDsxDa/s2yhxuy2D55mhm1nDOIlAjQrsFsZHFhJgqbrImlcg6YfkTJON8YO1NYwh0IJfEWSAbvJh9ZYWsKT7QKkM0OPvh83mRefpRkrptEU8dVgvxvAKWv29KE6LvrJILJvas7iSNi7nmP2eQdmGi5tauyxeT+MIkjaiKROrAA4EiOOyAG0XC4yCMky1DrT5lIr5pvaN1d5IrFkHTSaSCUZgFUk9WbEijGAc2LNWEJpBJYKyScruybSOirJGsFW9iInrMRwucgaRyYZuykmy6gdvsv1obVkru0dy5wN0tD9wkX9Eww/Epv8IWV0scLHao+vAdNp9ZJtchdtp7JjuiQBZ3obrixdpB3zYzoz1rIJlMbqzMh2+TztmQpsxiF7X8kqMiaVLzpVjzEwqzJ1BWfXfznQqDzwY4Hcubc11HXdL4gJBKyd7y6ME06OWLEV2cwY0kqMiC1ZXTZoXTu99G0rN+kALrJEkEKvvQshW/5z6HLtLJhnwQTRxt4totY7gTiPQzw8BlKzj+0OFlS+xkHV1hjGPrhgwTIkUShytnVVWJmA7KTu3dpPLeTClwSYBfBlIl+U94Epc2mkQykZI0Cw82mMVUtGAjkje/AoXsTIMWO2LMIY3+SbeWoUon6qHn0mbZNVlsgZszItNTPsMWDhdCDJgHaZT5b4wauFwaMj1F+GHyRnQA2tpbJjSMAYMU6DEiSw9Oo0OuMfAG5/3wKwLq3xiRlzk3BpNQSREOoqro2+C6G+YVhk1o2lfomSRpNaszVoOpMy2saV0ZdXW+SMHlwZSwNlGibdGbP5moVG5/b4q3QxZ5P3DJkt2WxZmqfM+/kH8BsA/DunjBnXOmePTJO7/NSgpYPm7xmzd+jfznHxhCVb3AuZaSIXRmyMPUJtFX3ubJYLa2Zq9HECwExYsgLgaO6RpbkQ9nM8wFNHfx2QUV1akCv2Y9DZJwFnIcCapIu6XmXJxva4dqwzexwbwI6L4trY59fjOHi6H8fRwSdnkjGAMwFpvaaN69eczq8lzCifowWThVB6Kb8KRgdZp9hz3MPqP3ZgRMaOec51hT4vTspgkO3MjkETPVbPApAaoOU12EqCgtcQU7R1/Z7U/z5DLG7yRSenPwQuhCVr0QSEO8ZOZ8AT38Y4LTJjhrxmiT8f9RBzDz4l91q9wAo1MGeNv7PazSlsyXXxagOoxw5+HXLACq6dqouchV0GK0krOXd4eaCGIX2uCWsza3Kxgo0t+FiZhFVXeG1HrLGzlbNFLabajfqtSnSeAdVNrFJmQ6Y7b6sBa3eAXgc2A+TbZ96mY2TxdMMOX8R/7RxaPwcOq0ZmKzzSKF/21bGzU/ox462sPfvgNEFdMDX/GPPrOtxhWCS7KlkEYj7S2gVh6aIRW4ABqScwM3C6GVsjcN1YJl9UhgxJ0LyP8Ot5DRUhmZQ5U2xjSSZzAGe3e4oIg64bJlvGbowsSCsl3nYXyaOthMEaIo5YmGY6QKNAjZ0bpR4LpwHTTzD7Z6McsghjtpEq2nMMQZLPmaFIrzMbxiBduqgMWlyP3aIzo4dMs52Ucb4vOLfK1/qyil7NhiZXZFfGQpVuIJhmwkJnIdJYZI2+NfTQejFLUk13rFiUbvKgRo0yx3/WgR/AprTq6YONw2J4eHo+HLspMXkPzN5RxszjibTMAj8BbsyS7Wq9tDasf88ae5bVpnWAETLOxA0QxGDptkCMmAsoGuxdNwBpr1o3xmYfAZidyRqljoylizzfhLkyBmwsQ8Q0BDGuU3N3a0BrMIwEqPo6CzFvI1ONzD8qZayNeySbkZBUkg98d2o0UQwWpOhMAs46oSQKQM/0iOSTEQKmbVksBiLb6jJfHzUwfBDObJGnk9Xk0Goe4tLY3AtrWWm/zgZUbJwZBai55S7oJ4xZbouvYkMXVWmXM87RvoN4MUOuR70vfYAzzirJGY1q0WyxG4n3q2gAn/ir2zSsmNbp3kBXXUfI22XfnQ67wYXTc2zWL02Gx7FheMjFsdd8+ahxSrz868r0DNOSDtDcqA+ZOfwRSCF2zZnV4+9m23GRIXqTh9o0H7HFmKOBqQFeazsdNs1ZQurA/UfvlpiJeSWrMY+doOB82Z0p23FZlDlUN9a530aRWYgvIGuYbpfPrBlbDi7sGBKHRiMJ48pJckWcA9JBjPJGWyBWlDOyRf6c5lDzj2mFcG4GkksblUnwhfsL9vgbcIYTSSM2n0tZ88oKyxn5mJhY6G+kzqlvTwbSgjtj4sSIjUNjb6mRXf6+xuy/jVv5OShGMkGpEcucFtP6sSuZ4yMyyDJt+ZF8j6cLSLNSxCa/pDVmuZyR6846eHECND4+FzL56IHNlWrLus7gNtQQtuFb43WNAK4yABaBWHkNYKZlE7v6szIfBz/fYb8RwB/NpYykpFmlCMkIxGbQliRLnytg9i4ZfWTL/cAP/MAOmJ0xX48wbEgcGlXGyGxLkDNq/ZhkmF3VmC3bJ8CYgUgGUcxwIVtOp0sWWSZPrLSsZfO4Hk3DoOn7XBMGmmcNePV6NG+ADSyHlHaBmDVuX6X6scJSSVmH1hQG2WO1hFByGaWm8HkTNixAvUwVWGNZVobnHol+VknjY6As2wqo8WKLz2FslWrL+rxau7t83KnDVzljcNwgkDYO0mOZk5vTsxyf1dVy1pyxfX4d/FE8E+zKyF36HHz5YOg2DlKoRx3Mydj9VnRkwlgOS3lyrxywzi0AmTuYqHCPlvUdjI3lJ4l07xKYR2liAwqDOeuSvm5TT3aLA08OW3pGgvdpRpHtVcLFpjU92dLjzjyh1iiPNAKb/XNgo1hBgXnczOHVSRYZAY3jfj7uTatd69mcMTHBbhs4sEmBtXo8YZAaSK6tY2EEBjpD6ExcWW3r7uAzrivsbwWq1Sga9XY9DeMFAlxq/bfMU8asg7IyGU5yibTBNuW1K7F2bHVrZAmhGoBwjtn92zeq0JksBndK7VTWiBOJo9SQuYAzySoLEsYiBodqm3+bff/gB2JTvmgUbF0YeGUAjQwWgXiqopSxSIdWs8kS0MZyRw2W7sDssN0N+B+DGXDQAegH56A9V4DU5x08/3ZSj5a4NAZWTJap6soo3+1mIOQUac0IpDZxLSeIFao12wVN50YY84rkYOkp4XX6rZQhY7xBHRfXAGlbfm8AlqoyBm0r5509xUASzV1dWSZpjHV24fv/6BaYfWPHiCWft+5m8TfwtnPMwvre55i9OWN2Arh2bosZyAo1ZiJD7OxTTRi0M/DVbepZZqhtV+Dksn4Gaks2GYO4DQAL0zdATf+ZyBLZdt4EmKmhSGkgq5tydEasOzreGKgRq2Vcd0bW+J5Y6oMYsVHDhhkBUMlQhaMFAjCL52MnnU1qypCUZ9VcxjiIKF8t8tXsQ7EdcOKL8Szq7CTMOXVidJHF1bjDVRKxq2zHMwklomU++CDaCU9mMn4PYclMY4dDRy1+RlIfoICrBrmiujUaIGtcH4G83GDMWH3kkWOBUX2TA9ERr+1h66h7K9TyxnZ2KDDd2C1kad0ltFRfxrLIBq7MQNVahRiz6dyIAZQa5OjAzModnHg3GbGkI1nbZXYHZrYkS9eYTTbwyrRC0EG4DraEdJzXVL0DvvvlPJ0n79LEe3uLSOAG2Ktt//j01Fk/ZyGzjH5KxkffB+MYro4GeEs7xkO2Wcj8tAEz72AXbM/f2LvbjTLMjMKlPQnm4rozzTS7YakzQ53StuX3aQvwYqHX3oXRQkrZLUgcNQltmjGYsGKcy8SANVaWqnA3ySnrv8UijosCnFji6DsfjTK9WKDgTE5DsMlXQEbjGJYNvPHpsIxB2+gvQ4NLhMycmu1bKePfi2I/P7BhVYBVsVgvZsn8yt+z83q0wIxFYHX65yeSSao167lmPoSEa61ZrDnjK31nhOH06kmOWa+mrAQEV2BWNwxZfP5pjRmWoPWYW5azYgWQMIzAgm3nq7yx/f0Sh/0sAD+yArMkLmRPi60SRjYC+TSA2Xsp45v92zFMOybMyfosAW11I380lR9KphkyS3w18MBa04YNA+a97oElgyJ7DLJAkiYyOBrgFdNFcsuYZW6LwqYtgdM7INjrvJhp6yCKgSrVhhViyBicOTOVnXmj0GiuYetSSmNDFDrnzKqxjNXuHVK79Wd8FaWYxXioRdIIBWcAMlWcujIqAKsk06oi0N7FFz8Pm+UByMEK33x9HbaTSepaPSRMOgmY3tGADN4MsSZq034SeCHWlcXOeR1OjFjkiytbZkOa6MHkY2UXuxhyhYJqSuIimOvZZU4RLInFhntyTlfBJgjU9evNTW3bfZW9NrBXR6bX/N2aDEp475mRpDYduRgSx0O241EsKTVlXY6Y6AKTQyJaV2bgkly3pQMbLO2pXe0YHlLj5k5R1uk5QWj/UuupICwTILdL/dDjJR3izvohmLVQVluw8vO1p58ZgjArtkzzpaPkCzu2djuiyBGUBbW6MTK7NnPMImTzEJ0bu4MebEdKEIVFB8cTHONi7oGNdBHIXeYn1RfMDztjdkOIN4um/7a63CsgU5XiSsZzLaBJ8ZqiNd2xfbLcYKJW8w+D2e+Cly8FkKN1ZVvWjOZ3kLUDVUP2eIvyx6xubJFIimNjZ8qWcOqZi1bKrDVDY85KiIPGEjZdEjapDkDGjoxsle8LY3avN0MwGdFqssmcAVpdpjb+KmHMP59LFjn8JZMu1gW0LUYoX3bgf+zAb8sZMwFY7vnYserslUL+jACp9zVmkdXyK2ljwphh4+TIDFgAYgLGAoOmUkfJDsvqymxjhe8iewxW91z/pcwa15slOWeXwCz5rMCscO1WX39juqyZl7iAvULbrGIE0gFWcHYkWWKXRlbeNtaMt76NSjVv3cmyf0/dbpeK1cWvgh7swzleneU3OKZjnGp76WLGkoXua+Iub1ckmWd3POQyRkDCpcU+n20muysjA7LMDMTVDKRTD7buNHyzQx4kVLNTXKnSCwLGWMao0yYUqzJl5TCnvJGPfBWVfhWwxqOYtdYc0NjJgKGyb77LJ0J0nFBAZDlL6tL5vuYsnzNyNjuQnSUbQda+lhE8vBEGYsFNRHBbsv95qYKTKYotjHj4wSszZ2qgSCxgYyJPx4L94UO4zunXQtnZ5SOvJdNOvTUZozF7hiQ8ywSgeRBYYQFhWEbXC9l83BYmbWoCTTgn17CwAbqiAUiULpLJi9wOLXt/AsZMLPEH0sKa0bxIGVfB4DIta5MhjwY5FUEsTJlFNiyzxWcrfaedXBmz74HZr7+zZFxXJp8vpzMos5wpqwLedp/9xMFxt85kfrTNL8SQldZBKEudWU2Yo2iXP4cPjSzxGZB1i/w7bMvOvsY97PP6FIBlNWVlYc6WLLIFdFnyuy4y0LH5+40O+y4APx6A2d8xw9G05eHZZ1gfDCagzCPaec+YfWaA2aMgzFm+qJJCljJyQDOSEOjGIC1SyY1sUmWMS82YSuwShkyXtwTI7QDWyA5rIAYMlogd3M0D2eeD2CoGbD0GwIhtG9b0zW2RA6W1/qzIvMrbkH1ydobsyzXWrksXSyZfpPkB/3imxoOofIRYWgKlue+dSBt3NWVVHsA1GWv357p+nLo0JiNVnGtWGygrvcaMdroDtkqmH+4rGANijRl3LIO7YNYLjRIqE+HSCjRMQMcKbrLaswmzeAx0lSjWRRzpxMclg3zEMtyvzW6sUZoJhDyHXAgg7v13844zqa0lnXcQAui1Vog1Ynz8LBWQ2gISVikiBlgY+2ATTM96sPv1U82wNteHXT2HYme0pHpwqouhz6hrYfCCjQY5ViIRykaVpZkwZO5hfVGYSvvCYx1APAeKwHx2src4ldPFC6UQF0lKLj6nF6kxWygY1V5bdHTEGibNAkJmxWIQrS1gJP7dq8qiwHECMgsGHwrCSuDqdm50KsRkW/r+3pFY5xfBNWh9e5AzfVvG206OrDITKGlklLkBZyW5ZZ+O2/B905NTE9iyIlQcIVAngw7fBEwbfjWKfee99owAXmDICIAdGzbsIDnlYhJiF2za7TwHbWHKdt/tLNoEaVbKgGIeDEEsrf1SSWCXw8daM7XJ75VsPjLLtLastBBpC3LdXUoZUlmj+gfvsshWUGkLyMwGjk2Ysppc0xX4uwD8SsD+SABm32w6eutOVUQXu20GHmw+X5JBic9Vjtm7ZPSRLXdll/+IzDGROypLZtSBV5ZskS4y+9UkckMKuGHkMsC1yBP5+xLg3GWODFj9pG4NynzpPJY9nvyZArNMpkhyxXDsyOAD4ugIZddkfQyylLkLEQUCGpd5O6fO6tha3RtyEJbWmVUsGWZGjJnnisfwunI2ebjy86SMG0sRlwYzDXhUogIPov9KRJoHIiNWbU8BuiEGTBOb5nn3hKOdI3xygU6W5Jft/7wJ/LzthInQEQOCVWSeVszZGcG/GW3NgfR3vVoE2paKF12Ow8we0xDpHWj3yPBSx9/D6IGFa4KbxGEEznyJO9YktC5RtCEbZLBTRQuY1YDrOl3DwDyRj1IwWnYM9Og6M7PLsbMU1JMdZFTjsjSS68G8Magm4xFA7rKX1WEijzSLB6BIorF4s3cQViyyYTxfpZBoodQh2tY2jFiR7qsvAdP9mtnlexWqOCtLlpkF2/y1NVhMQWyx17eFMOqHhF3jUxkj9a46QOv4JYRONxqwVMLEapZZJFTa4mdAfFxsDaJe9Y1Z2BrbRq58ZgRqTebnbJ+fuDKa/YP3C3vDiPXP3J5qFzVmGwlj3Tkz2gMgbVdP1g1CLFrqN6bQ/G4E0gcDSnBktETKaIu0j50ZpyujfjuKb28SJl0DC6yREDt2zODAkuG3N/awk1rrWFdWNwz41d89xMN+iwMRmOEpFe3owJuOFy3ZEfQse8+YfQYZsx3oQjTr8GSZxSxkV8PGwEg7/yKDzBi5DJiZ1Kp5Vh8mQJAt8ndGIWdsmiswo+Oc1rFJHRq7MkJAn++AILNkHB9A4dNFWLEuaWQwxxED1cwKh02TjLHXrS1s2QC5yNEOBzqzlJEJJMYyDEhckABLGc+cGP1k9NTf6NeTeRsy7QCynESsN1sK5iCFeSBTEF+ljGyVm6UMp6I6z5U7gS2LgCziQAoglnDpmXM2a8w4XDpCPJdtzjNRNYtL2c9a05HvrRTuUaVfJtUTJ/6H5HJXjXs2TWvLPl3t35ttaSP1e9P172SXKpk8bdEDORAne5XLWDFRQrf5KxZDo03ki+w2ob953rYpZ182YsD1rrJmL2Wh07P26jY6tSxqZPZsXYNLN9CFFY8eqh6NQTwyY5BDg0zaWOiMlMSJvkwV4EgusHmYDRI3J4YeJqct66QujvgL8JKanUIsQ7iAVdpYprQRWynjT4XZrwgFdoewZAdRiN3Y47C8xgwllzl2p8d6BshkWmrDnzBltbFqvgGFWLPNVoAWpYwawBxrzTjLrAbWzIYT47y2q4hZbeOAupMyZmYfoUZD5JeZTDFjywqQyjajK6PtWLW/34GfAuDHBjCr34Y0GyfLAQryI0tVBe+B2WcAmCXgC8Jq7ZY3qedCwoxldWbptgRwLSBKAVzGAAr4shNWzSW/bGGROmjaMGcLS8bLZUyVODhWAWaF3RE7GGJ7/QYgK9W9sXSykCxxAVhtmRvVjvVcs26QMtZJAdxIHCfTqI6adDaCqzsHSnOeWc3VfiasGqv97ITL2hl+eNoZehSMcXKtUH+6o+6rFtMR680qOZW42E0edMP1GqzfJyircacs4wbWGG2TEFkWFk4vxUIyRZUtzi5cHaCtBmlihHTKhiH4PNqQsHgiorRl8GaYyBh3JS24Se467J6d7SUj3EfocUo/2Qo8BshwCzJCd2BbKRVs8LPpUULauwcZPDuV7VkGuJLap6VY/EEIdIa+sM871Rak0s8rhLfgusa8LfYwdUgjw6pKIQs/EEhDYpcPYc0IqRgSJh0poHTpMFp4jyUAd0oanTLNPIA0H8LGQs6LKmnUHLN+9Dk1bJU0Qm4xbAASFIFZZplNQLbklxE2cnJ0Z8YryDZZbUogTTtwzK4FdjUzAeEiOd7JYht+kpmz20oj9p2LjNl/C8W+G4XA007KWKW2rSbOKUWA3ABZnI12lWNGgAuUVzYMQ3bW+jaljlJvZm4oVkbUMwM0HnAom6DpQs+BaJPfhxWmjPHW1lJFyggZ3EAC0OwCmO2s7zNHRgWYdctoIw2YPpEywoDvAez7APzhyZh9yeD+ekNm/tkEUp9bYLZhxbJ5iykIyQ0ViOHE8KM7OXYHRWXESg+Y1uws7O3ylSFLLfwV8G3qz7IMNBMgBrXP3wE3ZroIwIVw6waerNs70/pLrfXgjDMyAHEGfg3hOeWRVXFdVIMSE9arM2l1I4EMDBmxaf14jgin0SllkEXYhYFXFTfGAcIOCpc+yTF7lD2ruQgxgS4XDNniKqC+/uJgYj7TsXv49HAywbozIHqR7fS3riYWQYMBaXrxTKAKojSEzzYgWl5Tth7rA2yXXxMBpEoTV+g8hZTcEnqA1rqqN1Tal543f+zplKAJT9ZjidzPPW+FL5I73zws/eGHaD9D9dHnbssX27k2xh/mZGOjy6RthjSiW+PZr2dRnHrebl/+z/cNP+1wGAFFT8/8em5iOBZiQJZ5DOzCBqDxceTfmiEFZ4ZY56LiQf0WW4GzbX4Ebh2UIRVCuljm89ZXPt3kPFiKZaAZZkJEmZBKC64RTAI6FWZrDRmvd5EpYoODs7LSnUW+FmQakIdKU3fRLOEvpcbM7DdPVgwiVUSUKB4kieTPnZWrxJoVkTseHBqXWe030Na/V55hEFIKsWW3rbW+3WLaWC5pzFmiPozAUsYaotKN5Iy7AOl4lWZ1k3lYdAy8Bh4z9tjVmJ2za+eGNTJA8w86A7P67b6U8rKxx+798huY897nmL1D/wSI2QUrpmHQDGz8pC6NjT8qfVkliFpfxpK8fu34xm1R1+HCzhlizdmSY5Y5OirzlgE4kRZe1ZUFRkvNOHidCbBj044eKj3aTN8pidyS930AOMonY6DJ+8rAD2fMJP/cKxE7aVSwi9urn2SZIcoYXezydwYgmfv+FXPml0NNvDc1CW4U6/wB0AiY4Zjve8FcQDzivpgiTUt2xuNB98AtRVABF2/GmReVyxl9G+Yd6/n23v4ZGFvZs0qQkbd8b89R63O0eO/ynRePU1Gf8hbfgtTy8XnfqhP1+Ha8M2aF6spgxJiZgDAa9NB5/fe20DQRrGWui9zRtwC8pj1+QW56cWvmH5Mt6wChg65bukVHWbqAbHhgw73RF+HUEiZteTazJaSTCRAbmKZNv5XciN4Cp3fd0V2BLZJBNQk/66MHHYQg0VwyK8ao0qX+bAKzn4Jiv2IJbgvyRQJVYJOPJGA6yCFFPmn6XZVBWmTH6oOZZgzU7DaNQVT+2ADa3T5/Xr3RPl+ljPNKU+MPC/JFbxLG2tiylYmzZchjD8yujD3WbLU0CDplwnaSRZN9tg3LJtN+pcO+G8DXAeDJv7TSvi56fFV4LL0cC4FunyvG7F0y+siW+0t/6S8F9mcEceKy5izUfHVw0pkzft2s34C7HfyGnTOp9xqD6QxC2naMWDcGGOOKFHneTgI5ls3y2zqbtWHpgolIBrIShs2J+TJmEPuxEvaNgVK37r91lktAYogCqLWO5ToT2TPJyGCkh34bHY/7WDudN8ox0/NF18YmqkkUgC6SRq+CgSrhmQfs8s/Ysh3wUqexc3BGI+ZuORxki/xQLFenZrPSjlWRMyKxv1/8/Y1qzXgvyEJ/AzmnwYetmU4CxEBwinPNZsVYrCbbhReo+yLCexXq7aWIdWTBJd7t/liXfCOSe7yjHzp1j1WgLTyISvJ0P05SGdhePq4mt6s/tblHbiWf1bEp53p60Bcfe9lBz9iktYXpYg/DL3/8G1aiRq44OU94dKHg8C6OpTDfxDDYCX/IkMy2NSvK0RQBZQyfolzxjnh8gSfTLt/INh+be6Und0ZOBhhMl0+lhAmFZZl8kerKmE1Tu/zV6B/LcUl4rAck6hogDXIzQeJgEvnJmHWWOJnMcaRfDODvmvViENMPTFvT8ZkNQBATvYNpSJFAarLU9wuDkAC27HGAFsBaGwCQjDMTKWPBNAWZAlvb1GM5CXC9pUZOUWRZ5JEAC3/99NVDrVtXjzAYKu25t5MtZvb3O4BlzxhIsP01/VMA/EIAfwIAnvAl5Jrcs9pc7eEY34zfSxnfpX/N2c+YBtvJFTPwBAl9pmUyBi2wVyAL+1bHpAHVCqqYMXNtZ6+7YiDTAaKajBCoc5ZWMmPE39U/lhrS94pa5J/9YRppOGWY3epEhsOkA1OaGGSJuFvnB7fE4zgqgHIcB9p3bwTASqslA/ZmJrcGEG8EAFmyeDMzP+4rKmYzrKfWWs3sVlWd5augLrBm2ocjKaPVlU0bJoa+xnhd1ZllQO2xLrbUdxnVjLF8ycmvndEnW1U6yRxdA6Yx682GIyPXl2GVpA23u/ZYsLrR9LDZeRXeikOmOwirMqLY8vUCOEPwXIzWkuz/6ALAomgye2xo13bKpXWk8BHmUy3tayrMi9+38DgHWN4XhZfps+8Marrnsj4jgJ51iYWI0d03AdnmtoY4swPjZjTCE/lraKrhvIzRdSzD5dx6liqdYqnQtaABuG1brjmyVdITZIold1rUwqZFyggyhWCufv0tWgh4BnUtXUb/PQVptoCUnhgVu4sOjbRWM5BdC/i0WgBuJcE0vUbsRj1KZtU4tmFEgAnj5iUaDwbwxSptX0vDIMaZZitDlo+nyJ0mHJKzbnVCG7rs3GTMvg83E6lhpufM0KutTNmRsW4WWTNdxoRlM3F/fKge7bZ5b2lwdSnWas2upIxG2WWrlFEt8vuAAteWMQDbmeKfGXu4gMQ8Z+yaKXuA/UqNfK5kjwC+LwIzbJ7xV8Nwed3re2D2Lglq8jqtS8bsikFjsEOZXFvHRg131rbR91N5ogC4lKHCDIjmeSp9HEyaMl3cJrWQJ3ZsW2O2+yPTj0U2SXVeY19528R2jWw1Ng4hMMv75GStr+BQXRdTZsxpkIVBdH96HFj9AxiEjXl1pbqsin9GQokFu3w/D5l+xBBkd9tal9jU6HiyNaUIO5NWpZVexPSDdaAmQC3bCYsshIYuJyPdvsQiY2HLNHRac+Ky2r48tGC+t1QsGVkzTwSnw1J/J2V8u+q29//e/MnyGgf7gt77NP+V25QuFqy9f/OIEtzjMoM6ot+Tab0Zw7E1uwwpVIoGH2yhn5l/LJrAYJtfkunq0ohlmm/umKHeS7CKI063JB26EzqMSQoFTI+Sv0Zk3rCqTcepkVJA1TEGVrjIQIBhQ+1ZwpKJfNHZ3aSZYfQdwK27MhaY/cIlVJoZsqpsGCLo2tnrH4krY919LhJQrYyZyhJ3dWq3mXVQk+VqlFUWK1QRNiOiy5AymljL+yJltMAFT2mk1k9WAnusCGEjD/2cie7P7O/P/55viZ9Bf+Tvf2G7C9XImF0PSe4lGJ8S8HnXc8x+sgIzlSuqbDFhzjo7FrRIx3F4Y8ugDB2zUwR4ujmGib19Z8hcGDGVPLowZ31VHfCM9iizxu6ECTBzZQ2fCc4WqaXUoHXrewaByKz6Gbw2Ns8FULEb42ltW3KNBMmiMKxA5JKiqknzyqqQ6QmyMo+GH6AosJRkwmPSRlwwaNfSF0ijgZOwsTsYKxQ2DZI71ioIh3esCjizaJcf6s1onM+v9sFh2Fd7oQEzLFBqfR+P9xkcrgLGcr6uAOTiGLu1kTF7jZ67YD52T3fl1Nro9yP29HmgsmNraWxvHy1emCJu23rqgojompgfCT+R1ehWPAlYRqqpHOMZllv5D+73stQtrqTf00NbC1E0xYGb5SHSw4ExQQe8v6bBbr7tCtnCpEGCpT3IHKNIsQxL8YIV/dwlZLfgxKh2+bbJddK2+RJUn8sVLSGEINM9A2ukBBx535jZaGlOGaajvVlU+6Ws2tJPtc17Zr4Q2SqH1JvRDhQBa3dg9jNg9gtXluyB99ixbBuL/cz84xAR6LYujdwimQljxqwbhXDwdXdm5DBqYs7KrYSgaU7tO5cy6l+0tbFEyggBZP16vglTdqMndkmA2t7YAwMAZrVkFedOi2/494sd+GkA/j9P+CLWKHW7eOil5k29MPI9Y/Yu/eshxdLB9iYZYet0dNCDKVvLWKkzoOfKrrH8sG1vyFWo/oxdAzXYOTgvJoxY2sZs3gU7FmSOGfjS7zDYudomyzSP4yiIjodOWWO1g62eV9bnAbiR9PFGGWZDHtm22XPNBpqgurpKMtHKGWi11qPf0/i9u/f33amx1KtxnIx4qgnhVFdjEK/RF4MZM85W3skXe4f8Eoh58hBHpk5SazlxYuSA6cCUdTcTWxvddwoWHVTSFGTStvmBXBC4c9RjpmxlxTIRInsurnKOGCitxXIunpgsqOytO2S/2JlxArONjPA1gJrvcRsFFPsl0F2U/J6uMbnwXj9Nb4E/frGQ5bg9VPdtpY1nR0L3fu8X6bZpaLJhljznzfKRGHH+O/Ygp/Ylc4xiMFibVyD2f1SH5lU69ezUmP3u7HSwJxc5IrBqE25x7HMRxowN9UtYY2yZBV5uzXeyU2BuSjoJizYOS+LIESSMiT7TyiZE2yCm/htXRl/7rTuAH1GbDFmU5LwV5TTE898IBN0dGL+Kgi/cgQ9dT+zGuLxP5hWZt9SoXTBnow4Nm7q0Mpmwbrev+WbdjdHLNscsBk/f11XMyD6/14hBcs0wBuamlLFKqDSCzQeWgYMcmO1cF3PZor0WsFKWC5fL2kProfffbsBXAeAJX35bA3qfzxqzd8noI1vuijHLQIiAJWa71CJ/AKZHt7Vx+uvMWNm5P77Oe8kxg+5Px6cikSxkkMGZYMEU47mM2Q7kEcDr7eQ6O9MaNXZ87ICPpYts1d8ZMAbnnMPW89Tkt6HrXcQhY/zYVyfGVBVTAxJY4sFqXRk0LbnKHOW5U1cViPkkns46umEDtkGWFUmINDXe6upewlCyFkKX1nauzT7sRJNpYqHfd7ImWWYuu8PWG5PNAHFYEWL51omRpxcJkt47NHZ+LYI0bZUe79ocLF8n9/g5wcwPsU9qxIFn5kpfJV0/52uPsoV2tZ5rO41HLUEszWdbP+9tRp6zs8L47WLhdissPdU4kTPyZ74qQ/JxBsJd2DQ7qSXbmwnMm++ET/cMp5WzAVkmLNrBB6SMluabgZaJcktlwhicoawgLXhkqIV+icQTSxS13myx0NeOrNSYGXbWCJYwosJWecZobar9TCSjrxwAfiNuZKt/JLb7Z+zZcUFBak1a9j2uU7NyXZeGjXPjIl3UWrPOnFmw0++h01hqzaJ0sNLVWwnC9VS0mwxLxKw/E955X1PWt1voiXNmg39eR2bLtMelj4+ZhHRevwK/AcBfecKX7DF9xOMSkveM2Tv07wws9Y47AwECSmffUQDHGWBVQFzmnAhiXxiYbHPWLqR3zu+1rozAWWDyFLglgAgZW7dj7BKwtdSaXbB9pkCQGEyVPuo+L+8b02ZZrpvW3zFbSLJOPsfhaVcVkCXduBAiLQjLNz1/F1fGIwEJSr5VAmiesWeP/1jWjteQSpFjiXbS3MUuX3imkWNmQv9Rzz/VYrIZCTNrLo4Iq1jPJH/Kg7RRrSJAzozneWZ1GH/sks+iINJpTNMDJxMr4Poj96h1BDh7BoUaYptyRAuj5R5cBPt6WvfTIsqKYbTe1mHxcjD1u1j5BQZ5Cwzx+a05vcLdCFtz0LXPsQBTVBi3zV2VeZMlEOFYg7bjQiv6DGfNh4nMXVLoAYj5CNq+d7HupFLflxhy7eMjDxK08/cAuvVgaiODPHRu72MZdR6ZfhJHjZlJz97FYcNj7djiROGTdTNPTU2Y7QqywHQUfc1u4vk3+psisQnKPHVitAH0/NQghM+1Ul7RcGPMLSveYGXfkmVWckyzuDJ6lC8G4MWnDSe2+SmBqVpM+THYLlRavSELuZe0zz4yy75nrSWD1JjZxXSpO7usUSsP1Jxt6tYCu9YDr5/r2miRVStntWarXb6RhNHa3M791sCQ8WCQpQHQAEIt21UA9DpM8XqM2aOACxfT5f7wPXfG7Et0sz2TKmaDECoHqm8f+LzPMXuzf2rtzmBsx2zR62LEQbVm/bNTDZrvvkcGEkMuyQYUx3F097+HGDFm7s6yzRLL+y3jdcaEMeh6E9asn5Os1oscEnuTC7k3DgOQbuLR1YvN4KMQKC4zxsxT84/O8GugdCCm6Dsyb0oZ+WGYeGH4piSrZzCruSGDuZ3Rx6GrlNuXb/rz18SBRX//LCk7UH41qUNDLLID0X/BAETBoNjjc85ZKiXLKudU8xW9EFdzTBuclnJvGVs2GTPQmVgT5bLYX5dkmSiupFNSD/EpVNTvgTBk57+qBI5PEBKCiwObS51w57o7xjce5DPthtayO+/fr4E99tRy3ynmwC1K7+6OhpHz6ZnuXm0GudO2xz3I4pk3j9JE2wE32vYAsQNkirDRK6q1ei8Cv269y1UHCIZPWG4c9TDKKZ0h6n2UZ/m9MpSt8/xYFlM9DXHc56BEANNLYZKtEkYkskatkOlujkHj5xlEbnBrhztZcBjtwAs0dWwCvZJUqCHY59vCK8TWxZozI6bsrJu3SBgRa7syg0NjPWJC+ATsdvZ5h6uwloytC/m0KdUiuHSFyRa47ozZNCvA4U+A/cbo3ph02UMq96ZLf1a0d+h0rDVoyGrSsrwzqkszrmfbgDOVOwY2bZqEGIEzb0+KKW3kGjMngDY9HQGkBvkqX8wYsilZXGva/LVYstcz/HguEEuGDn6TA//8vcbsTBrtz5j+KQCz94zZpwLMUkbq0ddHgQwDPGbM+qwux2PGjCSEof6M2Z5HpJivM+/RfXuk7mxjtGEU/NzBZG1/RjVmHYjVzu61Y1U7C9YPGdWiuWSgjftPqx3sdWYL8NJ/BP7SeQOYcXg0EivyjeFHL93ojvLGbNlx//P27Ah1ZpZbb9yPEbnKI8lg9lOWPxlxNWLCVAMnjoyBGkxkjV3KeCAvlOOdWxouoK3faNlCP4AyG8ktToDAqfNVA/to23qy+eojPjqP/FbYHEOnp2GIyaOVub0+OOErmzLYrXkMrAZCJrA9fXR5b3rRoIEDFvLqIsit49ruQIhgAp8Ozhw3Hx1fC0jRA5/J7KwteXPjxjfO6x3X2+wj1vgsnkDIQrtgLcPHeynLZBEhYw+DZVM5ZNs5dwvsRAc8RnWRLh34CdRa2+vs57rL+7FtMr9pE00BvDnc70DLAuPXrp52cYzvMUUzcswQa8Yyp0Z9v9zpHFe1ZcAaKJ119m7gwOnpUFdI2liXxC+GcAWZyb4nXUQPXSEPzCqXbwXbe+xxRIYp1JERXKJUz3PLAijzfB7yjq3cOi3uUCZnHA0mwKXHcph+CEX4yl6hoOBVO0CvmNrrrorozNp8z8t0NqyKu0m1k5o0ZcEwmTFIjVrfvyp2/WMdZb4qIOOctFqilLFQTZrdpY1m1ozvC4VNrw6IhTLLejw6xLPU5d7odF8uyITzj8sNVyb79Rgxe0MglixbDHj1hC9aHJLQDsqVqN7pB1Dfm398loDZIwANFzVmmHb5Z8AsZcyIvWFzjFBvtpEaBsAmoE0t9VWSqCzbWwNmmUX+Zp6J46MRi2bEnjFDFqzvef18nplF7Hb8IpVkYJgyZAkQ07/iIAUUGRNmffVg7gEsFvkZGdXNDbW2rG6ggdac+YNYbF3AogYyuDNSL3wANrWTpGK5Pm9IGdvDsssZQ32Zb6SM1EkNAM3lvl2RO+V5AG0uTEMloeOZnPGA42lIGdcIaiS1ZFjqyaInXISLbV+Og0bwyRlvMFTtUeMiu6oeuCFrF14lnaM1dmkhGZ1laRbA3JCtVQ9zKpi1a+0bHYca2mhWB6ipZN/d2SRvmjHvAMQojc5rzCaroi/zicxGdl2SkT5JwfsyXQJaLWp9wxGUy6ntWQNF2SDtNNewcW9q763C6pTNziM9daC1rdRsgrI+iFMDa2KolbTSlD/YgelgIPsl1F0ZjXRzA6AlgVq2Ccg2YBfRHDltTTHjWg9LgRmCvNEobBeL+YeRGMzEpdFCDVmBujTO5Wb9jWVW+fzzIEJIy7IGAVXWsqhOqKgysJRo+n/W6S2Gy070jlELctNMIREarNOoqziSsrsMsLX88K/By08FGih7aq+BLSOGa1t/lnTZLalPw4O2mLVElo0/FwJkwaVRJZHq6lhCXdnMNZs1Z2ZlgDNcSBm1ljI6ifanmpHjIgL7luX9xey0PRv2Jq6KjwCtR7+XTPvpFfa1u5SxvA1L394ze+vA5/a2scrbXNm7ZPSRLfdn/syfeTYrlr3fySElMHpxRryax+CNHSEzKaSAqQDAtOaKlmOZY7C/17osRGMQexSg6TK70GoJeR7HUYHa7hgy4NsxdT1kWsCwfsfOWE49V9lQcM3GZs7AkOdALCvL6rinIjH8sBV0qRpwKbnakf3LsqtsLjgmqkX2oP6QaDgFeTI7lh2gRY9J4BCJdMpkx5ejYtDcslxeGIOlVx7MA7NWyCbElnDpullrFj6dXzte6wBg401nVmp2rc0qKEjZjw3AI9+xPLTazMjWX8PGQeGyxH21nmsEkj5+IE4j9BzAHDuHdQBxg6/HZOwXCRbDKZ8sKWR0WT+xiJBBBMxnXWiH1OKuaIGkzY5ha2g1OoYVCPVqvohubbGDIYDqlpvFGB+vyY4NIL6ctxvVk9n6Hpxa3Mbeg7yR6848iUpIe/4jKNfkjGhENHdHJ1gpkmMWHTQsMGURDsbAaE5SQ+ALLMjELIsIixnbCXtmEglmhHO8j/sT+uLg6UBKmsTKESNuJT507KQ00fxsKN42kkUBQ6rBHNkBhD69AIf9NLh9eXyvZLaUkknGktkOlJgvUcMQrZVbvncB2IoJKJNw68VGv7FlXRJZL2rParTVL2aDDzuTMpYG1250vdZl+M7eWhaZzsczgRgeAmX2LNYs2cZXDP7TZo7Zjhl7tHL+U6oxe59j9q1jzDYsmQKxh6SOyro1VqjuAABL78SdUGvIljaqzPGZro0aqgw1+biqN1OnSZ+uHZaRUH3fqKaM68A6Y8ZmKZ35Gp8flR92lo1kjGhsJPr5YPasyyo3TNmsb/MV/1QPvgOnqc9qlW9UjoV6d4WvRRgyj9lmQx2oKkCP9Wa+Y/yXz5YM4Yglvpp8GDFIgznjIrqa5JiRnLEHS3djkODI6JvjZxdjTbaMgLvIFTVEerXw4KZ6CJjuLJk3js3Sb63paUjirLn6bdRPeWOPrA7QY9RJHyYdbJoRTCcItBmbYHgCYgniGTM61JElzZ2ZT4MPvzNOs6arS+smsJx1VtbqwawxWS5AD7EcxiebdJf/eWTmBm6cAJGNT4JM1KY0cqybatzutWOT0xzL1egxM5tq7Zx34nhCp8GmWQSaDAqdj5EMJBgfM+s1Yyy4m4DR2v4PsGee5gp775B2dwmopZ/UnZUbGYEgYclsw57Z8hs06qrFKrBVzrgK6Ew8GA3suGhpwPSePVvjrnMwpv0+Y3MPUOxXSQgdUQOamgq2XOZghknv2S5/KU27rVln4FO3TQ2xDSjLXBdjnHewKBlU4Q3Aq8Y44cN5AHuuGdnmh/c8rU14MuAV7kybyftD5JBqBHIgAr+6Wf5Qxo5AKMske4aZMns1C6E+/zNjKaNJTZeHBL4qTqFrePS1sccjQGsVSz4uMTyTKb6mZHHPUAMfxhyzM8liNrypnZr7E+BtM1zvOgP3kwGYpQHQiCYej64jlTISI5ZJHEH1ZvagrX9m8JFJGU/ZLUQ7/T6/ZC6RO+MPBp30t6zjgm0bNWeUOcY1ZIWkiCMEO2HUTIBYkCRKrRnb4/cNl8DjJMAPQOmYgsmbkTfkOMsgDstwnRm/dtJpAQ6emLT7dvWr6MivtI2+T8zW+rIA1iotQ8HSEGDGNWbdejLbCT1uJj0OP8tP0pqx6KRXESVl2Wb3wd61cWezYM63qXIMhxzTWiODSG28sWfBDf1aTdga3ud6Xgv9XH2EKgW367GcKzZVUMnDsg/717lehS8PtxONRmTmK/hvrs01jc2LosLnHyRcN9uGZPax5c/bNGFgXbjTk9UGG0CpL7PS5I0CwhTIQTMWPFA0WsMSOStLZI55rdlaKcbMWRFwFp0Ye3fJN+wZs2OWsqs5ecSDAUtGM0TWmFnkW3Rl5JKpktWQEYE5MLLN02VZf5WWSUGZZfdSy9moIGWUWjNQPdor/2X3HuVVF3wz3/Rg29rmR0V0mWRSmbdjZ7kv0kcT8xCzjUHIjYKpp8yx3LqUsSxSxvslUClM2hYGF8iMPRYrHlrv49JECMC7YrOuWK7XPPPbaQ77ZU/4dtJe+7LE+tmwr/T/dKSM72vM3uDfVbZYAsRwxYypVX43rLiSMnY2am4y9LJ6Ntejzowm0zIr+ey7dgKQ/ATEXX0vMHhn39nlmLEdfgerBPy2ksakvmwBrXw+mqtmBpAtO/aJ1NGq4hYFOX4GflZJYzfQG68+GbIlj1nUf4ynsJO4qDPjjj0bujFLBqTYatJj/dmQPiZOjSnz5atGU/Od4g95vd8Oy26/7sMHO2/fOjHqaauLSLFS19+X+jLdUV8kjJ5W6Ixt1uMt3wBxotK/0rpeff98fWuC1wl0EDv55+3Tm5YiULuuVnVKr2y+6NJbPkNQnpu6ZBltEfBcNNwEmGmIFpt7FELXsChpJCA23SJd2mWLVT6ILZtLlcxVnt5bqMO5swtcM6aixzKkkwjWCmt3UtmyjEemdIbgwIhEtrg4LlJ5kpNJoFO/vlirM/MclI4sZzLVhNjoBwkkFR8FKxMT+g+ONBlbZYyab+Y2A5q7McaBXzBBD+YrT2NQlM1/5Du79wcxXv39gdws5CjRLITNQw6cW+7vzEG6CcihxiDTCESljBwmzRyW0/Wo5h6PAq7nmnm8DZYLb/87v2Da5ZdNh8AumDJ9ir8HZp8VxswShuxKomgJ47UDUpxXNqSMBLwUjHGNWZAobkCWJ/vzWk6Mj057pOaMWS0GmUkd2K7urJISMYDVLn1s2+CMM2W0WOoYctKwkTxq37wrIbGY4Ue7fFPckvX2gSDhG4o/UCnWQcNY3TMjIZWqusqTIYjWoWW46pQtCz2TukIXLWwzBWGZwfzRdiRFOlHCWMkJEhsLSsuO71lP1wQi7WMI9FR1GDaDqD0ETE/jj7rhK+ODYQfegvCy1jjmZ4/s457qMkSjiunxYWLqIh03V8fCx9qi9VCuRheJyQILh9XifnAsbLiByBxaUru3Vj/ZdIz0s2Wphq7LHU3jBjTDKybS+fbYIHrqYCnZCyfel9+csGgGWcb3XKbZ6u8+SBGiZjjLbAoKom4uiG98X6+EWMUVTfFXedW0w2eAZmLb3ZPPbosQUtkzE/85D/AwZkJlHUmz/FK3E0CWucRnGWedwGRgdUs6z2CGrL13IcOUI/BQa2w504wHEeaSmC3o87BPtl3tjGa0ZH6YlpiCpEHVu/lnTBuurTRPw6sTsMY1aCx5rLdmnx/ljP0ZchtXN3AM4BaZsszYIwI0e22zjh1QegS0PceA5jUB2ydRymgno8nZGvT++znMMXuXjD6y5S7s8k8Zsnteji0Oh2fr4mUJgC3AqwMN+i7XlCkIPK0j41cOsb6SQl5Y8LOD4RW442DoS9DW67ja9MrgqFnjd9BcEWvKjOWEoMgBAnvb+rOEzUsBGteipT1poFR9KCau8uk3mUiqcRlVASr4UqIJGQll67wHxvRXdFaxBq1lhXKe1JmZ1JstUkaiAZ0GxAKztrGXrNkTYZU4GGZdWdwzWzisXY3ZMbueA6CtcdOVxjQj3DMy5fcErWuemQHwo1IP3jbsjJ1McuFRyE49uwBCuPMDLBFjZo8D8HP+nuoJgwT2CAN2tb95Yzn0mvdxCQ9Y2qv7GaOxL1p2QYMJz+UXg726nd0l8chhHAHTIGdGkAGIUDBB6ijIMozy+MKV5WM/Je3cZeljha7gG5EqLGW0ZpvgYou/5pqVhSmb9X6G1Y9RWLKN/4TiGTvLaNYyriJ1ZRDZok5XXI3IjPFv0T2pNzOIvaQAFbc46GJlPUMcKs10INd5HVhrvyCDAWqbrwjzQHyfTTudn6zTLC6r01J7fkTDkiyo+igrINvUmkUpo4dBAn0yFBruex1G7BFQdAWUXld38FxwtvvOzDErF9Kjs1agdzbe2+V/hhgz9GDoDeO1Xa5L4UQqt8gHSW6XMWbAlOilwE1rwHbgiBFjUi+WAjIGflntGLOEG+nhmdNi6tBI+6WujMwMVgKNNwFU3RyEAZ66U2qbnJbN2MzMIr9uuBkKphYAhtVVPsMxo4Ml3hkaCxaYMKxW+YxvXAaHGNClHepdr9JF0uKeM2VKe41OQMWaBVBn/e0OdFUPtueB9qsXDN8yxthB16wyySJ5ccph3e3xbwmYq0PZ70lIdE24ol21X8xOsvbwnnJbMsdA7MVP0GEUzty6suaUkNZxx+yQcTj1vCgNLmBEXR8DgGbTP4sxBtNUYw/Mhvej0xlLEtBNKNLATNvaSY3o3ebvsUluncBNCG1Wpq9LKlsHbZwpDvLmLdExNAXDRllo1OBKz5FhcR9OC2fH9Sw2MnjpQIOla92MhSFoD5zudvlDyljoPaJqCFitAY16/wGg4YQ9XUf2CyACQ6P6Mdvmet3aXx1ruCVm+jGmN8s5y1g0NijJBFOaazwAlgA0N/HOKOvnUapFh/1ma10d9PAbkK1qwcuWgX1lk0q8txSLoC1FmQTKuFju2PW0LXm/6YbbSXdewV2KiIFtbdprf8cu0HdJwq2p7qzc7qHTdg+dLsKYYYlBf07g8zkoe4T5et3vvE1m7OzfE75drvJHAFlaHODA8b7G7F37d8EuXTFpkKyw1LJ9A+z6dd3dBLP6sykci66M2hZ/wG0xyy47zTjbZKPhxO3Rs9oxiIFIZoAix49rubiejNdvuh+ScdYfPbeMFdNrgNQA7NgItdSXZbd3gYoLwJDFaWldWSUHelHDeQuZDmHILnb5ntvpJ+qmGEd2KslWm21EOs89UleDHWN+SeSMPWA6IE2LRXRMCy4FcTsVwwq7LIjmqAObMGRbdSUQwFUdvosOwzH2ywJzdmA1MXacFxuuy7gfs8MNkNUnIftx2BhA1REaDTKHGK6PWeoBgQpt4gofqe11fsc9oA/6vgsrNwOzoxkL/98ChBtx4VpfFaO71prIcTz65WuBOYOvdYdsEtNDtTsKC6DJ18uz9qDuBJbDF0JzfXbI+eH9XJK23EOtSgDbPQg78KXtqDKDwLllejMo0st3TzrGO6t8D+YfDhYNMhyKMkNbrEEwQnePRbC4Jn8pzFsHAmyxH8mqGUMOM+82vy85YxbwDBLjQ3VrbAHTRoedr5FiUXF61tHtwE1Ur+eEgsmgXVE3RssRp4K2Q0DYo7SJMl04qT9bgB414SE27XVr4JLpWY1aQXtYSwZalzTeJjAr4Zn0Znlhb8t047mM2Nti287Wd2fMblitsrC99+zDp4/PBJD6XDNmD4ZKb+vSZD1+IWV0BWFk/gFQiPSJK+NDjBmBHtd1KMjKGC+dtvlcEklgB5b1GcHUwR5/V2PWjxeBNOs1Z2ZmHaAJixdcGTfsmW+ojMWJccuYYVNPVjcEygakmd9BGAphm27+Ucn4g3GMz7KtxcBQalgqVs06tHIukwTOar3InqmscQlhS3zxMyljbY3oOxkYNYjnv/6ZdB4hUMqDQ5+PGOlzs0y17agNht1GnZlWC9SEJTvbCoI9vpyVdr9iS30OKp77bWTIMG5LNlmzzuoPJsoioDHq4Pd8tLvSKZo2DHUAhT8zM6dHHdWX0OneNqsWrPXhtQVLj6Ub40YAg2zvwUxcywrrh8QcQUs83f09Joh5iw+Ao5LNPgNhM2bWfLCFTsyeCZfu8JAZ148ZdLuOEaaNMU3Zsgh0ozgXNN0Cl2jtd6FtGZEJXNTEjNgiXYSEa9G0YP8njhSLjca0APFNoHQma7y131yh41cGm3Y3/zCSLk4ObjWY9+B4N2HjWglYlqETzSLjCDfNItuRSyjRNt+lPKvYavyhsBPJaTDLsZX5CuZyDbMnTBFyVoiljZwb4CJlfFYvPHNmfHQdiTXmzunxoXU8WAN3JJrWs6Drgy6AWlBKGXb32l04D1t/ffD1XID0rVrfY4zZF5Nfgj/AkEk/5tMy//ja174W7O3fQo7ZWw2Y/qwAs2eAr9N5z8hCC1LGBi4qom0+A6ZQY0YL2Bm79Qj42r1/5PMzpi+gDZscsy5d3NWYSW6bE+OY5pjdywA9GHecASwFZwKkdZkUmB0X9/+AxaTXz47zXqV1DZzV2gbhPKr9nIwRucjbyZWRQeO2jamXPgMdJ5t0dWDcHZaaw5yqVvh1Ik2w4YeCQ1PcE5FlqA0ascAySu9b7ir3T4ywa743kkhmWWU1VK/ZBo1XYkAskU7VeoyTaFZJ8ucDhNTQCYksTwddtYOClu8zTmvPBGNHfl87Ll02hw4UW/7XOJ4W+3hD9t2vQaftOyb6Q1zP7AVPsOLB/vt+EfpQO09nVcDJLd9Cl3tmsqFlfvkarr2YelImWmt/HXlkiGCL2t6lpD1vrrYfppk1wrOOiKXpzFOpHrTnrfk49mjHHIj710Gek5ebixFKNTF9gQFfKEnPn0Kmi6+5ZsygBStAtYKNLFRCFKV5SkgBmonw0Al6+SJd5PwnpPJFSzm6aNS/OqaGfnlmnY+VWFpIprICtm7cFw499lluZyDWZLTdLpVeOxv6nRlIRvlZtJZ8daEtyVr1cC5F0q335/YAE5jgj7bFnjct1KUhuDkaboM1c+yzwb6VYOltsVxvckb2wOzpAG47Z6EH/gW7/Ap8znLM3iWjj2y5H/iBHwggR5gonLBUCxOG1fzDkhqvnZQxZcx2rowMuHR7CUhT+/ytY6POy763MRrZ2uCfzEuliBAESvNG9ADJFTsjNxhGZsloXgfhaVvIij/cH3o7xSnydB/uf9gWjSvo6R1icE5ZGrgciSc29AAI33iUq3giXfTsHnXpVqB2+ELtjY3tXBg3zoxjZxBRIyA2ky6Wkp7XwS3BtgqBfLEkYHnfmS9L5MEqRULbCAeNhh1OgA0EzHxDmRoJED0B8fP4encSdB9RCTbYnQY+mtQx4GcjoNIukl7qZ+4ExmY0wmBvWpe1Uui0O/F3rG6zSc9yIPQd7NQBWuaJmFI7dxdA1fbDZ8DzHaRUcimsA9x4uz47g+Vdc9jsTs1tyiw7ZBjHa7poODFitNcw83Zo2ScScKtUb2bwSoME41i0fWkOmx2cOrT+j8BkZwGtjlH0AXbd7tul/Q6/hVHTV8modI7aGIdfpUwZuYr0lGP1i1d3xkVCpFe5Xd5yitSXqa1+DJnujNkKTyzhmnzrXXdmrG1rx1N+ptpNVPxi5CrPiMlk8F+DpJc/mwYh9syOsC8NVJaJGyvLBIhXkJp/FAJqVUwx9LU8OK0mVvUlWf+j382Wf+76TtveKM9a5jS2469lCae2K5Olb/G/d6ktKzDDxzMsTy9o98fljHVQZu+ljO8YY/a6DNlzGbQE6FUCOW+VMVPr/rfFmL2u1FFcDhd7+o0rYmDMfPakuCaOAdww/8CUfw5ZY1+EpiOTLrKZCLaeiYsFP88r9UobtykzGiuX/lUmqmQpYwqFalKqdfX3nBFCBkhLw+saOK3h04oyMzf9ETbtpNlE7jrPnL+/zuhpjlX3F4ALJ+YoQ9RYG1SYtWZOO+UbCGhhHhuJeB/FGZ14mA13zPsFb2AHx9plgg64RwkZrI5SMC7DMk+UoJKzNUvaLMgqp/DGSV5oZPzSWThbahUj50QttXoHY8wQ0VcruHqJpYZsZ99pujo61zUEb3f5ZRX3VCNjkMmyuVcBjRHgj32xFCOhHX7K8u0mHLvEBxsWH1n3o9LR83VrIHzeQKCYtxjIldHuA9HdTWNgHEUBLh16zwvlUjgw9zSXZcXw6ZKwZyX9y+dEjjzCutV50Siuer8Xph8SBqz3pDIlW7eBG9llBVOmTqeiH/YbNiHTZ8yZXH/p2ViyKjVwWi0ksXJyHMAWjv3t3oUeF9PTOs1P5p1O4+/envnd567j0bbzX5HXGx2Xp/vx8tsAZqyzWB739rxnFt7s0Xeao/la4P9T+O4T8E0ZFcBa36B3y+zKNwfu4aDvgdlPXmD2UI0Z8lyyysxWa1uhZT1hchS8nbYzM/ggQJOxYM/NO7NdjVnCLKXrSOzs0xozki+qXf4Au2YWmDWdflFjtgVt2ptOJJG3LS646vGT0+BQeGUMGuEUd5DlxJzGLJrip37DdzEfSO3NkWlzMBsx5IMEzsJ7djAhm8luOclg64CALz93bOScM096ea7j9EvXnzq0NuDTVZ3ZQcG2UcpY298EWWXwa1XG5Xc1Z/HZ0f9fARzHsaFgz6YaVKE++lzqL7ouGqaRWLBDPwElCpZOVnayye3GPXnWhu15DAGD5ys3Eyo5C6SKDQtmpA+MNU+OKtnGgtIezKELL2temi7Hfe+6vQAAlKdWZyYJxMO3vYcIkx3+UmOGlXVLuluGGB6tnQ9NH0NgxnJp37Sl2C/lKQcFYdFc8ArLHj0cFheGzHz5ylbKyLimiLTRKcesM2e8WgZsC2BNiIKdo154EJkGRvPK2WAnEU0aBUzzNLfHQRb/+W7eBij5bQO0rrYl8z1Zh+/ax+9L/K6+X/6svZZhCjK8r3w+Ah8Zy72ykPIHQdtzgZQ/8P7TBIERmPkzWgtmysJd/nOVY/au/3uwpsxPgqT9ikGjzLNTV0YFigy62nXjJ1JDz4BTstxYsdS47bLLtvJFBnkM8M5cGYXF24Gz7C9zZcyy0UoHXaM/1RgzdWXUc0bdpaqgTXtdZlZ930OzajgvXJLPnqGASvOEJRou80mG2SCZeNO6PPegEc39FuafJ3Z6pVIHzT3Seur5zz0Z3kGnULZqubEHLNdiIkvetYs7/YRfDI0gDBiyUUt4kC0C0x6fa88OOnkdrh3oMjgnCAeCW7vHp0MtuqvXWc5HdXNZftaEnnVWPzUG7a6Ka3K9unbkfZEzJkCBpmu29H1b3FYsIG7IGk1KCLXGi5DFYPDOSqHp2h3yvgwexR3tN4twr14AK+GeylWAo5YMKRMIMTBUkGmUoN0BlwkIZJNEF1wFBctBPTmvVx6MiOeS/N0L6+Skk16yURxoxgJyKVG0/4hjKUa1lfsam5WQMqkY00o0rSGLIzd+UdXmoe0W8Mnirs6HMylr2xmBhLGurmyT2Ioi+Lf7AocMs36+ixCbWFMMPLvHa8yBWxLSfII6h2U+7cgO3FyyWwKCAhjKlsvWVyJ4WoBUoe8Kw8XT+rp5G/5E63qS1zIZMZfP9RazzFDg1e6zOzCj8un+rH5AbJOO+z4Cop4D5PyZIOtNQODZ+iYw0zj1vJcfh9VMh23fSxk/o4yZJRLCR15P67NwLmVk0PPGjBnnmEHs6xOglGZ/Jdu1Z7Bp24yzPu0kAy2TNt6ZqU2OWT9mHtH1wvAhCZcGuWGCDEME2JWT+2SpWS8ku/OoPX6l2wfHfmEFbAcxZSpHZJzTR+KqRX8Mp07momzZOjOyi4iLXb7WlxFwsx3SFMRYIZ8NadKz1wjaWIep4S5+etiXB5l+5hjoDrk8nBIfgkWVIk6rkOjneM2Y5ZRlPXK2ifvoe9him07za/7jOiUx588InGpn6GTP9XnK9JyzS0P66Ltag3PW8dEj5MrWJetwXJCGlkhJ6yMM4XVjc87Slry+SdF0lixLOSYmrPTEY09s4ujXYednKcKf6JK4ly7aECgW4cRAAdO9Y+20lplVpgYgOSgrgAx70HlnctKScq0da8Y5Zrj32Uv7zFFgRbPJfJYxFSIzx6ZKJC4DICPhVigDLDzgRqjSyaACyqYlyLI/EsvtbiNsmjCnQKqs76+YqkUmuJl2tb4F7JXzaR1sgQBWkGsSCKsto2yAsj69nVCnwOl2XLsJ8XhOs2LfEsssy59PV0DrTZm0T5Npe+767jVmg65FktshY5WOGMO+FD98voDZu2T0kS13BswYGO0YMZybf5wt5wzIBDjdO/ezcYZoBJKafQiYymzxGbzgBLCE5U6kjbsMNbXwf0jKSEDprM7MeHo/hhQD0MOiTYDXIlmkE5bWuGHjysjMInKZY6m4BmJBScK+GpUyzGoC0jqewVqOlbFnoNH1gGEs4q6lE7ncRWWCMS2n1vggkNasJK1GpuzOLbbCaCbTuDCuRvfFnfd/Nljvj4/G7SSL+XvH0WSMkR8DMNwXGa5VYGsAYids2fr4HIMXwRriEeiwQxQ9lFiX5jAwpOWFttvEdaPCPuRfnGyhPbrC013Pgqx1WaqJe/ZOncz32PfNWzKjC86tzHdHLgZH50vRT1VdyHswVqEgaROTj160ZALCxjLc+z+7kVjitGiBvyohdNqE+4qWHt2RsQxxsdaXZRLGErLNeAvREmZjU7IxLjQGWBlrJpEEdqM4FPLNYLv8UV9WWtlfx8aFmEMiOBkwKkBz5QzUDp9HUjSAzZFQf7QjOKYrIzNmqUzwKX9/BqBOl8/Wl0zzZP52WiJNVIlivc3A6CFN7NNKBGLd8MNnpEB/zA0A1gQkXc54mJQo5KKafayL5V2QM3btkUHL1wF1b5O5e4In5h/hwb/S9fvR8feM2WeIMTOsEsZlejIvrd1KwF0PQ+bx/SrgqwhbxnVVg7ViQIR9+HXatjPwlQCnR80+svDnnUX+rg4tgC/k9vmFTUEIuN0EyCndta2Fw2omwqYjWb1ZpswpwQsjI9Y9lmtUDXrm20tdLfUH40UdLbbGD6o+ka84kVwcSGx531hG7akTVs/G6WQnOyoMO3G0B5wmYZvUk3mshOblYRFpPkr2PPBQyaHSZIZibZk18w9v5ueryb4TWzbT0yqZJGtgs6f3q3la6tr1ZkdEKxRhEM+rdcdC3M0snC4YL3RBke285geHJx8rVQVrOuF4bWhleEm5eMMe3tkvsz2e3BfAFe3qEQ1AGHhRe+uaOS2yTRfS2AVe+OmVFL7rM5OMSd1wzpvbpJ11cch1NPHsHM6gMxGvSqd6SiQDE2StvqyUyIQZ1ZwZRL4ICaHuv8OCswwhl2OmXodRXOgBuu2s4csiRSxQx0UP/BKIb+PYa8t6dMiOuF7ORvf2XYTVwDFlxT2MH0s7hKVIx0wMNCGnhTkBnsaXkZVn3CQX7aayamU+iCxxO/HbU5QRirTw1CgjYcWYrfKTaYHlus33gVHTaSQ7RCZFLOtyfpPP7Xh4dFxM/9xSdcsAZ0ZlCSYDsfZ43dlVjdrrgKs3rWl7nTE2ev80GTPGK1ltwzLClRacvXXg8z7H7A1P+Ik8UYFI79DLdD/5jglA20kZFbAZuwf26WwGssles41kUuvHmF1bbPwz1m1Tb5bWnyUsnOux2NjjW1b3JTgp2Oar+QfLIbskVAO4M1ko78+oL4kd4jj+vrYztLnubkzaqZXSq1oF0/BnzWXm0bTOkJETI05IJqdts3TKru66mjoMUAKxhrGRM+PQatJrX/aoot/wqNkYO2eJ+YcnCFQMDB686e8KqVdMrMJEzjSrix0+uzFa8i3toHsQJcYu4D22gevx/aSc7lgIoYDkuTPNo+bHPCo8y/MSuJycUmTfQEk0l+e8sgZv3S4IqSOKU5YzaK9FgIVtW6LeNeHveo5byL2wTW0myTx9bTXD8bufznSA3Dy0Fr408ou6c74nZgcbRuFZIXDao45uMfbwyKSVx0RL0XlxH5hcEvhURMLYuJoGB22wZGqJ74OLK0G+6GRHEvk8LL/DhcOU813UO2NTa+bsm8E70P4GU+YiXRSMXOh0dCmjWcTTer1b1l1dRuOMKDzeiRJ3pAMyIx2mU3I2bj96WhPmSQ2Xy/ts2tV3eL7f1vfIDDqeTgw7brFGrIhM0QqxZm25UiI4A6eJF9SWQxokiz4dlavfwZk35uwQUJaxZcu0ndgEjxmL4IQ9O3uP11jX2TS5k/zoE/DJHAG6rDPjB1G23HvG7DPEmJ0yZQJ82F59t47MMKRg1pWFmrIEDNQMAAkIYgC0s8j3MyC1W2fGLGk4dwLIkLFTF9O0Jq8Qa9YZM7j7DdP+vksab2zJ39nFzJWR2r/cK8goxCRoemHIkmlAlzLuevxs4kG2+Py59nqzW2L+USmDmUfZ6Ob+SLHwkm32cFWuShWxhkpz2LSxdT5rMv2u4xlPEU+eNJYwaSd9Pn/eON3OK4SFiNmDr/ejqkRKTwDGzowI4dK65bXmDNsdvNeY9dwyCnpuFujsbOEz3XmaEBoiM+Ctiz/IpRrqmZzlaRJifG+Q0UBDAzeDbBO7ierkEGgRQthk0NAZprEPbR39klqMMYxyz+uwx69wCtzmzLT7znqV0OnO3zQkN+6+Pq34OV5vGovM4zUrHiY1zQM91qIDVu+/+3HvYeEVXdboZPTCfeh7jMA4ku2AWMuBm+2Z7evxBuA2NjOYkGRcqFCKM8sUCTB9AwmgXgZGPOyDC0RPVIEJiHOROEZ+7O6Q2t/N6OkM4mWWIdHAH1BZYzbIZr7ilADCgDT52QiUeRWr/QbMOundwRlj3/FZsbIlgFBxegrMPLJheiYc5wYgISGbpz39IaD8tusarl1dF9ejleR9Mi1l0kpk1XYsGMrKgJ2xZ/xX6cT2jDLWtw6wW2YteI1GH+P57Sto29WdnTFoV4zaMs8e6C/g9WSOb0vKaMAfeoJ/NAXDpnoki3JGKoZORPtdyvi5Cpj+yQDMlClLmDCVEwagsWPSMJSMCCxQBxQMqJQtU8YLibOitMkUBOk+Pypf3ACwHZhLt02MluabLbb1ZnarEy1ZYlRStOZMLfexkSjKspkpSFAlEVh2YS+jlDHJJjMdiSejDy67YkyzG+7y2p4FvnGSt7WeDB5vvjqOlKqyDHmgsz7UnWRgph79hDZ3tpO7sDUkFB94J30jpvfUeix30s8ioaNtpbozYhEpogkLuZ5shksjZEzV7WPNwEzY3FJv81Hr6FwPrNs78S7MycgwQwxejnijMUU+8rDuGVytwz6Ckzlo+p6H5T6dFYcMzynAuoMr/mlY314dIGyELvMR6I6G5s3nRVwPQ24Z1Tr2fQ7mNA1y+WSnBqQyznibpiEDSCFguQgG23Fi98U6os8IhLR8tOX3FE4C9cioHXOfbSE37uy+U86cofoKFJ0BNYvy+Jj2zuTwaPdofV9KrDWDogGh3G29ibBrIgsp2TMxZ84sVHspKDO6brnGzII+MEdIemcwAWSr7FLUfYjqbliuAFTvjCUirL8n74wBvoq4L4pUkbPRrKUvQCWNZ3JFCKKjsPexE24rcPPN+5BndrvlUsYLO/qrWrAzS3pcWdaX/fTMvKNSvZgyZ4EpK3E6a1M7RdrWO8y4bGPUxbVmkHozYc/UHKSeMGX1GUDtTYDe6zJmV5LK9np7gn1M3LPcNVQ8fz7Y+blkzN4lo49suT/4B//gFphlQE0B1xW7dlJjdrktWTbknu0AGtvnZ6zeRoLoJ+DMNtt6OPNMgdoO3CbzrQMyBq+y3kLHw5LjG6FFziraDoDuZIzyaNNul9XkjlKrsGa3eHcyNf5QcFaJqrnNgOmEUJuSCJe4L4vW+QPT8DjT9h5G7FVvYBVnRudcMwma5h1ibSZLFrch05DaM1uL8dIYsPgYcOKuGHTNB1YNDntOqWSZfLHCSEaFAdEgfJuCNkg0tdH2YmtBfBvgfrRg6PuxqASMjRR1jg7KJuDxlpdlbX4vsvKedtzA3B1MjZvdvc/VtLEuAWjMAM4wagyYClQ4DdF3oDDqrroBiRNkGOyPNZVsBylGdWTeM6MHYHCqnXMjkGOzSs1aL7qOR/ccrehMHPpxG3jnfq3VBmI7YKrB7N0HCzeZLg+q32lvbwTEJkAbLOCIGbAgP2UCj8/1PMb9eiFGboQhWmPi+JzarEkbAdOqk8OsmDWLWjlOOO634dMRHgQIZHLrtAB8bAmdLiJEvMFGnmD3W/TUy1HfszQxzzXjyjfPtK+ZVBEiaUQEYraJ/wIZfnT2rCSGmJrZNvCygtlbJoLeM5Kpf4LJsRpg64g2lCxhtKSVfnsBLzXafiZ1Yb6rBdswWYvl/YbtSr97O6kZ29SLBWdF2zBlJTKHpsYfZdxbh3Jf2TAuqyZwFurMbAVgV8xZAGT2GFDDA6zY1fTn1qVdsWa4m1G/aOYfRr80SKG5R27YLFrf8Mitvc8x+ywxZlmOWQsn3tWdaabY+NyytOLK7kxPzaSN3ZKdnBk1x8w2dvmPADQ7scbPAqi3rNoGkCl4DcdICbGTz96kjKDjVLqksTNlZItfOpCSPLgswDowYeLCGD7TOfcmS7oaHCrVHpDbybdc5I2uaEveqw3+aWa1C5GkZrG2kTKm9nWIK3EJZLPMqURsJTk12y0HZGr0EaSMnt/1DQ9lmWEjHARyE/tYRwaJkEaQMnZhYw2yxhqg3aw/4273KiBx6ZQfR407qnljsDuIMjSpG3IreyI9Zoe/nYcB1KKczMMXJmWnMWerQT1JEuucVofO0GCdVeogxe/sUwxHs6E8Ccb/SZhy1CVgOWbmMgvrWIt1yWIlvsQcVj1Ea6/fIiap+owKqJPR6+ubjFxdc+OyLLkBqAgIuy+7uh4XTrNjANzObA+X1oBopmrCdEtKjzwJ0NobgTDbVahmrhA0KicM2XRjBAoKyrjOs4DpmGtmxJhl6WhsUmK73A07AWe2yhNDfZmtpAqIaLFKYdK7vfGIn/nUZEDMtSY0y6lk+s03DoyKKoeEkYDJKKK7/Wmg/A347WfMGq4nLI6HanPvJzVguF3Xgp3WiRVixfRzWef3ZYxcGHeGHnwSwQ6V7fqrhlqo9MDW8UdmzbzO8UquN+ufFZApSFsGbK/6CXbh8vhMFu057/EY0/bXDfjTzfzDNmEkYjXlnhTXUoKrv/sMFz6/NWapvE/nKSiRz2dh075xZFzaQiCuCrhzlv8p4CLgk9rln0gbnw3CHpE8Xljqb9m7xNbezYwBGqierNq9Uv7W57VDeCZXdJJPmk7vAExy0JaaspN5peoAJN0i1JExgLFBj57f/fyIBFIaRJkkeSjxNQAZd2w17oq/aOL2sCTg9s62ujLqDgiVOCg9rK6MQ38pVN9yFydnPx7VV91mkkmUw7YVaqynZQoPK0kQ1ccxCiFdxJNI88wsEVzGBI2kw2u2LRbMM8F2pO/Ft3dg6JHt4g2S1N5SBNu35B9fbjWHrGc7swRyP3f/r+PeJnorlqAAF7qGHBdVNweVMJL0MWOaUkanJJxWr+XEprbMQr5ZpSmG0uS5bAgS3Ro91JnFoIKySBlz09pUFUg5y7voL8vtJUccWOGMb58qU0i5Hyw6QUIAWpCICo+wWuUj+n8AYhuZ7Mxio992ABXA00s4bov74sJeab7ZA/VfZ+/PWDCUa8ZsGHp0oNaYsVLOHRehr72u7L5Pvfwg+Fkp60XzOzA7NNvsqu5sA8oUwL2pqyOwr0v7lJi2GxwvJzDb/fPMXNVjq8G9rffA7F1nzHASKK2gSBkrxDyxpf6rgQZw1pYANDuOIzBqND2V010ALBf2jOvZHpL07UKoeXlpixP4TEGgBktvDESM6sY6kGXGzDqjpvPErTFY5GMNoF7OKdeS8Xs6bk6MWgfudYh/HEt5lPnGjV7c5dn0wyh4ehiCeMxjXsw/kGAc5ERXas+9Y5zUppxkZOtOkB4DYvxhhC5xm4L6xSYfAtjoPlulMI6LeNRqf7tjsX6nLvCJfRXvwKsAwyK/eypWmm8y/skVaGwKEiSAEvfbAVlNnjw9XcNQowmgR1EYzINoYzImhFUDWOgmFTVHXr5jvsTIY8GFK0vlCyicvwbr7SZQYqEuTOAKM3/Gx4EYRF+NzqPdxwOZ1YlxRdzZWJHkpJbh0vM4yFEXZiy4bO6wcHLsbJhfTgaNTS1ANYOehYwrs1WUeiGwVdjq8iZUDYOyVTagNYJZ1hqnioE4vg7Epnwxqxq7BcB1w42WBpBEWMekshiCkEHpFIRJzFfq/bbJaB6A7JhqQKuRqCx0ny625pOZrQTn9kqW0OllRwvowZAxZoI+xw53kHNQbdqtwvB/ht9+61oL9ki92NNrMGK36JAYasvKWjO21IvdVoAWXm+TIawJMAMxhiSJdLdZG14jKFMD4iF3bI/NwwWU2cw6W+rONgAsc3Pc1qX5SXzoW2TUHmHawjKGP+VAfQI+XB/+dmXpm8hr5oT3wOwd+rer6dq4MS61Ytl3ugvXBry5hDwHx0FWHlDGmS73qF3+IrXU942dW/LPkNeTndWaXQKwMzOR5P0i5zSz0mrOhuyz1rtokCiyhTXkfVJQ+YzrhOvcAMmFk2FH633examasEpQ+HX8Utu9vk2viekHDgmlRDQtrCJh3GUzG3Kjw/MDIR9G4Yy4MdYuWeRatNa62ovl7P4ahgoxw6b5qQKuQ0tusq5OJmfjaTGFyoNlu0kCGQYg0xozD8JFBGBmwzxfQRqgxvu2JKR5CiVNBpKeTSB9mmzTybr1BuaB3avp8vV1N5mAmfr2duXND/jFJFvAi7/eyVPW+/KpnxU2EWMG28sag+mHgjJfuL8IZAuQcFbW7OttALIyXBknqeeLxUclqaKNgOmV7fHAmsX8Mkt5O1vYMzXiCKHSHH1LLGTwyihCPBE1ONIKiKxk48wTqLQ+jORzOiCn2uCClUHLrCXv4KtpRBrqKLzDN4fjr186H57VkZ2xXHiQATutIdN6MVut7sefUYq3WGwyFVpLZBC93MFWwRIqvbBbvrozutjnZ8xZypbZWq4dKgVeE1xlQhU8CK4eBmFI1/uf2l3U+tG8QJ/TlcukQPb2gZnWhL2FHLO32r53yegjW+4MmNVaR55Mf891U6CAZ1DtltR5Oa+H16tGFcdx0I3emJEKepHEmTAFhDsAxyBKGC3fMX/UlgxoPvoezd4+AKRNGzlgujBQ4xq9TlY1aePROq5l3OOIedP3zEIog0dt4b6d9jaqLDOGZae5gCicsxFwj14AgYyvEZRxJNgyKubpCFPKoEHaZNktzh/p6HnSU9FsMQFtqcWkMF2L2N3XfCwn1MkUoEabuCeaBo4M9sBT5ZINNby3YetRw7JOfFe0ZvETiWLPLtsd8j5PgdnzH0jZCCKaq6KdLXE+CPnM9eQJXA+Pktyt5S+/mfExeAvH7y0smy6yhKu93mr0CEgvPB0/VmDWTT8GXeNagoXo3Ifo0LoJjbPAi50BDBP4dIdZR5juZAqCZghy/+TBmXFFSNF4XyvakEIdHYJbCCffMFGSX2aszyzSv2/4IDP/sJQh3GfApQyfYMbQt9Vcs97gYidbKkA5JGy6s0ed9cL/Fbj9bvjNFhYLVzVj5QF2rCQ1ZJozJm6Lo1aMc8mK1JQRK7YDanVXU8YA0YbwozNltQiLZXFskvPMOkA7xAjkEGB3JEAvlTRe5KDpd05z0t4A1F1Z8yfzDgD/NwdwN//IHgzMnGUjtJZ05fw9Y/au/WMwxPVJnaThMOJpEBhYMFeTEDIKAa0HBOSGfLEzPw1U9dt1t8cfy7R2VGXIQG6N3L62DyZgBxuwhM1yp1b5ff/O3iMJeE4cIblmi8O1O2PGbFeXKXYjFg7pvrFMtNehtX83Cuy+IRqpBNt7NvtoC9x276ndN7oT3NLMYye1DyvtetTXrSG6Shb6ZQVnfWBSMcsYOZM8ZsYxbpG9A8msXKaHf+omx7dMQ3RbREINQnSYnKCd2lLZJmCFCuo0rZtZM5WNpTIGrgpjg3qE92zdwW6MHW7dEsZs9+iyBuW4Fm2KDJ2Yhe7kGGV4CzDbkirZcfBzHomt2bst/hlT5Jvtu4uzJUJJdjDTTGRuWzlk8gz2tcucgrO8Hm/tbVsIJ7i0WT5hqzY1YRtzmnC0wkG6qvlTjnVdxOHpKkL9WtDEqcGHr2YfQTnkkTqyZNoWoGWX8ZREruAsGoaoa6GTlUif6onZhwnMscSz0E/YXwh26QHP4WdlAuDUM0Nt9Mknoh/GYH4i+HgHzrABZ1uDJ1i0xl8SsjdrH/RfZ8mOdXn7AgD8p/dbWcFdmvhA3VdgtG4ny902nxXcGQG2jCm7MPWoBLisrGdAWbx+HNo2/UauyEYDq7aRFvr6p6zZIazZAtQgBiEZg2bnBmKpcYjta9Sem532yHfp+zcD/jruV9GH+8FGLZLcSQfsMwWkPvdSxgup4iQ6TqSEAuoCEDGzADSImarKrHWA189NUie1DK5LOPJDzBpy18blPda8sQUUCpBaHBvp+HnmkijultaNOFrH1Aj0cs0epG1BvohZC3ZqWrK5Rp41oA/yJbTNmM4AO4XIHtLMWQNfA7+wJ3t3ZaSbapA0IrJiISLME8d5T/p/dsJWcNioC0CrPtGlmn2Egrlj+kOPHcG6I6q7GHEDZDLCyJJdI5mS3HRLI8OlNhy9rowzy6Klx6wts2GJH70aszqzGCod44adWuML8Hh9xuzz8e/d8gb5VrfmDbbHjJmRCUihmrL0T2zzh/lO7sYYmep4q5kGHi5SRlvASFZfdt9iWXg0DwBsl2u23h1KAtBODT8ylgzkwggsqMrYEKTSdN9Y5ZN5JjZMmZ3cxotlfj+cPG+b4E1xLuEQ6ZK4lwRJIn4UsP8Yfvv7Amg6Y8qCO2NZma4AuLhmbAfWblITdovGHp2GKicMGeSzE2BbzD66i4vN8cYSa8QelTKydX5Vq/0zI5AHbPVT02c7AWVYUzjPXB35+8DjcsbN+n/Ygf9fA2YfrCDMN1qTXecmMmrvgdlnAJgpoEIMEl7AFlbb/EXm2NdBnauxTK21sqU+AT0GDlXXL7JD38xXqd4AYgJq7Io9e2QaSzUT6/zQwTwzMUmYQVeQRHb6zuyabpP/1PBFgGrW+V3A+CUw82SMnTFMJm/k2rNMASj1aJVKvBbgpTlllVzO9Y448qBwYTUvrJn7BnUmNvpwKaij+V3KCKxIEgniVHvJ4PIoidkWg6Z3DwEAi1SRGZdeMVYW/ovBmwXIBqlWm3Ozre8/BZ6r1nP52mtpEHOnytnP9qSueuccc7ZNFTA+50aN11QSJvs28svszbbzVhq7/9ZiaS4T4vI72aZOd6S65JLQNAv4ctHRcfqxJwDFl/NgGxCx2n9MBrPIfm1gAAGzWXl23/cbog4TG3A2a84KCg3a5G6ufDOxbCCeQNZCPJU5+BZAWonAzCjrmyPmtjiZmDXttqaurByiOZyCWNfOlKCtDoxDvpg4mwyHRXwEt798B2aPGHiUE3v7kgdBd8BWNq6K3dijUiB0ZuhRNwyZZpQtJh+0z8LIDWOuVj+eZpLtpIwUNj1yzTwHZUdb10Fs2nECzLKY0AyMqUEIcC1zvJQ92mvJIP8S7m6MeIJ9OG+GS6C0Cz/MTla0jIEDUd/nmL1D/5hxEvCQTVtqrBh0NUCSfo8ZN3YbtJFwisKyPAZmmM76Z2CKQZ5TO+wMUHUgk9TQvY6JxwCHmZRxU0cWHBOVQfMVCd3a/oHkiRnoC2CamUaqSwtSSgXQSOSO3KOR6USu3Y2lw4CkxzoEF9yyRBdpKEnvJzTmrGOZKkpAfgio+g88CmfxlfGLuuDHQSiPyG50Fjk8usyCOHZiHGMLWcC0R0CmriUVD1YU26bYI0OcLFuM9+/VBMQCeOvcVwk5ZiZSxvXRw/HWGPYhbAYSu4LTjzCaf5zaz7tkai3LZdKOdVqwInEXoEuU7BmDQ9dLZmzymDn/DmzveSILtuDJ89m3AsCH0wL65bYoRmkl5x4ctkhVU/Ekn0/3FLQzH4UNT7UeIJb+ao4ZorxNZY6e0ESho68j05aMZkdpownktADRMut8Bm6cOja5pm4uEpeyAM5sYfAyO43V+CM1LlTHQzUD4ZzmrO7MJ8YpctghKtNCtvkaXQHBy0ooOMvqR7gaJBkbskPCjjFgG5/J2cSadPH+7weA8t8bQOZZdva3Z0gfhTULtWLW2LasXszW9535qpZb4BshagZvdP1VFwmj56AsPOZcasb72KXP9x2cuQC0nalIahKCE3OQkwy05amWDArjARYMz2DN5jV0//cE/4A6H9mz3TbF8Nq6cRP6XDFm75LRR7bcWcD0FYsm9VEAyRczkwwCBZml/qgJI9DDdvvBgl6B4Q5wSRsVCJmaYBAasWz/MxmgsmRyHHYMmSWSy7T/wkCu57tNo0Ybph7dxbKHQG9kkgvg4kBpPV4KxpJ7hiXTymGEVyDujHK34UxmrivzWzIExQyZrcYf4b1v8Ixm2fDIao031qVfpX77WVaZyzyTojnzu5RxdLgrPRFq8qRi0JXdvZMdujCFmNHLs+PKFvl1CBMnG5a7M7LCtJKUMSJrtQrRyjYFjnW00+L5q/UzE+P1xv8esrD353zhU2vPZyleLX/ql2j9V1xMQGwTPu0rEOc0Y0d6jkygF4sGS+OrDqhc0ci6A8N3sVJCmadxzOtnF+dF23obYgA7PcNFUPoCyLJCur5smSoFa0WqoWSLcsuMTkcp8ZQAed63ZSSflpJBqLTUuSRZeJAOzJrVKREcN+3BmAHA/wNePoLfvpzb2Yt9fbCzL7nBhxp6uLBfQarYv28RrGXZZcyKHe2EWEJrMjgsbI9/34aX5nXCDBfZ3vuFlHGwZZgyxiFprK1+jNi3Y2elj6QO7YQ5qxdujVvGLRlPZuBWpQ9S7QLwxb8PAPw/JzDDB1grOi/ECNnw5fxJvJcyvkP/BHAFtktZIVBdl7oq7uqVlJURIMid/6W+igOnxWBEa6g8Ya4gEktj8Kd1Y5Bas0R+aFlotW5HwN8pyGXHx4St6yD0ANWrsalJaz/nmJkwk2koOAO11s560s7hvqg1hrvrCNECQwhznFbIcm2YiV97IKdqtN5lpV/l0fwq5Vk2F/ISsYzZqkZcB6MSOY+RvpIHsWpt8hEQk6bujFW0l62B6R3bozlIAIojyEkQMBKRlG/5hhV5O71OgFZHLcy07yhhLbtUNCxrzQMLXMRo93/Ha9SYbSHqG+sJ6fPWZRDYBXu/7k64KrHs9XfMRdHllkRwPbC/ezHo83dypxydh/KMEbbXO81m0Y990DWdTcNqCrLYA9KWWP64ESqCasgcuetgjSTTAGgVGjBtAszWCi0Xsw+T+rMMlDi1d7HL3xR0LaYemsdcCIj1aJSOYXwuB5psFvcGto18XjPOLkcbQBbwPtmvsJPMoLHfP+2MtQyzkGM3gNl/Arf/CH77pXu26/Y8a/v+OYCvQqOW4pxotjf4wKwJm1b3G/OPpaZMXBkbUBsgq07CznVQ1VYwk0kZncw/+P221uwsgNrOa81U0qgg7jLXzF7PFORMDOOG/wDAX53AzH68m18/LntwofbHlst7YPaO/dN6oiswwWDrBJgp2FoARwKOkMj2ilje1xPWKV0nojzPHjQ42QKpDXumeWhbYJbJJXeGHA2U3ogh7L+uQnLGvl4+Vp44UPpu3/t3Ie6QYGliwjyqEQu9lnDDEbYsSAVdlEXMiglF4930qsrNkPwz+q0nlGn5avZxp2hjz825LbsOIXfzTKjAwV6RdPFQLabspPnUY/IwH7CpIDbaIbnpcmK3m+h4xi+edsuIL1tv4dMf0YPYsCxNm5JGyxNjgtnHypIxk1epKsiCM+Mdm1ZsxgZOU68864i7qypwXduSM33iVLhtly8Szdehp8LvhgfG4u02aee5K6PLiEpdBiJm/Y219+7rgfPlsPlDx3VCFAld8JPGuh5b5Bpko306vWCK9Ow9FjhxPRk0UDphXewsTNCFoTojmBgWMZSyBRdyjllWxWbbaZOry2WN999reeCKZWYq7JjIHE3RpiDSKzt8xsKKkyGnMeuOLvfvfs9O6T+s6DKkaEsdWmjNE5/2PwIvv3SpD9uxZkZW+OqaWMTWnk09TOvNEgZtZ32vACyrJ0tryuKfu42asmDSlWWPJbE3y6tH6/zOoAVnRg2bThwbj4xFSxiwnZzxSgKZ1qUlgO3Z1vuGP8y/tSf41+89mCLWzNtRKk/u1AHzfK5yzH4SADOVtZ0yMBvW7XJea4uCtirtVHmhJ9u1HZDi74h8L3t9jn1+odBn/qzHNZUvJu6RYRoDWgJsAXC1+TVzzsykjBBpJzY1YwnAXpbziNLvUka5M1RP+mPdebHdwEMSGiJb1rEMl2UddQ2SXgAZonwxzGc2rW6ex9zpdtJnjtBoSM1Zdpslu3yvVNNA1F3lRoKqlumAVaz1ZEoZ9qec5bAk2ib4ZrTOEwjlweyj0qh9N8C3wac5OTM6SRvncVnBSrfSBwG4CNIcwHHUpdN9CXTO0NDACbavudoVSj2AslJj/gfR2c7g2C+adbWSLFUgg0ixY65GMsBZ+HNmuL8cPrEvt5Ox3pX39ctw7ecNxxZx1PATpwlPLPNB+rrEvCfpK2Xwaa0h676Kpc1zYsqixPgeXcFixwhjPFStlQTu9XatAdPqJem4Bl+yM0EFGJrm8trNP2xzSpAnGZi875dssQ27zIVyrhTgzu9RmLNQe1YQrYNLBGbAD8DtXwbK00NM2PYz1X2VE1aNmTJ2UMyMPVJmzFaApvVomVeot9qysj53U1Zr9+eROfMaGTSWNPozmbOFAbsAZqeyROSujM8299gzbS8B/FAEZvjb13fLx++o7xmzzxgwOwE4wOrImNWV4UQaGTLPpN4rVPYI0+YPbD9towRFY8O0DUMQnoc8/0xZOB/uGsm8pB2nDo8JEC2ItvhV5kFYLCQsmDHbJSAVm+OpNWVZbdl0hzzpJzGpFD7T/AWUFWHXapQcKJbjuC+XzDTuMyExU9vmVcHWyl4Tu0gTgGaSW9ZrEQbLVQV0bbSZTAUuO8Y5A74Rb1ngyUwMP9jGe9pzWBAiqgNjxMyV4NMK9Rio7WrMMiOH2CpLHEMTNz5xJRzzHXC7ilxe17daNazbSeV3J06BW0mkrEydBx+SB75JMPN5aWJUyuJaTBiMFB07p+YYzL0xADTsDCf3UdsexHjANvunlAi62Je9YJ1nnigAOUoDa/B0GAwxMqHZQwCWK/ow++gsdQbUOFg6lzPaeOVw6VXOaCGA/uK3kPQJLcM2RW5Vm+Roy5wYXZSmTmaaiZkin4PtgBsDwrRje8KajTyXDlpkFDHWmAHAX4Pbv49afmlgvZj5Mq4/S9wW1b0xY8p2WWTW3DkXYGbCjBVqu7BjMKwB0iSf9DKzRJkxq6vs0M9YqUTKWMU6P4ROo20LFD59xpg1Vu1I3mc1Z1cGIaeWV7apPTthzzyCwT8L4D8XYIZzm3x/9ijV5wqYvUtGH9lyGQg7A2bPAG2prBHR6n2ACAJiaviBrA5M2a8M8GxYtS17psxaYvt/KXU8ywg7CZxeJJAZKJPj0MFraTlvdRMdoJJG2+WSnZ3fZ/6zrYzM423DqKzKE4ZM71Z+zIFOLiQOeKWu2IY70FArf6nRt0yVloUJZ3DVtWhOXE7GK0kcvYqHbo20HxiBeo4o/YiyNt+HSnoidXMCZx0SxLqxtfYMyHTz2WOqLkb9vqwFoRtoFIbrVBlXa012y9ewZm/7YZOz6sQxk7ujH+0W2lGD4q51osnNzTtItsiqenD7a9ugdXUZYGSlGBp3JsnHYEWF3e3tfUKQMdjvK86psLvF/wlDZq35DGtGxVNtbYbJiMl9g7HdJufWFsFMkAjzcSagF3LrPLrmWTuuw5kz1FQWAvdxDIc9Rp1sKvvxBW9zFDEJvWJC8aXaPYh7owKyiBQ01nlHxUZI6Zi1YXFt83Y5HRmdQqY9kTMya7YbOpitjm3HZtgnHagvsxIm2L8dCXNG9/6RXMBOjDeqqjEx/LB4yJd5vrmP4wyUCSCzrKAOs+jNLaECAzCrcPvD8PJLn11jFurEkryxQjlkrvPKrBsbrJg4LgY7fCNWTti2DshKmcoPjzb5ztJFck/Uv0wWONJiELPMsrDpUGuGx+rNwmN2l3N24tyY1aE9WqP2GkxZ//tDbRxUgNlOVu8PSgneB0x/1hmzEKyM1azjSrqowGoBbrJeILo07taVsl1qwKHLZ4CJQqzZ6n73qmDw4XnCLPVlOYsMyfdNXBQ7y+jdJKXLKMk+P8gLxdBk+/rovOROEKSM/WbKd66eueqJ6i/MOxIpDNUi9OJxdZZncMaOTmw8smSdQSLCPFFgsxyJGTEGYZYxRQS8RrV7pQd412UWeoKxjkPe645ADENgkolWF3bAAxiKNWa5hHE6M7LhQKXPs1PoiV1+DQLItcYsL6Feubr7fykwU00c79VZjdFYMgoOd/g7fb5t1Xyrn/0jSkitFAs/rqQmTAV+deE4NtuSCU4gZSnV8tPdkrFaj8d6x5Ql6zs7tOt+T31vlDXOY1CXS4QjD2RrQcroK2OmrJmVxKFRkIEMUaU1UAtIw+C0mBlTxd9NOntlTCvj2xYcGJk521dtrdLGlfZyYcMWy/suC+9mhSAipt9CdYdkmk2+ZjBjhXxY2KfFFIiR7wq735sTMQoebMh0l8m+G4Ovfg34vMdbR6E3uv896c/0B+Hlfwq/fTFmjWkG2S0JgS4rwzZeJYfsNJOs7e8hBh8QeaInrBgiOzbb1dgyzSyrJDMsK2DahjyDABmiVb4LQKt1X2uWujRe5ZxZrCTYZaClbX4gC83tsbq0Nv1DGP6YXkRPZ1R1/vTayCP80wE+73PM3j4wUykbAxmzKCeiz4v8rc/j72zMMExABZg1m94aww0yMx+x7lhoZpIP6yqBPAVRmHlprEzcvQLimsjLKHjsJiG7OrYzR8y2f8r2GYMxAczsMKnCwNpq4ap+py/D32nLjvVkElIGZovgzESChKj2q6z+APaWRzb3oHpuXOhSCsvZZUxccclspkpa72M6aiq2+YvuK9FjWo0jquNJwxTgRhPBQGwMLZIBRG/H0kYXCCXsUoBJFhwY47xZCTZrzFzKSHbl0CCA5inwsmD+4EKz22TMviX/NCkie/ZtBF3PqXW6WPYzZUNPFN6zyvEerRX81NrdgNkAZWTkoA4URXLKdpLGE8FfFBUj8Fpq9sHsH8Mm0DwPdxjOMbuNtdim7owloJqelmfTqUHGRtIoVvYQTAPFQ1Rb5lRjVgDcOGwaa6Rcodo2ttEfA3nyhDJkrp+alr2pLRuUnxbPlTjCmEsZAeD/Dbf/EF5+SWDDygVT5hvjDmXDQh6ZgC79rjJxEDdGzS3L7PIxbfIHYOoEGht+lFXO6Dug40mdmTJmkm3Wwdlprdmu3mxTd7bILDMm7KxO7oxV2zBnybz/AMB/tgCz78G98ixT9QAXgIzUOAXAFwD8F+8Zs3frWXohTdwAhUwqtwAJlSkmboVaI8XrLQlT1jtmTuBpTEvMNXZsHDKwubHkv/yT9Wk79Hg+ZFByEhHA7CAvG0Ki2/6zO2Mhdq7b7DtWx8XBvvF3nvn7WH1cPRmF7zImzzk45+ccEVVG0V8HchPDtFxLwJmST7uR/EW2OFKxxb882EoywiSBFadn99A0L2IbaclOMHOW0TRC+23FSZnxR9zBeDoUtNXBoGXH/Kz0OZMzulS1YQGNcUoYFAKCRG2AP6rzmRiqyQHRZY4+v0bAwIkvWGKMlzQYLpIha087cYhMQIiRVBK9bb5RVFFtjg9qq4iFvC+RxkjtMiJE8M28rb9g8JmZ+7/W6DVFgk/wFocJ+MA6qeBODFnGgMg8zxVZCZvTbesk4lqljIs9vs9O+QLObMqRO5OS1LJFAW1kyWKemJPIsV+P3gAWyxcj3JrTMufFLGA6WoxYwhLxNBdhZdHr2sh8u65ElOUp2fN7hC3KbZNQILh54fOKADACbvAkNiWAMUfqj8lATW30mUXzLiVkqPyEhMr/vfDyx1MGbNSZCWDrtWaaQbZ8vuWui9hY3iNzXzxxXsRaU9YllJUGQIMjo9jku+1rtwKA4TxSrTEjZ8YuZzw6u1YSxsySPLOEMTt2LNoDn3d2+XUDwHZDmGK9/3uzW9/TT8NGBvLI8JbIHsp7YPZZYcy2NvnMoCUs2ZV0Uc05dt/FZpoaWwzQR+BPl9P3gcFCrG/rx6QzWwvrl7CAanJS+PVMytgNT9gsBDObjBksZsoCIwmRRkJCpJNeeSppjIc3dV5U7vvstfhO2aV3npLMp/qyBet4lEewkaG6MgbDQnFqXPwWalJXFnrXicsDm3lUX+WMI0EVUlfW9XWUYxbE7XV1XGRWrAoarYnEEZ6ye9xJj6DHZFw8ExtaSCljAhPEmDkOYr/WkGlvRiEubBoCo5Zb+dc67a25Gi6CNYwCqmm4ZvDa68RmL41rtbrz4P0U+wB0DBhGGgGy7HWm+ud267hupM5q4Dhr4KXtucc957xi8wYkWt3Vfd4RqYFRjza3M77XrwCbmWFJQhWdoxWGjm+QxlDhPvH8ML/XePV9xZ2CH9JLo6Q6I1ElXyUsV2Xo4u33Y5h1aP2HHAWqFZrKhV4r6KJxy4Kki2aXqVtjod+bE9rg397cR4WuMerZBL/YNtus0rL3rdxIwti9HE2gYAmQL88xW45WaLeTZJC/ZiY4BohOjZtsszEQR9lmWmNWIIkFJqfJpA0yqGBZ7l5wBSlINRThi2UaPnUFhUldGbNKlorO/gQq/hJK+a+mjovbDDI7MfYok9EKNWTquChOi9k8tyhz5HaZrc6PpUQb+yKPr5O6r8xCviagjGM8lTGrNbJmAwzSKwMz39SchbozO5EuZs6M7Dz5AKN2lX3WlvmPDfhTKTD7bqwOuSzpt8zhzPJBW/tsAKm3ur53yegjW+45wOwN5qskUuvSMunkmZV95sRYBESUDmguQrSXejS2u7/6I3C1gKxEOsnyzLMMs0CFtFq7Zd/V1GNzvIaU0cwKMY5BY0fTPQoGA0izM0BGy5WqKj/yEXABaMa5YsQKuIK0GmUxXCMW3ObpZs7gDcjt9JXVy80/6NCo9786MQZ6j3bMTSQwZNihGo5+vbBeU9MoKzbFT5IBkI6XWTCd4M6whVqzCaUKuTJiFfNQjllf65HWk92XrdTFm915E34hdvj7/Uo63QysVEbG9BRhqXGO+47wGs1GmNd9uQl6OksXgU6NDnY7P3rj6KRWq+ORoeQ6sWiuJQI4A7zOOqa+/2Y1P+3GBWphxyOiHWCPxx8iRPNw9UzeqbbjfScxKZNCTcKs7SdnVCx9h3ZcrTOcNVj2W7iauktKN3yp6r9C4LTm/ZIgZSTw1YubFFhwg5HlX/k6T+SNfOxWWWMEXJmtRCzL6tdjGXb33qq0LAmYZnAGCZCewNekxbbEtQUm1SKjbHzpauB0WcUEpUzGjRMJuNxvQJ6SYGghX1m3EKYr9gqWqgmrHCi9voMkSzdxLuGte9ndgv9t1NvviS6Kt9WVcYAfcWLcZZJVZchsY32fJIA71ZbdCnBQ6HTfn1dSV1ZaZbETMPH53K0goEZg7EzK6H5imU9qGWXMHgqdTtwZA1NmiTujJtg8Ar4sly/WjWwxBW6G//0OVzx9Z6Jh4A5WpgCynQzoUwA+73PMPh3GDGvd1Q6QmSyU1WsttWedDarUAK7Xiiq9FYxdbGMJPL4P0nrGbgU53+u8qoQyMR/Bc9dLDN/RjwOzfnJerAEv7gFU6QkUknwCs/sBVaeQfNF2rBvWAhx+zAYpI3Ny7qs+zRPgwcYgXvL5FWuMV3Bi7Ou3eTNXiaPSgJA++tqLc5EueuKWKGNf2Q6EnSHDjiqgawfKFtCYSXOwo//Qve2iZDHueOQ+fHmIWNq8mV0WvzUfSx7YMSxCSBUy8mh/rccCyuIJTKyCE6AUgJln6ABIA6HZzjA9rll4smwiV+/tV7lRqFjoTO9zvSzdkyQcmdG254VfIfLJz5LMdu3YDN72Tn6V3e0yweTUhnXYxfTdsR7AzEjO6PHuGOSN3Kn3FfgiYawNNOxQhRuLlV2bxKxQg1ZWEUH7xdUmWMzNPMoCznZbK4Exc5LrMrsZTm+JjokggkdliwtjZtNXYtSY1Xv//4ZZN1YA3GzNK+tKUy75W0SZnFASrgsnosyTLLNNw0121IUtGw+wrU3DvwYv/zhq+btHiPTiwEiArW7yxtJMMsuli4eAr2CNX9ZpplJGW2WMXu6PryKAzEj4UaMzY30NKaO6M3pinR8kjXYO0C6BGTah1Lg2B8nq0TLjj9QgZM7/mwD+wBaYfcfKbUdg5uvNlkdTmFkLHq3vpYzvxL8ze3StGcvA0Y4VS+Yv03U4sdebKWOm8kXefsJQFWHEuE4qKzjQdZUGHJd1v8krSRwDo8fSRwFHxrVgxAgG6WWXRDY2rJK7ZOmSzCyHLLBbUgPYz0dSG3gGDvm3c6uWdNiUZNoRTjU5Q4RxhjN8kRu3RVdFflCk0kpPamctkmOLFNM2VNvo1ZY9gzbowRqtldn7fzzVkFcOnwG17H2iWTDElDnOVFJ+K2PG+BQdYZ4T73Yl3lgbahsTELbvd2+VabGkimpIiMux1vGt1HPrliXtUp74o8blmb8z44wPyeYiy/1+9NxEjhkvE68+mKAh3wsBYSQzDJPFPdZJ/thb7LSvztNqIwbmdIzaL88f2HUSuvOI2CByJ+BpZ01UvkYSGz5mjlleaTJ64yEDoDFcY5e6KqGxaOZD+ukkz6yVCJBKV5H1Y93ZRXrmWTmRKSJxZQTVnpXoPKGgjdhQNoOw5J1+mg+FQr8eH86oUeZ4CwYfsyqtkEQyC47WWlMsDF4EKvFS7bseDAv5NaP9NLOMnR2tEUdYM75dPputr+DfZ3ILXI+2rRSg28o4jwt2k5wddqDPu+26Xn8b1X4/rPy+Yd7B7FdgxBJrewVoWV1ZTQw9bGcwYsKuiY1+kok37PELMVuJhHFryPGolBGr+cfOOj8YgZQLA5ALS/2lJszWsOnwhMsy2R4wB9kAtf8lDN84B2ZZTWSWMplE9vAN8vMoZfwMMmap7PBBOeNpzdmu9ozaYnEz5/LFDiq0Fk2MRYKJRsKOFQYymMHSpdvQN6B2+opZU8bGGeO1sVnKpmXW+eoeqXVjC19CMsSiodhsjILXqDXL2DKRPWb1Z8GVkUuvqkeVzxaM1fz5B3IjrlUCKW19n4Gv4JFh0k4TuWW48OUB7tJDGR1RlTNyAVslFoCL5uSphAdAmVqOBWomuznbUi1kgZlabfMrYs5WrDMz3IRJM2HFdp5Ulvo+zrPEN4JKnFmXJt9/e3TS6tTY18GOTFDX57sT0PLaTulEE+NSNgKFtd6leQROfHRKPQISGKxpbiuZhrA8dpiWMNfV5X9h36eZRgBjzFrUKWUE5XN5oy+GNHKU5lUCJj7kkG6V+Jv7D4iZsXF4mcV0xm9TAgkqiQSdhwoPNKW5ickJf7GDtA6cO8Du95Aajnm/Fnrm2QBrI1vOWlt9SvWcZGxdysh0TOEwtX5ns03usO9zrpJuU5a9zKuNt74IkgoKgbMobXQkxg6LONKIac24ury1c9DExmkqBasPkhoZkj+G7QwNZSyrK0q7H8tNiMyAi22VNUJq0YKydFFAJGnYZvmOsJ1kv7d3Vqm0IAMn5syezrpffwBe/kfw8lODAYiXxA7/BIBlzBlOXsHW90VklyaMmbJlZTH8GEYfNDi6hEknwdKeSf98fRSGWrPsr04zEGXNfMOcHQLGjhMr/WMjazxeQ9KYZZclj/e/aYZ//ezCefqKbeQUj2gYpAiz3YTfNvCx98Ds7QEztbZXe/wELKTsGK+LpItO61LAtLRLtp0xN57Y5C+sVMKenQVjP0uuqLLFk6DtABxPDEkeCXkeElAzKxLEDdlHtoiyxqKVxD4/83h2YdEUNu0+F9+wUqMcxDfSIj9RBXqM5hryCJEx8khuTciltFLuSsIYQqRYAuZCifhaROcnOk1YzChjg4/OpLFLIzIJo0gZHwih8k3eVTwVmfOiy8g997Pi2KeT4YfugAcwuJvvQfAFOI5aW1/KxRGQk8rvjAozWeOCaZ2xyn2y4OeHCYaMuqSd3XIfIC1sko0wKEQ6cIGV1skOn2Pw0olFooBhLtQcAwrOaddJTRWnihn9Dqfm647R6jToIF7Mvd236RI1j9eGwYYK1/gYwBZWfJJnnJV2B7y20Iq0IxWo3YLfjWSBgNVugGJ0KL0B7MaGNlTpYx+xckTOAVmYtWZdP1fIvx2eFDbxSPTmfmYeGKps0TORYcF0DC2NQcMIgLfxW2RgxuJHDpzua/RLSePazfKkvaG/p5b4LcuqZGpJ/tmz23wlv4uVpwnOjIql///tfVvILVt61fjmv4PdURpiDFHwQfFBEPGSFx+8gdgm4kMSFTUqiCI+eCGtpsVoI4jp0w+dGIwk5KEDQZvGoN3BFyEXY3xQsQ9oR4OJIPGWRKOJ3VHsPjG95ufDqjnn+Mb8ZlWt/3LOf87eC/b+16VWrapZVbPmmGN8Y0QxppSQWTJY7FZVWFNsKVgDWVBuAK2zZoVWtQvMPg3Ht6CWDx4ae/Rw6O33W6C0ieFHxqbZqtbM4msIQ1YobDrJL+vSwkKyQpYwboqWhuM03HmXNfL5by9fkBqzysYjlG02gcMNTPkec4bzdvqZ0cdRMPXePhOL+DcB/Ow+MFswZpM8H6uh3TQseO45Zo8ap/KcjD6y5TIQkNjfp/VmC0A2SRxbzlYiZbSdPKxJspeBJZAlfLL+FCgxICG7/n5ururXdiR9rmBztWwzCQGkiI40Si0PjTPZeH95AoHkhiZAql+1jRkUB0u1xDcGxALIAo2ZXMNlAo4KaWjcUl1mTbOcMlKGuNot19w5yV202gzU1ChEiCclvVJbrz5CJRamL1RHIcPkYlLjDdxt3tGgx4wzWcPbn/SZWKgDg0zBBY1KiDCQmnwEsw2sasniz8YcswzGMYaP8kYTuLeIV270cKxZhOpP4wC/sTVuM4D1LpdjFoqsUDxCxWbzEdw1sgkG9wkG899JhKA1l0HcWYPUb0gHGtPmOxi8OTLGAOaG5EgaIdtKE0Q0WaGgxqkhPdNs7s0lYISgJ3eVdD7hKstE5Hg9OneGZ06VdxMoc4QWNWXCbGFWmOnoIM4T2ay1HQz9s3QzF97KxNmxiPlHvBJHnhm6IUj8fAZlHiZC1LSkTAb/nvHzC6IpU/wxWeUlsl0ahx2aGnMJYAoWkWSWLaf1/XjBoKdMkKYiU39xNET8dtTytbCt1ixY5N+DIasWwZVnbFhSY+ayLDNkhaMAxj++bXVARhb5HRzVHSkj5F6uUZ4iZXSpN2Pr/FBzVndqzWyYklTMDNrl6C8WVvtIjENwm0FIBX7SDR85OmlevJvdcqS4lv/qdexyjrrv9k3PieF6sxJMnyVjtscmJcBsj3nCChytWKVbQSMDKgpKBgclU22Wi9wx1HQpawakk5v6tyRsEZ+XLn+rgEPXbDHeZpYZ+kCLtsMxgZg73xjHrH7Mk6mULlEkqeJy2XTEyVLGSATMS7m4yNsOQyY2+QYpy7K1GUjlmwZi6UD/mboYZaQTTJwl5vPONOtITywnbev9QzV8pslEbtXEDVkTSWN4rrrMKuBsNitQsrLScNGnIOnZYWZIGS/IVfUXrIvksqnseXhbaw3Szc7UuEUDDPausA0Pi+RzkhfuMKW9HdT1gvm8yWhCXUBsk/RXcv1cmHfYJkL0eXDYebDE/XhTTC9NLnx34FnjknZlpUD7Zwx2onn+nIPGdqxy7o02IGDk6heKybjF0vcFxpBLJbYYgolI7ru4rbHcRf1coYRj09e+r0MUZhI2d9S5lNEmWNXaupDMtNBEygAwJlLGwZQNM4/xC2w54lI7ZiR1zJ1oMJtQsmEhBKMog0Z9aymxq2yyxsCUyaG4SwAbWMZoc9tmQq9BqcttndFjsJXcNrawy65a5OvN7u5oCPa/rqzZ3TcEQ4/MwCMFZsSSXUSyuAqtdgZwEhptGh6d1JdtEuRLIYBkUc64JyOsdqL+ClJ2jTlo2hWs1SR0mreNwdhRzRmSejObgeRlD2RiYbefSTlxni0DgBe/CBpoKfbXNs+GmeUzzk8kZXxVY/ZAYNbImgbS+HV7nrFRzGxtuV9O8sXGwDjXVyEaWHSA1b6vDNnw35jNP5osj4APNgOMzg6RHJLBTpXdYNfGSlK/ti2tVo3/gpiq1WfhPamBw2bs0ZehWjTUWsu2Oyn4o98pWpum9WVhDn2TMJKUMZMoTp8l7oy7wMwtGQR62h9E9dWKlrFYrtUDK0GO8oRJXNRi4fd8dmH0mty51d5xspbD7NAobAUqI8sF8tRe24UChOXSTh0EMoNi+1JGlQ3mdXieIv94WAbLZhn6hh/MIxxtY9y6BswOoocX40mSs6WZCCv4wr+XuRsOlqcDl2Q1RrDEJ89+T08tP7Ffg7EbZvuryJq0qWzHGXLSMVhk2xLFjPl+oLYkeh9KiM08nNa9Pi6ZLjJSfnq38cfSabJPPTRrfKVptDjJhDWDjPS5+EnATMrm5KI5GhKXXhdXSBhcpuvUkhozriuLg22WMWZujZbOQ+4QSU1MwPnKl/1SreDC6FKmiyhdZFkjFmxaQW6eGc5NqLzRF9ezFsAJtQeXz2jHzQmQ3Z25BL8Nbn8ItfzaUVd2N8sRU2B2F/ecXRn3XBiL1prJsktAd2XQumSwxMnPajFYOtSYIakxs9zjSkFZB2RIDEBq8nwDaBePFv1nXBrTMOqEMasJSMvq0XhashK4uyBKHh34IRzUlg3GLGH4cxp4h6dHKMV4BcyeJ2uWyhIVQCUujUFGKHK5bP2JafSQ5OnYpNZqmqMlZiCZdXzKumm9G29nA3MtWFrWYwRMgVgbduoz3hbaDgZrpmB4xdDpOhvAI0A5gc6E3SsYtWpFjquO+JkcqapcSc8pgWomeMU5q4yNCpEDsz6BKe9puCPLFftOaB2aWCaz9bova2enUbDsgMfioU4F3lGTFUx6zYYKJw2mzYnZweffDiz0VzxUZDx8Ibhb5YEbslBplzqzmSnL680yP6rsJDS5ng9A2bItXNR2fvBFha62BpUuv+CZsWeThzrVVfkhPF2VBsCV3PW8zPCoqRwz2st+R1wg8w0XO3W6rkOC14lztbfapNb0oEo1umi9h3NHFtjS4EI6Xlaiu4RaAhZymiiAFMbtDI7ybtJOSIeUDQPWtWcxa8yCO2P2zwJQ8+W2eDqpYnmA8zwPElGUL+zygamEi5uZ2bI8SiAvGzaZfLD0UNn6EHp2A7NRMBcYN8spQrw4cwl+Gm5/EbV8T1prhkVeGTNmarO/NP/gmjKbWbIWMA2tLRuyxg6ciDELDBm5MrrFurKpxgw7dvmYw6XDPVyCpqfXZAaSMWYteNpxgtU7WXc2lVcgrznLlnfD+wD871PA7Au3C2PVoXPAdJqU4pNQ4rFzzMLWvcoxux2UnZUTnjWn2LHHn0AUkzFZjhlIZsegStwj1ap/AkPy+4W2qTJb6O6BAWQ2j/8yGyi29GCgyfVjBLpSV0oCgauQ7Qzs9Vwyqh3T9mnvFdpu8PJ07nf2sdOL23JcXybbH60d3ctkly9lRj0OSrk5ho6ik3PCMSB5QgNaF09CKpVF4/wyZfs56ygNaLTcYKPNjtdExuhaX4UZ63qJVvls/gFIyHTmdJKOnVIRD9evMEhTa/yxKgsyxirDjoosYLqm6NrTz5QtWiFNmuRJClviYCwewOUMgnzgh4Nlz0d92Ls/6gY62dvLVrrl5x2fYzs/7Mo+2z5utRND8QnGbBpKX64h2Y8AjA1Hu5x+7tIG7PCvOT6KWm3WY0+bz4wZm3vcmdA2JE8MIE3Bs0tdKsc2N6fR+N4KcGj1VwNqTepYwpRHAXqaGZuAFFkbM4ZR2jgmbbDZ7c98eOYxMIVPcy0ZFgHT9NxLJB4zUV3GjGVgtZzj+zBVLFhm7lEjzdfMYno/r1ULvIMvzg7Fvh9u341avjoFZivmzJIcsiyTzJMQaS+JsQfVkpVh9NHAmbvh0mJr2u2qDpbMJVi6HoCeVcDyxJYpU4bcBGRVa3a5LFizxE7/Itt42fsLykBbALajOrPtVv9dMPzTsyfLi3fzyM1kosjWMxRvY8bsUc0/npPRR7bcDcBslV82CWz2lktqnTLjD0h+WWfjEqt7F4MNFuGwZI8Dn0PI9NYOdZNFYmOl+lxCey5/Ie+1/ZNCjS651LapCfgK1vNqlS9Sz+VEu3rgCxhr65qWXwGuE5zMLGU8ICZcWCoTiqYbfjh9ziVcFBp9IRduT2RZzII5GRkocwauYTtiX2zu2MaPaZp2Rm0R6mTLKbYU73pNp7uYz8Vyimd2Gl6N8Q3r5HDmirKfYtv8UQ9Tt+F3DWtQnk5/zXfrzSZ2f3kmukc2Y8VzrXp7v9ddYo3wprJQj8ydr4Df8vf81MY57HBRvwmwZReUr2m9sw1e51N52SSes5Tr97LvWd6UpcxooBAobsANSRFTYIecsq4gNWYm8MXo3fF8ODCODW05Zu3YajRYwcouH5MbY5QzjnjZaKGP8HlWGyeXWipbDPVnFuMbO7aB9P9lBqcrkJbt2S2s5C5faS2psQhzRnJH0yOg5eangVmF48+hlt+NYu+61oCp66ICM3FaNJMQ6DLQSMacgTLLphbWurJRW9bruxmQsfFHFXdGo7KDhV1+FqqydGVcWefX2Qyk8nsYc6CHtWY7TFmWc+YnQZnnrz+7sWWnbz8dmC07Pdt5X2+ap+ca31Jg9lKZf2SGGjoQkjqzFHytwNyKYeJlpQYNCWjjbbHEan5i5Whd5YRLIzNRKi9U4MTuicv3st/KmMHEAbOTbJIjVzDbZEDBqYRpt3avG6OWaepWA2DXuj+5uveuE6t70/kMwHxWBE5qwHbTr7GrSd2bLLJl3fQDwU19lr1oTU1d7HHXYeoA1YUe1EPVXl/mFTLdF6brmDnzOYRNQ1BuQBaG/UxqNc3MTTQH91MD6KobI3CRXLPagdsasHL7zVsWr6l23IZ5xNUWPQIUNirv2WMmth5uZOVukXDhUHHLRFyzHUY/t7txhkf7i81Wv9nvz5JHi+vibDbJY+vu+RZZw74sohFKhwlbpEBzbhzS3hjeDH4eYqaJbW2MkEdWMEQNGDBDZsAog24rDJ4t5n1Y3vPKBGpdB48SXN3BuXlvQzCz0yzOC9E1AXDRMTdyueCp5sypsdFAlMMXmar5vcxlqsCo1zCsiCejjbFu+BHrzdi5UXk6ZcxsMcxjN0TPFHyCjDpxaIJlkEsb2UhEwRiEsGSGrWOlZDZmKv3bG8emmsfFDix5zvb67pbh2E/A7UOo5a+HDDPbs7xPDDwy98Usv8xtdmX0xXMzuNtQ2ZO4QyWM1SRgGosMMzuQMiZ1Zqx+WdWaTdb5HDp9gjVLa8yUQUOsPbssgNnlBHvmhr8Bw3+/5UTpwMwyjbvtvPYl4nlVY/aMHgvGLBxZCS3GDgumrFmc5Zbnkm1mM2GDPdCVyf2AWcbXj+vGioXnLFek3ylJOLUCQl+9155zLRuDnQbCxDp/CYjl3FQAiyRYmy32IdN5YV4y236SYe4xY9P3lTHzPe8HlxlzNfnAYMcUpIHYMi7Lqha9NLLSLM/MR+gzU8SiU8NtEBlki613a4UUTqNlWs4qDdTqGBQ6cotJpfYc56xXDgFZiCheKyGx9GEJ4Cyu/xJusx6sySnwOQVlvqTC+inCLrKUNzaYS4+1io2RowF2HYZD/WRz5/qZcWx4UN/AW4dOfrV7dAJsxpCwZZr1aIXNMbC1ilPQdXttc71YMK8xDxmAbfhcIblhHUey9b0F9qi232/By5WcF1te2GZH32rL2saM32vAczDIFrLdKFvOWhi3hYvf6fj1bQ6yxC1QwY0MRjyAVtCWebUpMaJN7NRt3ddDb1u7Vsoq24KCVTMXmDPkodLm+RgJLlAnWlTbgunh2bPWa1wWnfmYjYumHy5m+3l+WdnhmUyuRpsM32wFyDhYmiK+OH85JVItd1/kwOkAhQqBNf7cJMkgERpMqHGCxUAaMN12Rmm+8Lzewpi1x4dQy1fCypdF10VDeF13jD1WtWWwBMQlDFkAZxtbd1cG4LlDZM4seX7G9ZBvf5gdGbnOLJMyLoOmte7sMoxAel1ZEnj9+fa8JGzZDoPGph8sc1xlncnrfwnDh289SV58ITP4R7MLYungYmRU7fGBz+uvvx4utUfIMXsFzBbAaMU2rQxBEmC1B26mZWXgH8yoxAa/cOBykyU2i3kKYS6UO9YMLzoAq+TFvbFWzRyjP2+SxPY8q4Pj502SqVJGYbkmB8iEBVN5qLKALhlqS4nioj5sNcRfSiV3vl8umB3oVVfGkRtakuV1hJO6RUxjnpddqYt8dcE2bJtNBJerFC4rmq18E9fiGEkGqx5txrxisoWE7GzAKK1mzeOdK+AZdVRIiM0EGNOwelUhJEutwqWZbxrMVHxVA1M2wBoA+YYduEH0w3CpUUK/DeBnM4qgJ5TR2cbqUBuyMcUANrR/tbkDjqIlNwIT7rNLMZ1QPQbZI4sXgbCPUyetNWu/x5HbYoxiNoM5/n64X3sHnPEnagB96OHYurW8nsFU9qDpahGsWKZU8KkAzJ1NPWyeLvC11LhuTGC1eFnwcRuXGHUcTapYhPWaPNrZBl8ljRYZtOSqui5V+mKXqdaMk8RMAtzVwWkY29+FSZdC9WVlGoTbbD05gTEP/B02+aTNNWbKQi2MPbh8qxAoc/JeAbl/lzsBW1jLGiewJqxaGzm4R1fHcHw0v86zilr6Vxh1Jmizv767dUj283D7clzKjwLli3drygp9xq6JF3Fb3KspY3ZtA2BRErm5MGJgwW6mofVlVVwYhTWbGDTcT8rIOWY1sdDfkzRm2WauLJmAyiOXRpU3ZsumwMzwP9zwFUSInwdm72qnmsXzV+tb59nlePY3NIuXrMbs7SRlTIDRLsji2qnk+YpBS001mFFqz3mZ7am6HU7TWvx58jwDlbPp8bHLokofjwBrykqqdFKeF7HEbzVpwc5+27e6WL4Q2Au5aYgZavo9SwDjNPInVlA/D873tmNSMTmu+cKxEUGtNHXimfhNjQ4BcWhUwGI7kHSSN3ocWUDQJW/85MYoU7Zhh0ymDD32+iCUOan9/LAX0zJ+XzBjnHSWsWaFIBeICbNg9OHwPndYCcTVzcmRwVmGJC3pnGsf8MfQcpLLdbZSHNVaGPGWz+WImWjOtvgeyB4CbKPw8frH2tVElXQkpyTwtlWjwopLfeMwY2kywKvfgJPpjZhbOBDcItl+HoMRGtJCxlUtnDq2bLeYt+EeCZZBbmYmYTOsLePdLMb6xIWPNg/W+0YsJIJ0jgFZNRC7hTAd0Fu657axhb6cTXUDOhtAc2tFsJvssZk5dBYs09DJwD01F/aFPC5nyqIdz/zXgrzRl5lnBSNzcIRAm7Bkca2OLK8s5pnxjmjkNAgEBS2lmhwqspQJr6AKJBf6Ujec7JEpU7WpphekgLHIphWsIxQLTXpZAexCAAYzq9ZY6CLC0o40v+A+w7KfhtvXopaP7jos2oaULiRPNKopUzbNE6AW3BrZ7EPCpMUaPwAynwHZBM4wSxn9pJSR/6YSRg2crpE165LGOuzzd3PNzkgbEYw79pkysdOnW/2fheEz9zlBrgHTMiGkEbzGE72JG5NZGEe8VDVmz8noI1uOGbNbWbM9mSLLEzNJokokV3JKXs+2rdd7+jCyCJLAPSBJZM/K0THY8N/jeQbIMoYw/V4C4oJvWCJNRCJVVIdFlUCy9NMo2BrERKomQ9kyO2DWSt0hMPayyjrpROYfXeLYVIF1QBzGMWE2yvOfUbIptHsVcJbtZQdfGiZqg8pjgDb0YVdNRQaVnO4GjSLkpOzg0IgYcM1VyJjGf9N40U9IFld53zUZc8baMlBNWW0CtG4E4onE0ae50qxwbmz5pW5gjo1ayFDDKknruqyO09auX6hU8cW0ameQGqhwG/VhPgBNq3na9HFdZthugi5uis5By5VEgQ2UkdTwKoMUmWY1il2rPS/NTPLrujxv20/basm27WvySppxilxhlzGGfJJR0+UkcZR4r96t2QCnnU3EAEedK5SS4khEt2PsG4jm884FmLVjNySavFGtzo499i9OVXCGIWFcebOr6o9DqEFBW+bRgcjyaQZbvKdQb5jb+3R5l0l62M7rEvLLBvdmwqSpMYjtToqsHjpnIEaUcDY21DsKq/7oDtZUox0nGylMLWfF9DlKJBOsiLjAdmauuAqhEJixy4K/BKKlcGPLXtx36Pgx1PJ7YOVrJvMPtbwvZSDcizKkmZRROMcGKovIGjdWja3xnQKbOyBzqSk7a6ixkvu5gLQTUsYAztSZkUFaHazZUbbZmQDqDKjVhUtju1VvIO7vwPD373tyvHiXbTS5R2fGlbY4vE8ypYaUnwBIPXeg97aRMu4As13pIoOdxIwjY+G0Hkyf99dNBqiSwJ26tWlbd4DbY4GvvedTG2UB3BmjiGG3j5WkEce1dsvnJOtUiWNqxHJG5sjAzEQmyGhg8slQi/xm9mGRra8b1rkQE6ZxX1XwijPTRla/nbWgWV1f7p2GBrtwg3SnCACNaECf4QyMpiKz8JZJr6lIytcBZFhP4qstyR3NjZtAJu8Qa7BptYfTDnbiWv8S5z29f/OyXFueB1XTnfB6GbP8Pk8G1gA4oiuiJ6er2qhfwbmE6jV1KnyWUbIfu0WKOJhukDFG/F1eNhqbxDDkeJyda8dULzwpBQeIV0YpUtrx+3G1vqst0SD3TKbpgeljsWyubOz9N8Ex7YR8DjkbtYwEIueZFqricw2Ytlhn1kAYO1IEKaPNjJrliGzlcLj/zyQSemSbtcSyBnzvSNg4YqkLMWVc28ZW+jOnvpY6zhjOEvOOsCi/XxCjEBT0XoYXi+aZWSJf5OYO/isJmdnJrXR/k51x3ZEy5KxuswYTWnt27+GpA/ZHUO2XAOW9S8v7zBof4sq4a4+fhUg3We/VHt/ZgZHDpAu5I0uwtMoW/cD445SUESRXxMyWBdt8dWSsMdfskjB7u1LGgwDqzEI/C6Xe/v4j3OGPPWTcfjX/cDrX8j5uDZNI5n95Aimj1oQ9Qo7ZSyVlPAvMFkBjj5XaBSc7oE3Xa2TtzoBsCTzacraFCG37yMYbXTJ5K9hi8JcBPn6fHCcrgbEq7VKTkGxlKxkcRjuKtelJBsQyUw89NlMNmbZtAuQmYOZhkDxP1NDEelADGqv97obph2G7b5Ay0EtkxkAkRc2wTPuti+Cby5kr33asuOcaGNBgOM8042VqDFnj9ElFD3WXUMqnrcSlMBtqjRtjJbrVEoDG9WYe3BmvvFTtsqtKa2DTj/j+nunHLP306sOMgnO+QxLaQPLmmEK0bVEz3SvKCBRNflaeidckLKsNk92DfUkzBMFC/OYybDf+vs05ELaotTIBSrFliK1ih9Hplj2OfZTk7bl/jQvNE/Cr++CWAz7NyHKuBVu0Xc5uy7LUYBZes5SR1jfZAUpn0idmpN7UbEaagYU0qrYcrW1BXGghUcwoUWzmaYzkEXcb4Cq0uQzxEDg4C+dedGqcY+hj/h6pemdBAbsjtv4a1L+3uCzPtRm2MWzFSH5RtvcVgPliCKqu9pmiK1xyJAmeQtnKmAnqRhzbTE7J7CnbEfkFDxmeORzvQy0/ALMvDbVkCqTYndHEuSazx1+Cue3AbMt2FqoQwCkxxaX/qzlztnJldOQMWpijRGL+gejQmFrnkxlIMAKpBOBsXW/mEJMQnA+gDsZjs2zzJ73gL9xeVaaMGQa7n9022czGfX9scLEnYbie+/qe9WOv/ipjxxJAldnOezLI35MV7rI0Z9anpiELQOKJc+G9gNle2x1Y49tOjdu9gKL+9i3behKM3/QdkF1+IJiImuFsslADxKxAHRKUFkbdpIwoQL3EeK82Q+WIfhpBJYiIgcwiNzMN7kJILKR+SQNtXZxMVnVlBaFArAfAUAMEr3/DkmBaGRv6SqUzS6JWJiAzr+Wp3LH2waOafjSgVvtQVNe0ts5HuiO1n88r7itQJ7G0qQOmBReMjH1ZE0W+Qx+t0t/Y2dA9GcSHMZkwtuEHbHnO+h4DxYYrjjh5MFUfzoBzbw5zkG+eT9oqu8fmi7befuSk++50asqNusV2CNe5zW4S2KG0ipp/kMGMI/FyZ4YrXoVRSDSqv5xMQIyyAoe8MU64FOHhTKwlR/h0HKhbiLCGwMXIi48tIp084+0ijroWsayJQsF8QR0WAl8be1bYFEQPl0dLfDYCCSDNF9SlJf10f99kh5LCOlPaT2YA7//4d4C9F7V8L1B+6RwaXYaU0VmGyDVliQV+IYfGBsZKXM43Ex0FZNVne/ylNPAAyOwGTCMptfb9WrOUMasSPl2BS92vNeuGIAkouyQ1aJcIvmbG7Pr3J3CH9wL49w89KV68G8CdpRN248Rf1Gv7nCX56IwZXpl/PCVjluaU6bKaTbYaxDfJ3oJ5WwJBlfpRvduKMcOCPcq2TQEns1OZQ6S+Dt85AWYyU5WjfTlch5qhNNljc68k0w8dMikLVhfLRZwVl5sYs7qa6tDxHvXM3Md04FaJeSsbu3WHLnPsUgWLNuFsrtAs9HnW2qlurU/m66yTjsK5snwU4BBwc1kh2UlaEs7Wuq+mBel3Gw6eFhYNa+C1ZsoO+oDFgfSdf2MY4z1n6TpQGrdVI/hlh2tc7VgctQ3GPJndXjWBhfKi/VH8nofDmQaemJuVowybx9jtt51Ai8XmONrKmAhh+2h+d7sMSiv6wde7yoC6MYetZ3V3dl0DH46O08iUAyRTQQKmbbbMt4xFI0DWLP9YX+fUsVhjypygllPcwbA1QQBew0PVAw9mgakekCCzQC+ITosqa7Qlk25JxtkU8abZZPR+kCuW0e9OCiyWs29/+VCwEYhZYv5Bjour/G9VmCJLo7EaU7BBDFnfeKNOhVAkS7B9m1HEux5hRGr/Fm4fAMpHds1ACmWfTTVm2b/IjkVQZyGrLMgYxSr/dL0W9vO8sjqziTE7y5zVnZqz5tBYDwDlmdBpk1r31d/r86/HBT/yGNTPi/L/tlmKW1fmCUirLx9j9pyMPrLlFJjdwIzshUun0sUkryyV3q1Ak7JnGQhbAKXwGUsZ2QlSX++1i4IhcjbU11wbNi2bbLsfgMuMacQCmO6B1hVLeQf5oQVIs53PiussupaSyNR3qx+DWOgHVaAYHIZZN5ci22hS19fvWR/FBn6sPFwOMjMvcqcbOqI5x+TUKIi0Fb3pzjDL4GLTHwiomfVY6BvCACxD6PNrXxiAXOfRc/OP2mWNjSmrwpp5MHpf8XVcn+SjxnAbILU6q6vrHjNCW15YM9bgIGawc1/LFcNmetGMMlie1kKJicUwMdogRqYDUGvSxejb7t3e37rsOQSNG9VVscSyhyOPE9rImMSZgejcCgdMU31cs8c3cWL0YYZhKh00C8HXTuvr5iJ8vLr5CgV8b9vrxM20tib8ghFKzeYm14vTjWz4nQntzQ3SbIbfxIw3Or/ltXWpZKdiKES6JMCMwZha5qvc0ZBEahidhRYAUHuX+fTIw5QNjA1RYZMc3/UugRkxrkxrtWbjswo2BQFiELUtZhtsOY1g4jHgRmwaY57tuQJevwzAxlb5sNx9kYlKTS6w1TgUSYyL9j2mB9AJjGnQNKNTJHloj8YbfAeqfSlQPjjXmgkrxtvJy0zB0ZJX1hBKKaN2y8jVsMT6slrJQl/Yp37LslnOl7oxWi5j3GXN2vt1KGIqYq0ZZ5sF6/xWa6ZGIOUAkCXW+hwufVlZ6he8H3f4u6cmVc8AM/s5AtjzfT6Xna9UD/XxGa7XX389rO8RcsxeZrv8CXDpZwKcJkB7IpPs8DMBBhzYDAJRae3THnuVsFy7gOsGYIZV6LOARRwxeFjY8LfXWtd2BKTv+9mKHT3TLmjmeOqJIaRIUP3RZ1V9NYxUgURaMTDT0quAZVwcGRPHRo4n09q0URgk2stQI8PFc7xzWyLnZPpBmkyX6cF+B/Nko6mAjsX6ShVWxPC2PhT0lKcCVmJCrgTzMFNfwxAvSuB8s8xXMLaSM+a1ZnUKqEa9DIv+jo88SvaazXwHVZxIxj0ZfW97zZ46DbQ4h1R7ZjgxGBknR0TwL/rgGK+b14CaiH7JwILvtVbFFbOtw6fNQEvHCn1Z38XG4hLe2fLTWEbXGZwNENWaBCk0wONjGF9lpsPBbexiskM5cZP6cWvL6lPH4gzUOFg8k+lg2OlzdEGw8e9jaIu2+To+n4qbLNcCTzMduU2+JSCn0PWVsz6Du7ojprqCq8gG+2E5sgS27+ch03PO1zgzZpkSlylOcE6NQDJ7/Rb/dje6yFa6FfwENUJOUw4sXW2oZS52MPse6srqPOwxW7QnG8GURIfwKI/Xrl1reW0dIr1gzFzqzCaTkEJW+TaADVvks/siyxgVONW1oyGDs0zCOBmAHLBl4XWl31dXxqzmrA5wmdWaMfDi2rNqOShbMoMF70fBNz7mifACbwgwO5p68J1O6vLyMWbP/XHEmO0N2kWaOC2/9znLE/X1arCPGKK8ZNv2ZI1ZThq7E6psMgONOyHY6WcMKEfmtYFfZ9uv2WGyLBL2K1jm62th2VTaGF7z50mG2dFVP6SMtiac2vPq0SSk2+JjKEjafadZ5TfL3pow871jrFKepUYhjK84i1gJKPbY5xFHVQcTj3bZVgG/I1TJh6JGdi1oMbU62hdlWJ4TYxZ46rRL04FUXcy9gSBUyy9rxh6Fhi9jcMi32BmsWZqYplLHipVG8eKDXTIGYWQ1DxsMWu2sTbOK3wa97YRgRmmzXG9D4uoJYxPqp64nWMvPavVrwcG427hbfz4INCNQYRE6EqPT3UmJ5fPIT3Xg0fbNg/8egtX+mAwxOvfHRjvNilTfLOk7WTuYP4P177oUjjWWckQ7cL6Od+auN691e5QYs+MIcdyN2WvtYVngNdcSmhPrichgMuNZioxZLbJixRMQRswqFzoB4kxRwqTA5DmB6IaqrNTgalniOABypayzRfzyiX+j5WYvyPFOpvQuZO4BMtpwiwoIK5hjHdlpnhAfY+DWtKXMhGZgzqTpoWDNF2QFa577RjrJUNm9ClTPpWDM1zWrj/f40BYk/dqgGsmlcZVbZsn7zVXSY21ZNXEzbLVl5MRYmWmqc8D0odU89gFa9dkuP3NoVOOPurLOrzGAmtmzYG1fZsOSy04ItQIzZsocgBe83wq+8bHPhCswy/InbKGWwY6i5gkYM7yqMXtqYDa5MIax7Qy+lsurHG/n9VDBuZ95nckY9XUDZNn+B/nirUD1BHN4+HrF+h3svwK0W4AqEubxPq/3gZlHc6AAesiFsan52hRwn+AWK/2+HNWHBRljRiCZ1Jwxk8djR8ylOxEElYgAA1XBTIGyJGUGYwzQun8/EtaLgEGoM+MpTGVcbN6uRNYw+D8PXfSdeCbqjHkMmB6SxsGe+cRjWo8r5hwqGjSr++JUQbQNzauIJ31I5IwnfnjALvbwwVYjMDsjw6uLzfjarp7c4+qQBiYSOqcRKp+XmNidYMvRs79cJi1ADFQ/OlvwM+/bBFc8u32TlbzxpFlktY0YYyeQ4KhTrRZ1YhJl4CE/gHPVENrFJ6VwgPgdXFqA/HOHRPLQfpo04Dqlh0dgZhqgtTXQCuME20VPAqgNw6l0XQsX54I8VHW1Yz0wTenn6WDOMmA2YI4LoxahO1sDmUza5OweRrZ637beEkW6u+wvAzWj7mL77A6zF0u30fcFiYnIzplMxLE8MtqsbhsQEqrLONds66ML5Zj12mHEGUMrTz2U/NAWIv1aypJpblnPKEts8jm7zEuoJ3NPQBgFTDfPqjRUGrOM8aF2+Zpp5okJiGdW+WL8oWxaNwI5Y2BiO7VlamzyBExZB2b2Bh3nIxizwzU1+fYrxuz5A7MMbAjttbK2h7JOCVs0vW5EljBmh+CCvpPKH3deT2zXLVK9M4ziQ9dxVIOXgaOs9uwEWNbv7IKwpK4wm4YpsahuLFX1nqjeGTR7amUQE1VkMU7Srkoh03EOaMYzIIik0wCcCztPPKlxus0sQHAz2Ta8SRU7BcCjFZpl1aRNHpWy9SQDsVB/JiiUUbG4mhjJGpHAn0pWBAM+ckwtOswqGHbdSFkvtciPAdPReD8CqtpDqqmsjuRhQyV2HTw51RilPKHMfkeeahv8Co4JYGmFrd0P7h5+4n7JDE5kVX2xStd0BgVdO9WErnIXBkjiFumJTaIzCEnqeBwKKOP+ZzFtAeSEtrVDG8a0dlQdSF14q+AIWBLnxcHuLUPHgDlEq197JZiA5Fdb7JAtwDTFNxbCpS+ASB6zxDOOoo7IUq3x2VCfG/wuAWurAdNSBUjkUuhOW+2ZY4oA64pSi1YmGSkZSsEQ65oLRFHK3hyuK6njB73mmsi2ViNmf8q040rBx374Bs4K4OW1UWNmeW6ZZ7llVHNWC+WWEegiENaZJJ/t8XtNWSGwoqCGgQ9yq/w0VBoxUDqTNTpLKCHGHyJjDHVmPhuBhJq5kwAtqzF7SlA2GLOyYMAMO/5umOvRnoAxe+45Zs/J6CNbbpGhNRl7nFhG688UjPWQaJU6MoAjqeIpOeEN7M4pBu4hoIrrv7Qe7ETQ9aqND0GgAFBbAOvd14t13lR7ljJmMpaF5N5Ul8B6j470OsbqSjbWuuusm629/1j6yL+RnsXLzBuLw88WEh06uhJRKftJ6+hcvf05JbvKyJjZtHRU7nMw9sRFzTLFsVeVZu99qjdjgFbJTHtAiNphlYI0X4C2OW65Ut5TDTlKnfEOhOX1GAzjjiG3nEFUDF9iUWCl6p+w502SSDDRuK4puYMMGR5CllmoS5x6z8Eg1YNb0rSNG2sYXeDVsTGjhX06rTuLLWCe5yh6dpyPujmF/plkj3PkqraJSzYcbSdHrg01ouVHwDbG0YfT4XX+IrpBBgVnY8xAFoCdonEBZaSBVpdGyOtAtHuAXQ1cgTismgAgI7BdJJb7EsBTW3eZvBrjxmKBMk16hjs6zzyVMk53kFWxmZNRLY8CPU7AsRzSnDK/IQ6MRSzyMSSOQdqoNWkQR8gpBoVQZSGQxRNqgSVVfaZShU/6+NCGJF4bcspWU6Ynp8hbAyi7fsYOx8H8o0QjkIklq2QKYhGA7VnmO07Y5SeArMsWIazYCev8LNOsW+pjWOT7ym0Ss8QxhE1fQdnXWcE3PeWBn6WMe9M+CtZyYPaKMXtGj9Xg+75gDYBvYcphGXqvr6eFLsvrJfBLXuNomYQ1U+BiJxnCMwYlZySLWc3WUX7c3vec7s2+AGl8F5lkh1jEFqyMU85cQzrh3Wcsiwyw2B1+u68Zdb7ATES1PdFw6UnGiDxzKq03w2JB7sDUa8YgBhvKGYDsaAXM8RZyQEwmaQxuJkgs1lX3ZZjThSNTFCtZYn6VBU7F4uB9A0vXEyc+9ySSWgGaBRMSRxQBzjlrLnYl7t1Sr9ustxqoxsrBt8GrNjnk5DCFZujGF915EKOGTKWRINfFzqlVKR9r+9pq0SqxvrxZdbS+88S7CE55XUES2GvLhgMiIc54irbfMgIoxFJdSye9t/GokxvnXjeosPYaBIiNjEGGKUqvhOLAc8dwoOynah0YukGCvm1buHi0KOFOGizO7bVoQdFbe01iJzmKSVGTzbLGPe1cim2a24QHLqrS0VchqM17FDpuVnZriVYRAGaTxz+wNq5QG/2SGuRzuDSkH+fVuom5YYmMljryeok3jgl88Z4UMfuwGaTBI4ZutXDucZnJ+7+wlEJ3klgw3cFBwWWI/GnBmZc3cClfDy9fMmwvt2NeC3BXZrbMOM/MrpllYonf6su4tmwlYZxMM3C7lDF9fiBlVBOQLGTaReJYxQik/b147i6Z1ZpxADXVnv2UGz6Igr/91Ad9ADNbALNVOqktBzqPfbY+NpB6U66m5/K4XC5pDln2ngKIhblH6tOpUkdm0yBGGTcAorPAZfd7J9mre0kWs/1HHjVwBAZ331uAzYmmoWVK0uap+cdq2j7JMmvv3VXGD4jkkrebdDNp8yh7rOQZbSWWbfRfvESHJ0Ac5xkStBl6i/VkXIblkXzYzcciZwmRCnLBxAoSVioeF8as0iir0sZ4QgsyXeiJ+0kqvcTETkXYZaHeywlAVoqZNaoYG+KfGN5rYvbhHZRFR8ZKYG7mP9RW/2rIwW53Bket28C7rSlxWaDZEfJqMaqnqiSk9LF/1iYpDGaV3ADbvtfRJmxJX5Ug22rRjIEP+ns0fXNdbwVZ2DtJHLcj5ARinUxOnEa2jgEI2cq/NU+VCQZOeiATmxogvXUYVpXyDsYkg56u3VXSaR9pkO91GIGwCQnVANUNBLtMbHg3IgGqeTTQ4BQLj+AWTmdWVbpFiKYJjHkS4IqUucuGKAy98veig+Mqo4QBW6Hhiy+D13SnuK8qBwOq2EdoBZ32lXa0A00JiGiE2LtTkrOned6W+Uwi+nHMm7520ERCGfdb4kV+wCUbIGPS3tTHN8Pt+1Dte+HllwWzD2ODD2LIikgeyfDDhSHzRMJYy8wsBTv8BVO2J2XMANlKyjiZf2CuKZus85Ow6crujHXIEkPANANPLAHaj/sd3os7/OibccBf2BuPeJ49DWP2yvzjERizM4wJciv9MyAj8Ko7AGLFBtk93kPG3S5YoOk2cl9gtgBPpwHbPcDYWWYvBXJSYxaO69G+L669AsAuSO5VFEHENfiVS1Uq5yhJELTkfHJZVU3MDPv4vEaIEsKmaWxaXaRaAVRS0FJ16dQKjQaysDRelgdFrQZNGLJqSf2Y4ipP6D/HWi5HLE9aaySD5T7AJTBAg/NCosfr0KXSuKd2axDv++2pUX80+VBz/rko2UMOgzaHVobNvbmLq+WyfqrbrIPYutj2k8lHgsOnSiinEhxaXx+Yu4dg9Mi8Rkg9wVjP9nvsg5ZKqlX9nOsnUknJl+vHx8m4hA0++kyLkM8VydnlIv10WjTKTud4xWhoCpWZ8rZMUk+jHLOEUCriyc4THCbe63Y0ZZyn963wS1YZUuQK4iFVM//g+W+bgFq6k0it34lBW1oMWEwWANnYU9lkYNrMIvXnjIG2ybhGXIZDkAVML2zy2ZURG/u26yU86ekTWo0RpBN1N8kX6xNxBruPH0a1r4CVbwXKb7kCrw2EfX4LkiaGjGWPzJZ18FIEkEmY9JI5w8ycLcKWUynjMsMsAWiBCUMeMK2OjZNLY1ZrhrFvYb/KQp5p+EG/w5+xNwmUXRmzz2E/pwM4V2OGPkPyqGfr66+/Htb3CDlmL52U8YFA5BZwFoYGWa3ZWYB3Arzw9+1G8HILILoZJD0UdJ54X8FXOo13hmF8CIutY97JrMCiuzyY/VI7ZcVBWdyXZ0lYO07zEmTNCiSfR95xA7O5nEAgaphRkdOfXRnlLqUjZA2YTkf9tF51bpThrvUaLt2LSsu42I8HqnCWhvbhn4d6pPFbc7UawzEIA8f1T5Ulj4TKA4AnJN0t1QHOiR6gojEywf4dovzcqn26nT7JFdmATwZvwZQvbUHXyq7AnBqHJfcQagwb+e5kOCi5ZtZhKgh1qplbjfSNArRt5yIlvm3Uq20M2eZmGXK3232lRQV0as+CtweDqeaaKNXE5NdinFwgfQvFIICO1QYazax7OfBlbK0DCPb4WMgYTZwbF2RU7MERg6QTPIDZKsIWM16q6L4LA9ohQYwMmMk8WlkANAVn+a0g4BOfyaQp9oswrSUlWA3fqGdRkCcmitJC5h4K0Do4pD69WEKQAVK4WKM+k+coOzjnnahydCTX5c19/BsAvxXVPgYvXzObfdDrLUgaxQIYq3VmzEL0TBHmLJEy+hkp4x5bhmPLfM4vy+zylzVmPpt/qCHIxfM8s5oD0O/EF+CPv9kH+oV/lk5wexin5E9jl/+sGbjnZPSRLdeK6W8BXCsQdAtg2gM9C9h/E0g6AB6nlt1pl0dpr1vbZa+9VqBuwX4dMYz3bq92d8qSqJjI0awbh+AYI+BGBeJMPHXVHgZr1p9jP7/Mce2IQQMMP9XNyIjDaSThghwn90bIDb3JSSxKFgMQgxh9iPd/bzMx4q40eNTPBHipDLFBq0L7y+wYsyZsr2CdSatAcFlUCWMFQuSzyhlrgDEMFJ10Tr3cjFgrYwkcIrY1jNyyYXPvoT6s0ijS4cOQoMsJY7TCiLKrlAOmnpc+ndoIVvcDoFdQ9lmTO1ZOWh81mcPwhLw03SlXDds6t+Vkq5oDo/POBDbuKt/02mScRtvFoedVcsysS5TbTnqrw3Pljq4Xrm+5be61A6nr+8bp2GMKwVtuXJNwgkDYYO96FJwP1nMEbdPsTGPFoKArcZOYpADUeU3Ad18YiMWQKuPVnK6xFkChCj0Xt0WI82KsUJujq30pnByvJ/sQE7m5NItt8jirs/iAu08T9FkI1BWbmTJImRfngncyKysFy+5c5qKp1EkJX3CZi1ugFbxl4ivHHwXsB1DLNwH2njRQerPJ9xYm3RJbCkn/WIliM3O2kjLWPSkjxGYex+BskjcmNvmh7iyrN6tSi5a5M0q2mZfdmrPPeMGf9xf4zreCyclrzFY9iO9OtLyyy3+GD871uoEh22XEbgVzR58pSEuyvB4KJm4FYDe//xCQm8gQ/RZp5GOxbwvgnC3fXRld/SlMMsPqyOfsmczimohtdq7duJ3xjJO0Ajmu2XOTDwBDas1in0dTsJP7YdbpSYhP12hyOOkBMAMiS1bbAB4j2TP4lNdYX7YolnMy7MBUxcUCx7lIsPbBIQS2gfixOVA62uGD4N2QxwExhyuakWyD01qxIjOZPULWl3H4nfyeSt9YuDckdRZ4DZfRtXMWUo1Oj4lXIqJccuzMrMokiZRRbQ4xVw2chZorgdttGw11uoDNhzQ1sJmNafRu1RjYuDF5Qu23sXp12o8o+QzCUecatAZGB6jLAsD6sfHoGOk1ssd5voeP9jNsThNCKDF5FOgbnwFcn6wRbZ/NnfTOEGlnSOKbwY4J/zrO7SYytiA/tO2b1kFblpBmEp6RgcflVJWU3BmXavE8AEXCtQkTEy8kI23bJFHckS2auO0Gkovn01JW0wigr4BXBlJr0mKsmnjLqmIqgI/A8Y9xKf8AtXxZs8O/Shob9VUGIKuYzT/YcZFCppmAqwtA5isZo+2HS6fGHxDwRX8nlgy0P5hrzNhKv+pzMQJZsWVu+CTu8AdQ8J/fKrBwrTFbTffY3vB4OZR7VWP2jB579u0MKNrMZPbd5DO1wU/BCdvMY9/1cZoW0PytZJvbuh2JpHELlQZyuWOW8aW/nzJ8yfvLz3ZYqtUxyiiYs8DztJzxxDlz9B3TuCetK4PHLOQW+VVpBrUmcDWAKouMGJNJzIC55OB2wxGSrsmYOz+rPMl9kpn85MPA3kyjeaxoPY8bNhXJER2UUX4hC8DToZaTKb1+7pRkFv0cr59ewvh1ODIagS0jcaTEBGMWnc6vLZjnb+tLCqK6JUGoAfPIWnjCaDBISDLt5otThGRO+WZTyHcd8sN0qskp22pxA83yuCoxbv37G2Ayk3YezCBLLivmDDs2PhnvzKxfZPkaU8LWqy3g26T9mL6aGacp+yz8iETQ9T7FUZuE06MDac5ZcrD2cPa8dlZlBl+pzR+k5gwzYAt2g5gy3gZLvRMpMHV9Rt2TTTcCB3AX7HnmvLJCgK3VnkWYl2WfRceMLGTaVhutzJlIFk3xDCjqsVA9me/Y4GNtlc/tw6abqZZU7SU16yLYrKrxB/dnBQu/37fi8R/h+B1wez9Q/jJgd8MqvwzJIjFmVUKlqyU2+QTW0tqrE1b54Z++5zuvEykjs2MMxjTLLLgy+syU6euLEyC9bufna8EH8QLfZMD/eSsP7Fxj5vsM7i70egJgpjVhj5Bj9rIyZisGLICzFUtzw2cTS5YZU9zIsK0AB4Ohyb3xrCX9CTbsST7DwqnxHgzZaZbs5GdHLFqpHuukw02dJSYsVZRoMFAYafucyabMTR6+YM48kQ554qlxeEvds2l0mTWtycxq1an4KMRf7QDkeRUakoGj1xhEHQZ00aYbIXGqTiMW3fKME4wsQJYeB+DgfQsiSshzxsBSq9YBDoVTT4P6OvbYWaw3QqV7jZXUivlC+xSjAaz/JLkHIar7HFZtsu13AZnTqLaBvx6qbYHNavuROniQdivIOXuLeFyX6fUwwGWv4VImvC0VcqDm/LtWL9fNZxw5kygTHg2ANRBKUHibYKlhbkIrO02cScOvbdJN76N6kTCGSzyhaqT+LsocgRyVazCERcCIzOHUwn7bdBU1Nm2uJ/MgU2ywQZ0VLWXMIrayCWuFXDpgylE3S+dAeoLA5K/RmGZy7V0dlqzMD2KSuOq1beWZkDGdjDpDkjUWzifP7vGzAD6Aap+A219DLV/ZWDO1xue8MicJ4+TKSADN+XZjUksm9WWhlFrqy/JwlTw9JrBmiazRBZSlrowC0porI7/fQ6cBeMHH/Q7fgIJPPYeD+gKfTTipI8v8bIJ23Pdf2eU/T2B2K0jAAUg4AlM3AbEzYOwhgOMME/SU7XP03Sdo30dvHwZmviCcQo0B38jrAF2qXBr24cJmlJkwCkN+i6QRSyQrclC2lu+wPiaxdMyy1sNsrCMLcQ3ADIkJyATMMDNDwTJ/tVcMgvLhl0+dtSWDZwuBtzONy0YenryOrAybfIwtnION0WdMK+Vhxcy16tvyzQmQZwLa4N55uxno1AEejKR1ZvBuxx+dA/teWR0xD0yK8ngOm7V+DUVx2/I+AqvNgvV8BKMeJYOhxaxrwyzaE1L93WYQsWmIOzA1dmccv+/G4Cw6WJq5OJp6CINu+2LCtF3BVrSXMbMAdTuxuW1Pbe81o5GeXyZMepcmyj44CMolLHz1IWVMY77Uq10G86HQyXOkMo/4YTu8npNkODhXytXSRI7DAzXWi5VwtQ72LLJl/L4CwxxWYrH73KVNBLXNE3GmogIbcsYMdNnqENBvl6TFNShgfxgpbHt6R0KiivAUNj+Tx78C7Pei2h8G7Our49dUAmGVasvqDmN2+A87z7HzPPvnO893mLO9oOnJrbHO1vnBuRH44VrwmhX8PXGweouB2eeQx9Ia1vVmixwzexog9azX95yMPrLlGJgdgItTnz8WyLgnGLsVcDjyEOr7ALOwTMgUunGZvTq1J26/PVDqN3xeapz4HjddzA6MoetQC3SbX/db32XM2CFxlQ9MGd1bU+d5O1P3oXpMAWjZtDG2u146WBPnE3YzgbgxMhM2jctFJoaMonQagFWZtzepNPOJd8jgW0DiCUPBhh8M2thlMZqARGDHtVEdTFVPbOJHVpXLLIDxOeW+mDsUZ1pmfQjYeWA64sp9GV3nM3HZAYz8hoCwjNGFa0KdR6Dks5gx7rNTeaRjElNrAWY4qTwCoQ3rZOagDjkeMlHhpGl2T+rwnCAKgWJwDl0AZeJkaghOLfNVJ9dec5dgtGFi9KFaONbOwRauFHPfoPVhlnJlJkw3pnFhSxaMoki26EEAaUbQpBBY498qAShOItb+eRjQCVHNk25muZDKbcze9ck6aWKT9ZiaIyJKHaHMGS03Za6tbq9KrZlj984QPjKsZOPPZR4ewEfh+D6veL87/oQbvqgDGq4hy2rLjOLQDmzymTVb1phhBmmcX+YLUMZs2eTKCGHIstoyYc68xuU2oPYzXvEdbvgwCn76uR3IKzBbDRH9tnPRX0Jg9twfZ4KVbwRnp5c5AHG3LHcTYFkAkSP27ibgI7V7h6HWZ0DfWRB1j+UevIw8ShfxSVF23Zt+TW54Kn/UYvLAiAmcUPA1ATx57/hWunJB2vsmB0Ol0ayxoGm54S5MGpKaNEsAZM6MmQ7WJ+Egf0vn12dJU+0cSH4UYoB0tPgImVjTFsQbTbhOVMEXzhGyzA/7bJCkssA9QJhACFy0LLU6ID2LrorLkVwu98tBNdc12hYNMB8rjfdy0zqxYe0R6rnS09fk+MgnPJqW3fPVZZHWbCIFLi5zGxws2OSNzYEzlm1qenwiQ1W6HriW3yxNPRSk2SxphKCO3oFZBgcJiDkxWXHvdUoEUxB8jIS/6/WDWjMGMg5R1m7u20xYNo2eTq3yqdl0noqbjA9nJxlb1hjisQzNbjMzFuSNlpObsBkQmq/GsD7LGVMbSQwpY/G3oyvBTwH4Oq/45mp4X3X8qVrxnu6kz/VkLiCMLfKLyBYXoCzki4pdvuaYHdrlI3FlRO7EmDFoWc4ZM2XV8Rl3fDsc3wLgvz3XA3iVMp4dkr0FNWavcsweOIVS61OBqjNA5ywL9uDlbgErNwKl02D/ndpeCXArFbPPgroeWnJz1xq0EN0lticuAI0H6ynGwb7dxHKSKfhA04DLD9DlJGFUOJMAsxSg2RwYfbSM+w5rolKo7HSIkC3O50eAN4s0XZivyBBZECx6qE/z5LMQd+2eDvQtEk+UtaVAS/PScirKF2gimsmnDjzBZ8R5EJefdYEX8fRnRSKVGMisMKDLvjtyXBl3Xz0dZzxlq77P5DyUCZBshRMLm036agQCg/kdlnA5A6RKnh5yZWtDPksoGWRsGdkSBuasyPUQa8ci64UA38ZVWEJ9ZN34r7ItXYI0MQNc6x3T4VkEfzOfXhDl4VO/rgY6qgZPvJJM5L+GBGTZTvqa5Te3UAub5ZmZ5TSYKbq02MnwRuUn7XN+/ASA98Pxre7409XxJ2vFF3VjD4qkqSUyZE6OjX6D6ccEzjDXlZ11Z+yfqazR1zVnVd0aB5P2M9XxEQDfBuC/PPcD98I+l9yvz7C0iXPjyyhlfBsCs9Og5ARTdQvb9KgA5IjRekSG6AyQfXB7PWL7Pnp7Jb1BaR2rIXgChEGhiVQRMhOrZBDf3Fm95IkkJuCWQqTVDlCDLdi5zLo8gC6/oU9cUH8pWkxsLF06V60/S3+0IgqoVuYC2dxZrDXz4OHmU1ONiozZGl9Bm9bOmByRWI+WMGbTCchrnFKgd5afyZYg3tzqm1TaFbBCcnPr1V9tFGM7Yb0pyMNkFpJEt9/wyC64AzC66Al9cmJsY1afBttTqLsElvuqN/HDiONpMmf+xHZnjPvybJdvi5E9lI5BZNkmeigV8W3vlIm/tl2wzv6JZbtmCu4Cn51udOC551oyrj8b37epFde3xECe2ux4O01LSfdlfHlI51xs51AkOJovF1sMOG2vg870mFiAtukmYUdX9HN9/CcAfwkV3+KG9znwB93xy1ttWQdEwozVM1JGLBwZE3CWZpkldWae/F1JGadcszpiPzdg9l8d+Bgcfwv2fBmyCZjhs1hb/mYd90rWmN/1XwGzt/hxQ1DyY7BCj/adhwCWRwSFuh9ZpMCZersz37Mbjqnf4zs7w7CbQazVg4HnxFh47CayVnMZZ2k4tSMHX6jz+0i6KV/JrjL2QvGrn5i1cuSf++o3FzpNeLLDR5eB7zMIApTmoUZmOoEwlHORMuaCwHmH5/+z13yeRhnYPELfzsBmJ199k/UxO+A9yypkhSNK5LI6sDFkvgY3s0W8J7HSTuYTYQDcjRJ05iKe0G4EqjfzEFuBy8Ut2RRoC0YrFOvNrqhIkkS8B1lv7diMNmrCNlR2zTRRGvrMXmxW9twdVsI9174y7lx3jPR4HKfSObsKbocqcosP8A2YZaN+HACzie5Z8TVt2TJ9zxZuqevZcKPJDxMxcAk8G8LVgiBMnIMaon0+/xZ2hnU1qfMKUY8iR3RurnZcufmHj82QRyI39cCivswzkObREV/SUSloTVHkwZ3RGSFWPNP6sjOPH4fj62D4gFf8fr/KHX+9MmdpwDTOSxkDEFtlmXkEaqeljJAcsxVT5vjXAD4M4BNw/Nzb7UBda8zOGH+cH1K/VMDsORl9ZMtljNkDQNN9gNOpNr9V2veIbNARiD1a5hRLtve9N7E9Hwv8lWyMtsxlRiJFE3lif7/MQdVh1hY5CPMzOAjLbGK643siTdthJ4CEKQNSTea0UVktE0831wVI9B1sGPzwpL6LZ999GsDF1CtMgzqwZb2wXzbJE+NvcOZZtN6X574GflPzs/GDI4muHsBphqjIzTcm4OhzePIEvD04RE4431dnZZwxcNqoPZCcAtrslUfwo1LBfMaC9sHrAkLr7Igj2fOpjSM4S5sghnGHdayY1Hm5iLGZ8ZLBeMqcMVgjuDB9L4PHJVwpM9TiKzSDRPEKLAmA8gDITISMNu3Uylw7q2abbhLbR3erG4klsYGWN496I03W+FgcEpvr3CZIbNJlLzOjRQER5F47seBLuerbdo7/DQAfdcd3OfAb3PFVteL3ueFXV7bGLwNc3SJl9HtKGdX8g6WMWdi0z/9+xIFPAPhuOD4Fw+XteoCujFkyObScRTie53+VY/aMHjvA7KEA7bA9H8KUPTXwuC8wu3UfHgp87wlm790WJ9m4UjFP9nLNzZTfuehGQr2OAbiM555MWiIJsNaxpSd1Lx3Y1Z3Wqpk9/hn3I1/MNWMGZmFn6syyhXoy+V3XpNasU7bUfsKW9hs2gRm2MYimIS7OcSqT1F/Q+rK89o2BoNdKDBK1rshdBxOT3KRcptrlME3iMLfOsOigOuOq0vfpOI5AbI9FLzrNL6xGts+w+Hx8tirygRTZ+CJ0yqjt04TkHi2gFiPrQKwW7Ly1Jek/VXGa1SVNEzuIJhqRuZjbaVfdU0oyQMcMtHSAntEyhwDNpqvRE9iVAzq9PkzWvhbu+QTELPmF+UxbBWx0ILS89g7uPst6XkxW+7aDhVIMpZef5Yd2WuOhlPFoR4B7CE+e6+PnAby+/fur7vjN7viqCvyuavh1wQgkkTD6I0sZ3fcljZn5R3X8kAPf48A/dOCfv1PG7S/wBh6PlX0JGbO3OzBrEjsKavb7gpRErncmE2sJkMxsD0CcOY437cSZdfI2HbTRoxuRPNK57Sd/286vBHmtiYClXUdXxSae4BlPJuwzr4gFoeR+codWNpEndNzp65Te8IMNyyiFve/kACsfiiNlteIve8KszdvlkYvBMnyYwF1s3cjHXIPLr9O21WQ7ndbjFpwUXcbPTUpnCbjllLM+6N+6yauszrvdezX1pLmeXHUbBQ6pY7N+byPXitadGGKKas//6qHSVNFHYKbnezXgSBkVlSSGfYOMjrDLMF0yLIb0UwKk+40DPRcuunFuv0BANIR7O7GznPHnV0dBeDKnEJSem5wT6MHZVzkdcb0aNO1xG8IAvO1LKTu9W5JhmC23nGnyZBAUZa/zVEPeb7SY6Jm1NumRWE7b0s5M4i2sP8dywsHSq7tgVhl0GSLh2wDSuL44wUJxYLLf1OH9DEcfLWeLLrUsfrGFr+FovvcdA8qyxz/b/v0Vd/xGd/z2DaT9qgr8ymo5g3arlDGAL+xLGSfzD+DHquM/VMf3u+GfAPgUgM+/0w7Evl3+rcPbJwBmJwfgpwe9tjfafwc+jgb9jymxuxFgPApgeQrW6DH28RHB1qNOMpyJSzgF+I9gqK+AbUIGLZZ3uw1t+31mcHfx9JETkp/b+b0uzB/p/TQsOvtk3eBRdpXLHu1Q2unS5Bo+vXZEDIN+O5L0rdbC2YEm0dm5QNKXDoieuo0ifOzJRIGPoGvfcXYMkwwiB3RucY+MdMdYOvuhreXJKcJyxljJlnvmL1o6fL6y1Pd0tiStBw2f+dw2Imf0M/2aWsQuL5sd+ZoaVtj5LtMW5icKqmz5wxkutAXTtObEdF2rYdqeUM8ypssSr5pE3pg1vZUbbj52EiMf3blsZ2BrRxYiN5d1v50fPw/gkwA+CceHAbwbjt8EwxdXx1dXwy+uwG/bQNgvDOYgoHo05FJGRx4sLQzZ/93+/mB1fMYNH3fHp93wL96ONWM3A7M3Pk8X4wFj68rmS49T7W1x5r5izJ5Bu73JLNE7HmC/6efVCrL4OdxjOHajPzslk0kXd4HZubXugof9m3e2I2eRYeZ1jpPfy8FlllSVeb0dYb+cQWM/yLpoK1/uif71lYvL6UO4NkHxdA2Om1b55l/56824tU/wG3bM8YB2eYbmCKXc/67ywDuMnT44tvsdT2Acg3BbmsTYyV99grvtCg/77T+eQqcHEVj2hDv+jnp8DsAPbs8/vp04X7J1QV/uhvdsDNfvrIZfEZgxS9gyAWTb6x+rhh/Y1vNpN3z/xrj/z5exwf8/KN3SXB79k9cAAAAASUVORK5CYII=)}.cp-wrapper{position:absolute;width:173px;height:211px;background:white;border:solid 1px #CCC;box-shadow:0 0 20px rgba(0,0,0,0.2);z-index:99999;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:none}.cp-wrapper.cp-visible{display:block}.cp-position-top .cp-wrapper{top:-154px}.cp-position-right .cp-wrapper{right:0}.cp-position-bottom .cp-wrapper{top:auto}.cp-position-left .cp-wrapper{left:0}.cp-with-opacity.cp-wrapper{width:194px}.cp-wrapper .cp-grid{position:absolute;top:1px;left:1px;width:150px;height:150px;background-position:-120px 0;cursor:crosshair}.cp-wrapper .cp-grid-inner{position:absolute;top:0;left:0;width:150px;height:150px}.cp-mode-saturation .cp-grid{background-position:-420px 0}.cp-mode-saturation .cp-grid-inner{background-position:-270px 0;background-image:inherit}.cp-mode-brightness .cp-grid{background-position:-570px 0}.cp-mode-brightness .cp-grid-inner{background-color:black}.cp-mode-wheel .cp-grid{background-position:-720px 0}.cp-slider,.cp-opacity-slider{position:absolute;top:1px;left:152px;width:20px;height:150px;background-color:white;background-position:0 0;cursor:row-resize}.cp-mode-saturation .cp-slider{background-position:-60px 0}.cp-mode-brightness .cp-slider{background-position:-20px 0}.cp-mode-wheel .cp-slider{background-position:-20px 0}.cp-opacity-slider{left:173px;background-position:-40px 0;display:none}.cp-with-opacity .cp-opacity-slider{display:block}.cp-grid .cp-picker{position:absolute;top:70px;left:70px;width:12px;height:12px;border:solid 1px black;border-radius:10px;margin-top:-6px;margin-left:-6px;background:none}.cp-grid .cp-picker>div{position:absolute;top:0;left:0;width:8px;height:8px;border-radius:8px;border:solid 2px white;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.cp-picker{position:absolute;top:0;left:0;width:18px;height:2px;background:white;border:solid 1px black;margin-top:-2px;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;z-index:2}.cp-tabs{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:absolute;bottom:0;color:#777;left:0;right:0;background:#eee}.cp-tabs>div{display:inline-block;padding:6px 0 4px;font-family:Helvetica, sans-serif;font-size:11px;border-left:1px solid #ddd;width:48px;border-right:0;text-align:center;cursor:pointer}.cp-tabs>div:first-child{border-left:0}.cp-tabs>div.active{background-color:#fff}.cp-tabs>div.cp-tab-transp{width:100%;border-top:1px solid #ddd}.cp-theme-default.cp-wrapper{width:auto;display:inline-block}.cp-theme-default .cp-input{height:20px;width:auto;display:inline-block;padding-left:26px}.cp-theme-default.cp-position-right .cp-input{padding-right:26px;padding-left:inherit}.input-group .cp-theme-bootstrap:not(:first-child) .cp-input{border-top-left-radius:0;border-bottom-left-radius:0}.g-colorpicker-preview-wrap{background-image:url(data:image/jpeg;base64,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)}html,body{height:100%}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;overflow:hidden}b,strong{font-weight:600}.bigger{font-size:1.2rem}.button-bar{text-align:right;float:right;z-index:0}.alert{font-size:1rem;padding:0.5rem 1.5rem;position:relative}@media only all and (max-width: 47.938em){.alert{padding:0.5rem 1rem}}.badge,.sidebar-open #admin-sidebar #admin-menu li .badges .updates,.gpm .gpm-testing{display:inline-block;font-size:0.8rem;font-weight:500;border-radius:10px;padding:0px 6px;min-width:20px;line-height:20px;text-align:center}.empty-state{margin:0 auto;text-align:center;padding-top:100px}.default-animation,.scheduler-content #cron-install{-webkit-transition:all 0.5s ease;-moz-transition:all 0.5s ease;transition:all 0.5s ease}.pulse{animation-name:pulse_animation;animation-duration:2000ms;transform-origin:70% 70%;animation-iteration-count:infinite;animation-timing-function:linear}@keyframes pulse_animation{0%{transform:scale(1)}30%{transform:scale(1)}40%{transform:scale(1.08)}50%{transform:scale(1)}60%{transform:scale(1)}70%{transform:scale(1.05)}80%{transform:scale(1)}100%{transform:scale(1)}}.default-border-radius{border-radius:4px}.default-glow-shadow{box-shadow:0 0 20px rgba(0,0,0,0.2)}.default-box-shadow{box-shadow:0 0 10px 2px rgba(0,0,0,0.03)}.login-box-shadow{box-shadow:0 10px 100px 0px rgba(0,0,0,0.4)}.padding-horiz{padding-left:7rem;padding-right:7rem}@media only all and (max-width: 59.938em){.padding-horiz{padding-left:4rem;padding-right:4rem}}@media only all and (max-width: 47.938em){.padding-horiz{padding-left:1rem;padding-right:1rem}}.padding-vert{padding-top:3rem;padding-bottom:3rem}@media only all and (max-width: 47.938em){.hidden-mobile{display:none !important}}body{font-weight:400}@media only all and (max-width: 47.938em){body{font-size:0.8rem}}h1,h2,h3,h4,h5,h6{font-weight:400;text-rendering:optimizeLegibility;letter-spacing:-0px}h1{font-size:3.1rem}@media only all and (max-width: 47.938em){h1{font-size:2.5rem;line-height:1.2;margin-bottom:2.5rem}}@media only all and (min-width: 48em) and (max-width: 59.938em){h2{font-size:2rem}}@media only all and (max-width: 47.938em){h2{font-size:1.9rem}}@media only all and (min-width: 48em) and (max-width: 59.938em){h3{font-size:1.6rem}}@media only all and (max-width: 47.938em){h3{font-size:1.5rem}}@media only all and (min-width: 48em) and (max-width: 59.938em){h4{font-size:1.25rem}}@media only all and (max-width: 47.938em){h4{font-size:1.15rem}}h1{letter-spacing:-3px}h2{letter-spacing:-2px}h3{letter-spacing:-1px}i.fa.fa-heart.pulse,i.fa.fa-heart-o.pulse{color:#920}pre{padding:2rem;border-radius:3px}pre code{background:inherit}.label{vertical-align:middle;border-radius:100%;height:1rem;min-width:1rem;line-height:1rem;display:inline-block;text-align:center;font-size:.6rem;margin-right:0.75rem}form .form-display-wrapper h1{padding:0 !important;font-size:1.7rem !important;border-bottom-width:2px !important}form .form-display-wrapper h2{padding:0 !important;font-size:1.5rem !important;border-bottom-width:1px !important}form .form-display-wrapper h3{padding:0 !important;font-size:1.3rem !important;border-bottom-width:1px !important}form .form-display-wrapper h4{padding:0 !important;font-size:1.1rem !important}form .form-display-wrapper blockquote{padding:15px}form .form-display-wrapper blockquote p{font-size:0.9rem;color:inherit}form .form-display-wrapper blockquote p:last-child{margin-bottom:0}form .form-display-wrapper code,form .form-display-wrapper pre{font-size:0.8rem}form .form-display-wrapper table{border-collapse:collapse;width:100%}form .form-display-wrapper table th{padding:10px !important}form .form-display-wrapper table th:last-child{border-right:none}form .form-display-wrapper table td{padding:10px !important}form .form-display-wrapper table td:last-child{border-right:none}form .form-display-wrapper table tr:last-child{border-bottom:none}@font-face{font-family:"rockettheme-apps";font-weight:normal;font-style:normal;src:url("../fonts/rockettheme-apps/rockettheme-apps.eot");src:url("../fonts/rockettheme-apps/rockettheme-apps.eot?#iefix") format("embedded-opentype"),url("../fonts/rockettheme-apps/rockettheme-apps.woff") format("woff"),url("../fonts/rockettheme-apps/rockettheme-apps.ttf") format("truetype"),url("../fonts/rockettheme-apps/rockettheme-apps.svg#rockettheme-apps") format("svg")}i.fa-grav,i.fa-grav-spaceman,i.fa-grav-text,i.fa-grav-full,i.fa-grav-logo,i.fa-grav-symbol,i.fa-grav-logo-both,i.fa-grav-both,i.fa-gantry,i.fa-gantry-logo,i.fa-gantry-symbol,i.fa-gantry-logo-both,i.fa-gantry-both{font-family:'rockettheme-apps';speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-grav-logo:before,.fa-grav-text:before{content:"\61"}.fa-grav-symbol:before,.fa-grav:before,.fa-grav-spaceman:before{content:"\62"}.fa-grav-logo-both:before,.fa-grav-both:before,.fa-grav-full:before{content:"\66"}.fa-gantry-logo:before{content:"\64"}.fa-gantry:before,.fa-gantry-symbol:before{content:"\63"}.fa-gantry-logo-both:before,.fa-gantry-both:before{content:"\65"}form h1,form h3,form .form-spacer h3{padding:0 1.5rem 0.5rem;margin:0 0 1rem;font-size:1.5rem;text-align:left;letter-spacing:-1px}form h3{padding-left:0}form p{padding:0 1.5rem}form pre{padding:1.5rem 3rem}form .nowrap{white-space:nowrap}form .form-description p{padding:0}@media only all and (max-width: 47.938em){form .button{padding:0.25rem 0.5rem}}form .form-field{margin-bottom:1rem;padding-left:1.5rem}@media only all and (max-width: 47.938em){form .form-field{padding-left:1rem}}form .form-field .file-thumbnail-wrapper{display:inline-block;position:relative}form .form-field .file-thumbnail{max-height:150px;vertical-align:top;display:inline-block;margin-bottom:10px}form .form-field .file-thumbnail-remove{position:absolute;text-align:center;display:block;top:3px;right:3px;width:25px;height:25px;border-radius:100%}form .form-field .file-thumbnail-remove .fa{font-size:20px;line-height:25px;vertical-align:top}form .form-data{padding-right:1.5rem}@media only all and (max-width: 47.938em){form .form-data{padding-right:1rem}}form .form-input-addon-wrapper{display:flex}form .form-input-addon-wrapper .form-input-addon{display:flex;padding:0 5px;justify-content:center;align-items:center}form .form-input-addon-wrapper .form-input-addon.copy-to-clipboard{cursor:pointer}form .form-input-addon-wrapper input{height:35px;display:table-cell}form .form-input-addon-wrapper .form-input-prepend{border-radius:4px 0 0 4px;border-right-width:0 !important}form .form-input-addon-wrapper .form-input-append{border-radius:0 4px 4px 0;border-left-width:0 !important}form .form-input-addon-wrapper input:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}form .form-input-addon-wrapper input:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}form .overlay{padding-top:1rem;padding-bottom:1px}form .overlay.bottom{margin:2rem auto -2rem auto}form .required{display:inline-block;font-family:helvetica, arial;vertical-align:middle;line-height:0;font-size:30px;margin-left:0px;margin-bottom:-5px}form label{padding:5px 0;font-weight:400;margin:0}form label p{margin:0}form label [data-hint]{display:inline}form label.inline{display:inline;vertical-align:middle;margin-right:15px}form label.toggleable{display:inline}form .form-sublabel p{opacity:0.8;font-size:0.9rem;margin:10px 20px 0 0;padding:0}form input,form select,form textarea,form button,form .selectize-input{font-size:.9rem;line-height:1.5;border-radius:4px;-webkit-font-smoothing:antialiased}form .form-input-file{position:relative;min-height:70px;border-radius:4px}form .form-input-file ul{margin:1rem 0;text-align:left;font-size:1rem;border-radius:4px}form .form-input-file p{display:block;height:100%;text-align:center;margin:0;padding:0.8rem 1rem 0;font-size:1.2rem}form .form-input-file input{display:none}form .selectize-dropdown{z-index:100000}form .grid.vertical{-webkit-flex-flow:column;-moz-flex-flow:column;flex-flow:column}form .compact .form-field{margin-bottom:0.2rem}form .compact .form-list-wrapper li{display:flex;flex-wrap:wrap;justify-content:flex-start}form .compact .form-list-wrapper li .form-field{width:50%}@media only all and (max-width: 47.938em){form .compact .form-list-wrapper li .form-field{width:100%}}form .form-select-wrapper,form .selectize-control.single .selectize-input{position:relative}form .form-select-wrapper:after,form .selectize-control.single .selectize-input:after{margin-top:0;border:0;position:absolute;content:'\f078';font-family:'FontAwesome';right:12px;top:50%;line-height:0;pointer-events:none}form .selectize-input{display:block !important;box-shadow:none;padding:5px 30px 5px 10px;margin:0}form .selectize-input>input{font-size:.9rem;line-height:1.5}form .selectize-control.single.plugin-remove_button .selectize-input .item,form .selectize-control.single.plugin-remove_button .selectize-input .remove-single{cursor:pointer;margin:0 3px 3px 0;padding:2px 6px;display:inline-block;border-radius:2px 0 0 2px !important}form .selectize-control.single.plugin-remove_button .selectize-input .remove-single{border-radius:0 2px 2px 0 !important;margin-left:-2px}form .selectize-control.single.plugin-remove_button .selectize-input,form .selectize-control.multi .selectize-input{padding:.375rem .375rem}form .selectize-control.single.plugin-remove_button .selectize-input.has-items,form .selectize-control.multi .selectize-input.has-items{padding-top:6px;padding-bottom:4px}form .selectize-control.single.plugin-remove_button .selectize-input .item,form .selectize-control.single.plugin-remove_button .selectize-input .remove-single,form .selectize-control.multi .selectize-input .item,form .selectize-control.multi .selectize-input .remove-single{border-radius:2px;line-height:1.5}form .selectize-control.single.plugin-remove_button .selectize-input .item>img,form .selectize-control.multi .selectize-input .item>img{max-height:20px !important;max-width:inherit !important}form .selectize-control.single .selectize-input:after{right:27px}form .selectize-control.single .selectize-input.dropdown-active:after{content:'\f077'}form .x-small{max-width:5rem !important}form .x-small.form-input-addon-wrapper{max-width:inherit !important}form .x-small.form-input-addon-wrapper input{max-width:5rem !important}form .small{max-width:10rem !important}form .small.form-input-addon-wrapper{max-width:inherit !important}form .small.form-input-addon-wrapper input{max-width:10rem !important}form .medium:not(.switch-toggle),form .medium.form-input-addon-wrapper input{max-width:20rem}form .medium:not(.switch-toggle) textarea,form .medium.form-input-addon-wrapper input textarea{height:7rem}form .medium:not(.switch-toggle)[data-grav-array-type="container"],form .medium.form-input-addon-wrapper input[data-grav-array-type="container"]{max-width:100% !important}form .medium:not(.switch-toggle)[data-grav-array-type="container"] input[type=text],form .medium.form-input-addon-wrapper input[data-grav-array-type="container"] input[type=text]{width:calc(70% - 110px)}form .large,form .large.form-input-addon-wrapper input{max-width:30rem !important}form .large textarea,form .large.form-input-addon-wrapper input textarea{height:10rem}form .large[data-grav-array-type="container"],form .large.form-input-addon-wrapper input[data-grav-array-type="container"]{max-width:100% !important}form .large[data-grav-array-type="container"] input[type=text],form .large.form-input-addon-wrapper input[data-grav-array-type="container"] input[type=text]{width:calc(100% - 110px)}form select{width:100%;-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:5px 30px 5px 10px;cursor:pointer;margin:0}form input[type=text],form input[type=password],form input[type=email],form input[type=date],form input[type=tel],form input[type=time],form input[type=week],form input[type=month],form input[type=number],form input[type=color],form input[type=url]{width:100%}form textarea{width:100%}form .form-frontmatter-wrapper{border-radius:4px}form .switch-toggle label{cursor:pointer}form .switch-toggle a,form .switch-toggle label{outline:none !important}form .dynfields input[type=text],form [data-grav-field="array"] input[type=text],form [data-grav-field="multilevel"] input[type=text]{width:40%;float:left;margin:0 5px 5px 0}form .dynfields .form-row,form [data-grav-field="array"] .form-row,form [data-grav-field="multilevel"] .form-row{display:inline-block;width:100%;margin-right:0;max-width:100% !important}form .dynfields .form-row span,form [data-grav-field="array"] .form-row span,form [data-grav-field="multilevel"] .form-row span{padding:0.5rem;display:inline-block;line-height:1.5;cursor:pointer}form .dynfields .form-row span[data-grav-array-action="sort"],form [data-grav-field="array"] .form-row span[data-grav-array-action="sort"],form [data-grav-field="multilevel"] .form-row span[data-grav-array-action="sort"]{float:left;cursor:move}form .dynfields .form-row.array-field-value_only,form [data-grav-field="array"] .form-row.array-field-value_only,form [data-grav-field="multilevel"] .form-row.array-field-value_only{width:100%}form [data-grav-field="multilevel"] input:disabled{background-color:lightgray}form [data-grav-array-type="container"].one-child [data-grav-array-action="sort"]{display:none}form .button-bar{margin-top:1rem;padding:1.2rem 3rem;width:100%;border-bottom-left-radius:5px;border-bottom-right-radius:5px}form .checkboxes{display:inline-block;padding:5px 0}form .checkboxes label{display:inline;cursor:pointer;position:relative;padding:0 0 0 2rem;margin-right:15px}form .checkboxes label:before{content:"";display:inline-block;width:1.5rem;height:1.5rem;top:50%;left:0;margin-top:-0.75rem;margin-right:10px;position:absolute;border-radius:4px}form .checkboxes input[type=checkbox]{display:none}form .checkboxes input[type=checkbox]:checked+label:before{content:"\f00c";font-family:"FontAwesome";font-size:1.2rem;line-height:1;text-align:center}form .checkboxes.toggleable label{margin-right:0}.form-display-wrapper p{padding-left:0;padding-right:0}.form-display-wrapper p:first-child{margin-top:0}div.frontmatter{margin-bottom:3rem}textarea.frontmatter{height:130px !important}textarea.frontmatter+.CodeMirror{height:130px}.form-order-wrapper .notice{padding:5px}.form-order-wrapper ul.orderable{list-style:none;margin:0;padding:0}.form-order-wrapper ul.orderable li{padding:0.2rem 1rem;border-radius:4px;margin:3px 0;position:relative}.form-order-wrapper ul.orderable li.drag-handle{cursor:move}.form-order-wrapper ul.orderable li.drag-handle::after{content:'\f0c9';font-family:FontAwesome;position:absolute;right:10px}.form-order-wrapper ul.orderable li i{font-size:0.8rem}.form-list-wrapper ul[data-collection-holder]{list-style:none;margin:0;padding:0}.form-list-wrapper ul[data-collection-holder]>li{padding:1rem;border-radius:4px;margin:3px 0;position:relative}.form-list-wrapper ul[data-collection-holder]>li .item-actions{position:absolute;right:10px;top:4px}.form-list-wrapper ul[data-collection-holder]>li .item-actions .fa{cursor:pointer}.form-list-wrapper ul[data-collection-holder]>li.collection-collapsed{height:70px;overflow:hidden;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}@media only all and (max-width: 47.938em){.form-list-wrapper ul[data-collection-holder]>li.collection-collapsed{height:120px}}.form-list-wrapper ul[data-collection-holder][data-collection-nosort]>li{cursor:default}.form-list-wrapper .collection-actions{text-align:right}.form-list-wrapper .collection-sort{position:absolute;top:0;left:0;width:32px;bottom:0;cursor:move}@media only all and (max-width: 47.938em){.form-list-wrapper .collection-sort{width:20px}.form-list-wrapper .collection-sort .fa{font-size:0.8rem}}.form-list-wrapper .collection-sort .fa{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%)}.form-list-wrapper [data-collection-nosort] .collection-sort{display:none}.form-label.block{position:relative}.form-label.block:hover{z-index:2}.form-label.block label{z-index:2}#admin-main .admin-block h2{font-size:1.25rem;margin:0 0 .5rem;letter-spacing:normal}.form-fieldset{margin:1rem 1.5rem}.form-fieldset--label label{display:table;font-size:1.25rem;padding:.5rem 1rem;width:100%}.form-fieldset--label h2{margin:0 !important}.form-fieldset--label .actions{font-size:initial;display:table-cell;text-align:right;vertical-align:middle}.form-fieldset--label+.form-data{margin-top:1rem;padding:0}.form-fieldset--cursor{cursor:pointer}.form-fieldset--info{font-size:small}.form-fieldset>input:checked ~ .form-data,.form-fieldset--collapsible .open,.form-fieldset input:checked ~ .form-label .form-fieldset--collapsible .close{display:block}.form-fieldset>.form-data,.form-fieldset--collapsible .close,.form-fieldset input:checked ~ .form-label .form-fieldset--collapsible .open{display:none}.filepicker-field-image{width:30px;vertical-align:middle;margin-right:5px}.filepicker-field-name{vertical-align:middle}.permissions-container .permission-value{width:50%;float:left;margin:0 5px 5px 0;height:39px}.permissions-container .switch-toggle{line-height:37px;margin:0 5px 5px 0}[data-grav-field="cron"]{display:flex;align-items:center}.cron-install{margin:1rem;padding:0;border-radius:4px}form .cron-install pre{padding:1rem;margin:0 1.5rem;line-height:1}.cron-install .setup-status{font-weight:bold}.cron-status,.backups-history{margin-bottom:4rem}.cron-status .cron-at code,.backups-history .cron-at code{font-size:120%;padding:2px 10px;border-radius:2px}.cron-status .badge,.cron-status .sidebar-open #admin-sidebar #admin-menu li .badges .updates,.sidebar-open #admin-sidebar #admin-menu li .badges .cron-status .updates,.cron-status .gpm .gpm-testing,.gpm .cron-status .gpm-testing,.backups-history .badge,.backups-history .sidebar-open #admin-sidebar #admin-menu li .badges .updates,.sidebar-open #admin-sidebar #admin-menu li .badges .backups-history .updates,.backups-history .gpm .gpm-testing,.gpm .backups-history .gpm-testing{white-space:nowrap}.cron-status tr.deleting,.backups-history tr.deleting{background:#efefef;color:#bbb;text-shadow:0 1px white}.cron-status tr.deleting button,.cron-status tr.deleting .button,.backups-history tr.deleting button,.backups-history tr.deleting .button{background:#777;pointer-events:none}form .cron-job-list li,form .backups-list li{display:flex;flex-direction:row;flex-wrap:wrap}form .cron-job-list .form-field.grid,form .backups-list .form-field.grid{width:calc(50% - 5px)}@media only all and (max-width: 47.938em){form .cron-job-list .form-field.grid,form .backups-list .form-field.grid{width:100%}}table,tbody,thead{display:inline-block;width:100%}.gpm-details{width:100%;-webkit-box-flex:auto;-moz-box-flex:auto;box-flex:auto;-webkit-flex:auto;-moz-flex:auto;-ms-flex:auto;flex:auto}td{border:0}tr{-webkit-transition:all 0.2s ease;-moz-transition:all 0.2s ease;transition:all 0.2s ease;display:-webkit-box;display:-moz-box;display:box;display:-webkit-flex;display:-moz-flex;display:-ms-flexbox;display:flex;-webkit-box-lines:multiple;-moz-box-lines:multiple;box-lines:multiple;-webkit-flex-wrap:wrap;-moz-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-moz-box-align:center;box-align:center;-webkit-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;-ms-flex-align:center}tr th,tr td{display:block;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;-webkit-flex:1;-moz-flex:1;-ms-flex:1;flex:1}tr th.shrink,tr td.shrink{-webkit-box-flex:0;-moz-box-flex:0;box-flex:0;-webkit-flex:0;-moz-flex:0;-ms-flex:0;flex:0}tr th.double,tr td.double{-webkit-box-flex:2;-moz-box-flex:2;box-flex:2;-webkit-flex:2;-moz-flex:2;-ms-flex:2;flex:2}tr th.triple,tr td.triple{-webkit-box-flex:3;-moz-box-flex:3;box-flex:3;-webkit-flex:3;-moz-flex:3;-ms-flex:3;flex:3}tr th.quadruple,tr td.quadruple{-webkit-box-flex:4;-moz-box-flex:4;box-flex:4;-webkit-flex:4;-moz-flex:4;-ms-flex:4;flex:4}tr th.quintuple,tr td.quintuple{-webkit-box-flex:5;-moz-box-flex:5;box-flex:5;-webkit-flex:5;-moz-flex:5;-ms-flex:5;flex:5}tr th.sextuple,tr td.sextuple{-webkit-box-flex:6;-moz-box-flex:6;box-flex:6;-webkit-flex:6;-moz-flex:6;-ms-flex:6;flex:6}tr th.septuple,tr td.septuple{-webkit-box-flex:7;-moz-box-flex:7;box-flex:7;-webkit-flex:7;-moz-flex:7;-ms-flex:7;flex:7}tr th.octuple,tr td.octuple{-webkit-box-flex:8;-moz-box-flex:8;box-flex:8;-webkit-flex:8;-moz-flex:8;-ms-flex:8;flex:8}tr th.nontuple,tr td.nontuple{-webkit-box-flex:9;-moz-box-flex:9;box-flex:9;-webkit-flex:9;-moz-flex:9;-ms-flex:9;flex:9}tr th.dectuple,tr td.dectuple{-webkit-box-flex:10;-moz-box-flex:10;box-flex:10;-webkit-flex:10;-moz-flex:10;-ms-flex:10;flex:10}tr th:first-child,tr td:first-child{padding-left:1.5rem}@media only all and (max-width: 47.938em){tr th:first-child,tr td:first-child{padding-left:1rem}}tr th:last-child,tr td:last-child{padding-right:1.5rem}@media only all and (max-width: 47.938em){tr th:last-child,tr td:last-child{padding-right:1rem}}tr th.left,tr td.left{text-align:left}tr th.center,tr td.center{text-align:center}tr th.right,tr td.right{text-align:right}tr th.right.pad,tr td.right.pad{padding-right:1.5rem}tr th.right.pad .form-data,tr td.right.pad .form-data{padding-right:0}tr th.left.pad,tr td.left.pad{padding-left:1.5rem}tr th.ellipsis,tr td.ellipsis{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}tr th.ellipsis.rtl,tr td.ellipsis.rtl{text-align:left}tr th.rtl,tr td.rtl{direction:rtl}tr th{font-weight:bold}tr td.gpm-actions{padding-right:2.5rem}tr td.gpm-actions{line-height:1;text-align:right;position:relative}tr td.gpm-actions .gpm-details-expand{position:absolute;top:12px;right:12px}tr td.gpm-details{margin:0;padding:0}@media only all and (max-width: 47.938em){tr td.gpm-details{word-wrap:break-word}}tr td.gpm-details>.table-wrapper{display:none}tr td.gpm-details>.table-wrapper td{border-bottom:0}tr td.gpm-details tbody{width:100%}tr:last-child td{border-bottom:0}table.noflex{display:table;table-layout:auto}table.noflex thead{display:table-header-group}table.noflex tbody{display:table-row-group}table.noflex tr{display:table-row}table.noflex tr td,table.noflex tr th{flex:none;display:table-cell}.button-group{position:relative;display:inline-block;vertical-align:top}.button-group>.button:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.button-group>.button:first-child{margin-left:0 !important}.button-group>.button+.dropdown-toggle{text-align:center;padding-right:8px;padding-left:8px}.button-group>.button+.dropdown-toggle i{margin:0}.button-group>.button:last-child:not(:first-child),.button-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0 !important;border-bottom-left-radius:0 !important}.button-group .button+.button,.button-group .button+.button-group,.button-group .button-group+.button,.button-group .button-group+.button-group{margin-left:-3px}.button-group:last-child .dropdown-menu{left:inherit;right:0}.button-group .dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:150px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;-webkit-background-clip:padding-box;background-clip:padding-box;border-radius:4px}.button-group .dropdown-menu .button{margin-right:0 !important}.button-group .dropdown-menu.language-switcher{min-width:50px}.button-group .dropdown-menu.lang-switcher{min-width:150px;left:inherit;right:0}.button-group .dropdown-menu.lang-switcher button{width:100%}.button-group .dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden}.button-group .dropdown-menu li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143}.button-group .dropdown-menu li>a:focus,.button-group .dropdown-menu li>a:hover{text-decoration:none}.open>.dropdown-menu{display:block}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}#admin-main .admin-block a.gumroad-button{padding:0.5rem 1rem !important;font-size:inherit !important;font-weight:400 !important;line-height:inherit !important;font-family:inherit !important;border-radius:4px !important;box-shadow:inherit !important}#admin-main .admin-block a.gumroad-button .gumroad-button-logo{display:none !important;background-image:none !important}#error{text-align:center;display:flex;align-items:center;justify-content:center;height:100%;padding-bottom:6rem}#error h1{font-size:4.9rem}#error p{margin:1rem 0}#admin-login{max-width:24rem;margin:0 auto}#admin-login.wide{max-width:50rem}#admin-login.wide h1{height:100px}#admin-login.wide h1.custom-logo img{height:80px}#admin-login.wide form>.padding{padding:3rem 2rem 8rem 2rem}#admin-login.wide form>.padding>div{width:49%;display:inline-block}@media only all and (max-width: 47.938em){#admin-login.wide form>.padding>div{width:100%;margin-right:0}}#admin-login.wide form>.padding .form-field{padding:0 1rem}#admin-login.wide form label{padding:0}#admin-login.wide form input{margin-bottom:1rem;text-align:left}#admin-login.wide form input::-webkit-input-placeholder{font-size:1rem;line-height:2rem}#admin-login.wide form input::-moz-placeholder{font-size:1rem;line-height:2rem}#admin-login.wide form input:-moz-placeholder{font-size:1rem;line-height:2rem}#admin-login.wide form input:-ms-input-placeholder{font-size:1rem;line-height:2rem}#admin-login.wide .grid{display:block}#admin-login.wide .form-label,#admin-login.wide .form-data{display:block;width:100%;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;-webkit-flex:1;-moz-flex:1;-ms-flex:1;flex:1}#admin-login .form-field{padding-left:0;margin-bottom:0}#admin-login .form-label{display:none}#admin-login .form-data{padding-right:0}#admin-login .wrapper-spacer{width:100% !important;display:block !important;padding:0 1rem}#admin-login .wrapper-spacer h3{padding-left:1rem}#admin-login .instructions{display:block;padding:2rem 3rem 0;margin:0;font-size:1.3rem}#admin-login .instructions p{margin:0}#admin-login h1{font-size:0;color:transparent;height:216px;margin:0;overflow:hidden;display:flex;align-items:center;justify-content:center}#admin-login h1.text-logo{text-align:center;font-size:4rem}#admin-login h1.custom-logo img{max-height:160px}#admin-login h1.default-logo svg{height:320px}#admin-login h5{margin-top:0;text-align:center}#admin-login form{position:relative}#admin-login form .padding{padding:3rem 3rem 6rem 3rem}#admin-login form input{margin-bottom:2rem;font-size:1.4rem;line-height:1.5;text-align:center;font-weight:300;-webkit-font-smoothing:auto}#admin-login form .form-actions{display:block !important;width:100% !important;text-align:center;position:absolute;bottom:0;left:0;right:0;padding:1.5rem 0}#admin-login form .form-actions .button{margin-bottom:2px}#admin-login form .form-actions button:first-child{margin-right:1rem}#admin-login .alert{text-align:center;padding:1rem 3rem}.twofa-secret-code{font-weight:bold}.twofa-wrapper{margin-top:5px}.twofa-secret{position:absolute;opacity:0;visibility:hidden;transition:opacity 600ms, visibility 600ms}.twofa-secret.show{position:static;visibility:visible;opacity:1}.sidebar-closed:not(.sidebar-quickopen) #admin-sidebar{float:left;visibility:visible;opacity:1;width:4.2rem}.sidebar-closed:not(.sidebar-quickopen) #admin-sidebar #admin-logo h3{font-size:0}.sidebar-closed:not(.sidebar-quickopen) #admin-sidebar #admin-logo h3:first-letter{font-size:2.5rem}.sidebar-closed:not(.sidebar-quickopen) #admin-sidebar .custom-logo{margin-right:0;padding-right:15px}.sidebar-closed:not(.sidebar-quickopen) #admin-sidebar .custom-logo img{padding:0 5px}.sidebar-closed:not(.sidebar-quickopen) #admin-sidebar #grav-logo-small{display:inline-block}.sidebar-closed:not(.sidebar-quickopen) #admin-sidebar #grav-logo-large{display:none}.sidebar-closed:not(.sidebar-quickopen) #admin-sidebar .front-end{display:none}.sidebar-closed:not(.sidebar-quickopen) #admin-sidebar #admin-user-details{padding:15px 0 15px 10px;text-align:left}.sidebar-closed:not(.sidebar-quickopen) #admin-sidebar .admin-user-names{display:none}.sidebar-closed:not(.sidebar-quickopen) #admin-sidebar #admin-menu em{display:none}.sidebar-closed:not(.sidebar-quickopen) #admin-sidebar #admin-menu li .badges .updates{border-radius:100%;position:absolute;top:4px;right:10px;font-size:9px;line-height:12px;padding:1px 3px;min-width:15px}.sidebar-closed:not(.sidebar-quickopen) #admin-sidebar #admin-menu li .badges .count{display:none}@media only all and (min-width: 74.938em){.sidebar-closed:not(.sidebar-quickopen) #admin-sidebar #open-handle .fa-angle-right:before{content:"\f105"}}.sidebar-closed:not(.sidebar-quickopen) #admin-main{margin-left:4.2rem}.sidebar-open #admin-sidebar{z-index:10001;float:left;visibility:visible;opacity:1;width:240px}.sidebar-open #admin-sidebar h3{font-size:1.2rem}.sidebar-open #admin-sidebar h3:first-letter{font-size:1.2rem}.sidebar-open #admin-sidebar #grav-logo-small{display:none}.sidebar-open #admin-sidebar #grav-logo-large{display:inline-block}.sidebar-open #admin-sidebar #admin-user-details{padding:15px 10px;text-align:left}.sidebar-open #admin-sidebar .admin-user-names{display:block}.sidebar-open #admin-sidebar .front-end{display:inline-block}.sidebar-open #admin-sidebar #admin-menu em{display:inline}.sidebar-open #admin-sidebar #admin-menu li .badges .updates{border-bottom-right-radius:0;border-top-right-radius:0;position:relative;top:auto;right:auto}.sidebar-open #admin-sidebar #admin-menu li .badges .count{display:inline-block}@media only all and (min-width: 47.938em) and (max-width: 74.938em){.sidebar-open #admin-sidebar #open-handle .fa-angle-right:before{content:"\f104"}}#admin-sidebar{position:absolute;left:0;top:0;bottom:0;width:100%;max-height:100vh;opacity:0;z-index:3}#admin-sidebar #open-handle{display:block;position:absolute;cursor:pointer;right:0;top:0;width:15px;height:4.2rem;text-align:center;line-height:4.2rem;font-size:12px;border-top-right-radius:3px;border-bottom-right-radius:3px}@media only all and (min-width: 74.938em){#admin-sidebar #open-handle .fa-angle-right:before{content:"\f104"}}#admin-sidebar.sidebar-is-visible{opacity:1;visibility:visible}@media only all and (max-width: 47.938em){#admin-sidebar{z-index:5}}@media only all and (min-width: 47.938em){#admin-sidebar{float:left;visibility:visible;opacity:1;width:4.2rem}}@media only all and (min-width: 74.938em){#admin-sidebar{float:left;visibility:visible;opacity:1;width:240px}}#admin-logo{height:4.2rem;display:flex;align-items:center;justify-content:center}#admin-logo.nav-hover .admin-logo{margin-right:0}#admin-logo .admin-logo{margin-right:-10px;display:flex;align-items:center}@media only all and (min-width: 47.938em){#admin-logo .admin-logo{margin-right:0}#admin-logo .admin-logo.custom-logo{padding-right:15px}#admin-logo .admin-logo.custom-logo img{padding:0 5px}}#admin-logo .admin-logo a:not(.front-end){display:inherit}#admin-logo .admin-logo.custom-logo img{max-height:30px}#admin-logo .front-end{margin-left:15px;opacity:0.6}@media only all and (max-width: 47.938em){#admin-logo .front-end{display:none}}@media only all and (min-width: 47.938em){#admin-logo .front-end{display:none}}@media only all and (min-width: 74.938em){#admin-logo .front-end{display:inline-block}}#admin-logo #grav-logo-small{height:30px;display:none}@media only all and (min-width: 47.938em){#admin-logo #grav-logo-small{display:inline-block}}@media only all and (min-width: 74.938em){#admin-logo #grav-logo-small{display:none}}#admin-logo #grav-logo-large{height:30px}@media only all and (min-width: 47.938em){#admin-logo #grav-logo-large{display:none}}@media only all and (min-width: 74.938em){#admin-logo #grav-logo-large{display:inline-block}}#admin-logo h3{font-size:1.5rem;margin:0}@media only all and (min-width: 47.938em){#admin-logo h3{font-size:0}#admin-logo h3:first-letter{font-size:2.5rem}}@media only all and (min-width: 74.938em){#admin-logo h3{font-size:1.5rem}#admin-logo h3:first-letter{font-size:1.5rem}}#admin-logo h3 i{font-size:1rem;vertical-align:middle;margin-top:-1px}.block-userinfo img{width:200px;height:200px;object-fit:cover}.block-userinfo ~ .block-file{margin-top:-5rem !important}#offline-status{padding:.2rem 1rem;text-align:center;display:none}#offline-status .fa-plane{-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}#admin-user-details img{width:47px;height:47px;object-fit:cover}#admin-user-details,.admin-user-details{padding:15px 10px;min-height:80px;overflow:hidden;text-align:left}@media only all and (min-width: 47.938em) and (max-width: 74.938em){#admin-user-details,.admin-user-details{padding:1rem 10px;text-align:center}}#admin-user-details img,.admin-user-details img{-webkit-transition:all 0.5s ease;-moz-transition:all 0.5s ease;transition:all 0.5s ease;border-radius:100%;float:left}#admin-user-details .admin-user-names,.admin-user-details .admin-user-names{margin-left:60px;margin-top:5px;display:block}@media only all and (min-width: 47.938em) and (max-width: 74.938em){#admin-user-details .admin-user-names,.admin-user-details .admin-user-names{display:none}}#admin-user-details .admin-user-names h4,#admin-user-details .admin-user-names h5,.admin-user-details .admin-user-names h4,.admin-user-details .admin-user-names h5{margin:0;font-size:1rem;line-height:1.3}#admin-user-details .admin-user-names .badge,#admin-user-details .admin-user-names .sidebar-open #admin-sidebar #admin-menu li .badges .updates,.sidebar-open #admin-sidebar #admin-menu li .badges #admin-user-details .admin-user-names .updates,#admin-user-details .admin-user-names .gpm .gpm-testing,.gpm #admin-user-details .admin-user-names .gpm-testing,.admin-user-details .admin-user-names .badge,.admin-user-details .admin-user-names .sidebar-open #admin-sidebar #admin-menu li .badges .updates,.sidebar-open #admin-sidebar #admin-menu li .badges .admin-user-details .admin-user-names .updates,.admin-user-details .admin-user-names .gpm .gpm-testing,.gpm .admin-user-details .admin-user-names .gpm-testing{font-size:0.8rem}#admin-user-details .admin-user-names h5,.admin-user-details .admin-user-names h5{opacity:0.7;font-size:0.9rem}#admin-nav-quick-tray{margin:0;list-style:none;padding-left:26px;padding-right:10px}#admin-nav-quick-tray li{cursor:pointer;width:24px;margin:3px 0 5px}#admin-nav-quick-tray i{transition:all .2s ease-in-out}#admin-nav-quick-tray i:hover{transform:scale(1.2)}.content-padding #messages.default-box-shadow{margin-bottom:1.5rem}@media only all and (max-width: 47.938em){.content-padding #messages.default-box-shadow{margin-bottom:0.5rem}}.admin-menu-wrapper{height:calc(100vh - 180px);overflow:hidden}@media only all and (min-width: 47.938em) and (max-width: 74.938em){.admin-menu-wrapper{height:calc(100vh - 136px)}}#admin-menu{display:block;margin:0;padding:0;list-style:none}#admin-menu em{font-style:normal}@media only all and (min-width: 47.938em) and (max-width: 74.938em){#admin-menu em{display:none}}#admin-menu li .badges{float:right;margin-right:1rem}#admin-menu li .badges .badge,.sidebar-open #admin-sidebar #admin-menu li .badges .updates,#admin-menu li .badges .gpm .gpm-testing,.gpm #admin-menu li .badges .gpm-testing{display:inline-block;margin-right:-5px}#admin-menu li .badges .updates{display:none}@media only all and (min-width: 47.938em) and (max-width: 74.938em){#admin-menu li .badges .count{display:none}}#admin-menu li .badges.with-updates .count{border-bottom-left-radius:0;border-top-left-radius:0}#admin-menu li .badges.with-updates .updates{border-bottom-right-radius:0;border-top-right-radius:0;display:inline-block}@media only all and (min-width: 47.938em) and (max-width: 74.938em){#admin-menu li .badges.with-updates .updates{border-radius:100%;position:absolute;top:4px;right:10px;font-size:9px;line-height:12px;padding:1px 3px;min-width:15px}}#admin-menu li a{-webkit-transition:all 0.2s ease;-moz-transition:all 0.2s ease;transition:all 0.2s ease;display:block;padding-left:25px;padding-top:0.7rem;padding-bottom:0.7rem;position:relative}#admin-menu li a .fa{-webkit-transition:all 0.2s ease;-moz-transition:all 0.2s ease;transition:all 0.2s ease;margin-right:8px}#admin-menu li.selected a{padding-left:16px}@media only all and (min-width: 74.938em){body.sidebar-quickopen #admin-main{margin-left:4.2rem}}@media only all and (min-width: 47.938em){#admin-main{margin-left:4.2rem}}@media only all and (min-width: 74.938em){#admin-main{margin-left:240px}}#admin-main .form-label label .hint:after,#admin-main .form-label label [data-hint]:after{max-width:300px;min-width:150px;display:inline-table;white-space:initial;line-height:1.2}#admin-main h1{margin:0;font-size:1.3rem;text-align:left;letter-spacing:-1px;line-height:1.5}#admin-main .padding{padding:1.5rem}#admin-main .lines-button{display:none;margin-left:.375rem}#admin-main .lines-button:focus{outline:none}@media only all and (max-width: 47.938em){#admin-main .lines-button{display:inline-block}}#admin-main .button{white-space:nowrap}#admin-main .titlebar{position:relative;height:4.2rem;padding:0 1.5rem;z-index:4;box-shadow:0px 3px 15px 0px rgba(0,0,0,0.1)}@media only all and (max-width: 47.938em){#admin-main .titlebar{padding:0 .375rem 0 4rem}}#admin-main .titlebar h1{display:block;white-space:nowrap;overflow:hidden;-ms-text-overflow:ellipsis;text-overflow:ellipsis}@media only all and (max-width: 47.938em){#admin-main .titlebar h1{font-size:18px}#admin-main .titlebar h1 i{display:none}}#admin-main .titlebar .button-bar{padding:0;white-space:nowrap;z-index:4}#admin-main .titlebar .preview{font-size:90%}#admin-main .titlebar .button{padding:0.5rem 0.4rem;vertical-align:top;margin-right:-2px}@media only all and (max-width: 47.938em){#admin-main .titlebar .button{font-size:0;padding:0.5rem 0.5rem;min-height:36px}#admin-main .titlebar .button i{font-size:20px;margin-right:0}}@media only all and (max-width: 20.875em){#admin-main .titlebar .button{padding:0.5rem 0.2rem}}@media only all and (max-width: 47.938em){#admin-main .titlebar .dropdown-menu .button{font-size:0.9rem}}#admin-main .admin-block .alert{margin-top:-2rem;margin-bottom:2rem}#admin-main .alert:before{font-family:FontAwesome;margin-right:5px}#admin-main .alert.info:before{content:"\f0a1"}#admin-main .alert.warning:before{content:"\f071"}#admin-main .alert.note:before{content:"\f24a"}#admin-main .alert.grav:before{font-family:rockettheme-apps;content:"\62";vertical-align:bottom}#admin-main .alert.grav #grav-update-button{margin-top:-3px;float:right}#admin-main .grav-update{padding:0 1.5rem;margin-top:-2rem;margin-bottom:1rem;display:none}#admin-main .grav-update:after{content:"";display:table;clear:both}@media only all and (max-width: 47.938em){#admin-main .grav-update{padding:0 1rem}}#admin-main .grav-update .button{float:right;margin-top:0.6rem;margin-left:1rem;line-height:1.5}#admin-main .grav-update p{line-height:3rem;margin:0;display:block;white-space:nowrap;overflow:hidden;-ms-text-overflow:ellipsis;text-overflow:ellipsis}#admin-main .grav-update span[class="hint--left"]::after{line-height:1.5}#admin-main .grav-update i{padding-right:0.5rem}#admin-main .grav-update.grav+.content-wrapper{height:calc(100vh - 4.2rem - 3rem)}#admin-main .content-wrapper{position:relative;overflow-y:hidden;height:calc(100vh - 4.2rem)}#admin-main .content-wrapper .gm-scrollbar.-vertical{right:5px;top:5px;width:8px;border-radius:4px}#admin-main .content-padding{padding:1.5rem}@media only all and (max-width: 47.938em){#admin-main .content-padding{padding:0}}#admin-main .admin-block{padding:1rem 0}#admin-main .admin-block:empty{padding:0}#admin-main .admin-block h1{padding:0 1.5rem 0.5rem;margin:0 0 1rem}@media only all and (max-width: 47.938em){#admin-main .admin-block h1{padding:0 1rem 0.5rem}}#admin-main .admin-block h1.no_underline{border-bottom:0}#admin-main .admin-block .button-bar{margin:0 1.5rem}@media only all and (max-width: 47.938em){#admin-main .admin-block .button-bar{margin:-1rem 0 1rem 0;padding:0 1rem;text-align:center}#admin-main .admin-block .button-bar .button{width:100%}}#admin-main .flush-bottom.button-bar{margin:1rem -1.5rem -1rem;padding:1rem;float:none;display:flex}@media only all and (max-width: 47.938em){#admin-main .flush-bottom.button-bar.stats-bar{font-size:90%}#admin-main .flush-bottom.button-bar.stats-bar b{font-size:1.5rem}}#admin-main .flush-bottom.button-bar .button{margin:0;height:37px}#admin-main .flush-bottom.button-bar .button.dropdown-toggle{margin-left:-4px}#admin-main .danger,#admin-main .success{position:relative}#admin-main .danger.button-bar,#admin-main .success.button-bar{margin:2rem 0 -1rem;height:70px;padding:1rem;float:none}.content-padding #admin-dashboard .dashboard-item-flex:nth-of-type(2n) .dashboard-item{margin-right:0;margin-left:.75rem}.content-padding #admin-dashboard .dashboard-item-flex:nth-of-type(2n+1) .dashboard-item{margin-left:0;margin-right:.75rem}.content-padding #admin-dashboard .dashboard-item{margin-bottom:1.5rem}#admin-dashboard{display:-webkit-box;display:-moz-box;display:box;display:-webkit-flex;display:-moz-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-moz-flex-flow:row wrap;flex-flow:row wrap}#admin-dashboard .dashboard-item-flex{display:-webkit-box;display:-moz-box;display:box;display:-webkit-flex;display:-moz-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:column nowrap;-moz-flex-flow:column nowrap;flex-flow:column nowrap;width:50%}@media only all and (max-width: 47.938em){#admin-dashboard .dashboard-item-flex{width:100%}#admin-dashboard .dashboard-item-flex .dashboard-item{margin-right:0px !important;margin-left:0px !important;margin-bottom:10px !important}}#admin-dashboard .dashboard-item>div:not(.widget-content){padding:1rem 1.5rem}#admin-dashboard .dashboard-item h1 .right{float:right}#admin-dashboard .dashboard-item.admin-block{padding:1rem 0}#admin-dashboard .dashboard-item.admin-block ul{margin:0 0 1rem 0;padding:0;list-style:none}#admin-dashboard .dashboard-item.admin-block li{margin:0;padding:0.425rem 1.5rem 0.2rem 1.5rem}#admin-dashboard .dashboard-item.admin-block li:last-child{border-bottom:0}#admin-dashboard .chart-loader{position:relative;text-align:center}#admin-dashboard .chart-loader .fa{display:block;position:absolute;width:100%;text-align:center;font-size:4rem;margin-top:-2rem}#admin-dashboard .widget-content{position:relative;min-height:335px;padding:0}#admin-dashboard .widget-loader{position:absolute;text-align:center;top:50%;left:50%;width:100%;transform:translate(-50%, -50%)}#admin-dashboard .widget-loader .fa{font-size:4rem}#admin-dashboard #updates p{text-align:center;margin:0}#admin-dashboard #updates .updates-chart{width:50%;float:left}#admin-dashboard #updates .flush-bottom.button-bar{height:70px;flex-direction:row-reverse}#admin-dashboard #updates .chart-wrapper{position:relative}#admin-dashboard #updates .backups-chart{position:relative;width:50%;float:left}#admin-dashboard #updates .ct-chart{min-height:157px}#admin-dashboard #updates .numeric{display:block;position:absolute;width:100%;text-align:center;font-size:1.5rem;line-height:1}#admin-dashboard #updates .numeric em{display:block;font-style:normal;font-size:1rem}#admin-dashboard #updates .admin-update-charts{min-height:191px}#admin-dashboard #updates .admin-update-charts:after{content:"";display:table;clear:both}#admin-dashboard #updates .button-bar>.button:not(.dropdown-toggle){margin-left:4px;height:37px}#admin-dashboard #popularity .ct-chart{min-height:171px}#admin-dashboard #popularity .button-bar{height:100px;padding:0 1rem;display:flex}#admin-dashboard .dashboard-item .admin-statistics-chart{position:relative}#admin-dashboard .dashboard-item p{text-align:center;margin:0}#admin-dashboard .dashboard-item .stat{width:33%;text-align:center;display:flex;flex-direction:column;justify-content:center}#admin-dashboard .dashboard-item .stat b{display:block;font-size:2.5rem;line-height:1;font-weight:300}#admin-dashboard .dashboard-item .stat i{display:block;font-style:normal}#admin-dashboard #news-feed .date{float:right;z-index:1}#admin-dashboard #news-feed li a{display:block;white-space:nowrap;overflow:hidden;-ms-text-overflow:ellipsis;text-overflow:ellipsis;z-index:0}#admin-dashboard #news-feed li a:before{font-family:FontAwesome;margin-right:10px;content:"\f1ea"}.no-flick,.card-item{-webkit-transform:translate3d(0, 0, 0)}.card-row{-webkit-box-pack:justify;-moz-box-pack:justify;box-pack:justify;-webkit-justify-content:space-between;-moz-justify-content:space-between;-ms-justify-content:space-between;-o-justify-content:space-between;justify-content:space-between;-ms-flex-pack:justify}.card-item{overflow:hidden;padding:1rem;margin:0;position:relative;width:31%;max-width:21rem;margin-bottom:2rem}@media only all and (min-width: 48em) and (max-width: 59.938em){.card-item{width:48%}}@media only all and (max-width: 47.938em){.card-item{width:100%}}.card-item h4{font-size:1.2rem;line-height:1.2}.user-details{text-align:left}.user-details img{border-radius:100%}.user-details h2{margin:0;font-size:2.1rem !important;margin:1rem 0 0 !important;line-height:1.5}.user-details h2 span{font-size:1.2rem}.user-details h5{font-size:1.1rem;margin:0;linep-height:1.2}.user-details .gravatar{font-size:0.9rem;padding:0}@media only all and (max-width: 47.938em){.user-details{text-align:center}}#footer{text-align:center;padding:3rem 0 1rem}#footer p{margin:0}.ct-chart .ct-series .ct-bar{stroke-width:20px}#popularity .ct-chart{margin:0 -10px -10px}#popularity .ct-chart .ct-chart-bar{padding:4px 6px 1px 10px}#latest .page-title,#latest .page-route{overflow:auto}#overlay{position:fixed;width:25%;height:100%;z-index:999999;left:75%;top:0;display:none}@media only all and (max-width: 47.938em){#overlay{display:none !important}}.gpm-item-info+#blueprints .block-tabs{padding-top:16px}.plugins-notifications-container,.themes-notifications-container{margin-bottom:2rem}.package-dependencies-container li{height:35px}.package-dependencies-container li .button{height:30px;float:right;margin-right:10px;line-height:20px}.pointer-events-disabled{pointer-events:none}.updates-checked .button-reinstall-package{display:inline-block !important}.updates-checked.has-updates .button-reinstall-package{display:none !important}.preview-wrapper .content-padding{position:absolute;top:0;bottom:0;right:0;left:0}@media only all and (max-width: 47.938em){#admin-main .config-wrapper-system .form-tabs.side-tabs>.tabs-nav{display:none;width:0}#admin-main .config-wrapper-system .form-tabs.side-tabs>.tabs-content{width:100%}#admin-main .config-wrapper-system .form-tabs.side-tabs>.tabs-content .tab__content{display:block}}.logs-output form .form-data{display:flex;margin:0 -0.5rem}@media only all and (max-width: 30em){.logs-output form .form-data{display:block}}.logs-output form .forms-select-wrapper{display:block;flex:1;margin:0.5rem 0.5rem}.logs-output h1{margin-top:2rem !important}.logs-output h3{padding:0.5rem 1.5rem;font-size:1.1rem}@media only all and (max-width: 47.938em){.logs-output h3{padding:0.5rem 1rem}}.logs-output table.noflex td{vertical-align:top}.logs-output table.noflex td.date,.logs-output table.noflex td.level{white-space:nowrap}.logs-output table.noflex td.date{opacity:0.7}.logs-output table.noflex th.level,.logs-output table.noflex td.level{padding:0.375rem 30px}.logs-output table.noflex td.level{font-weight:bold}.logs-output table.noflex td.level span.badge,.logs-output table.noflex td.level .sidebar-open #admin-sidebar #admin-menu li .badges span.updates,.sidebar-open #admin-sidebar #admin-menu li .badges .logs-output table.noflex td.level span.updates,.logs-output table.noflex td.level .gpm span.gpm-testing,.gpm .logs-output table.noflex td.level span.gpm-testing{font-size:0.7rem}.logs-output table.noflex td.message{width:100%;word-break:break-all}.logs-output table.noflex .trace ol{margin:10px 0;padding:0;font-size:0.7rem}.logs-output table.noflex .trace ol code{font-size:0.7rem;display:block;white-space:pre-wrap}.pages-list{list-style:none;margin:0;padding:0}.pages-list ul{list-style:none;margin:0;padding:0}.pages-list li{margin:0;padding:0}.pages-list .row{-webkit-transition:all 0.2s ease;-moz-transition:all 0.2s ease;transition:all 0.2s ease;line-height:2.5rem;padding-right:3rem}@media only all and (max-width: 47.938em){.pages-list .row{padding-right:1rem}}.pages-list .row [data-hint]:after{line-height:1 !important;width:auto !important;white-space:nowrap !important}.pages-list .row p.page-route{display:block;margin:0;line-height:1;font-size:0.9rem;word-break:break-all}.pages-list .row p.page-route .spacer{display:inline-block;margin:0 0.3rem}.pages-list .row .hint--bottom:before,.pages-list .row .hint--bottom:after{left:4px}.pages-list .row .hint:after,.pages-list .row [data-hint]:after{border-radius:2px}.pages-list .row .badge.lang,.pages-list .row .sidebar-open #admin-sidebar #admin-menu li .badges .lang.updates,.sidebar-open #admin-sidebar #admin-menu li .badges .pages-list .row .lang.updates,.pages-list .row .gpm .lang.gpm-testing,.gpm .pages-list .row .lang.gpm-testing{margin-left:8px}.pages-list .page-home{font-size:1.4rem;margin-left:10px;vertical-align:middle}.pages-list .page-info{font-size:1.1rem;margin-left:10px;vertical-align:middle}.pages-list .page-edit{text-overflow:ellipsis;display:inline-block;max-width:100%;white-space:nowrap;overflow-x:hidden}.pages-list .page-item__row{display:flex}.pages-list .page-item__toggle{width:25px;line-height:1;padding-top:7px}.pages-list .page-item__content{padding:5px 0;width:calc(100% - 50px)}.pages-list .page-item__content-name{line-height:1}.pages-list .page-item__content-hint{line-height:1.3;vertical-align:middle;max-width:100%}.pages-list .page-item__tools{width:90px;text-align:right;font-size:1.4rem}.pages-list .page-item__tools .page-view{margin-right:4px}.pages-list .page-icon{color:#0082BA;font-weight:700}.pages-list .page-icon.children-open:before{content:'\f056'}.pages-list .page-icon.children-closed:before{content:'\f055'}.pages-list .page-icon.not-routable{color:#CE431D}.pages-list .page-icon.not-visible{color:#999}.pages-list .page-icon.modular{color:#9055AF}.pages-list #pages-legend{margin-top:1.5rem;text-align:center}.pages-list #pages-legend h2,.pages-list #pages-legend ul,.pages-list #pages-legend li{display:inline-block}.pages-list #pages-legend li{margin-right:0.75rem}#page-filtering{margin:0 1.5rem 1rem}#page-filtering:after{content:"";display:table;clear:both}@media only all and (max-width: 47.938em){#page-filtering{margin:-1rem 1rem 1rem}}#page-filtering .page-filters{width:60%;float:left}@media only all and (max-width: 47.938em){#page-filtering .page-filters{width:100%}}#page-filtering .page-search{position:relative;width:40%;float:left;padding-left:2rem;text-indent:2.5rem}#page-filtering .page-search:after{position:absolute;right:15px;top:10px;content:'\f002';font-family:'FontAwesome'}@media only all and (max-width: 47.938em){#page-filtering .page-search{width:100%;padding-top:1rem;padding-left:0rem}#page-filtering .page-search:after{top:1.5rem}}#page-filtering .page-shortcuts{clear:both;padding-top:5px}#page-filtering .page-shortcuts:after{content:"";display:table;clear:both}#page-filtering .selectize-control.single.plugin-remove_button .selectize-input,#page-filtering .selectize-control.multi .selectize-input{padding:.375rem .375rem}#page-filtering .selectize-control.single.plugin-remove_button .selectize-input.has-items,#page-filtering .selectize-control.multi .selectize-input.has-items{padding-top:6px;padding-bottom:4px}#page-filtering .selectize-control.single.plugin-remove_button .selectize-input input,#page-filtering .selectize-control.multi .selectize-input input{font-size:.9rem;line-height:1.5}#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item,#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item.active,#page-filtering .selectize-control.multi .selectize-input .item,#page-filtering .selectize-control.multi .selectize-input .item.active{color:#777;background:#eee;padding:2px 10px}#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item[data-value='Routable'],#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item.active[data-value='Routable'],#page-filtering .selectize-control.multi .selectize-input .item[data-value='Routable'],#page-filtering .selectize-control.multi .selectize-input .item.active[data-value='Routable']{background:#CE431D;color:#fff}#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item[data-value='NonRoutable'],#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item.active[data-value='NonRoutable'],#page-filtering .selectize-control.multi .selectize-input .item[data-value='NonRoutable'],#page-filtering .selectize-control.multi .selectize-input .item.active[data-value='NonRoutable']{color:#CE431D}#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item[data-value='Visible'],#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item.active[data-value='Visible'],#page-filtering .selectize-control.multi .selectize-input .item[data-value='Visible'],#page-filtering .selectize-control.multi .selectize-input .item.active[data-value='Visible']{background:#0082BA;color:#fff}#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item[data-value='NonVisible'],#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item.active[data-value='NonVisible'],#page-filtering .selectize-control.multi .selectize-input .item[data-value='NonVisible'],#page-filtering .selectize-control.multi .selectize-input .item.active[data-value='NonVisible']{color:#0082BA}#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item[data-value='Modular'],#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item.active[data-value='Modular'],#page-filtering .selectize-control.multi .selectize-input .item[data-value='Modular'],#page-filtering .selectize-control.multi .selectize-input .item.active[data-value='Modular']{background:#9055AF;color:#fff}#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item[data-value='NonModular'],#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item.active[data-value='NonModular'],#page-filtering .selectize-control.multi .selectize-input .item[data-value='NonModular'],#page-filtering .selectize-control.multi .selectize-input .item.active[data-value='NonModular']{color:#9055AF}#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item[data-value='Published'],#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item.active[data-value='Published'],#page-filtering .selectize-control.multi .selectize-input .item[data-value='Published'],#page-filtering .selectize-control.multi .selectize-input .item.active[data-value='Published']{background:#0093B8;color:#fff}#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item[data-value='NonPublished'],#page-filtering .selectize-control.single.plugin-remove_button .selectize-input .item.active[data-value='NonPublished'],#page-filtering .selectize-control.multi .selectize-input .item[data-value='NonPublished'],#page-filtering .selectize-control.multi .selectize-input .item.active[data-value='NonPublished']{color:#0093B8}.admin-form-wrapper{position:relative}#admin-topbar{position:absolute;right:5px;height:50px}@media only all and (max-width: 47.938em){#admin-topbar{position:relative;width:100%;right:0;padding:0 .5rem;margin:-2rem 0 2rem 0}}#admin-topbar #admin-mode-toggle,#admin-topbar #admin-lang-toggle{height:38px;display:inline-block;vertical-align:inherit}@media only all and (max-width: 47.938em){#admin-topbar #admin-mode-toggle,#admin-topbar #admin-lang-toggle{float:right}}#admin-topbar #admin-lang-toggle{z-index:3;float:left;margin-right:5px}#admin-topbar #admin-lang-toggle button{padding:0.3rem 0.5rem;height:100%}#admin-topbar #admin-lang-toggle .dropdown-menu button{width:100%}#admin-topbar .switch-grav{border:0;line-height:38px}@media only all and (max-width: 47.938em){#admin-topbar .switch-toggle{width:100%}}.depth-0 .row{padding-left:3rem}@media only all and (max-width: 47.938em){.depth-0 .row{padding-left:1rem}}.depth-1 .row{padding-left:6rem}@media only all and (max-width: 47.938em){.depth-1 .row{padding-left:2rem}}.depth-2 .row{padding-left:9rem}@media only all and (max-width: 47.938em){.depth-2 .row{padding-left:3rem}}.depth-3 .row{padding-left:12rem}@media only all and (max-width: 47.938em){.depth-3 .row{padding-left:4rem}}.depth-4 .row{padding-left:15rem}@media only all and (max-width: 47.938em){.depth-4 .row{padding-left:5rem}}.depth-5 .row{padding-left:18rem}@media only all and (max-width: 47.938em){.depth-5 .row{padding-left:6rem}}.depth-6 .row{padding-left:21rem}@media only all and (max-width: 47.938em){.depth-6 .row{padding-left:7rem}}.depth-7 .row{padding-left:24rem}@media only all and (max-width: 47.938em){.depth-7 .row{padding-left:8rem}}.depth-8 .row{padding-left:27rem}@media only all and (max-width: 47.938em){.depth-8 .row{padding-left:9rem}}.depth-9 .row{padding-left:30rem}@media only all and (max-width: 47.938em){.depth-9 .row{padding-left:10rem}}.selectize-route-option{display:flex}.selectize-route-option>:first-child{flex:0 0 auto;width:auto;margin-right:10px}.selectize-route-option>:last-child{flex:1 1 auto}.selectize-route-option>:last-child>*{display:block}.hidden{display:none !important}.switch-toggle input[type=radio]{display:none !important}html.remodal-is-locked{overflow:hidden;touch-action:none}.remodal,[data-remodal-id]{display:none}.remodal-overlay{position:fixed;z-index:99999;top:-5000px;right:-5000px;bottom:-5000px;left:-5000px;display:none}.remodal-wrapper{position:fixed;z-index:100000;top:0;right:0;bottom:0;left:0;display:none;overflow:auto;text-align:center;-webkit-overflow-scrolling:touch}.remodal-wrapper:after{display:inline-block;height:100%;margin-left:-0.05em;content:''}.remodal-overlay,.remodal-wrapper{backface-visibility:hidden}.remodal{position:relative;outline:none;text-size-adjust:100%}.remodal-is-initialized{display:inline-block}.remodal-bg.remodal-is-opening,.remodal-bg.remodal-is-opened{-webkit-filter:blur(3px);filter:blur(3px)}.remodal-overlay{background:rgba(43,46,56,0.9)}.remodal-overlay.remodal-is-opening,.remodal-overlay.remodal-is-closing{animation-duration:0.3s;animation-fill-mode:forwards}.remodal-overlay.remodal-is-opening{animation-name:remodal-overlay-opening-keyframes}.remodal-overlay.remodal-is-closing{animation-name:remodal-overlay-closing-keyframes}.remodal-wrapper{padding:10px 10px 0}.remodal{box-sizing:border-box;width:100%;margin-bottom:10px;padding:35px;transform:translate3d(0, 0, 0)}.remodal.remodal-is-opening,.remodal.remodal-is-closing{animation-duration:0.3s;animation-fill-mode:forwards}.remodal.remodal-is-opening{animation-name:remodal-opening-keyframes}.remodal.remodal-is-closing{animation-name:remodal-closing-keyframes}.remodal,.remodal-wrapper:after{vertical-align:middle}.remodal-close{position:absolute;top:0;left:0;display:block;overflow:visible;width:35px;height:35px;margin:0;padding:0;cursor:pointer;transition:color 0.2s;text-decoration:none;border:0;outline:0;background:transparent}.remodal-close:before{font-family:Arial, "Helvetica CY", "Nimbus Sans L", sans-serif !important;font-size:25px;line-height:35px;position:absolute;top:0;left:0;display:block;width:35px;content:"\00d7";text-align:center}@keyframes remodal-opening-keyframes{from{transform:scale(1.05);opacity:0}to{transform:none;opacity:1}}@keyframes remodal-closing-keyframes{from{transform:scale(1);opacity:1}to{transform:scale(0.95);opacity:0}}@keyframes remodal-overlay-opening-keyframes{from{opacity:0}to{opacity:1}}@keyframes remodal-overlay-closing-keyframes{from{opacity:1}to{opacity:0}}@media only screen and (min-width: 641px){.remodal{max-width:700px}}.lt-ie9 .remodal-overlay{background:#2b2e38}.lt-ie9 .remodal{width:700px}.remodal{padding:35px 0 0;text-align:left;box-shadow:0 10px 20px rgba(0,0,0,0.3);border-radius:3px}.remodal ul li{margin-left:27px;list-style-type:square}.admin-pages .form-tabs:first-child{margin-top:-1rem}.admin-pages .form-tabs .tabs-nav{margin-right:180px}@media only all and (max-width: 47.938em){.admin-pages .form-tabs .tabs-nav{margin-right:0 !important}}.form-tabs .tabs-nav{display:-webkit-box;display:-moz-box;display:box;display:-webkit-flex;display:-moz-flex;display:-ms-flexbox;display:flex;padding-top:1px}.form-tabs .tabs-nav a{-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;-webkit-flex:1;-moz-flex:1;-ms-flex:1;flex:1;transition:color 0.5s ease, background 0.5s ease;cursor:pointer;min-height:50px;text-align:center;padding:4px;display:flex;align-items:center;justify-content:center}.form-tabs .tabs-nav span{display:inline-block;line-height:1.1}.form-tabs.subtle .tabs-nav{margin-right:0 !important}.form-tabs .tabs-content .tab__content{display:none;padding-top:2rem}.form-tabs .tabs-content .tab__content.active{display:block}.form-tabs.side-tabs{display:flex;background:none}.form-tabs.side-tabs>.tabs-nav{width:20%;display:block;padding-top:1.5rem}.form-tabs.side-tabs>.tabs-nav a{text-align:left;flex:none;justify-content:left;padding-left:15px}.form-tabs.side-tabs>.tabs-content{width:80%}.grav-editor-content{position:relative}.grav-editor [data-grav-editor]{height:300px}.grav-editor [data-grav-editor].code-mirrored{padding:0;border:0;position:absolute;z-index:-1;display:block !important}.grav-editor .CodeMirror .CodeMirror-vscrollbar,.grav-editor .CodeMirror .CodeMirror-hscrollbar{z-index:1}.grav-editor .CodeMirror-scroll{position:static;padding-bottom:15px}.grav-editor .grav-editor-resizer{height:6px;cursor:row-resize;border-radius:0 0 3px 3px;margin-top:-2px;position:relative;z-index:1}.grav-editor .CodeMirror-selected{background:#D6E8FF}.grav-editor .CodeMirror-gutters{height:100% !important}.grav-editor-fullscreen{position:fixed;top:4.2rem;left:0;bottom:0;right:0;z-index:3;padding:0 !important;margin:0 !important;display:flex;flex-direction:column}.grav-editor-fullscreen .grav-editor-content{display:flex;flex-direction:column;flex:1 0 0;overflow-y:auto}.grav-editor-fullscreen .grav-editor-content,.grav-editor-fullscreen .CodeMirror-wrap,.grav-editor-fullscreen .grav-editor-preview{height:100% !important}.grav-editor-fullscreen .grav-editor-toolbar{flex:0 0 auto}.grav-editor-fullscreen .grav-editor-toolbar,.grav-editor-fullscreen .grav-editor-toolbar ul li:first-child a,.grav-editor-fullscreen .grav-editor-toolbar-flip ul li:last-child a{border-radius:0 !important}.grav-editor-fullscreen .grav-editor-resizer{display:none}.grav-editor-fullscreen .grav-editor-content.is-active{display:flex}.grav-editor-toolbar{-webkit-box-align:stretch;-moz-box-align:stretch;box-align:stretch;-webkit-align-items:stretch;-moz-align-items:stretch;-ms-align-items:stretch;-o-align-items:stretch;align-items:stretch;-ms-flex-align:stretch;display:-webkit-box;display:-moz-box;display:box;display:-webkit-flex;display:-moz-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-moz-box-orient:horizontal;box-orient:horizontal;-webkit-flex-direction:row;-moz-flex-direction:row;flex-direction:row;-ms-flex-direction:row}.grav-editor-toolbar .grav-editor-actions{-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;-webkit-flex:1;-moz-flex:1;-ms-flex:1;flex:1}.grav-editor-toolbar .grav-editor-modes{flex-shrink:0;margin-right:2px}.grav-editor-toolbar .grav-editor-modes ul{float:right}.grav-editor-toolbar:after{content:"";display:table;clear:both}.grav-editor-toolbar ul{list-style:none;margin:0;padding:0}.grav-editor-toolbar ul li{display:inline-block}.grav-editor-toolbar ul li:first-child a{border-top-left-radius:4px}.grav-editor-toolbar ul a{display:inline-block;cursor:pointer;background-color:transparent;border-radius:0;padding:0.4rem;margin-top:-1px;border:1px solid transparent}.grav-editor-toolbar ul a,.grav-editor-toolbar ul a:hover,.grav-editor-toolbar ul a:active,.grav-editor-toolbar ul a:focus{outline:0}.grav-editor-toolbar .dropdown-menu li{width:50%}.grav-editor-hide-toolbar{display:none}.grav-editor-toolbar .grav-editor-modes li:first-child a{border-top-left-radius:0}.grav-editor-toolbar .grav-editor-modes li:last-child a{border-top-right-radius:4px}.grav-editor-toolbar-flip{float:right}.grav-editor-toolbar-flip ul li:last-child a{border-top-right-radius:4px}.grav-editor-content,.grav-editor-preview{cursor:text;display:none}.grav-editor-content:after,.grav-editor-preview:after{content:"";display:table;clear:both}.grav-editor-content.is-active,.grav-editor-preview.is-active{display:block}.grav-editor-content .CodeMirror-lines{padding:10px 0}#admin-main .grav-editor-preview{padding:20px;overflow-y:scroll;position:relative;background:#fff;line-height:1.5}#admin-main .grav-editor-preview h1,#admin-main .grav-editor-preview h2,#admin-main .grav-editor-preview h3,#admin-main .grav-editor-preview h4,#admin-main .grav-editor-preview h5,#admin-main .grav-editor-preview h6{padding:0;margin:2rem 0 1rem;border-bottom:0 !important}#admin-main .grav-editor-preview p{padding:0;margin:0.5rem 0 1rem}#admin-main .grav-editor-preview pre{padding:1rem}.grav-editor-toolbar [data-hint]:after{white-space:nowrap !important;max-width:300px;width:auto !important}.grav-editor-toolbar p{margin-top:10px;margin-bottom:10px;padding-left:20px}.cm-s-paper.CodeMirror{color:#666;line-height:1.4;font-size:16px}.cm-s-paper .cm-link{color:#0082ba}.cm-s-paper .cm-comment{color:gray}.cm-s-paper .cm-header{color:#595959}.cm-s-paper .cm-strong{color:#4d4d4d}.cm-s-paper .cm-em{color:#4d4d4d}.cm-s-paper .cm-string{color:#0082BA}.cm-s-paper .cm-tag{color:#4d4d4d}.cm-s-paper .cm-bracket{color:#666}.cm-s-paper .cm-hr{color:#ccc;font-weight:bold}.cm-s-paper .cm-keyword{color:#0082ba}.cm-s-paper .cm-atom{color:#9055af}.cm-s-paper .cm-meta{color:#666}.cm-s-paper .cm-number{color:#7f8c8d}.cm-s-paper .cm-def{color:#00f}.cm-s-paper .cm-variable{color:black}.cm-s-paper .cm-property{color:black}.cm-s-paper .cm-operator{color:black}.cm-s-paper .cm-string-2{color:#f50}.cm-s-paper .cm-meta{color:#555}.cm-s-paper .cm-error{color:#f00}.cm-s-paper .cm-qualifier{color:#555}.cm-s-paper .cm-builtin{color:#555}.cm-s-paper .cm-attribute{color:#7f8c8d}.cm-s-paper .cm-quote{color:#888}.cm-s-paper .cm-header-1{font-size:140%}.cm-s-paper .cm-header-2{font-size:120%}.cm-s-paper .cm-header-3{font-size:110%}.cm-s-paper .cm-negative{color:#d44}.cm-s-paper .cm-positive{color:#292}.cm-s-paper .cm-header,.cm-s-paper .cm-strong{font-weight:bold}.cm-s-paper .cm-em{font-style:italic}.cm-s-paper .cm-link{text-decoration:underline}.cm-s-paper .cm-invalidchar{color:#f00}.dropzone{position:relative;border-radius:4px;min-height:4rem}.dropzone.dz-clickable{cursor:pointer}.dropzone.dz-drag-hover{border-color:rgba(0,0,0,0.15);background:rgba(0,0,0,0.04)}.dropzone.dz-started .dz-message{display:none}.dropzone .dz-message{opacity:1;-ms-filter:none;filter:none}.dropzone .dz-preview{position:relative;display:inline-block;margin:1rem 1rem 1.5rem 1rem;vertical-align:top}.dropzone .dz-preview.dz-file-preview [data-dz-thumbnail]{display:none}.dropzone .dz-preview.dz-error .dz-error-mark{display:block}.dropzone .dz-preview.dz-success .dz-success-mark{display:block}.dropzone .dz-preview .dz-success-mark,.dropzone .dz-preview .dz-error-mark{font-family:FontAwesome;display:none;position:absolute;width:22px;height:22px;font-size:18px;line-height:25px;border-radius:100%;text-align:center;right:2px;top:2px}.dropzone .dz-preview .dz-success-mark span,.dropzone .dz-preview .dz-error-mark span{display:none}.dropzone .dz-preview:hover .dz-success-mark,.dropzone .dz-preview:hover .dz-error-mark{display:none}.dropzone .dz-preview .dz-success-mark::after{content:'\f00c'}.dropzone .dz-preview .dz-error-mark::after{content:'\f12a'}.dropzone .dz-preview .dz-progress{position:absolute;top:150px;left:0px;right:0px;height:4px;display:none}.dropzone .dz-preview .dz-progress .dz-upload{display:block;position:absolute;top:0;bottom:0;left:0;width:0%}.dropzone .dz-preview .dz-error-message{display:none;position:absolute;top:0;left:0;right:0;font-size:0.9rem;line-height:1.2;padding:8px 10px;z-index:500}.dropzone .dz-preview.dz-processing .dz-progress{display:block}.dropzone .dz-preview:hover .dz-remove,.dropzone .dz-preview:hover .dz-unset,.dropzone .dz-preview:hover .dz-view,.dropzone .dz-preview:hover .dz-insert,.dropzone .dz-preview:hover .dz-metadata{display:block}.dropzone .dz-preview.dz-processing:hover .dz-unset{display:none}.dropzone .dz-preview:hover.dz-error .dz-error-message{display:block}.dropzone .dz-preview .dz-filename{position:absolute;z-index:1;bottom:0;width:100%;padding:0 5px;background:rgba(0,0,0,0.3);color:#fff;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dropzone .dz-preview .dz-remove,.dropzone .dz-preview .dz-unset,.dropzone .dz-preview .dz-view,.dropzone .dz-preview .dz-insert,.dropzone .dz-preview .dz-metadata{display:none;position:absolute;width:25px;height:25px;right:-26px;font-size:0;cursor:pointer}.dropzone .dz-preview .dz-remove:hover:after,.dropzone .dz-preview .dz-unset:hover:after,.dropzone .dz-preview .dz-view:hover:after,.dropzone .dz-preview .dz-insert:hover:after,.dropzone .dz-preview .dz-metadata:hover:after{color:#fff}.dropzone .dz-preview .dz-remove:after,.dropzone .dz-preview .dz-unset:after,.dropzone .dz-preview .dz-view:after,.dropzone .dz-preview .dz-insert:after,.dropzone .dz-preview .dz-metadata:after{text-align:center;display:block;color:#999;font-family:FontAwesome;font-size:18px;line-height:25px}.dropzone .dz-preview .dz-insert{top:-1px;border-top-right-radius:4px}.dropzone .dz-preview .dz-insert:after{content:'\f055'}.dropzone .dz-preview .dz-view{top:24px}.dropzone .dz-preview .dz-view:after{content:'\f06e'}.dropzone .dz-preview .dz-remove{top:72px}.dropzone .dz-preview .dz-remove:after{content:'\f1f8'}.dropzone .dz-preview .dz-unset{top:96px;border-bottom-right-radius:4px}.dropzone .dz-preview .dz-unset:after{content:'\f00d'}.dropzone .dz-preview .dz-metadata{top:49px}.dropzone .dz-preview .dz-metadata:after{content:'\f05a'}.dropzone .dz-preview.dz-no-editor .dz-view{top:-1px;border-top-right-radius:4px}.dropzone .dz-preview.dz-no-editor .dz-remove{top:24px}.dropzone .dz-preview.dz-no-editor .dz-unset{top:72px}.dropzone .dz-preview:hover .dz-remove.file-thumbnail-remove,.dropzone .dz-preview:hover .dz-unset.file-thumbnail-remove{display:block}.dropzone .dz-preview:hover .dz-remove:not(.file-thumbnail-remove),.dropzone .dz-preview:hover .dz-unset:not(.file-thumbnail-remove){border-left:0}.dropzone .dz-preview.dz-processing .dz-details{overflow:hidden}.dropzone .dz-preview .dz-details{width:200px;height:150px;position:relative;font-size:0.8rem;background-image:url("../images/transparent.png")}.dropzone .dz-preview .dz-details img{position:absolute;object-fit:contain;top:0;left:0;width:200px;height:150px}.dropzone .dz-preview .dz-details .dz-size{position:absolute;bottom:-28px;left:0;right:0;text-align:center;font-size:0.8rem;height:28px;line-height:28px}.dropzone.dz-clickable .dz-message,.dropzone.dz-clickable .dz-message span{cursor:pointer;text-align:center;font-size:1.2rem;line-height:1.4;margin:1rem 0}.dropzone *{cursor:default}.toast-title{font-weight:bold}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#ffffff}.toast-message a:hover{color:#cccccc;text-decoration:none}.toast-close-button{position:relative;right:-0.3em;top:-0.3em;float:right;font-size:20px;font-weight:bold;color:#ffffff;-webkit-text-shadow:0 1px 0 #ffffff;text-shadow:0 1px 0 #ffffff;opacity:0.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80)}.toast-close-button:hover,.toast-close-button:focus{color:#000000;text-decoration:none;cursor:pointer;opacity:0.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}button.toast-close-button{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.toast-top-center{top:0;right:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{top:0;right:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{top:12px;left:12px}.toast-top-right{top:5rem;right:1.5rem}.toast-bottom-right{right:12px;bottom:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{position:fixed;z-index:999999}#toast-container *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#toast-container>div{position:relative;overflow:hidden;margin:0 0 6px;padding:15px 15px 15px 50px;width:300px;border-radius:3px;background-position:15px center;background-repeat:no-repeat;color:#ffffff;opacity:0.9;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=90);filter:alpha(opacity=90)}#toast-container>:hover{opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);cursor:pointer}#toast-container>.toast-info{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important}#toast-container>.toast-error{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important}#toast-container>.toast-success{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important}#toast-container>.toast-warning{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important}#toast-container.toast-top-center>div,#toast-container.toast-bottom-center>div{width:300px;margin:auto}#toast-container.toast-top-full-width>div,#toast-container.toast-bottom-full-width>div{width:96%;margin:auto}.toast-warning{background-color:#f89406}.toast-warning .button{background-color:#c67605;color:rgba(255,255,255,0.85);border-radius:4px;background:#945904}.toast-warning .button:hover{background:#a47229;color:#fff}.toast-warning .button.dropdown-toggle{border-left:1px solid #7c4a03}.toast-progress{position:absolute;left:0;bottom:0;height:4px;background-color:#000000;opacity:0.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}@media all and (max-width: 240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container .toast-close-button{right:-0.2em;top:-0.2em}}@media all and (min-width: 241px) and (max-width: 480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container .toast-close-button{right:-0.2em;top:-0.2em}}@media all and (min-width: 481px) and (max-width: 768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}}.gpm td{border:0}.gpm .gpm-search{position:relative;padding:0 3rem 0.5rem 1.5rem;text-indent:2.5rem}.gpm .gpm-search:after{position:absolute;right:60px;top:10px;content:'\f002';font-family:'FontAwesome'}@media only all and (max-width: 47.938em){.gpm .gpm-search{padding-left:1rem}}.gpm .sort-actions{float:right;margin:0 24px}@media only all and (max-width: 47.938em){.gpm .sort-actions{float:none}}.gpm .sort-actions>*{font-size:1rem;display:inline-block;vertical-align:middle}.gpm .sort-actions .sort-icon{cursor:pointer}.gpm .sort-actions select{margin:0;display:none}.gpm .sort-actions .selectize-input{width:9rem;vertical-align:middle;line-height:21px}.gpm .sort-actions .selectize-input input{line-height:21px}.gpm .gpm-name{white-space:nowrap}@media only all and (max-width: 47.938em){.gpm .gpm-name{white-space:normal}}.gpm .gpm-version{padding-left:0.5rem;font-size:0.9rem}.gpm .gpm-testing{border-radius:3px;padding:0 4px;font-size:0.75rem}.gpm .theme .gpm-testing{line-height:1rem;position:absolute;top:0;right:0;border-radius:0 0 0 3px;line-height:1.3}.gpm .gpm-actions .enabled,.gpm .gpm-actions .disabled{font-size:1.6rem}.gpm .gpm-item-info{position:relative;padding-bottom:1rem;margin-bottom:3rem;overflow:hidden}.gpm .gpm-item-info .gpm-item-icon{position:absolute;right:1.5rem;font-size:20rem}.gpm .gpm-item-info table{position:relative}.gpm .gpm-item-info td{border:0;text-align:left !important}.gpm .gpm-item-info td.double{padding-left:2rem}.gpm .gpm-item-info td:first-child{white-space:nowrap}@media only all and (max-width: 47.938em){.gpm .gpm-item-info td{word-wrap:break-word;word-break:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;-ms-hyphens:auto;hyphens:auto}}.gpm .gpm-item-info tr:hover{background:inherit}.gpm .badge.update,.gpm .sidebar-open #admin-sidebar #admin-menu li .badges .update.updates,.sidebar-open #admin-sidebar #admin-menu li .badges .gpm .update.updates,.gpm .update.gpm-testing{display:inline-block;border-radius:4px;padding:2px 10px;margin-left:1rem}.gpm .gpm-ribbon{overflow:hidden;white-space:nowrap;position:absolute;top:1rem;right:-2rem;-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.gpm .gpm-ribbon a{display:block;font-weight:bold;font-size:0.9rem;padding:5px 40px;text-align:center}.gpm .themes{padding:1.5rem}.gpm .themes .card-item{padding:1rem 1rem 4rem}.gpm .themes .gpm-screenshot{text-align:center}.gpm .themes .gpm-screenshot img{margin:0 auto;max-height:300px}.gpm .themes .gpm-name{margin-bottom:0.5rem;white-space:inherit}.gpm .themes .gpm-actions{margin:1rem -1rem -1rem -1rem;height:4rem;text-align:center;padding:1rem;font-size:1rem;font-weight:bold;position:absolute;left:0;right:0;bottom:1rem}.gpm .themes .active-theme .gpm-actions,.gpm .themes.inactive-theme .gpm-actions{line-height:2rem}.gpm .themes .inactive-theme .gpm-actions{display:block;font-weight:normal}.gpm #gpm-release-toggle{float:right;margin-right:3rem}#phpinfo img{display:none}#phpinfo table{margin:1rem 0 0}#phpinfo tr:hover{background:transparent}#phpinfo td{word-wrap:break-word;overflow:hidden}#phpinfo td h1{margin:0rem -1.5rem 0rem !important}#phpinfo hr{border-bottom:0}#phpinfo h1{font-size:2.3rem}#phpinfo h2{font-size:1.7rem;margin:3rem 1.5rem 0rem !important}.lines-button{display:inline-block;padding:.5rem .5rem 1rem;transition:.3s;cursor:pointer;user-select:none;border-radius:.4rem;position:absolute;z-index:100002;top:10px;background:transparent}.lines-button:hover{opacity:1}.lines-button:active{transition:none;background:rgba(0,0,0,0.1)}.lines{position:relative;display:inline-block;width:2rem;height:.28571rem;border-radius:.14286rem;transition:.3s}.lines:before,.lines:after{display:inline-block;width:2rem;height:.28571rem;border-radius:.14286rem;transition:.3s;position:absolute;left:0;content:'';transform-origin:.14286rem center}.lines:before{top:.5rem}.lines:after{top:-.5rem}.sidebar-mobile-open .lines-button.x .lines{background:transparent !important}.sidebar-mobile-open .lines-button.x .lines:before,.sidebar-mobile-open .lines-button.x .lines:after{transform-origin:50% 50%;top:0;width:2rem}.sidebar-mobile-open .lines-button.x .lines:before{transform:rotate3d(0, 0, 1, 45deg)}.sidebar-mobile-open .lines-button.x .lines:after{transform:rotate3d(0, 0, 1, -45deg)}.single-notification.alert{position:relative}.hide-notification{right:1rem;position:absolute}.alert.raw{padding:0 !important}#admin-dashboard #notifications .admin-block{padding:1rem 0}#admin-dashboard #notifications li{white-space:nowrap;overflow:hidden;-ms-text-overflow:ellipsis;text-overflow:ellipsis}.dashboard-notifications-container,.plugins-notifications-container,.themes-notifications-container{margin-bottom:1rem}.dashboard-notifications-container .raw,.plugins-notifications-container .raw,.themes-notifications-container .raw{position:relative}.dashboard-notifications-container .raw .hide-notification,.plugins-notifications-container .raw .hide-notification,.themes-notifications-container .raw .hide-notification{position:absolute;top:5px;right:5px;line-height:1;font-size:1.2rem;color:#333;text-shadow:-1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff}.dashboard-notifications-container:empty,.plugins-notifications-container:empty,.themes-notifications-container:empty{margin:0}#admin-main .content-padding div[class*=notifications-container] .alert a:hover{text-decoration:underline}#admin-main .content-padding div[class*=notifications-container] .alert a.button,#admin-main .content-padding div[class*=notifications-container] .alert span.button,#admin-main .content-padding div[class*=notifications-container] .alert button.button{float:right;text-decoration:none}#admin-main .content-padding .top-notifications-container{margin-bottom:1rem}#admin-main .content-padding .top-notifications-container .alert a{cursor:pointer}@media only all and (max-width: 47.938em){#admin-main .content-padding .top-notifications-container{padding-bottom:20px}}#admin-main #notifications td:last-child{position:relative}#admin-main #notifications td:last-child span{position:absolute;display:block;right:2rem;top:6px}#admin-main #notifications .badge.alert,#admin-main #notifications .sidebar-open #admin-sidebar #admin-menu li .badges .alert.updates,.sidebar-open #admin-sidebar #admin-menu li .badges #admin-main #notifications .alert.updates,#admin-main #notifications .gpm .alert.gpm-testing,.gpm #admin-main #notifications .alert.gpm-testing{margin:0;font-size:0.7rem;text-transform:uppercase;margin-right:5px;border-radius:4px;vertical-align:middle;line-height:1.7}#admin-main #notifications .badge.alert i,#admin-main #notifications .sidebar-open #admin-sidebar #admin-menu li .badges .alert.updates i,.sidebar-open #admin-sidebar #admin-menu li .badges #admin-main #notifications .alert.updates i,#admin-main #notifications .gpm .alert.gpm-testing i,.gpm #admin-main #notifications .alert.gpm-testing i{margin-right:3px}.pages-list-container{width:30%;flex:none !important;padding:0 !important}.pages-list-container .pages-list{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pages-list-container .pages-list.depth-0 .row{padding-left:.5rem}.pages-list-container .pages-list .depth-1 .row{padding-left:1rem}.pages-list-container .pages-list .page-item .page-link{cursor:pointer}.pages-list-container .pages-list .row.active{background-color:rgba(0,166,207,0.1)}.media-page-dropzone{width:100%;margin-bottom:20px}.media-container.size-2-3{flex:none !important;width:70%}.media-container .filters{padding-bottom:30px;width:100%}.media-container .filters .filter-wrapper{margin-left:10px;margin-right:10px}.media-container .filters .filter-wrapper:after{margin-top:-25px;border:0px none;content:"\f078";font-family:"FontAwesome";right:12px;top:50%;line-height:0;color:#9BA2A6;pointer-events:none;float:right;padding-right:10px}.media-container .filters .filter option{padding:8px}.media-container .files{padding-left:30px;padding-right:30px}.media-container .admin-media-title{color:#333;margin:0px;font-size:.8rem;line-height:1.3}.media-container .card-item{width:49%}.media-container .card-item.empty-space{background:rgba(255,255,255,0.4);border:1px solid rgba(0,0,0,0.1) !important;color:rgba(0,0,0,0.3);max-width:none;display:block;text-align:center;top:20%;height:10rem !important;width:50% !important;margin:0 auto !important;font-size:2rem;line-height:8rem;transform:translateY(-50%)}.media-container .media-details{text-align:center}.media-container .media-details img{width:50%}.media-container .media-details h2{margin:0;font-size:1.8rem}.media-container .media-details h5{color:#9BA2A6;font-size:1.1rem;margin:0}.media-container h1{padding:0 2rem 0.5rem !important}.media-container .admin-form-wrapper{width:100%;padding-bottom:20px}.spinning-wheel{text-align:center}.media-container.in-modal h1{font-size:30px !important;margin-top:0px}.media-container.in-modal a{color:#333}.media-container.in-modal .pages-list li{list-style-type:none;margin-left:0}.remodal.remodal-mediapicker{max-width:70vw;padding:0}.media-container{height:70vh}.media-container .grid{justify-content:flex-start}.media-container.in-modal .gm-scrollbar{background:rgba(255,255,255,0.2)}.media-container.in-modal .gm-scrollbar .thumb{background-color:rgba(0,0,0,0.2) !important}.media-container.in-modal .gm-scrollbar .thumb:hover,.media-container.in-modal .gm-scrollbar .thumb.active{background-color:rgba(0,0,0,0.3) !important}.media-container h5{border-bottom:1px solid #ddd;padding:0.5rem 15px;margin:0;height:55px}.media-container .filter-wrapper{float:right;margin-right:5px;margin-top:10px}.media-container .filter-date{width:150px}.media-container .filter-type{width:100px;margin-right:15px}.media-container .pages-list-container{position:relative}.media-container .pages-list-container .pages-list{border-top:0;padding-right:10px;font-size:90%}.media-container .pages-list-container .pages-list .row{transition:none !important;line-height:2rem}.media-container .pages-list-container .pages-list .depth-1 .row{padding-left:2rem}.media-container .pages-list-container .pages-list .depth-2 .row{padding-left:3rem}.media-container .pages-list-container .pages-list .depth-3 .row{padding-left:4rem}.media-container .pages-list-container .pages-list .depth-4 .row{padding-left:5rem}.media-container .pages-list-container .pages-list .depth-5 .row{padding-left:6rem}.media-container .pages-list-container .pages-list .depth-6 .row{padding-left:7rem}.media-container .pages-list-container .pages-list .depth-7 .row{padding-left:8rem}.media-container .pages-list-container .pages-list .depth-8 .row{padding-left:9rem}.media-container .pages-list-container .pages-list .depth-9 .row{padding-left:10rem}.media-container .pages-list-container .mediapicker-scroll{position:absolute;top:55px;bottom:3px;height:inherit}.media-container .thumbs-list-container{position:relative}.media-container .thumbs-list-container .media-range{position:absolute;right:15px;bottom:5px}.media-container .thumbs-list-container .mediapicker-scroll{position:absolute;top:55px;bottom:30px;height:inherit}.media-container .thumbs-list-container .gm-scrollbar.-horizontal{display:none}.media-container #admin-media{padding:15px;height:100%;align-items:flex-start;align-content:flex-start}.media-container #admin-media .card-item{width:100px;padding:2px;margin:0 5px 5px 0;border:0}.media-container #admin-media .card-item img{display:block}.media-container #admin-media .admin-media-details{position:relative}.media-container #admin-media .admin-media-details .admin-media-title{position:absolute;width:100%;bottom:0;background:rgba(0,0,0,0.3);color:#fff;font-size:10px;overflow:hidden;line-height:2;text-indent:2px;white-space:nowrap}[data-remodal-id="metadata"] h1>*{vertical-align:middle}[data-remodal-id="metadata"] h1 strong{display:inline-block;max-width:75%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}[data-remodal-id="metadata"] .metadata-preview{display:flex;margin:0 3rem}[data-remodal-id="metadata"] .metadata-preview .meta-preview{max-width:200px}[data-remodal-id="metadata"] .metadata-preview .meta-content ul{margin:0}.jqCron-selector{position:relative}.jqCron-cross,.jqCron-selector-title{cursor:pointer;border-radius:3px;border:1px solid #ddd;margin:0 0.2em;padding:0 0.5em}.jqCron-container.disable .jqCron-cross:hover,.jqCron-container.disable .jqCron-selector-title:hover,.jqCron-cross,.jqCron-selector-title{background:#eee;border-color:#ddd}.jqCron-cross:hover,.jqCron-selector-title:hover{background-color:#ddd;border-color:#aaa}.jqCron-cross{border-radius:1em;font-size:80%;padding:0 0.3em}.jqCron-selector-list{background:#eee;border:1px solid #aaa;-webkit-box-shadow:2px 2px 3px #ccc;box-shadow:2px 2px 3px #ccc;left:0.2em;list-style:none;margin:0;padding:0;position:absolute;top:1.5em;z-index:5}.jqCron-selector-list li{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;cursor:default;display:inline-block !important;margin:0;padding:0.1em 0.4em;width:100%}.jqCron-selector-list li.selected{background:#0088cc;color:white}.jqCron-selector-list li:hover{background:#5fb9e7;color:white}.jqCron-selector-list.cols2{width:4em}.jqCron-selector-list.cols2 li{width:50%}.jqCron-selector-list.cols3{width:6em}.jqCron-selector-list.cols3 li{width:33%}.jqCron-selector-list.cols4{width:8em}.jqCron-selector-list.cols4 li{width:25%}.jqCron-selector-list.cols5{width:10em}.jqCron-selector-list.cols5 li{width:20%}.jqCron-error .jqCron-selector-title{background:#fee;border:1px solid #fdd;color:red}.jqCron-container.disable *{color:#888}.jqCron-container.disable .jqCron-selector-title{background:#eee !important}#backups-stats #backups-usage{position:relative;margin-bottom:1.5rem}#backups-stats #backups-usage h1{position:absolute;right:1rem;top:5px}#backups-stats .backups-usage-wrapper{height:40px;background:linear-gradient(90deg, #2ECC40 0%, #01FF70 20%, #FFDC00 40%, #FF851B 60%, #FF4136 80%, #85144b 100%)}#backups-stats .backups-usage-wrapper>div{float:right;height:40px}#backups-stats .backups-usage-wrapper>div.full{width:100%}.backups-content #admin-dashboard{display:block}.backups-content #admin-dashboard #backups-stats{margin-left:1rem;margin-right:1rem}.backups-content #admin-dashboard #backups-stats h1{padding:0rem}@media only all and (max-width: 47.938em){.backups-content #admin-dashboard #backups-stats h1{font-size:1.1rem}}#admin-main .admin-block .scheduler-content .alert{margin-top:-1rem;margin-bottom:2rem}.scheduler-content .secondary-accent .button{float:right;margin-top:-3px}.scheduler-content #cron-install{padding:0 1.5rem}.scheduler-content #cron-install pre{padding:0.5rem}.scheduler-content #cron-install.hide{display:none}#admin-main .admin-block .report-output .alert{margin-top:0;margin-bottom:0}.report-output td .key{font-weight:bold}.direct-install-content{padding:30px}.direct-install-content .button{margin-top:10px;margin-bottom:50px}
+
+/*# sourceMappingURL=template.css.map */
diff --git a/user/plugins/admin/themes/grav/css-compiled/template.css.map b/user/plugins/admin/themes/grav/css-compiled/template.css.map
new file mode 100644
index 0000000..4b59452
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css-compiled/template.css.map
@@ -0,0 +1 @@
+{"version":3,"file":"template.css","sources":["template.scss","vendor/bourbon/_bourbon.scss","vendor/bourbon/settings/_prefixer.scss","vendor/bourbon/settings/_px-to-em.scss","vendor/bourbon/helpers/_convert-units.scss","vendor/bourbon/helpers/_gradient-positions-parser.scss","vendor/bourbon/helpers/_is-num.scss","vendor/bourbon/helpers/_linear-angle-parser.scss","vendor/bourbon/helpers/_linear-gradient-parser.scss","vendor/bourbon/helpers/_linear-positions-parser.scss","vendor/bourbon/helpers/_linear-side-corner-parser.scss","vendor/bourbon/helpers/_radial-arg-parser.scss","vendor/bourbon/helpers/_radial-positions-parser.scss","vendor/bourbon/helpers/_radial-gradient-parser.scss","vendor/bourbon/helpers/_render-gradients.scss","vendor/bourbon/helpers/_shape-size-stripper.scss","vendor/bourbon/helpers/_str-to-num.scss","vendor/bourbon/functions/_assign.scss","vendor/bourbon/functions/_color-lightness.scss","vendor/bourbon/functions/_flex-grid.scss","vendor/bourbon/functions/_golden-ratio.scss","vendor/bourbon/functions/_grid-width.scss","vendor/bourbon/functions/_modular-scale.scss","vendor/bourbon/functions/_px-to-em.scss","vendor/bourbon/functions/_px-to-rem.scss","vendor/bourbon/functions/_strip-units.scss","vendor/bourbon/functions/_tint-shade.scss","vendor/bourbon/functions/_transition-property-name.scss","vendor/bourbon/functions/_unpack.scss","vendor/bourbon/css3/_animation.scss","vendor/bourbon/css3/_appearance.scss","vendor/bourbon/css3/_backface-visibility.scss","vendor/bourbon/css3/_background.scss","vendor/bourbon/css3/_background-image.scss","vendor/bourbon/css3/_border-image.scss","vendor/bourbon/css3/_border-radius.scss","vendor/bourbon/css3/_box-sizing.scss","vendor/bourbon/css3/_calc.scss","vendor/bourbon/css3/_columns.scss","vendor/bourbon/css3/_filter.scss","vendor/bourbon/css3/_flex-box.scss","vendor/bourbon/css3/_font-face.scss","vendor/bourbon/css3/_hyphens.scss","vendor/bourbon/css3/_hidpi-media-query.scss","vendor/bourbon/css3/_image-rendering.scss","vendor/bourbon/css3/_keyframes.scss","vendor/bourbon/css3/_linear-gradient.scss","vendor/bourbon/css3/_perspective.scss","vendor/bourbon/css3/_radial-gradient.scss","vendor/bourbon/css3/_transform.scss","vendor/bourbon/css3/_transition.scss","vendor/bourbon/css3/_user-select.scss","vendor/bourbon/css3/_placeholder.scss","vendor/bourbon/addons/_button.scss","vendor/bourbon/addons/_clearfix.scss","vendor/bourbon/addons/_directional-values.scss","vendor/bourbon/addons/_ellipsis.scss","vendor/bourbon/addons/_font-family.scss","vendor/bourbon/addons/_hide-text.scss","vendor/bourbon/addons/_html5-input-types.scss","vendor/bourbon/addons/_position.scss","vendor/bourbon/addons/_prefixer.scss","vendor/bourbon/addons/_retina-image.scss","vendor/bourbon/addons/_size.scss","vendor/bourbon/addons/_timing-functions.scss","vendor/bourbon/addons/_triangle.scss","vendor/bourbon/addons/_word-wrap.scss","vendor/bourbon/_bourbon-deprecated-upcoming.scss","configuration/nucleus/_base.scss","configuration/nucleus/_core.scss","configuration/nucleus/_breakpoints.scss","configuration/nucleus/_layout.scss","configuration/nucleus/_typography.scss","configuration/nucleus/_nav.scss","configuration/template/_base.scss","configuration/template/_colors.scss","configuration/template/_variables.scss","nucleus/functions/_base.scss","nucleus/functions/_direction.scss","nucleus/functions/_range.scss","nucleus/mixins/_base.scss","nucleus/mixins/_breakpoints.scss","nucleus/mixins/_utilities.scss","template/modules/_base.scss","template/modules/_buttons.scss","template/modules/_toggle-switch.scss","template/modules/_datetimepicker.scss","template/modules/_scrollbars.scss","template/modules/_colorpicker.scss","template/_colors.scss","template/_core.scss","template/_extensions.scss","template/_utilities.scss","template/_header.scss","template/_typography.scss","template/_fonts.scss","template/_forms.scss","template/_tables.scss","template/_buttons.scss","template/_errors.scss","template/_login.scss","template/_admin.scss","template/_pages.scss","template/_remodal.scss","template/_tabs.scss","template/_editor.scss","template/_dropzone.scss","template/_toastr.scss","template/_gpm.scss","template/_phpinfo.scss","template/_nav-toggle.scss","template/_notifications.scss","template/_media.scss","template/_jqcron.scss","template/_tools.scss","template/_custom.scss"],"sourcesContent":["// REQUIRED DEPENDENCIES - DO NOT CHANGE\n\n// Load Third Party Libraries\n@import \"vendor/bourbon/bourbon\";\n\n// Load Nucleus Configuration\n@import \"configuration/nucleus/base\";\n\n// Load Template Configuration\n@import \"configuration/template/base\";\n\n// Load Nucleus Mixins and Functions\n@import \"nucleus/functions/base\";\n@import \"nucleus/mixins/base\";\n\n// Load Template Library\n@import \"template/modules/base\";\n@import \"template/colors\";\n//------------------------------------------\n\n// TEMPLATE COMPONENTS\n\n// Core\n@import \"template/core\";\n\n// Extensions\n@import \"template/extensions\";\n\n// Utilities\n@import \"template/utilities\";\n\n// Header\n@import \"template/header\";\n\n// Typography\n@import \"template/typography\";\n\n// Special Fonts\n@import \"template/fonts\";\n\n// Forms\n@import \"template/forms\";\n\n// Tables\n@import \"template/tables\";\n\n// Buttons\n@import \"template/buttons\";\n\n// Errors\n@import \"template/errors\";\n\n// Login\n@import \"template/login\";\n\n// Admin\n@import \"template/admin\";\n\n// Pages\n@import \"template/pages\";\n\n// Remodal\n@import \"template/remodal\";\n\n// Tabs\n@import \"template/tabs\";\n\n// Editor\n@import \"template/editor\";\n\n// Dropzone\n@import \"template/dropzone\";\n\n// Toastr\n@import \"template/toastr\";\n\n// GPM\n@import \"template/gpm\";\n\n// PHPInfo\n@import \"template/phpinfo\";\n\n// Nav Toggle\n@import \"template/nav-toggle\";\n\n// Notifications\n@import \"template/notifications\";\n\n// Media\n@import \"template/media\";\n@import \"template/jqcron\";\n\n// Tools\n@import \"template/tools\";\n\n// Custom\n@import \"template/custom\";\n","// Settings\n@import \"settings/prefixer\";\n@import \"settings/px-to-em\";\n\n// Custom Helpers\n@import \"helpers/convert-units\";\n@import \"helpers/gradient-positions-parser\";\n@import \"helpers/is-num\";\n@import \"helpers/linear-angle-parser\";\n@import \"helpers/linear-gradient-parser\";\n@import \"helpers/linear-positions-parser\";\n@import \"helpers/linear-side-corner-parser\";\n@import \"helpers/radial-arg-parser\";\n@import \"helpers/radial-positions-parser\";\n@import \"helpers/radial-gradient-parser\";\n@import \"helpers/render-gradients\";\n@import \"helpers/shape-size-stripper\";\n@import \"helpers/str-to-num\";\n\n// Custom Functions\n@import \"functions/assign\";\n@import \"functions/color-lightness\";\n@import \"functions/flex-grid\";\n@import \"functions/golden-ratio\";\n@import \"functions/grid-width\";\n@import \"functions/modular-scale\";\n@import \"functions/px-to-em\";\n@import \"functions/px-to-rem\";\n@import \"functions/strip-units\";\n@import \"functions/tint-shade\";\n@import \"functions/transition-property-name\";\n@import \"functions/unpack\";\n\n// CSS3 Mixins\n@import \"css3/animation\";\n@import \"css3/appearance\";\n@import \"css3/backface-visibility\";\n@import \"css3/background\";\n@import \"css3/background-image\";\n@import \"css3/border-image\";\n@import \"css3/border-radius\";\n@import \"css3/box-sizing\";\n@import \"css3/calc\";\n@import \"css3/columns\";\n@import \"css3/filter\";\n@import \"css3/flex-box\";\n@import \"css3/font-face\";\n@import \"css3/hyphens\";\n@import \"css3/hidpi-media-query\";\n@import \"css3/image-rendering\";\n@import \"css3/keyframes\";\n@import \"css3/linear-gradient\";\n@import \"css3/perspective\";\n@import \"css3/radial-gradient\";\n@import \"css3/transform\";\n@import \"css3/transition\";\n@import \"css3/user-select\";\n@import \"css3/placeholder\";\n\n// Addons & other mixins\n@import \"addons/button\";\n@import \"addons/clearfix\";\n@import \"addons/directional-values\";\n@import \"addons/ellipsis\";\n@import \"addons/font-family\";\n@import \"addons/hide-text\";\n@import \"addons/html5-input-types\";\n@import \"addons/position\";\n@import \"addons/prefixer\";\n@import \"addons/retina-image\";\n@import \"addons/size\";\n@import \"addons/timing-functions\";\n@import \"addons/triangle\";\n@import \"addons/word-wrap\";\n\n// Soon to be deprecated Mixins\n@import \"bourbon-deprecated-upcoming\";\n","// Variable settings for /addons/prefixer.scss\n$prefix-for-webkit: true !default;\n$prefix-for-mozilla: true !default;\n$prefix-for-microsoft: true !default;\n$prefix-for-opera: true !default;\n$prefix-for-spec: true !default; // required for keyframe mixin\n","$em-base: 16px !default;\n","//************************************************************************//\n// Helper function for str-to-num fn.\n// Source: http://sassmeister.com/gist/9647408\n//************************************************************************//\n@function _convert-units($number, $unit) {\n $strings: 'px' 'cm' 'mm' '%' 'ch' 'pica' 'in' 'em' 'rem' 'pt' 'pc' 'ex' 'vw' 'vh' 'vmin' 'vmax', 'deg', 'rad', 'grad', 'turn';\n $units: 1px 1cm 1mm 1% 1ch 1pica 1in 1em 1rem 1pt 1pc 1ex 1vw 1vh 1vmin 1vmax, 1deg, 1rad, 1grad, 1turn;\n $index: index($strings, $unit);\n\n @if not $index {\n @warn \"Unknown unit `#{$unit}`.\";\n @return false;\n }\n @return $number * nth($units, $index);\n}\n","@function _gradient-positions-parser($gradient-type, $gradient-positions) {\n @if $gradient-positions\n and ($gradient-type == linear)\n and (type-of($gradient-positions) != color) {\n $gradient-positions: _linear-positions-parser($gradient-positions);\n }\n @else if $gradient-positions\n and ($gradient-type == radial)\n and (type-of($gradient-positions) != color) {\n $gradient-positions: _radial-positions-parser($gradient-positions);\n }\n @return $gradient-positions;\n}\n","//************************************************************************//\n// Helper for linear-gradient-parser\n//************************************************************************//\n@function _is-num($char) {\n $values: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' 0 1 2 3 4 5 6 7 8 9;\n $index: index($values, $char);\n @return if($index, true, false);\n}\n","// Private function for linear-gradient-parser\n@function _linear-angle-parser($image, $first-val, $prefix, $suffix) {\n $offset: null;\n $unit-short: str-slice($first-val, str-length($first-val) - 2, str-length($first-val));\n $unit-long: str-slice($first-val, str-length($first-val) - 3, str-length($first-val));\n\n @if ($unit-long == \"grad\") or\n ($unit-long == \"turn\") {\n $offset: if($unit-long == \"grad\", -100grad * 3, -0.75turn);\n }\n\n @else if ($unit-short == \"deg\") or\n ($unit-short == \"rad\") {\n $offset: if($unit-short == \"deg\", -90 * 3, 1.6rad);\n }\n\n @if $offset {\n $num: _str-to-num($first-val);\n\n @return (\n webkit-image: -webkit- + $prefix + ($offset - $num) + $suffix,\n spec-image: $image\n );\n }\n}\n","@function _linear-gradient-parser($image) {\n $image: unquote($image);\n $gradients: ();\n $start: str-index($image, \"(\");\n $end: str-index($image, \",\");\n $first-val: str-slice($image, $start + 1, $end - 1);\n\n $prefix: str-slice($image, 0, $start);\n $suffix: str-slice($image, $end, str-length($image));\n\n $has-multiple-vals: str-index($first-val, \" \");\n $has-single-position: unquote(_position-flipper($first-val) + \"\");\n $has-angle: _is-num(str-slice($first-val, 0, 0));\n\n @if $has-multiple-vals {\n $gradients: _linear-side-corner-parser($image, $first-val, $prefix, $suffix, $has-multiple-vals);\n }\n\n @else if $has-single-position != \"\" {\n $pos: unquote($has-single-position + \"\");\n\n $gradients: (\n webkit-image: -webkit- + $image,\n spec-image: $prefix + \"to \" + $pos + $suffix\n );\n }\n\n @else if $has-angle {\n // Rotate degree for webkit\n $gradients: _linear-angle-parser($image, $first-val, $prefix, $suffix);\n }\n\n @else {\n $gradients: (\n webkit-image: -webkit- + $image,\n spec-image: $image\n );\n }\n\n @return $gradients;\n}\n","@function _linear-positions-parser($pos) {\n $type: type-of(nth($pos, 1));\n $spec: null;\n $degree: null;\n $side: null;\n $corner: null;\n $length: length($pos);\n // Parse Side and corner positions\n @if ($length > 1) {\n @if nth($pos, 1) == \"to\" { // Newer syntax\n $side: nth($pos, 2);\n\n @if $length == 2 { // eg. to top\n // Swap for backwards compatability\n $degree: _position-flipper(nth($pos, 2));\n }\n @else if $length == 3 { // eg. to top left\n $corner: nth($pos, 3);\n }\n }\n @else if $length == 2 { // Older syntax (\"top left\")\n $side: _position-flipper(nth($pos, 1));\n $corner: _position-flipper(nth($pos, 2));\n }\n\n @if (\"#{$side} #{$corner}\" == \"left top\") or (\"#{$side} #{$corner}\" == \"top left\") {\n $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});\n }\n @else if (\"#{$side} #{$corner}\" == \"right top\") or (\"#{$side} #{$corner}\" == \"top right\") {\n $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});\n }\n @else if (\"#{$side} #{$corner}\" == \"right bottom\") or (\"#{$side} #{$corner}\" == \"bottom right\") {\n $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});\n }\n @else if (\"#{$side} #{$corner}\" == \"left bottom\") or (\"#{$side} #{$corner}\" == \"bottom left\") {\n $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});\n }\n $spec: to $side $corner;\n }\n @else if $length == 1 {\n // Swap for backwards compatability\n @if $type == string {\n $degree: $pos;\n $spec: to _position-flipper($pos);\n }\n @else {\n $degree: -270 - $pos; //rotate the gradient opposite from spec\n $spec: $pos;\n }\n }\n $degree: unquote($degree + \",\");\n $spec: unquote($spec + \",\");\n @return $degree $spec;\n}\n\n@function _position-flipper($pos) {\n @return if($pos == left, right, null)\n if($pos == right, left, null)\n if($pos == top, bottom, null)\n if($pos == bottom, top, null);\n}\n","// Private function for linear-gradient-parser\n@function _linear-side-corner-parser($image, $first-val, $prefix, $suffix, $has-multiple-vals) {\n $val-1: str-slice($first-val, 0, $has-multiple-vals - 1 );\n $val-2: str-slice($first-val, $has-multiple-vals + 1, str-length($first-val));\n $val-3: null;\n $has-val-3: str-index($val-2, \" \");\n\n @if $has-val-3 {\n $val-3: str-slice($val-2, $has-val-3 + 1, str-length($val-2));\n $val-2: str-slice($val-2, 0, $has-val-3 - 1);\n }\n\n $pos: _position-flipper($val-1) _position-flipper($val-2) _position-flipper($val-3);\n $pos: unquote($pos + \"\");\n\n // Use old spec for webkit\n @if $val-1 == \"to\" {\n @return (\n webkit-image: -webkit- + $prefix + $pos + $suffix,\n spec-image: $image\n );\n }\n\n // Bring the code up to spec\n @else {\n @return (\n webkit-image: -webkit- + $image,\n spec-image: $prefix + \"to \" + $pos + $suffix\n );\n }\n}\n","@function _radial-arg-parser($G1, $G2, $pos, $shape-size) {\n @each $value in $G1, $G2 {\n $first-val: nth($value, 1);\n $pos-type: type-of($first-val);\n $spec-at-index: null;\n\n // Determine if spec was passed to mixin\n @if type-of($value) == list {\n $spec-at-index: if(index($value, at), index($value, at), false);\n }\n @if $spec-at-index {\n @if $spec-at-index > 1 {\n @for $i from 1 through ($spec-at-index - 1) {\n $shape-size: $shape-size nth($value, $i);\n }\n @for $i from ($spec-at-index + 1) through length($value) {\n $pos: $pos nth($value, $i);\n }\n }\n @else if $spec-at-index == 1 {\n @for $i from ($spec-at-index + 1) through length($value) {\n $pos: $pos nth($value, $i);\n }\n }\n $G1: null;\n }\n\n // If not spec calculate correct values\n @else {\n @if ($pos-type != color) or ($first-val != \"transparent\") {\n @if ($pos-type == number)\n or ($first-val == \"center\")\n or ($first-val == \"top\")\n or ($first-val == \"right\")\n or ($first-val == \"bottom\")\n or ($first-val == \"left\") {\n\n $pos: $value;\n\n @if $pos == $G1 {\n $G1: null;\n }\n }\n\n @else if\n ($first-val == \"ellipse\")\n or ($first-val == \"circle\")\n or ($first-val == \"closest-side\")\n or ($first-val == \"closest-corner\")\n or ($first-val == \"farthest-side\")\n or ($first-val == \"farthest-corner\")\n or ($first-val == \"contain\")\n or ($first-val == \"cover\") {\n\n $shape-size: $value;\n\n @if $value == $G1 {\n $G1: null;\n }\n\n @else if $value == $G2 {\n $G2: null;\n }\n }\n }\n }\n }\n @return $G1, $G2, $pos, $shape-size;\n}\n","@function _radial-positions-parser($gradient-pos) {\n $shape-size: nth($gradient-pos, 1);\n $pos: nth($gradient-pos, 2);\n $shape-size-spec: _shape-size-stripper($shape-size);\n\n $pre-spec: unquote(if($pos, \"#{$pos}, \", null))\n unquote(if($shape-size, \"#{$shape-size},\", null));\n $pos-spec: if($pos, \"at #{$pos}\", null);\n\n $spec: \"#{$shape-size-spec} #{$pos-spec}\";\n\n // Add comma\n @if ($spec != ' ') {\n $spec: \"#{$spec},\"\n }\n\n @return $pre-spec $spec;\n}\n","@function _radial-gradient-parser($image) {\n $image: unquote($image);\n $gradients: ();\n $start: str-index($image, \"(\");\n $end: str-index($image, \",\");\n $first-val: str-slice($image, $start + 1, $end - 1);\n\n $prefix: str-slice($image, 0, $start);\n $suffix: str-slice($image, $end, str-length($image));\n\n $is-spec-syntax: str-index($first-val, \"at\");\n\n @if $is-spec-syntax and $is-spec-syntax > 1 {\n $keyword: str-slice($first-val, 1, $is-spec-syntax - 2);\n $pos: str-slice($first-val, $is-spec-syntax + 3, str-length($first-val));\n $pos: append($pos, $keyword, comma);\n\n $gradients: (\n webkit-image: -webkit- + $prefix + $pos + $suffix,\n spec-image: $image\n )\n }\n\n @else if $is-spec-syntax == 1 {\n $pos: str-slice($first-val, $is-spec-syntax + 3, str-length($first-val));\n\n $gradients: (\n webkit-image: -webkit- + $prefix + $pos + $suffix,\n spec-image: $image\n )\n }\n\n @else if str-index($image, \"cover\") or str-index($image, \"contain\") {\n @warn \"Radial-gradient needs to be updated to conform to latest spec.\";\n\n $gradients: (\n webkit-image: null,\n spec-image: $image\n )\n }\n\n @else {\n $gradients: (\n webkit-image: -webkit- + $image,\n spec-image: $image\n )\n }\n\n @return $gradients;\n}\n","// User for linear and radial gradients within background-image or border-image properties\n\n@function _render-gradients($gradient-positions, $gradients, $gradient-type, $vendor: false) {\n $pre-spec: null;\n $spec: null;\n $vendor-gradients: null;\n @if $gradient-type == linear {\n @if $gradient-positions {\n $pre-spec: nth($gradient-positions, 1);\n $spec: nth($gradient-positions, 2);\n }\n }\n @else if $gradient-type == radial {\n $pre-spec: nth($gradient-positions, 1);\n $spec: nth($gradient-positions, 2);\n }\n\n @if $vendor {\n $vendor-gradients: -#{$vendor}-#{$gradient-type}-gradient(#{$pre-spec} $gradients);\n }\n @else if $vendor == false {\n $vendor-gradients: \"#{$gradient-type}-gradient(#{$spec} #{$gradients})\";\n $vendor-gradients: unquote($vendor-gradients);\n }\n @return $vendor-gradients;\n}\n","@function _shape-size-stripper($shape-size) {\n $shape-size-spec: null;\n @each $value in $shape-size {\n @if ($value == \"cover\") or ($value == \"contain\") {\n $value: null;\n }\n $shape-size-spec: \"#{$shape-size-spec} #{$value}\";\n }\n @return $shape-size-spec;\n}\n","//************************************************************************//\n// Helper function for linear/radial-gradient-parsers.\n// Source: http://sassmeister.com/gist/9647408\n//************************************************************************//\n@function _str-to-num($string) {\n // Matrices\n $strings: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9';\n $numbers: 0 1 2 3 4 5 6 7 8 9;\n\n // Result\n $result: 0;\n $divider: 0;\n $minus: false;\n\n // Looping through all characters\n @for $i from 1 through str-length($string) {\n $character: str-slice($string, $i, $i);\n $index: index($strings, $character);\n\n @if $character == '-' {\n $minus: true;\n }\n\n @else if $character == '.' {\n $divider: 1;\n }\n\n @else {\n @if not $index {\n $result: if($minus, $result * -1, $result);\n @return _convert-units($result, str-slice($string, $i));\n }\n\n $number: nth($numbers, $index);\n\n @if $divider == 0 {\n $result: $result * 10;\n }\n\n @else {\n // Move the decimal dot to the left\n $divider: $divider * 10;\n $number: $number / $divider;\n }\n\n $result: $result + $number;\n }\n }\n @return if($minus, $result * -1, $result);\n}\n","@function assign-inputs($inputs, $pseudo: null) {\n $list : ();\n\n @each $input in $inputs {\n $input: unquote($input);\n $input: if($pseudo, $input + \":\" + $pseudo, $input);\n $list: append($list, $input, comma);\n }\n\n @return $list;\n}","// Programatically determines whether a color is light or dark\n// Returns a boolean\n// More details here http://robots.thoughtbot.com/closer-look-color-lightness\n\n@function is-light($hex-color) {\n $-local-red: red(rgba($hex-color, 1.0));\n $-local-green: green(rgba($hex-color, 1.0));\n $-local-blue: blue(rgba($hex-color, 1.0));\n\n $-local-lightness: ($-local-red * 0.2126 + $-local-green * 0.7152 + $-local-blue * 0.0722) / 255;\n\n @return $-local-lightness > .6;\n}\n","// Flexible grid\n@function flex-grid($columns, $container-columns: $fg-max-columns) {\n $width: $columns * $fg-column + ($columns - 1) * $fg-gutter;\n $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;\n @return percentage($width / $container-width);\n}\n\n// Flexible gutter\n@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) {\n $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;\n @return percentage($gutter / $container-width);\n}\n\n// The $fg-column, $fg-gutter and $fg-max-columns variables must be defined in your base stylesheet to properly use the flex-grid function.\n// This function takes the fluid grid equation (target / context = result) and uses columns to help define each.\n//\n// The calculation presumes that your column structure will be missing the last gutter:\n//\n// -- column -- gutter -- column -- gutter -- column\n//\n// $fg-column: 60px; // Column Width\n// $fg-gutter: 25px; // Gutter Width\n// $fg-max-columns: 12; // Total Columns For Main Container\n//\n// div {\n// width: flex-grid(4); // returns (315px / 995px) = 31.65829%;\n// margin-left: flex-gutter(); // returns (25px / 995px) = 2.51256%;\n//\n// p {\n// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%;\n// float: left;\n// margin: flex-gutter(4); // returns (25px / 315px) = 7.936508%;\n// }\n//\n// blockquote {\n// float: left;\n// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%;\n// }\n// }","@function golden-ratio($value, $increment) {\n @return modular-scale($value, $increment, $golden)\n}\n","@function grid-width($n) {\n @return $n * $gw-column + ($n - 1) * $gw-gutter;\n}\n\n// The $gw-column and $gw-gutter variables must be defined in your base stylesheet to properly use the grid-width function.\n//\n// $gw-column: 100px; // Column Width\n// $gw-gutter: 40px; // Gutter Width\n//\n// div {\n// width: grid-width(4); // returns 520px;\n// margin-left: $gw-gutter; // returns 40px;\n// }\n","// Scaling Variables\n$golden: 1.618;\n$minor-second: 1.067;\n$major-second: 1.125;\n$minor-third: 1.2;\n$major-third: 1.25;\n$perfect-fourth: 1.333;\n$augmented-fourth: 1.414;\n$perfect-fifth: 1.5;\n$minor-sixth: 1.6;\n$major-sixth: 1.667;\n$minor-seventh: 1.778;\n$major-seventh: 1.875;\n$octave: 2;\n$major-tenth: 2.5;\n$major-eleventh: 2.667;\n$major-twelfth: 3;\n$double-octave: 4;\n\n@function modular-scale($value, $increment, $ratio) {\n $v1: nth($value, 1);\n $v2: nth($value, length($value));\n $value: $v1;\n\n // scale $v2 to just above $v1\n @while $v2 > $v1 {\n $v2: ($v2 / $ratio); // will be off-by-1\n }\n @while $v2 < $v1 {\n $v2: ($v2 * $ratio); // will fix off-by-1\n }\n\n // check AFTER scaling $v2 to prevent double-counting corner-case\n $double-stranded: $v2 > $v1;\n\n @if $increment > 0 {\n @for $i from 1 through $increment {\n @if $double-stranded and ($v1 * $ratio) > $v2 {\n $value: $v2;\n $v2: ($v2 * $ratio);\n } @else {\n $v1: ($v1 * $ratio);\n $value: $v1;\n }\n }\n }\n\n @if $increment < 0 {\n // adjust $v2 to just below $v1\n @if $double-stranded {\n $v2: ($v2 / $ratio);\n }\n\n @for $i from $increment through -1 {\n @if $double-stranded and ($v1 / $ratio) < $v2 {\n $value: $v2;\n $v2: ($v2 / $ratio);\n } @else {\n $v1: ($v1 / $ratio);\n $value: $v1;\n }\n }\n }\n\n @return $value;\n}\n","// Convert pixels to ems\n// eg. for a relational value of 12px write em(12) when the parent is 16px\n// if the parent is another value say 24px write em(12, 24)\n\n@function em($pxval, $base: $em-base) {\n @if not unitless($pxval) {\n $pxval: strip-units($pxval);\n }\n @if not unitless($base) {\n $base: strip-units($base);\n }\n @return ($pxval / $base) * 1em;\n}\n","// Convert pixels to rems\n// eg. for a relational value of 12px write rem(12)\n// Assumes $em-base is the font-size of \n\n@function rem($pxval) {\n @if not unitless($pxval) {\n $pxval: strip-units($pxval);\n }\n\n $base: $em-base;\n @if not unitless($base) {\n $base: strip-units($base);\n }\n @return ($pxval / $base) * 1rem;\n}\n","// Srtips the units from a value. e.g. 12px -> 12\n\n@function strip-units($val) {\n @return ($val / ($val * 0 + 1));\n}\n","// Add percentage of white to a color\n@function tint($color, $percent){\n @return mix(white, $color, $percent);\n}\n\n// Add percentage of black to a color\n@function shade($color, $percent){\n @return mix(black, $color, $percent);\n}\n","// Return vendor-prefixed property names if appropriate\n// Example: transition-property-names((transform, color, background), moz) -> -moz-transform, color, background\n//************************************************************************//\n@function transition-property-names($props, $vendor: false) {\n\t$new-props: ();\n\t\n\t@each $prop in $props {\n\t\t$new-props: append($new-props, transition-property-name($prop, $vendor), comma);\n\t}\n\n\t@return $new-props;\n}\n\n@function transition-property-name($prop, $vendor: false) {\n\t// put other properties that need to be prefixed here aswell\n\t@if $vendor and $prop == transform {\n\t\t@return unquote('-'+$vendor+'-'+$prop);\n\t}\n\t@else {\n\t\t@return $prop;\n\t}\n}","// Convert shorthand to the 4-value syntax\n\n@function unpack($shorthand) {\n @if length($shorthand) == 1 {\n @return nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1);\n }\n @else if length($shorthand) == 2 {\n @return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 1) nth($shorthand, 2);\n }\n @else if length($shorthand) == 3 {\n @return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 3) nth($shorthand, 2);\n }\n @else {\n @return $shorthand;\n }\n}\n\n","// http://www.w3.org/TR/css3-animations/#the-animation-name-property-\n// Each of these mixins support comma separated lists of values, which allows different transitions for individual properties to be described in a single style rule. Each value in the list corresponds to the value at that same position in the other properties.\n\n// Official animation shorthand property.\n@mixin animation ($animations...) {\n @include prefixer(animation, $animations, webkit moz spec);\n}\n\n// Individual Animation Properties\n@mixin animation-name ($names...) {\n @include prefixer(animation-name, $names, webkit moz spec);\n}\n\n\n@mixin animation-duration ($times...) {\n @include prefixer(animation-duration, $times, webkit moz spec);\n}\n\n\n@mixin animation-timing-function ($motions...) {\n// ease | linear | ease-in | ease-out | ease-in-out\n @include prefixer(animation-timing-function, $motions, webkit moz spec);\n}\n\n\n@mixin animation-iteration-count ($values...) {\n// infinite | \n @include prefixer(animation-iteration-count, $values, webkit moz spec);\n}\n\n\n@mixin animation-direction ($directions...) {\n// normal | alternate\n @include prefixer(animation-direction, $directions, webkit moz spec);\n}\n\n\n@mixin animation-play-state ($states...) {\n// running | paused\n @include prefixer(animation-play-state, $states, webkit moz spec);\n}\n\n\n@mixin animation-delay ($times...) {\n @include prefixer(animation-delay, $times, webkit moz spec);\n}\n\n\n@mixin animation-fill-mode ($modes...) {\n// none | forwards | backwards | both\n @include prefixer(animation-fill-mode, $modes, webkit moz spec);\n}\n","@mixin appearance ($value) {\n @include prefixer(appearance, $value, webkit moz ms o spec);\n}\n","//************************************************************************//\n// Backface-visibility mixin\n//************************************************************************//\n@mixin backface-visibility($visibility) {\n @include prefixer(backface-visibility, $visibility, webkit spec);\n}\n","//************************************************************************//\n// Background property for adding multiple backgrounds using shorthand\n// notation.\n//************************************************************************//\n\n@mixin background($backgrounds...) {\n $webkit-backgrounds: ();\n $spec-backgrounds: ();\n\n @each $background in $backgrounds {\n $webkit-background: ();\n $spec-background: ();\n $background-type: type-of($background);\n\n @if $background-type == string or list {\n $background-str: if($background-type == list, nth($background, 1), $background);\n\n $url-str: str-slice($background-str, 0, 3);\n $gradient-type: str-slice($background-str, 0, 6);\n\n @if $url-str == \"url\" {\n $webkit-background: $background;\n $spec-background: $background;\n }\n\n @else if $gradient-type == \"linear\" {\n $gradients: _linear-gradient-parser(\"#{$background}\");\n $webkit-background: map-get($gradients, webkit-image);\n $spec-background: map-get($gradients, spec-image);\n }\n\n @else if $gradient-type == \"radial\" {\n $gradients: _radial-gradient-parser(\"#{$background}\");\n $webkit-background: map-get($gradients, webkit-image);\n $spec-background: map-get($gradients, spec-image);\n }\n\n @else {\n $webkit-background: $background;\n $spec-background: $background;\n }\n }\n\n @else {\n $webkit-background: $background;\n $spec-background: $background;\n }\n\n $webkit-backgrounds: append($webkit-backgrounds, $webkit-background, comma);\n $spec-backgrounds: append($spec-backgrounds, $spec-background, comma);\n }\n\n background: $webkit-backgrounds;\n background: $spec-backgrounds;\n}\n","//************************************************************************//\n// Background-image property for adding multiple background images with\n// gradients, or for stringing multiple gradients together.\n//************************************************************************//\n\n@mixin background-image($images...) {\n $webkit-images: ();\n $spec-images: ();\n\n @each $image in $images {\n $webkit-image: ();\n $spec-image: ();\n\n @if (type-of($image) == string) {\n $url-str: str-slice($image, 0, 3);\n $gradient-type: str-slice($image, 0, 6);\n\n @if $url-str == \"url\" {\n $webkit-image: $image;\n $spec-image: $image;\n }\n\n @else if $gradient-type == \"linear\" {\n $gradients: _linear-gradient-parser($image);\n $webkit-image: map-get($gradients, webkit-image);\n $spec-image: map-get($gradients, spec-image);\n }\n\n @else if $gradient-type == \"radial\" {\n $gradients: _radial-gradient-parser($image);\n $webkit-image: map-get($gradients, webkit-image);\n $spec-image: map-get($gradients, spec-image);\n }\n }\n\n $webkit-images: append($webkit-images, $webkit-image, comma);\n $spec-images: append($spec-images, $spec-image, comma);\n }\n\n background-image: $webkit-images;\n background-image: $spec-images;\n}\n","@mixin border-image($borders...) {\n $webkit-borders: ();\n $spec-borders: ();\n\n @each $border in $borders {\n $webkit-border: ();\n $spec-border: ();\n $border-type: type-of($border);\n\n @if $border-type == string or list {\n $border-str: if($border-type == list, nth($border, 1), $border);\n\n $url-str: str-slice($border-str, 0, 3);\n $gradient-type: str-slice($border-str, 0, 6);\n\n @if $url-str == \"url\" {\n $webkit-border: $border;\n $spec-border: $border;\n }\n\n @else if $gradient-type == \"linear\" {\n $gradients: _linear-gradient-parser(\"#{$border}\");\n $webkit-border: map-get($gradients, webkit-image);\n $spec-border: map-get($gradients, spec-image);\n }\n\n @else if $gradient-type == \"radial\" {\n $gradients: _radial-gradient-parser(\"#{$border}\");\n $webkit-border: map-get($gradients, webkit-image);\n $spec-border: map-get($gradients, spec-image);\n }\n\n @else {\n $webkit-border: $border;\n $spec-border: $border;\n }\n }\n\n @else {\n $webkit-border: $border;\n $spec-border: $border;\n }\n\n $webkit-borders: append($webkit-borders, $webkit-border, comma);\n $spec-borders: append($spec-borders, $spec-border, comma);\n }\n\n -webkit-border-image: $webkit-borders;\n border-image: $spec-borders;\n border-style: solid;\n}\n\n//Examples:\n// @include border-image(url(\"image.png\"));\n// @include border-image(url(\"image.png\") 20 stretch);\n// @include border-image(linear-gradient(45deg, orange, yellow));\n// @include border-image(linear-gradient(45deg, orange, yellow) stretch);\n// @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round);\n// @include border-image(radial-gradient(top, cover, orange, yellow, orange));\n","//************************************************************************//\n// Shorthand Border-radius mixins\n//************************************************************************//\n@mixin border-top-radius($radii) {\n @include prefixer(border-top-left-radius, $radii, spec);\n @include prefixer(border-top-right-radius, $radii, spec);\n}\n\n@mixin border-bottom-radius($radii) {\n @include prefixer(border-bottom-left-radius, $radii, spec);\n @include prefixer(border-bottom-right-radius, $radii, spec);\n}\n\n@mixin border-left-radius($radii) {\n @include prefixer(border-top-left-radius, $radii, spec);\n @include prefixer(border-bottom-left-radius, $radii, spec);\n}\n\n@mixin border-right-radius($radii) {\n @include prefixer(border-top-right-radius, $radii, spec);\n @include prefixer(border-bottom-right-radius, $radii, spec);\n}\n","@mixin box-sizing ($box) {\n// content-box | border-box | inherit\n @include prefixer(box-sizing, $box, webkit moz spec);\n}\n","@mixin calc($property, $value) {\n #{$property}: -webkit-calc(#{$value});\n #{$property}: calc(#{$value});\n}\n","@mixin columns($arg: auto) {\n// || \n @include prefixer(columns, $arg, webkit moz spec);\n}\n\n@mixin column-count($int: auto) {\n// auto || integer\n @include prefixer(column-count, $int, webkit moz spec);\n}\n\n@mixin column-gap($length: normal) {\n// normal || length\n @include prefixer(column-gap, $length, webkit moz spec);\n}\n\n@mixin column-fill($arg: auto) {\n// auto || length\n @include prefixer(column-fill, $arg, webkit moz spec);\n}\n\n@mixin column-rule($arg) {\n// || || \n @include prefixer(column-rule, $arg, webkit moz spec);\n}\n\n@mixin column-rule-color($color) {\n @include prefixer(column-rule-color, $color, webkit moz spec);\n}\n\n@mixin column-rule-style($style: none) {\n// none | hidden | dashed | dotted | double | groove | inset | inset | outset | ridge | solid\n @include prefixer(column-rule-style, $style, webkit moz spec);\n}\n\n@mixin column-rule-width ($width: none) {\n @include prefixer(column-rule-width, $width, webkit moz spec);\n}\n\n@mixin column-span($arg: none) {\n// none || all\n @include prefixer(column-span, $arg, webkit moz spec);\n}\n\n@mixin column-width($length: auto) {\n// auto || length\n @include prefixer(column-width, $length, webkit moz spec);\n}\n","@mixin filter($function: none) {\n // [\n @include prefixer(perspective, $depth, webkit moz spec);\n}\n\n@mixin perspective-origin($value: 50% 50%) {\n @include prefixer(perspective-origin, $value, webkit moz spec);\n}\n","// Requires Sass 3.1+\n@mixin radial-gradient($G1, $G2,\n $G3: null, $G4: null,\n $G5: null, $G6: null,\n $G7: null, $G8: null,\n $G9: null, $G10: null,\n $pos: null,\n $shape-size: null,\n $fallback: null) {\n\n $data: _radial-arg-parser($G1, $G2, $pos, $shape-size);\n $G1: nth($data, 1);\n $G2: nth($data, 2);\n $pos: nth($data, 3);\n $shape-size: nth($data, 4);\n\n $full: $G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10;\n\n // Strip deprecated cover/contain for spec\n $shape-size-spec: _shape-size-stripper($shape-size);\n\n // Set $G1 as the default fallback color\n $first-color: nth($full, 1);\n $fallback-color: nth($first-color, 1);\n\n @if (type-of($fallback) == color) or ($fallback == \"transparent\") {\n $fallback-color: $fallback;\n }\n\n // Add Commas and spaces\n $shape-size: if($shape-size, '#{$shape-size}, ', null);\n $pos: if($pos, '#{$pos}, ', null);\n $pos-spec: if($pos, 'at #{$pos}', null);\n $shape-size-spec: if(($shape-size-spec != ' ') and ($pos == null), '#{$shape-size-spec}, ', '#{$shape-size-spec} ');\n\n background-color: $fallback-color;\n background-image: -webkit-radial-gradient(unquote(#{$pos}#{$shape-size}#{$full}));\n background-image: unquote(\"radial-gradient(#{$shape-size-spec}#{$pos-spec}#{$full})\");\n}\n","@mixin transform($property: none) {\n// none | \n @include prefixer(transform, $property, webkit moz ms o spec);\n}\n\n@mixin transform-origin($axes: 50%) {\n// x-axis - left | center | right | length | %\n// y-axis - top | center | bottom | length | %\n// z-axis - length\n @include prefixer(transform-origin, $axes, webkit moz ms o spec);\n}\n\n@mixin transform-style ($style: flat) {\n @include prefixer(transform-style, $style, webkit moz ms o spec);\n}\n","// Shorthand mixin. Supports multiple parentheses-deliminated values for each variable.\n// Example: @include transition (all 2s ease-in-out);\n// @include transition (opacity 1s ease-in 2s, width 2s ease-out);\n// @include transition-property (transform, opacity);\n\n@mixin transition ($properties...) {\n // Fix for vendor-prefix transform property\n $needs-prefixes: false;\n $webkit: ();\n $moz: ();\n $spec: ();\n\n // Create lists for vendor-prefixed transform\n @each $list in $properties {\n @if nth($list, 1) == \"transform\" {\n $needs-prefixes: true;\n $list1: -webkit-transform;\n $list2: -moz-transform;\n $list3: ();\n\n @each $var in $list {\n $list3: join($list3, $var);\n\n @if $var != \"transform\" {\n $list1: join($list1, $var);\n $list2: join($list2, $var);\n }\n }\n\n $webkit: append($webkit, $list1);\n $moz: append($moz, $list2);\n $spec: append($spec, $list3);\n }\n\n // Create lists for non-prefixed transition properties\n @else {\n $webkit: append($webkit, $list, comma);\n $moz: append($moz, $list, comma);\n $spec: append($spec, $list, comma);\n }\n }\n\n @if $needs-prefixes {\n -webkit-transition: $webkit;\n -moz-transition: $moz;\n transition: $spec;\n }\n @else {\n @if length($properties) >= 1 {\n @include prefixer(transition, $properties, webkit moz spec);\n }\n\n @else {\n $properties: all 0.15s ease-out 0s;\n @include prefixer(transition, $properties, webkit moz spec);\n }\n }\n}\n\n@mixin transition-property ($properties...) {\n -webkit-transition-property: transition-property-names($properties, 'webkit');\n -moz-transition-property: transition-property-names($properties, 'moz');\n transition-property: transition-property-names($properties, false);\n}\n\n@mixin transition-duration ($times...) {\n @include prefixer(transition-duration, $times, webkit moz spec);\n}\n\n@mixin transition-timing-function ($motions...) {\n// ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier()\n @include prefixer(transition-timing-function, $motions, webkit moz spec);\n}\n\n@mixin transition-delay ($times...) {\n @include prefixer(transition-delay, $times, webkit moz spec);\n}\n","@mixin user-select($arg: none) {\n @include prefixer(user-select, $arg, webkit moz ms spec);\n}\n","@mixin placeholder {\n $placeholders: \":-webkit-input\" \":-moz\" \"-moz\" \"-ms-input\";\n @each $placeholder in $placeholders {\n &:#{$placeholder}-placeholder {\n @content;\n }\n }\n}\n","@mixin button ($style: simple, $base-color: #4294f0, $text-size: inherit, $padding: 7px 18px) {\n\n @if type-of($style) == string and type-of($base-color) == color {\n @include buttonstyle($style, $base-color, $text-size, $padding);\n }\n\n @if type-of($style) == string and type-of($base-color) == number {\n $padding: $text-size;\n $text-size: $base-color;\n $base-color: #4294f0;\n\n @if $padding == inherit {\n $padding: 7px 18px;\n }\n\n @include buttonstyle($style, $base-color, $text-size, $padding);\n }\n\n @if type-of($style) == color and type-of($base-color) == color {\n $base-color: $style;\n $style: simple;\n @include buttonstyle($style, $base-color, $text-size, $padding);\n }\n\n @if type-of($style) == color and type-of($base-color) == number {\n $padding: $text-size;\n $text-size: $base-color;\n $base-color: $style;\n $style: simple;\n\n @if $padding == inherit {\n $padding: 7px 18px;\n }\n\n @include buttonstyle($style, $base-color, $text-size, $padding);\n }\n\n @if type-of($style) == number {\n $padding: $base-color;\n $text-size: $style;\n $base-color: #4294f0;\n $style: simple;\n\n @if $padding == #4294f0 {\n $padding: 7px 18px;\n }\n\n @include buttonstyle($style, $base-color, $text-size, $padding);\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n}\n\n\n// Selector Style Button\n//************************************************************************//\n@mixin buttonstyle($type, $b-color, $t-size, $pad) {\n // Grayscale button\n @if $type == simple and $b-color == grayscale($b-color) {\n @include simple($b-color, true, $t-size, $pad);\n }\n\n @if $type == shiny and $b-color == grayscale($b-color) {\n @include shiny($b-color, true, $t-size, $pad);\n }\n\n @if $type == pill and $b-color == grayscale($b-color) {\n @include pill($b-color, true, $t-size, $pad);\n }\n\n @if $type == flat and $b-color == grayscale($b-color) {\n @include flat($b-color, true, $t-size, $pad);\n }\n\n // Colored button\n @if $type == simple {\n @include simple($b-color, false, $t-size, $pad);\n }\n\n @else if $type == shiny {\n @include shiny($b-color, false, $t-size, $pad);\n }\n\n @else if $type == pill {\n @include pill($b-color, false, $t-size, $pad);\n }\n\n @else if $type == flat {\n @include flat($b-color, false, $t-size, $pad);\n }\n}\n\n\n// Simple Button\n//************************************************************************//\n@mixin simple($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {\n $color: hsl(0, 0, 100%);\n $border: adjust-color($base-color, $saturation: 9%, $lightness: -14%);\n $inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%);\n $stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%);\n $text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%);\n\n @if is-light($base-color) {\n $color: hsl(0, 0, 20%);\n $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);\n }\n\n @if $grayscale == true {\n $border: grayscale($border);\n $inset-shadow: grayscale($inset-shadow);\n $stop-gradient: grayscale($stop-gradient);\n $text-shadow: grayscale($text-shadow);\n }\n\n border: 1px solid $border;\n border-radius: 3px;\n box-shadow: inset 0 1px 0 0 $inset-shadow;\n color: $color;\n display: inline-block;\n font-size: $textsize;\n font-weight: bold;\n @include linear-gradient ($base-color, $stop-gradient);\n padding: $padding;\n text-decoration: none;\n text-shadow: 0 1px 0 $text-shadow;\n background-clip: padding-box;\n\n &:hover:not(:disabled) {\n $base-color-hover: adjust-color($base-color, $saturation: -4%, $lightness: -5%);\n $inset-shadow-hover: adjust-color($base-color, $saturation: -7%, $lightness: 5%);\n $stop-gradient-hover: adjust-color($base-color, $saturation: 8%, $lightness: -14%);\n\n @if $grayscale == true {\n $base-color-hover: grayscale($base-color-hover);\n $inset-shadow-hover: grayscale($inset-shadow-hover);\n $stop-gradient-hover: grayscale($stop-gradient-hover);\n }\n\n box-shadow: inset 0 1px 0 0 $inset-shadow-hover;\n cursor: pointer;\n @include linear-gradient ($base-color-hover, $stop-gradient-hover);\n }\n\n &:active:not(:disabled),\n &:focus:not(:disabled) {\n $border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%);\n $inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%);\n\n @if $grayscale == true {\n $border-active: grayscale($border-active);\n $inset-shadow-active: grayscale($inset-shadow-active);\n }\n\n border: 1px solid $border-active;\n box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active;\n }\n}\n\n\n// Shiny Button\n//************************************************************************//\n@mixin shiny($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {\n $color: hsl(0, 0, 100%);\n $border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81);\n $border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122);\n $fourth-stop: adjust-color($base-color, $red: -79, $green: -70, $blue: -46);\n $inset-shadow: adjust-color($base-color, $red: 37, $green: 29, $blue: 12);\n $second-stop: adjust-color($base-color, $red: -56, $green: -50, $blue: -33);\n $text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114);\n $third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48);\n\n @if is-light($base-color) {\n $color: hsl(0, 0, 20%);\n $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);\n }\n\n @if $grayscale == true {\n $border: grayscale($border);\n $border-bottom: grayscale($border-bottom);\n $fourth-stop: grayscale($fourth-stop);\n $inset-shadow: grayscale($inset-shadow);\n $second-stop: grayscale($second-stop);\n $text-shadow: grayscale($text-shadow);\n $third-stop: grayscale($third-stop);\n }\n\n border: 1px solid $border;\n border-bottom: 1px solid $border-bottom;\n border-radius: 5px;\n box-shadow: inset 0 1px 0 0 $inset-shadow;\n color: $color;\n display: inline-block;\n font-size: $textsize;\n font-weight: bold;\n @include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%);\n padding: $padding;\n text-align: center;\n text-decoration: none;\n text-shadow: 0 -1px 1px $text-shadow;\n\n &:hover:not(:disabled) {\n $first-stop-hover: adjust-color($base-color, $red: -13, $green: -15, $blue: -18);\n $second-stop-hover: adjust-color($base-color, $red: -66, $green: -62, $blue: -51);\n $third-stop-hover: adjust-color($base-color, $red: -93, $green: -85, $blue: -66);\n $fourth-stop-hover: adjust-color($base-color, $red: -86, $green: -80, $blue: -63);\n\n @if $grayscale == true {\n $first-stop-hover: grayscale($first-stop-hover);\n $second-stop-hover: grayscale($second-stop-hover);\n $third-stop-hover: grayscale($third-stop-hover);\n $fourth-stop-hover: grayscale($fourth-stop-hover);\n }\n\n cursor: pointer;\n @include linear-gradient(top, $first-stop-hover 0%,\n $second-stop-hover 50%,\n $third-stop-hover 50%,\n $fourth-stop-hover 100%);\n }\n\n &:active:not(:disabled),\n &:focus:not(:disabled) {\n $inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122);\n\n @if $grayscale == true {\n $inset-shadow-active: grayscale($inset-shadow-active);\n }\n\n box-shadow: inset 0 0 20px 0 $inset-shadow-active;\n }\n}\n\n\n// Pill Button\n//************************************************************************//\n@mixin pill($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {\n $color: hsl(0, 0, 100%);\n $border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%);\n $border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%);\n $border-top: adjust-color($base-color, $hue: -1, $saturation: -30%, $lightness: -15%);\n $inset-shadow: adjust-color($base-color, $hue: -1, $saturation: -1%, $lightness: 7%);\n $stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%);\n $text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%);\n\n @if is-light($base-color) {\n $color: hsl(0, 0, 20%);\n $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);\n }\n\n @if $grayscale == true {\n $border-bottom: grayscale($border-bottom);\n $border-sides: grayscale($border-sides);\n $border-top: grayscale($border-top);\n $inset-shadow: grayscale($inset-shadow);\n $stop-gradient: grayscale($stop-gradient);\n $text-shadow: grayscale($text-shadow);\n }\n\n border: 1px solid $border-top;\n border-color: $border-top $border-sides $border-bottom;\n border-radius: 16px;\n box-shadow: inset 0 1px 0 0 $inset-shadow;\n color: $color;\n display: inline-block;\n font-size: $textsize;\n font-weight: normal;\n line-height: 1;\n @include linear-gradient ($base-color, $stop-gradient);\n padding: $padding;\n text-align: center;\n text-decoration: none;\n text-shadow: 0 -1px 1px $text-shadow;\n background-clip: padding-box;\n\n &:hover:not(:disabled) {\n $base-color-hover: adjust-color($base-color, $lightness: -4.5%);\n $border-bottom: adjust-color($base-color, $hue: 8, $saturation: 13.5%, $lightness: -32%);\n $border-sides: adjust-color($base-color, $hue: 4, $saturation: -2%, $lightness: -27%);\n $border-top: adjust-color($base-color, $hue: -1, $saturation: -17%, $lightness: -21%);\n $inset-shadow-hover: adjust-color($base-color, $saturation: -1%, $lightness: 3%);\n $stop-gradient-hover: adjust-color($base-color, $hue: 8, $saturation: -4%, $lightness: -15.5%);\n $text-shadow-hover: adjust-color($base-color, $hue: 5, $saturation: -5%, $lightness: -22%);\n\n @if $grayscale == true {\n $base-color-hover: grayscale($base-color-hover);\n $border-bottom: grayscale($border-bottom);\n $border-sides: grayscale($border-sides);\n $border-top: grayscale($border-top);\n $inset-shadow-hover: grayscale($inset-shadow-hover);\n $stop-gradient-hover: grayscale($stop-gradient-hover);\n $text-shadow-hover: grayscale($text-shadow-hover);\n }\n\n border: 1px solid $border-top;\n border-color: $border-top $border-sides $border-bottom;\n box-shadow: inset 0 1px 0 0 $inset-shadow-hover;\n cursor: pointer;\n @include linear-gradient ($base-color-hover, $stop-gradient-hover);\n text-shadow: 0 -1px 1px $text-shadow-hover;\n background-clip: padding-box;\n }\n\n &:active:not(:disabled),\n &:focus:not(:disabled) {\n $active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%);\n $border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%);\n $border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%);\n $inset-shadow-active: adjust-color($base-color, $hue: 9, $saturation: 2%, $lightness: -21.5%);\n $text-shadow-active: adjust-color($base-color, $hue: 5, $saturation: -12%, $lightness: -21.5%);\n\n @if $grayscale == true {\n $active-color: grayscale($active-color);\n $border-active: grayscale($border-active);\n $border-bottom-active: grayscale($border-bottom-active);\n $inset-shadow-active: grayscale($inset-shadow-active);\n $text-shadow-active: grayscale($text-shadow-active);\n }\n\n background: $active-color;\n border: 1px solid $border-active;\n border-bottom: 1px solid $border-bottom-active;\n box-shadow: inset 0 0 6px 3px $inset-shadow-active;\n text-shadow: 0 -1px 1px $text-shadow-active;\n }\n}\n\n\n\n// Flat Button\n//************************************************************************//\n@mixin flat($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {\n $color: hsl(0, 0, 100%);\n\n @if is-light($base-color) {\n $color: hsl(0, 0, 20%);\n }\n\n background-color: $base-color;\n border-radius: 3px;\n border: none;\n color: $color;\n display: inline-block;\n font-size: inherit;\n font-weight: bold;\n padding: 7px 18px;\n text-decoration: none;\n background-clip: padding-box;\n\n &:hover:not(:disabled){\n $base-color-hover: adjust-color($base-color, $saturation: 4%, $lightness: 5%);\n\n @if $grayscale == true {\n $base-color-hover: grayscale($base-color-hover);\n }\n\n background-color: $base-color-hover;\n cursor: pointer;\n }\n\n &:active:not(:disabled),\n &:focus:not(:disabled) {\n $base-color-active: adjust-color($base-color, $saturation: -4%, $lightness: -5%);\n\n @if $grayscale == true {\n $base-color-active: grayscale($base-color-active);\n }\n\n background-color: $base-color-active;\n cursor: pointer;\n }\n}\n","// Modern micro clearfix provides an easy way to contain floats without adding additional markup.\n//\n// Example usage:\n//\n// // Contain all floats within .wrapper\n// .wrapper {\n// @include clearfix;\n// .content,\n// .sidebar {\n// float : left;\n// }\n// }\n\n@mixin clearfix {\n &:after {\n content:\"\";\n display:table;\n clear:both;\n }\n}\n\n// Acknowledgements\n// Beat *that* clearfix: [Thierry Koblentz](http://www.css-101.org/articles/clearfix/latest-new-clearfix-so-far.php)\n","// directional-property mixins are shorthands\n// for writing properties like the following\n//\n// @include margin(null 0 10px);\n// ------\n// margin-right: 0;\n// margin-bottom: 10px;\n// margin-left: 0;\n//\n// - or -\n//\n// @include border-style(dotted null);\n// ------\n// border-top-style: dotted;\n// border-bottom-style: dotted;\n//\n// ------\n//\n// Note: You can also use false instead of null\n\n@function collapse-directionals($vals) {\n $output: null;\n\n $A: nth( $vals, 1 );\n $B: if( length($vals) < 2, $A, nth($vals, 2));\n $C: if( length($vals) < 3, $A, nth($vals, 3));\n $D: if( length($vals) < 2, $A, nth($vals, if( length($vals) < 4, 2, 4) ));\n\n @if $A == 0 { $A: 0 }\n @if $B == 0 { $B: 0 }\n @if $C == 0 { $C: 0 }\n @if $D == 0 { $D: 0 }\n\n @if $A == $B and $A == $C and $A == $D { $output: $A }\n @else if $A == $C and $B == $D { $output: $A $B }\n @else if $B == $D { $output: $A $B $C }\n @else { $output: $A $B $C $D }\n\n @return $output;\n}\n\n@function contains-falsy($list) {\n @each $item in $list {\n @if not $item {\n @return true;\n }\n }\n\n @return false;\n}\n\n@mixin directional-property($pre, $suf, $vals) {\n // Property Names\n $top: $pre + \"-top\" + if($suf, \"-#{$suf}\", \"\");\n $bottom: $pre + \"-bottom\" + if($suf, \"-#{$suf}\", \"\");\n $left: $pre + \"-left\" + if($suf, \"-#{$suf}\", \"\");\n $right: $pre + \"-right\" + if($suf, \"-#{$suf}\", \"\");\n $all: $pre + if($suf, \"-#{$suf}\", \"\");\n\n $vals: collapse-directionals($vals);\n\n @if contains-falsy($vals) {\n @if nth($vals, 1) { #{$top}: nth($vals, 1); }\n\n @if length($vals) == 1 {\n @if nth($vals, 1) { #{$right}: nth($vals, 1); }\n } @else {\n @if nth($vals, 2) { #{$right}: nth($vals, 2); }\n }\n\n // prop: top/bottom right/left\n @if length($vals) == 2 {\n @if nth($vals, 1) { #{$bottom}: nth($vals, 1); }\n @if nth($vals, 2) { #{$left}: nth($vals, 2); }\n\n // prop: top right/left bottom\n } @else if length($vals) == 3 {\n @if nth($vals, 3) { #{$bottom}: nth($vals, 3); }\n @if nth($vals, 2) { #{$left}: nth($vals, 2); }\n\n // prop: top right bottom left\n } @else if length($vals) == 4 {\n @if nth($vals, 3) { #{$bottom}: nth($vals, 3); }\n @if nth($vals, 4) { #{$left}: nth($vals, 4); }\n }\n\n // prop: top/right/bottom/left\n } @else {\n #{$all}: $vals;\n }\n}\n\n@mixin margin($vals...) {\n @include directional-property(margin, false, $vals...);\n}\n\n@mixin padding($vals...) {\n @include directional-property(padding, false, $vals...);\n}\n\n@mixin border-style($vals...) {\n @include directional-property(border, style, $vals...);\n}\n\n@mixin border-color($vals...) {\n @include directional-property(border, color, $vals...);\n}\n\n@mixin border-width($vals...) {\n @include directional-property(border, width, $vals...);\n}\n","@mixin ellipsis($width: 100%) {\n display: inline-block;\n max-width: $width;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n","$georgia: Georgia, Cambria, \"Times New Roman\", Times, serif;\n$helvetica: \"Helvetica Neue\", Helvetica, Roboto, Arial, sans-serif;\n$lucida-grande: \"Lucida Grande\", Tahoma, Verdana, Arial, sans-serif;\n$monospace: \"Bitstream Vera Sans Mono\", Consolas, Courier, monospace;\n$verdana: Verdana, Geneva, sans-serif;\n","@mixin hide-text {\n overflow: hidden;\n\n &:before {\n content: \"\";\n display: block;\n width: 0;\n height: 100%;\n }\n}\n","//************************************************************************//\n// Generate a variable ($all-text-inputs) with a list of all html5\n// input types that have a text-based input, excluding textarea.\n// http://diveintohtml5.org/forms.html\n//************************************************************************//\n$inputs-list: 'input[type=\"email\"]',\n 'input[type=\"number\"]',\n 'input[type=\"password\"]',\n 'input[type=\"search\"]',\n 'input[type=\"tel\"]',\n 'input[type=\"text\"]',\n 'input[type=\"url\"]',\n\n // Webkit & Gecko may change the display of these in the future\n 'input[type=\"color\"]',\n 'input[type=\"date\"]',\n 'input[type=\"datetime\"]',\n 'input[type=\"datetime-local\"]',\n 'input[type=\"month\"]',\n 'input[type=\"time\"]',\n 'input[type=\"week\"]';\n\n// Bare inputs\n//************************************************************************//\n$all-text-inputs: assign-inputs($inputs-list);\n\n// Hover Pseudo-class\n//************************************************************************//\n$all-text-inputs-hover: assign-inputs($inputs-list, hover);\n\n// Focus Pseudo-class\n//************************************************************************//\n$all-text-inputs-focus: assign-inputs($inputs-list, focus);\n\n\n\n// You must use interpolation on the variable:\n// #{$all-text-inputs}\n// #{$all-text-inputs-hover}\n// #{$all-text-inputs-focus}\n\n// Example\n//************************************************************************//\n// #{$all-text-inputs}, textarea {\n// border: 1px solid red;\n// }\n\n\n\n//************************************************************************//\n// Generate a variable ($all-button-inputs) with a list of all html5\n// input types that have a button-based input, excluding button.\n//************************************************************************//\n$inputs-button-list: 'input[type=\"button\"]',\n 'input[type=\"reset\"]',\n 'input[type=\"submit\"]';\n\n// Bare inputs\n//************************************************************************//\n$all-button-inputs: assign-inputs($inputs-button-list);\n\n// Hover Pseudo-class\n//************************************************************************//\n$all-button-inputs-hover: assign-inputs($inputs-button-list, hover);\n\n// Focus Pseudo-class\n//************************************************************************//\n$all-button-inputs-focus: assign-inputs($inputs-button-list, focus);\n\n// Active Pseudo-class\n//************************************************************************//\n$all-button-inputs-active: assign-inputs($inputs-button-list, active);\n\n\n\n// You must use interpolation on the variable:\n// #{$all-button-inputs}\n// #{$all-button-inputs-hover}\n// #{$all-button-inputs-focus}\n// #{$all-button-inputs-active}\n\n// Example\n//************************************************************************//\n// #{$all-button-inputs}, button {\n// border: 1px solid red;\n// }\n","@mixin position ($position: relative, $coordinates: null null null null) {\n\n @if type-of($position) == list {\n $coordinates: $position;\n $position: relative;\n }\n\n $coordinates: unpack($coordinates);\n\n $top: nth($coordinates, 1);\n $right: nth($coordinates, 2);\n $bottom: nth($coordinates, 3);\n $left: nth($coordinates, 4);\n\n position: $position;\n\n @if ($top and $top == auto) or (type-of($top) == number) {\n top: $top;\n }\n\n @if ($right and $right == auto) or (type-of($right) == number) {\n right: $right;\n }\n\n @if ($bottom and $bottom == auto) or (type-of($bottom) == number) {\n bottom: $bottom;\n }\n\n @if ($left and $left == auto) or (type-of($left) == number) {\n left: $left;\n }\n}\n","//************************************************************************//\n// Example: @include prefixer(border-radius, $radii, webkit ms spec);\n//************************************************************************//\n// Variables located in /settings/_prefixer.scss\n\n@mixin prefixer ($property, $value, $prefixes) {\n @each $prefix in $prefixes {\n @if $prefix == webkit {\n @if $prefix-for-webkit {\n -webkit-#{$property}: $value;\n }\n }\n @else if $prefix == moz {\n @if $prefix-for-mozilla {\n -moz-#{$property}: $value;\n }\n }\n @else if $prefix == ms {\n @if $prefix-for-microsoft {\n -ms-#{$property}: $value;\n }\n }\n @else if $prefix == o {\n @if $prefix-for-opera {\n -o-#{$property}: $value;\n }\n }\n @else if $prefix == spec {\n @if $prefix-for-spec {\n #{$property}: $value;\n }\n }\n @else {\n @warn \"Unrecognized prefix: #{$prefix}\";\n }\n }\n}\n\n@mixin disable-prefix-for-all() {\n $prefix-for-webkit: false !global;\n $prefix-for-mozilla: false !global;\n $prefix-for-microsoft: false !global;\n $prefix-for-opera: false !global;\n $prefix-for-spec: false !global;\n}\n","@mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $retina-suffix: _2x, $asset-pipeline: false) {\n @if $asset-pipeline {\n background-image: image-url(\"#{$filename}.#{$extension}\");\n }\n @else {\n background-image: url(\"#{$filename}.#{$extension}\");\n }\n\n @include hidpi {\n @if $asset-pipeline {\n @if $retina-filename {\n background-image: image-url(\"#{$retina-filename}.#{$extension}\");\n }\n @else {\n background-image: image-url(\"#{$filename}#{$retina-suffix}.#{$extension}\");\n }\n }\n\n @else {\n @if $retina-filename {\n background-image: url(\"#{$retina-filename}.#{$extension}\");\n }\n @else {\n background-image: url(\"#{$filename}#{$retina-suffix}.#{$extension}\");\n }\n }\n\n background-size: $background-size;\n\n }\n}\n","@mixin size($size) {\n $height: nth($size, 1);\n $width: $height;\n\n @if length($size) > 1 {\n $height: nth($size, 2);\n }\n\n @if $height == auto or (type-of($height) == number and not unitless($height)) {\n height: $height;\n }\n\n @if $width == auto or (type-of($height) == number and not unitless($width)) {\n width: $width;\n }\n}\n","// CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie)\n// Timing functions are the same as demo'ed here: http://jqueryui.com/demos/effect/easing.html\n\n// EASE IN\n$ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530);\n$ease-in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190);\n$ease-in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220);\n$ease-in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060);\n$ease-in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715);\n$ease-in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035);\n$ease-in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335);\n$ease-in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045);\n\n// EASE OUT\n$ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940);\n$ease-out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000);\n$ease-out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000);\n$ease-out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000);\n$ease-out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000);\n$ease-out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000);\n$ease-out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000);\n$ease-out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275);\n\n// EASE IN OUT\n$ease-in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955);\n$ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000);\n$ease-in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000);\n$ease-in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000);\n$ease-in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950);\n$ease-in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000);\n$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860);\n$ease-in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550);\n","@mixin triangle ($size, $color, $direction) {\n height: 0;\n width: 0;\n\n $width: nth($size, 1);\n $height: nth($size, length($size));\n\n $foreground-color: nth($color, 1);\n $background-color: if(length($color) == 2, nth($color, 2), transparent);\n\n @if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) {\n\n $width: $width / 2;\n $height: if(length($size) > 1, $height, $height/2);\n\n @if $direction == up {\n border-left: $width solid $background-color;\n border-right: $width solid $background-color;\n border-bottom: $height solid $foreground-color;\n\n } @else if $direction == right {\n border-top: $width solid $background-color;\n border-bottom: $width solid $background-color;\n border-left: $height solid $foreground-color;\n\n } @else if $direction == down {\n border-left: $width solid $background-color;\n border-right: $width solid $background-color;\n border-top: $height solid $foreground-color;\n\n } @else if $direction == left {\n border-top: $width solid $background-color;\n border-bottom: $width solid $background-color;\n border-right: $height solid $foreground-color;\n }\n }\n\n @else if ($direction == up-right) or ($direction == up-left) {\n border-top: $height solid $foreground-color;\n\n @if $direction == up-right {\n border-left: $width solid $background-color;\n\n } @else if $direction == up-left {\n border-right: $width solid $background-color;\n }\n }\n\n @else if ($direction == down-right) or ($direction == down-left) {\n border-bottom: $height solid $foreground-color;\n\n @if $direction == down-right {\n border-left: $width solid $background-color;\n\n } @else if $direction == down-left {\n border-right: $width solid $background-color;\n }\n }\n\n @else if ($direction == inset-up) {\n border-width: $height $width;\n border-style: solid;\n border-color: $background-color $background-color $foreground-color;\n }\n\n @else if ($direction == inset-down) {\n border-width: $height $width;\n border-style: solid;\n border-color: $foreground-color $background-color $background-color;\n }\n\n @else if ($direction == inset-right) {\n border-width: $width $height;\n border-style: solid;\n border-color: $background-color $background-color $background-color $foreground-color;\n }\n\n @else if ($direction == inset-left) {\n border-width: $width $height;\n border-style: solid;\n border-color: $background-color $foreground-color $background-color $background-color;\n }\n}\n","@mixin word-wrap($wrap: break-word) {\n word-wrap: $wrap;\n\n @if $wrap == break-word {\n overflow-wrap: break-word;\n word-break: break-all;\n }\n}\n","//************************************************************************//\n// These mixins/functions are deprecated\n// They will be removed in the next MAJOR version release\n//************************************************************************//\n@mixin inline-block {\n display: inline-block;\n @warn \"inline-block mixin is deprecated and will be removed in the next major version release\";\n}\n","// Core\n@import \"core\";\n\n// Breakpoints\n@import \"breakpoints\";\n\n// Layout\n@import \"layout\";\n\n// Typography\n@import \"typography\";\n\n// Nav\n@import \"nav\";","// Border Radius\n$core-border-radius:\t\t\trem(3) !default;","// Media Device Breakpoints\n$large-desktop-container:\t\t75.000em !default;\n$desktop-container:\t\t\t\t60.000em !default;\n$tablet-container:\t\t\t\t48.000em !default;\n$large-mobile-container:\t\t30.000em !default;\n$mobile-container:\t\t\t\t100% !default;\n\n// Breakpoint Variables For Particles\n$media: \"all\" !default;\n$mobile-only: \"#{$media} and (max-width:#{$tablet-container - 0.062})\" !default;\n$no-mobile: \"#{$media} and (min-width:#{$tablet-container})\" !default;\n$small-mobile-range: \"#{$media} and (max-width:#{$large-mobile-container})\" !default;\n$large-mobile-range: \"#{$media} and (min-width:#{$large-mobile-container + 0.063}) and (max-width:#{$tablet-container - 0.062})\" !default;\n$tablet-range: \"#{$media} and (min-width:#{$tablet-container}) and (max-width:#{$desktop-container - 0.062})\" !default;\n$desktop-range: \"#{$media} and (min-width:#{$desktop-container}) and (max-width:#{$large-desktop-container - 0.062})\" !default;\n$large-desktop-range: \"#{$media} and (min-width:#{$large-desktop-container})\" !default;\n","// Content Block Spacing Variables\n$content-margin:\t\t\t\t0.625rem !default;\n$content-padding:\t\t\t\t0.938rem !default;\n\n// Fixed Block Variables\n$fixed-block-full:\t\t\t\tpercentage(1/4) !default;\n$fixed-block-desktop:\t\t\tpercentage(1/3) !default;\n$fixed-block-tablet:\t\t\tpercentage(1/2) !default;","// Base Sizes\n$core-font-size:\t\t\t\t.9rem !default;\n$core-line-height:\t\t\t\t1.5 !default;\n\n// Heading Sizes\n$h1-font-size:\t\t\t\t\t$core-font-size + 2.20 !default;\n$h2-font-size:\t\t\t\t\t$core-font-size + 1.50 !default;\n$h3-font-size:\t\t\t\t\t$core-font-size + 1.10 !default;\n$h4-font-size:\t\t\t\t\t$core-font-size + 0.75 !default;\n$h5-font-size:\t\t\t\t\t$core-font-size + 0.35 !default;\n$h6-font-size:\t\t\t\t\t$core-font-size - 0.15 !default;\n\n// Spacing\n$leading-margin:\t\t\t\t$core-line-height * 1rem !default;\n","// Dropdowns\n$dropdown-width:\t\t\t\t140px !default;\n$flyout-width:\t\t\t\t\t140px !default;","// Colors\n@import \"colors\";\n\n// Variables\n@import \"variables\";\n","// Core\n$white:\t\t\t\t\t\t\t#ffffff;\n$black:\t\t\t\t\t\t\t#000000;\n$light-gray:\t\t\t\t\t#dddddd;\n\n$core-border-color:\t\t\t\t$light-gray;\n$core-border-color-hover: \t\tdarken($core-border-color, 10);\n\n$code-text:\t\t\t\t\t\t#c7254e;\n$code-bg:\t\t\t\t\t\t#f9f2f4;\n$pre-text:\t\t\t\t\t\t#237794;\n$pre-bg:\t\t\t\t\t\t#f6f6f6;\n\n// Shadows\n$core-box-shadow: \t\t\t\tinset 0 1px 3px hsla(0, 0%, 0%, 0.06);\n","// Sizes\n$header-height: 5rem;\n$footer-height: 6rem;\n$border-radius: 4px;\n\n// Font Weights\n$font-weight-bold: 600;\n$font-weight-regular: 400;\n$font-weight-light:300;\n\n// Global Paddings\n$padding-default: 1.5rem;\n$padding-horiz: 7rem;\n$padding-vert: 3rem;\n\n//Forms\n$form-label-width: 30%;\n$form-border-radius: 4px;\n$form-border-width: 1px;\n$form-label-pad: 5px 0 5px 3rem;\n$form-input-pad: 5px 10px;\n$form-select-pad: 5px 30px 5px 10px;\n","@import \"direction\";\n@import \"range\";","@function opposite-direction($dir) {\n\t@if $dir == 'left' {\n\t\t@return right;\n\t}\n\t@else if $dir == 'right' {\n\t\t@return left;\n\t}\n\t@else if $dir == 'ltr' {\n\t\t@return rtl;\n\t}\n\t@else if $dir == 'rtl' {\n\t\t@return ltr;\n\t}\n\t@else if $dir == 'top' {\n\t\t@return bottom;\n\t}\n\t@else if $dir == 'bottom' {\n\t\t@return top;\n\t}\n\t@else {\n\t\t@warn \"#{$dir} is not a direction! Make sure your direction is all lowercase!\";\n\t\t@return false;\n\t}\n}\n\n@function named-direction($dir) {\n\t@if $dir == 'ltr' {\n\t\t@return left;\n\t}\n\t@else if $dir == 'rtl' {\n\t\t@return right;\n\t}\n\t@else {\n\t\t@warn \"#{$dir} is not a valid HTML direction! Make sure you are using a valid HTML direction\";\n\t\t@return false;\n\t}\n}","@function lower-bound($range){\n\t@if length($range) <= 0 {\n\t\t@return 0;\n\t}\n\t@return nth($range,1);\n}\n\n@function upper-bound($range) {\n\t@if length($range) < 2 {\n\t\t@return 999999999999;\n\t}\n\t@return nth($range, 2);\n}","@import \"breakpoints\";\n@import \"utilities\";","// Breakpoints\n@mixin breakpoint($breakpoints, $media: all) {\n @each $breakpoint in $breakpoints {\n @if $breakpoint == large-desktop-range {\n @media only #{$media} and (min-width: $large-desktop-container) { @content; }\n }\n @else if $breakpoint == desktop-range {\n @media only #{$media} and (min-width: $desktop-container) and (max-width: $large-desktop-container - 0.062) { @content; }\n }\n @else if $breakpoint == tablet-range {\n @media only #{$media} and (min-width: $tablet-container) and (max-width: $desktop-container - 0.062) { @content; }\n }\n @else if $breakpoint == large-mobile-range {\n @media only #{$media} and (min-width: $large-mobile-container + 0.063) and (max-width: $tablet-container - 0.062) { @content; }\n }\n @else if $breakpoint == mini-nav-range {\n @media only #{$media} and (min-width: $tablet-container - 0.062) and (max-width: $large-desktop-container - 0.062) { @content; }\n }\n @else if $breakpoint == small-mobile-range {\n @media only #{$media} and (max-width: $large-mobile-container) { @content; }\n }\n @else if $breakpoint == no-mobile {\n @media only #{$media} and (min-width: $tablet-container) { @content; }\n }\n @else if $breakpoint == mobile-only {\n @media only #{$media} and (max-width: $tablet-container - 0.062) { @content; }\n }\n @else if $breakpoint == desktop-only {\n @media only #{$media} and (max-width: $desktop-container - 0.062) { @content; }\n }\n @else if $breakpoint == mobile-plus {\n @media only #{$media} and (min-width: $mobile-container - 0.062) { @content; }\n }\n @else if $breakpoint == tablet-plus {\n @media only #{$media} and (min-width: $tablet-container - 0.062) { @content; }\n }\n @else if $breakpoint == desktop-plus {\n @media only #{$media} and (min-width: $desktop-container - 0.062) { @content; }\n }\n @else if $breakpoint == large-desktop-plus {\n @media only #{$media} and (min-width: $large-desktop-container - 0.062) { @content; }\n }\n }\n}\n","\n\n// List Reset\n%list-reset {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n// Vertical Centering\n%vertical-align {\n\tposition: relative;\n\ttop: 50%;\n\t-webkit-transform: translateY(-50%);\n\t-moz-transform: translateY(-50%);\n\t-o-transform: translateY(-50%);\n\t-ms-transform: translateY(-50%);\n\ttransform: translateY(-50%);\n}\n\n// Columns\n@mixin columns($columns) {\n\twidth: percentage(1/$columns);\n}\n\n// Float with margin variable\n@mixin float($direction, $margin: 0) {\n\tfloat: $direction;\n\tmargin-#{opposite-direction($direction)}: $margin;\n}\n","@import \"buttons\";\n@import \"toggle-switch\";\n@import \"datetimepicker\";\n@import \"scrollbars\";\n@import \"colorpicker\";\n","%button {\n\tdisplay: inline-block;\n\tpadding: 0.5rem 1rem;\n font-weight: 400;\n cursor: pointer;\n vertical-align: middle;\n outline: none;\n\n &:active {\n margin: 1px 0 -1px 0;\n }\n\n i {\n //margin-right: 5px;\n }\n\n &.button-small {\n padding: 4px 8px;\n font-size: 95%;\n }\n\n &.button-x-small {\n padding: 2px 5px;\n font-size: 90%;\n }\n\n &:disabled {\n opacity: .6;\n cursor: no-drop;\n }\n\n}\n\n@mixin button-color($color, $text:$white, $lighter:null) {\n\tcolor: rgba($text, 0.85);\n\tborder-radius: $border-radius;\n background: $color;\n\n @if ($lighter == null) {\n $lighter: lightness($color) > 50;\n }\n\n @if ($lighter) {\n &:hover {\n background: shade($color,15%);\n color: $text;\n }\n &.dropdown-toggle {\n border-left: 1px solid lighten($color, 5%);\n }\n } @else {\n &:hover {\n background: tint($color,15%);\n color: $text;\n }\n &.dropdown-toggle {\n border-left: 1px solid darken($color, 5%);\n }\n }\n}\n",".switch-toggle {\n display: inline-block;\n display: inline-flex;\n overflow: hidden;\n border-radius: $form-border-radius;\n line-height: 35px;\n\n input[type=radio] {\n position: absolute;\n visibility: hidden;\n display: none;\n }\n\n label {\n display: inline-block;\n cursor: pointer;\n padding: 0 15px;\n white-space: nowrap;\n }\n}\n",".datetime-picker-wrapper {\n position: relative;\n input {\n padding-right: 2.5rem;\n }\n\n .field-icons {\n position: absolute;\n top: 0;\n right: 10px;\n bottom: 0;\n line-height: 2.5rem;\n }\n}\n\n.collapse {\n display: none;\n &.in {\n display: block;\n }\n\n tr &.in {\n display: table-row;\n }\n\n tbody &.in {\n display: table-row-group;\n }\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n @include transition(height .35s ease, visibility .35s ease);\n}\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0,0,0,0);\n border: 0;\n}\n\n/* Bootstrap variables styling */\n.bootstrap-datetimepicker-widget {\n &.dropdown-menu {\n position: absolute;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 160px;\n background-color: #fff;\n -webkit-background-clip: padding-box;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, .15);\n border-radius: 4px;\n box-shadow: 0 6px 12px rgba(0, 0, 0, .175);\n }\n\n .list-unstyled {\n padding-left: 0;\n list-style: none;\n }\n\n .collapse {\n display: none;\n visibility: hidden;\n\n &.in {\n display: block;\n visibility: visible;\n }\n }\n\n .datepicker-years .picker-switch {\n cursor: default !important;\n background: inherit !important;\n }\n\n .table-condensed > thead > tr > th {\n padding: 5px;\n }\n\n table {\n display: table;\n\n thead {\n display: table-header-group;\n }\n\n tbody {\n display: table-row-group;\n }\n\n tr {\n display: table-row;\n\n &:hover {\n background: inherit;\n }\n\n th, td {\n border: 0;\n display: table-cell;\n &:first-child {\n padding-left: inherit;\n }\n\n &:last-child {\n padding-right: inherit;\n }\n }\n }\n }\n}\n\n\n\n/*!\n * Datetimepicker for Bootstrap 3\n * ! version : 4.7.14\n * https://github.com/Eonasdan/bootstrap-datetimepicker/\n */\n$bs-datetimepicker-timepicker-font-size: 1.2em !default;\n$bs-datetimepicker-active-bg: white !default;\n$bs-datetimepicker-active-color: blue !default;\n$bs-datetimepicker-border-radius: 3px !default;\n$bs-datetimepicker-btn-hover-bg: #ddd !default;\n$bs-datetimepicker-disabled-color: #ccc !default;\n$bs-datetimepicker-alternate-color: #ccc !default;\n$bs-datetimepicker-secondary-border-color: #ccc !default;\n$bs-datetimepicker-secondary-border-color-rgba: rgba(0, 0, 0, 0.2) !default;\n$bs-datetimepicker-primary-border-color: white !default;\n$bs-datetimepicker-text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25) !default;\n\n.bootstrap-datetimepicker-widget {\n list-style: none;\n\n &.dropdown-menu {\n margin: 2px 0;\n padding: 4px;\n width: 19em;\n\n &.timepicker-sbs {\n @media (min-width: 768px) {\n width: 38em;\n }\n\n @media (min-width: 992px) {\n width: 38em;\n }\n\n @media (min-width: 1200px) {\n width: 38em;\n }\n }\n\n &:before, &:after {\n content: '';\n display: inline-block;\n position: absolute;\n }\n\n &.bottom {\n &:before {\n border-left: 7px solid transparent;\n border-right: 7px solid transparent;\n border-bottom: 7px solid $bs-datetimepicker-secondary-border-color;\n border-bottom-color: $bs-datetimepicker-secondary-border-color-rgba;\n top: -7px;\n left: 7px;\n }\n\n &:after {\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-bottom: 6px solid $bs-datetimepicker-primary-border-color;\n top: -6px;\n left: 8px;\n }\n }\n\n &.top {\n &:before {\n border-left: 7px solid transparent;\n border-right: 7px solid transparent;\n border-top: 7px solid $bs-datetimepicker-secondary-border-color;\n border-top-color: $bs-datetimepicker-secondary-border-color-rgba;\n bottom: -7px;\n left: 6px;\n }\n\n &:after {\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-top: 6px solid $bs-datetimepicker-primary-border-color;\n bottom: -6px;\n left: 7px;\n }\n }\n\n &.pull-right {\n &:before {\n left: auto;\n right: 6px;\n }\n\n &:after {\n left: auto;\n right: 7px;\n }\n }\n }\n\n .list-unstyled {\n margin: 0;\n }\n\n a[data-action] {\n padding: 6px 0;\n }\n\n a[data-action]:active {\n box-shadow: none;\n }\n\n .timepicker-hour, .timepicker-minute, .timepicker-second {\n width: 54px;\n font-weight: bold;\n font-size: $bs-datetimepicker-timepicker-font-size;\n margin: 0;\n }\n\n button[data-action] {\n padding: 6px;\n }\n\n .btn[data-action=\"incrementHours\"]::after {\n @extend .sr-only;\n content: \"Increment Hours\";\n }\n\n .btn[data-action=\"incrementMinutes\"]::after {\n @extend .sr-only;\n content: \"Increment Minutes\";\n }\n\n .btn[data-action=\"decrementHours\"]::after {\n @extend .sr-only;\n content: \"Decrement Hours\";\n }\n\n .btn[data-action=\"decrementMinutes\"]::after {\n @extend .sr-only;\n content: \"Decrement Minutes\";\n }\n\n .btn[data-action=\"showHours\"]::after {\n @extend .sr-only;\n content: \"Show Hours\";\n }\n\n .btn[data-action=\"showMinutes\"]::after {\n @extend .sr-only;\n content: \"Show Minutes\";\n }\n\n .btn[data-action=\"togglePeriod\"]::after {\n @extend .sr-only;\n content: \"Toggle AM/PM\";\n }\n\n .btn[data-action=\"clear\"]::after {\n @extend .sr-only;\n content: \"Clear the picker\";\n }\n\n .btn[data-action=\"today\"]::after {\n @extend .sr-only;\n content: \"Set the date to today\";\n }\n\n .picker-switch {\n text-align: center;\n\n &::after {\n @extend .sr-only;\n content: \"Toggle Date and Time Screens\";\n }\n\n td {\n padding: 0;\n margin: 0;\n height: auto;\n width: auto;\n line-height: inherit;\n\n span {\n line-height: 2.5;\n height: 2.5em;\n width: 100%;\n }\n }\n }\n\n table {\n width: 100%;\n margin: 0;\n\n\n & td,\n & th {\n text-align: center;\n border-radius: $bs-datetimepicker-border-radius;\n }\n\n & th {\n height: 29px;\n line-height: 29px;\n width: 29px;\n\n &.picker-switch {\n width: 145px;\n }\n\n &.disabled,\n &.disabled:hover {\n background: none;\n color: $bs-datetimepicker-disabled-color;\n cursor: not-allowed;\n }\n\n &.prev::after {\n @extend .sr-only;\n content: \"Previous Month\";\n }\n\n &.next::after {\n @extend .sr-only;\n content: \"Next Month\";\n }\n }\n\n & thead tr:first-child th {\n cursor: pointer;\n\n &:hover {\n background: $bs-datetimepicker-btn-hover-bg;\n }\n }\n\n & td {\n height: 54px;\n line-height: 54px;\n width: 54px;\n\n &.cw {\n font-size: .8em;\n height: 20px;\n line-height: 20px;\n color: $bs-datetimepicker-alternate-color;\n }\n\n &.day {\n height: 29px;\n line-height: 29px;\n width: 29px;\n }\n\n &.day:hover,\n &.hour:hover,\n &.minute:hover,\n &.second:hover {\n background: $bs-datetimepicker-btn-hover-bg;\n cursor: pointer;\n }\n\n &.old,\n &.new {\n color: $bs-datetimepicker-alternate-color;\n }\n\n &.today {\n position: relative;\n\n &:before {\n content: '';\n display: inline-block;\n border: 0 0 7px 7px solid transparent;\n border-bottom-color: $bs-datetimepicker-active-bg;\n border-top-color: $bs-datetimepicker-secondary-border-color-rgba;\n position: absolute;\n bottom: 4px;\n right: 4px;\n }\n }\n\n &.active,\n &.active:hover {\n background-color: $bs-datetimepicker-active-bg;\n color: $bs-datetimepicker-active-color;\n text-shadow: $bs-datetimepicker-text-shadow;\n }\n\n &.active.today:before {\n border-bottom-color: #fff;\n }\n\n &.disabled,\n &.disabled:hover {\n background: none;\n color: $bs-datetimepicker-disabled-color;\n cursor: not-allowed;\n }\n\n span {\n display: inline-block;\n width: 54px;\n height: 54px;\n line-height: 54px;\n margin: 2px 1.5px;\n cursor: pointer;\n border-radius: $bs-datetimepicker-border-radius;\n\n &:hover {\n background: $bs-datetimepicker-btn-hover-bg;\n }\n\n &.active {\n background-color: $bs-datetimepicker-active-bg;\n color: $bs-datetimepicker-active-color;\n text-shadow: $bs-datetimepicker-text-shadow;\n }\n\n &.old {\n color: $bs-datetimepicker-alternate-color;\n }\n\n &.disabled,\n &.disabled:hover {\n background: none;\n color: $bs-datetimepicker-disabled-color;\n cursor: not-allowed;\n }\n }\n }\n }\n\n &.usetwentyfour {\n td.hour {\n height: 27px;\n line-height: 27px;\n }\n }\n}\n\n.input-group.date {\n & .input-group-addon {\n cursor: pointer;\n }\n}\n","/**\n * gemini-scrollbar\n * @version 1.3.2\n * @link http://noeldelgado.github.io/gemini-scrollbar/\n * @license MIT\n */\n\n/* disable selection while dragging */\n.gm-scrollbar-disable-selection {\n -webkit-touch-callout: none;\n -webkit-user-select: none;\n -khtml-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n/* fallback for native floating scrollbars */\n.gm-prevented {\n -webkit-overflow-scrolling: touch;\n}\n.gm-prevented .gm-scrollbar {\n display: none;\n}\n\n/* actual gemini-scrollbar styles */\n.gm-scrollbar-container {\n position: relative;\n overflow: hidden!important;\n width: 100%;\n height: 100%;\n}\n\n.gm-scrollbar {\n position: absolute;\n right: 2px;\n bottom: 2px;\n z-index: 2;\n border-radius: 3px;\n}\n\n.gm-scrollbar.-vertical {\n width: 6px;\n top: 2px;\n}\n\n.gm-scrollbar.-horizontal {\n height: 6px;\n left: 2px;\n}\n\n.gm-scrollbar .thumb {\n position: relative;\n display: block;\n width: 0;\n height: 0;\n cursor: pointer;\n border-radius: inherit;\n}\n\n.gm-scrollbar.-vertical .thumb {\n width: 100%;\n}\n\n.gm-scrollbar.-horizontal .thumb {\n height: 100%;\n}\n\n.gm-scrollbar-container .gm-scroll-view {\n width: 100%;\n height: 100%;\n overflow: scroll;\n -webkit-overflow-scrolling: touch;\n}\n\n/* @option: autoshow */\n.gm-scrollbar-container.gm-autoshow .gm-scrollbar {\n opacity: 0;\n transition: opacity 120ms ease-out;\n}\n.gm-scrollbar-container.gm-autoshow:hover .gm-scrollbar,\n.gm-scrollbar-container.gm-autoshow:focus .gm-scrollbar {\n opacity: 1;\n transition: opacity 340ms ease-out;\n}\n\n.gm-resize-trigger {\n position: absolute;\n display: block;\n top: 0;\n left: 0;\n height: 100%;\n width: 100%;\n overflow: hidden;\n pointer-events: none;\n z-index: -1;\n visibility: hidden;\n}\n",".g-colorpicker {\n position: relative;\n border-radius: $core-border-radius;\n max-width: 100%;\n\n input {\n width: 100% !important;\n }\n\n i {\n position: absolute;\n top: 15px;\n right: 10px;\n z-index: 2;\n }\n\n .g-colorpicker-preview-wrap {\n cursor: default;\n position: absolute;\n top: 0;\n right: 0;\n width: 43px;\n height: 43px;\n border-radius: 0 3px 3px 0;\n border: 1px solid rgba(0, 0, 0, 0.2);\n\n }\n\n .g-colorpicker-preview {\n position: absolute;\n top: 0px;\n right: 0px;\n bottom: 0px;\n left: 0px;\n border-radius: 0 3px 3px 0;\n }\n}\n\n.cp-sprite {\n background-image: url(data:image/png;base64,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);\n}\n\n/* Panel */\n.cp-wrapper {\n position: absolute;\n width: 173px;\n height: 211px;\n background: white;\n border: solid 1px #CCC;\n box-shadow: 0 0 20px rgba(0, 0, 0, .2);\n z-index: 99999;\n @include box-sizing(content-box);\n display: none;\n}\n\n.cp-wrapper.cp-visible {\n display: block;\n}\n\n/* Panel positioning */\n.cp-position-top .cp-wrapper {\n top: -154px;\n}\n\n.cp-position-right .cp-wrapper {\n right: 0;\n}\n\n.cp-position-bottom .cp-wrapper {\n top: auto;\n}\n\n.cp-position-left .cp-wrapper {\n left: 0;\n}\n\n.cp-with-opacity.cp-wrapper {\n width: 194px;\n}\n\n.cp-wrapper .cp-grid {\n position: absolute;\n top: 1px;\n left: 1px;\n width: 150px;\n height: 150px;\n background-position: -120px 0;\n cursor: crosshair;\n}\n\n.cp-wrapper .cp-grid-inner {\n position: absolute;\n top: 0;\n left: 0;\n width: 150px;\n height: 150px;\n}\n\n.cp-mode-saturation .cp-grid {\n background-position: -420px 0;\n}\n\n.cp-mode-saturation .cp-grid-inner {\n background-position: -270px 0;\n background-image: inherit;\n}\n\n.cp-mode-brightness .cp-grid {\n background-position: -570px 0;\n}\n\n.cp-mode-brightness .cp-grid-inner {\n background-color: black;\n}\n\n.cp-mode-wheel .cp-grid {\n background-position: -720px 0;\n}\n\n.cp-slider,\n.cp-opacity-slider {\n position: absolute;\n top: 1px;\n left: 152px;\n width: 20px;\n height: 150px;\n background-color: white;\n background-position: 0 0;\n cursor: row-resize;\n}\n\n.cp-mode-saturation .cp-slider {\n background-position: -60px 0;\n}\n\n.cp-mode-brightness .cp-slider {\n background-position: -20px 0;\n}\n\n.cp-mode-wheel .cp-slider {\n background-position: -20px 0;\n}\n\n.cp-opacity-slider {\n left: 173px;\n background-position: -40px 0;\n display: none;\n}\n\n.cp-with-opacity .cp-opacity-slider {\n display: block;\n}\n\n/* Pickers */\n.cp-grid .cp-picker {\n position: absolute;\n top: 70px;\n left: 70px;\n width: 12px;\n height: 12px;\n border: solid 1px black;\n border-radius: 10px;\n margin-top: -6px;\n margin-left: -6px;\n background: none;\n}\n\n.cp-grid .cp-picker > div {\n position: absolute;\n top: 0;\n left: 0;\n width: 8px;\n height: 8px;\n border-radius: 8px;\n border: solid 2px white;\n @include box-sizing(content-box);\n}\n\n.cp-picker {\n position: absolute;\n top: 0;\n left: 0;\n width: 18px;\n height: 2px;\n background: white;\n border: solid 1px black;\n margin-top: -2px;\n @include box-sizing(content-box);\n z-index: 2;\n}\n\n/* Tabs */\n.cp-tabs {\n @include box-sizing(border-box);\n position: absolute;\n bottom: 0;\n color: #777;\n left: 0;\n right: 0;\n background: #eee;\n\n > div {\n display: inline-block;\n padding: 6px 0 4px;\n font-family: Helvetica, sans-serif;\n font-size: 11px;\n border-left: 1px solid #ddd;\n width: 48px;\n border-right: 0;\n text-align: center;\n cursor: pointer;\n\n &:first-child {\n border-left: 0;\n }\n\n &.active {\n background-color: #fff;\n }\n\n &.cp-tab-transp {\n width: 100%;\n border-top: 1px solid #ddd;\n }\n }\n}\n\n/* Default theme */\n.cp-theme-default.cp-wrapper {\n width: auto;\n display: inline-block;\n}\n\n.cp-theme-default .cp-input {\n height: 20px;\n width: auto;\n display: inline-block;\n padding-left: 26px;\n}\n\n.cp-theme-default.cp-position-right .cp-input {\n padding-right: 26px;\n padding-left: inherit;\n}\n\n.input-group .cp-theme-bootstrap:not(:first-child) .cp-input {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.g-colorpicker-preview-wrap {\n background-image: url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQECAgICAgICAgICAgMDAwMDAwMDAwP/2wBDAQEBAQEBAQIBAQICAgECAgMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwP/wAARCAAyADIDAREAAhEBAxEB/8QAGgABAAMBAQEAAAAAAAAAAAAAAAQFBwYJCv/EAD4QAAAGAAUBBQQGBwkAAAAAAAECAwQFBhITFBUWCAARGCUmByh21iQ3OFWVtRciJ1SGl7RCR2NmZ5amxub/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A+xep1OR6apFa9XpZlLREsyUqbdvU1F30iSRfLtphFZZGYbQLYrIraBWKYxVjKAoYgAQQExigkKnI3S1J9RkWsyb0hm9jbYrFyCi6VqNHUEjVrMIpsW7Z1EC9cqVxcWxRfAmcDkxnTETAUJtw96HbuAeUcH1e7cw8v1HJtLoNu2XkGblcfWzs3Jw4iYcXebCE2QtkddKqn05xaL1vd2bKNqaspIJoJVU0jQTtXUwsm+buXUuLJynXFwbGFiChxOTGRMBMJQVO2R3TVHLUW9IvZaXlnqlsbuKmmg+jiRz5BtDoorLTDmBclelcwKxjFKiZMEzEEDiImKUKWp1OR6apFa9XpZlLREsyUqbdvU1F30iSRfLtphFZZGYbQLYrIraBWKYxVjKAoYgAQQExihoXix9nX3Ldfw6C+Y+wZ7U5C1XSRWi+oxN6zpDdkpIRatsjSUGONakl2zdimjMNWtcUcvRiHT4SthXOB0wOfAIpgYoJCQtUbak6dTk3qnT2o9jY948j40ktVQqssRqe9KKXo7V45SZJOXkhqXO4ALIQOAHSygAgTbh6H27w0fTd01fNeH/tDytFpeN7jq+TbRj1b/Jw5Go7j9+PLDAE2Qj6rG1VO405Rkp1CKMo2QeM4+SPLWoLVLHakvSalFO6eNknqTZ5Ialtt4AyADiBEsoBICpx9VukctKdRijJnd271SPi0rZJHoMiaqpINnDFRGHauq4m5ZDLunwFcigcTqAcmMQTApQpanIWq6SK0X1GJvWdIbslJCLVtkaSgxxrUku2bsU0Zhq1rijl6MQ6fCVsK5wOmBz4BFMDFDQuA9LH7/Sv5lPPmrsHFcw8UPoDbuD7R6w3bV8m1G3+S7doNLX8rN5Bm52cbDk4cA4sRQcw4P7tG3bprfR/NdXosr9If0vceN6V3j2jk2HJ14ajI78aePuID7KP+fee/wALbVxb/ceu13I/8HKyf7eP9UHD+D+8vuO6a31hwrSaLK/SH9E27kmqd49o5NiztAGoyO7Anj7yA4f4ofX+48H2j0ftOk5NqNv863HX6qv5WbyDKyck2HJxYxxYSg5h4ofQG3cH2j1hu2r5NqNv8l27QaWv5WbyDNzs42HJw4BxYig8H/8AqH/xL/03YJtskKrdI5GL6c02TO7t3qchKK1ONPQZE1VSQct3ya0w6a1xNyyGXdMRM2Bc4nUAh8AgmJigj5CqxtVUp1xTZKdQijKSj2byQjTy1qC1Sx3R6KoneiNXjZJ6k2eR+mc7gAMgAgCdLKECBCp/ofcfEv8ATd00nCuYftDytFquSbdpOTbRj1bDOxZGo7id2PLHAEKPj7VG2pS43FR6p09qPZKQZs5CSJLVUKrLEdEoqadFI6eOUmSTl5H6Ztt4CyECCJEsoRIC2R9qukijKdOaj1nSG7JOPlEqnJEoMca1JLuXD5RaHdOq4o5ejEOmIGcggcDpgQmMRTEpQurZIVW6RyMX05psmd3bvU5CUVqcaegyJqqkg5bvk1ph01riblkMu6YiZsC5xOoBD4BBMTFDPeA9U/7/AHX+ZTP5q7BoVsqcd01RyN6oqz2Wl5Z6nU3De2KIPo4kc+QczCyyKMO2gXJXpXMCiUpjLGTBMxwEgiJTFBH1OOulVU6jJRZ63u7NlJWxKLj1EEqqaRoJ3TWHRUYuGzqXFk5TriAuSg+BQ4nPgOmAlAoQqf70O48/8o4PpNp4f5fqOTarX7jvXIM3K4+jk5WThxHxYu8uEIUfbJG6WpTpzlEWTekM3slU0pSPTXStRo6gkdOodZR84cuogXrlSuIA5MDEEzgc+AiYiUSgtlskemqRRotFRZS0RLMk7Y4cWxNd9IkkXy7mHWRRWh3MC2KyK2gUTFKZEygKGOInEBKUoXVsqcd01RyN6oqz2Wl5Z6nU3De2KIPo4kc+QczCyyKMO2gXJXpXMCiUpjLGTBMxwEgiJTFDPfFj7RfuWlfh078x9g6ip1OR6apFa9XpZlLREsyUqbdvU1F30iSRfLtphFZZGYbQLYrIraBWKYxVjKAoYgAQQExigkKnI3S1J9RkWsyb0hm9jbYrFyCi6VqNHUEjVrMIpsW7Z1EC9cqVxcWxRfAmcDkxnTETAUJtw96HbuAeUcH1e7cw8v1HJtLoNu2XkGblcfWzs3Jw4iYcXebCE2QtkddKqn05xaL1vd2bKNqaspIJoJVU0jQTtXUwsm+buXUuLJynXFwbGFiChxOTGRMBMJQVO2R3TVHLUW9IvZaXlnqlsbuKmmg+jiRz5BtDoorLTDmBclelcwKxjFKiZMEzEEDiImKUKWp1OR6apFa9XpZlLREsyUqbdvU1F30iSRfLtphFZZGYbQLYrIraBWKYxVjKAoYgAQQExihoXix9nX3Ldfw6C+Y+wOrH6uoX41jvyKx9gUH7LD/4K9pX9ZauwcV0f/3h/wAJf9m7BxVB+1O/+NfaV/R2rsDqx+sWF+Co789sfYNq6sfq6hfjWO/IrH2Dz27B/9k=);\n}","// +-------------------------------------------------------------------------+\n// | Flat Colors |\n// +-------------------------------------------------------------------------+\n\n// Greens\n$color-navy: #001f3f;\n$color-blue: #0074D9;\n$color-aqua: #7FDBFF;\n$color-teal: #39CCCC;\n$color-olive: #3D9970;\n$color-green: #2ECC40;\n$color-lime: #01FF70;\n$color-yellow: #FFDC00;\n$color-orange: #FF851B;\n$color-red: #FF4136;\n$color-maroon: #85144b;\n$color-fuchsia: #F012BE;\n$color-purple: #B10DC9;\n\n","html, body {\n\theight: 100%;\n}\n\nbody {\n\t-webkit-font-smoothing: antialiased;\n \t-moz-osx-font-smoothing: grayscale;\n overflow: hidden;\n}\n\nb, strong {\n\tfont-weight: $font-weight-bold\n}\n\n.bigger {\n font-size: 1.2rem;\n\n}\n\n.button-bar {\n text-align: right;\n float: right;\n z-index: 0;\n}\n\n.alert {\n font-size: 1rem;\n padding: 0.5rem $padding-default;\n position: relative;\n\n @include breakpoint(mobile-only) {\n padding: 0.5rem 1rem;\n }\n}\n\n.badge {\n display: inline-block;\n font-size: 0.8rem;\n font-weight: 500;\n border-radius: 10px;\n padding: 0px 6px;\n min-width: 20px;\n line-height: 20px;\n text-align: center;\n}\n\n.empty-state {\n margin: 0 auto;\n text-align: center;\n padding-top: 100px;\n}\n",".default-animation {\n @include transition(all 0.5s ease);\n}\n\n// Pulse Animation\n.pulse {\n\tanimation-name: pulse_animation;\n\tanimation-duration: 2000ms;\n\ttransform-origin:70% 70%;\n\tanimation-iteration-count: infinite;\n\tanimation-timing-function: linear;\n}\n\n@keyframes pulse_animation {\n\t0% { transform: scale(1); }\n\t30% { transform: scale(1); }\n\t40% { transform: scale(1.08); }\n\t50% { transform: scale(1); }\n\t60% { transform: scale(1); }\n\t70% { transform: scale(1.05); }\n\t80% { transform: scale(1); }\n\t100% { transform: scale(1); }\n}\n\n.default-border-radius {\n border-radius: 4px;\n}\n\n.default-glow-shadow {\n box-shadow: 0 0 20px rgba(0,0,0,0.2);\n}\n\n.default-box-shadow {\n box-shadow: 0 0 10px 2px rgba(0,0,0,0.03);\n}\n\n.login-box-shadow {\n\tbox-shadow: 0 10px 100px 0px rgba(0,0,0,0.4);\n}\n\n.padding-horiz {\n\tpadding-left: $padding-horiz;\n\tpadding-right: $padding-horiz;\n\t@include breakpoint(desktop-only) {\n\t\tpadding-left: $padding-horiz - 3rem;\n\t\tpadding-right: $padding-horiz - 3rem;\n\t}\n\n\t@include breakpoint(mobile-only) {\n\t\tpadding-left: $padding-horiz - 6rem;\n\t\tpadding-right: $padding-horiz - 6rem;\n\t}\n}\n\n.padding-vert {\n\tpadding-top: $padding-vert;\n\tpadding-bottom: $padding-vert;\n}\n","@include breakpoint(mobile-only) {\n .hidden-mobile {\n display: none!important;\n }\n}\n","// Header styling\n\n\n","// Body Base\nbody {\n\tfont-weight: 400;\n @include breakpoint(mobile-only) {\n font-size: 0.8rem;\n }\n}\n\n// Headings\nh1, h2, h3, h4, h5, h6 {\n\tfont-weight: 400;\n\ttext-rendering: optimizeLegibility;\n\tletter-spacing: -0px;\n}\n\nh1 {\n\tfont-size: $h1-font-size;\n @include breakpoint(mobile-only) {\n font-size: 2.5rem;\n line-height: 1.2;\n margin-bottom: 2.5rem;\n }\n}\n\nh2 {\n\t@include breakpoint(tablet-range) {\n\t\tfont-size: $h2-font-size - .40;\n\t}\n\t@include breakpoint(mobile-only) {\n\t\tfont-size: $h2-font-size - .50;\n\t}\n}\n\nh3 {\n\t@include breakpoint(tablet-range) {\n\t\tfont-size: $h3-font-size - .40;\n\t}\n\t@include breakpoint(mobile-only) {\n\t\tfont-size: $h3-font-size - .50;\n\t}\n}\n\nh4 {\n\t@include breakpoint(tablet-range) {\n\t\tfont-size: $h4-font-size - .40;\n\t}\n\t@include breakpoint(mobile-only) {\n\t\tfont-size: $h4-font-size - .50;\n\t}\n}\n\nh1 {\n\tletter-spacing: -3px;\n}\n\nh2 {\n\tletter-spacing: -2px;\n}\n\nh3 {\n\tletter-spacing: -1px;\n}\n\n// Icon Tweaks\ni.fa {\n\t&.fa-heart, &.fa-heart-o {\n\t\t&.pulse {\n\t\t\tcolor: #920\n\t\t}\n\t}\n}\n\npre {\n\tpadding: 2rem;\n\tborder-radius: 3px;\n\tcode {\n\t\tbackground: inherit;\n\t}\n}\n\n// Label\n.label {\n vertical-align: middle;\n border-radius: 100%;\n height: 1rem;\n min-width: 1rem;\n line-height: 1rem;\n display: inline-block;\n text-align: center;\n font-size: $core-font-size - 0.3rem;\n margin-right: 0.75rem;\n}\n\n// Display RESET\nform {\n .form-display-wrapper {\n h1 {\n padding: 0 !important;\n font-size: 1.7rem !important;\n border-bottom-width: 2px !important;\n }\n\n h2 {\n padding: 0 !important;\n font-size: 1.5rem !important;\n border-bottom-width: 1px !important;\n }\n\n h3 {\n padding: 0 !important;\n font-size: 1.3rem !important;\n border-bottom-width: 1px !important;\n }\n\n h4 {\n padding: 0 !important;\n font-size: 1.1rem !important;\n }\n\n blockquote {\n padding: 15px;\n p {\n font-size: 0.9rem;\n color: inherit;\n &:last-child {\n margin-bottom: 0;\n }\n }\n }\n\n code, pre {\n font-size: 0.8rem;\n }\n\n table {\n border-collapse: collapse;\n width: 100%;\n\n th {\n padding: 10px !important;\n &:last-child {\n border-right: none;\n }\n }\n\n td {\n padding: 10px !important;\n &:last-child {\n border-right: none;\n }\n }\n\n tr {\n &:last-child {\n border-bottom: none;\n }\n }\n }\n }\n\n}\n\n","// RocketTheme Apps Support\n@include font-face('rockettheme-apps', '../fonts/rockettheme-apps/rockettheme-apps');\n\ni.fa-grav, i.fa-grav-spaceman, i.fa-grav-text, i.fa-grav-full,\ni.fa-grav-logo, i.fa-grav-symbol, i.fa-grav-logo-both, i.fa-grav-both,\ni.fa-gantry, i.fa-gantry-logo, i.fa-gantry-symbol, i.fa-gantry-logo-both, i.fa-gantry-both {\n font-family: 'rockettheme-apps';\n speak: none;\n font-style: normal;\n font-weight: normal;\n font-variant: normal;\n text-transform: none;\n line-height: 1;\n\n /* Better Font Rendering =========== */\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n// Grav\n.fa-grav-logo:before, .fa-grav-text:before {\n content: \"\\61\";\n}\n.fa-grav-symbol:before, .fa-grav:before, .fa-grav-spaceman:before {\n content: \"\\62\";\n}\n.fa-grav-logo-both:before, .fa-grav-both:before, .fa-grav-full:before {\n content: \"\\66\";\n}\n\n// Gantry\n.fa-gantry-logo:before {\n content: \"\\64\";\n}\n.fa-gantry:before, .fa-gantry-symbol:before {\n content: \"\\63\";\n}\n.fa-gantry-logo-both:before, .fa-gantry-both:before {\n content: \"\\65\";\n}","// Forms\nform {\n\n h1, h3, .form-spacer h3 {\n padding: 0 $padding-default 0.5rem;\n margin: 0 0 1rem;\n font-size: 1.5rem;\n text-align: left;\n letter-spacing: -1px;\n }\n\n h3 {\n padding-left: 0;\n }\n\n p {\n padding: 0 $padding-default;\n }\n\n pre {\n padding: 1.5rem 3rem;\n }\n\n .nowrap {\n white-space: nowrap;\n }\n\n .form-description {\n p {\n padding: 0;\n }\n }\n\n .button {\n @include breakpoint(mobile-only) {\n padding: 0.25rem 0.5rem;\n }\n }\n\n .form-field {\n margin-bottom: 1rem;\n padding-left: $padding-default;\n\n @include breakpoint(mobile-only) {\n padding-left: 1rem;\n }\n\n\n\n .file-thumbnail-wrapper {\n display: inline-block;\n position: relative;\n }\n\n .file-thumbnail {\n max-height: 150px;\n vertical-align: top;\n display: inline-block;\n margin-bottom: 10px;\n }\n\n .file-thumbnail-remove {\n position: absolute;\n text-align: center;\n display: block;\n top: 3px;\n right: 3px;\n width: 25px;\n height: 25px;\n border-radius:100%;\n\n .fa {\n font-size: 20px;\n line-height: 25px;\n vertical-align: top;\n }\n }\n }\n\n .form-data {\n padding-right: $padding-default;\n\n @include breakpoint(mobile-only) {\n padding-right: 1rem;\n }\n }\n\n .form-input-addon-wrapper {\n display: flex;\n\n .form-input-addon {\n display: flex;\n padding: 0 5px;\n justify-content: center;\n align-items: center;\n\n &.copy-to-clipboard {\n cursor: pointer;\n }\n }\n input {\n height: 35px;\n display: table-cell;\n }\n .form-input-prepend {\n border-radius: 4px 0 0 4px;\n border-right-width: 0 !important;\n\n }\n .form-input-append {\n border-radius: 0 4px 4px 0;\n border-left-width: 0 !important;\n }\n\n input:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n\n input:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n }\n\n .overlay {\n padding-top: 1rem;\n padding-bottom: 1px;\n\n &.bottom {\n margin: 2rem auto -2rem auto;\n }\n }\n\n .required {\n display: inline-block;\n font-family: helvetica, arial;\n vertical-align: middle;\n line-height: 0;\n font-size: 30px;\n margin-left: 0px;\n margin-bottom: -5px;\n }\n\n label {\n padding: 5px 0;\n font-weight: 400;\n margin:0;\n\n p {\n margin: 0;\n }\n\n [data-hint] {\n display: inline;\n }\n\n &.inline {\n display: inline;\n vertical-align: middle;\n margin-right: 15px;\n }\n\n &.toggleable {\n display: inline;\n }\n }\n\n .form-sublabel {\n p {\n opacity: 0.8;\n font-size: 0.9rem;\n margin: 10px 20px 0 0;\n padding: 0;\n }\n }\n\n input, select, textarea, button, .selectize-input {\n font-size: $core-font-size;\n line-height: $core-line-height;\n border-radius: $form-border-radius;\n -webkit-font-smoothing: antialiased;\n }\n\n .form-input-file {\n\n position: relative;\n min-height: 70px;\n border-radius: $form-border-radius;\n\n ul {\n margin: 1rem 0;\n text-align: left;\n font-size: 1rem;\n border-radius: $form-border-radius;\n }\n\n p {\n display: block;\n height: 100%;\n text-align: center;\n margin: 0;\n padding: 0.8rem 1rem 0;\n font-size: 1.2rem;\n }\n\n input {\n display: none;\n }\n }\n\n\n .selectize-dropdown {\n z-index: 100000;\n }\n\n // vertical alignment of forms\n .grid.vertical {\n @include flex-flow(column);\n }\n\n // compact\n .compact {\n .form-field {\n margin-bottom: 0.2rem;\n }\n\n .form-list-wrapper {\n li {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-start;\n\n .form-field {\n width: 50%;\n\n @include breakpoint(mobile-only) {\n width: 100%;\n }\n }\n }\n }\n }\n\n .form-select-wrapper, .selectize-control.single .selectize-input {\n position: relative;\n\n &:after {\n margin-top: 0;\n border: 0;\n position: absolute;\n content: '\\f078';\n font-family: 'FontAwesome';\n right: 12px;\n top: 50%;\n line-height: 0;\n pointer-events: none;\n }\n }\n\n .selectize-input {\n display: block !important;\n box-shadow: none;\n padding: $form-select-pad;\n margin: 0;\n\n > input {\n font-size: $core-font-size;\n line-height: $core-line-height;\n }\n }\n\n .selectize-control.single.plugin-remove_button .selectize-input {\n .item, .remove-single {\n cursor: pointer;\n margin: 0 3px 3px 0;\n padding: 2px 6px;\n display: inline-block;\n border-radius: 2px 0 0 2px !important;\n }\n .remove-single {\n border-radius: 0 2px 2px 0 !important;\n margin-left: -2px;\n }\n }\n\n .selectize-control.single.plugin-remove_button .selectize-input,\n .selectize-control.multi .selectize-input {\n padding: ($leading-margin / 4) ($leading-margin / 4);\n\n &.has-items {\n padding-top: 6px;\n padding-bottom: 4px;\n }\n\n .item, .remove-single {\n border-radius: 2px;\n line-height: 1.5;\n }\n\n .item > img {\n max-height: 20px !important;\n max-width: inherit !important;\n }\n }\n\n .selectize-control.single .selectize-input {\n\n &:after {\n right: 27px;\n }\n &.dropdown-active {\n &:after {\n content: '\\f077';\n }\n }\n }\n\n .x-small {\n max-width: 5rem !important;\n\n &.form-input-addon-wrapper {\n max-width: inherit !important;\n input {\n max-width: 5rem !important;\n }\n }\n }\n\n .small {\n max-width: 10rem !important;\n\n &.form-input-addon-wrapper {\n max-width: inherit !important;\n input {\n max-width: 10rem !important;\n }\n }\n }\n\n .medium:not(.switch-toggle), .medium.form-input-addon-wrapper input {\n max-width: 20rem;\n textarea {\n height: 7rem;\n }\n\n &[data-grav-array-type=\"container\"] {\n max-width: 100% !important;\n input[type=text] {\n width: calc(70% - 110px);\n }\n }\n }\n\n .large, .large.form-input-addon-wrapper input {\n max-width: 30rem !important;\n\n textarea {\n height: 10rem;\n }\n\n &[data-grav-array-type=\"container\"] {\n max-width: 100% !important;\n input[type=text] {\n width: calc(100% - 110px);\n }\n }\n }\n\n select {\n width: 100%;\n -webkit-appearance:none;\n -moz-appearance:none;\n appearance:none;\n padding: $form-select-pad;\n cursor: pointer;\n margin: 0;\n }\n\n input[type=text],\n input[type=password],\n input[type=email],\n input[type=date],\n input[type=tel],\n input[type=time],\n input[type=week],\n input[type=month],\n input[type=number],\n input[type=color],\n input[type=url] {\n width: 100%;\n }\n\n textarea {\n width: 100%;\n }\n\n .form-frontmatter-wrapper {\n border-radius: $form-border-radius;\n }\n\n .switch-toggle {\n label {\n cursor: pointer;\n }\n\n a,label {\n outline: none !important;\n }\n }\n\n .dynfields, [data-grav-field=\"array\"], [data-grav-field=\"multilevel\"] {\n\n input[type=text] {\n width: 40%;\n float: left;\n margin: 0 5px 5px 0;\n }\n\n .form-row {\n display: inline-block;\n width: 100%;\n margin-right: 0;\n max-width: 100% !important;\n\n span {\n padding: 0.5rem;\n display: inline-block;\n line-height: 1.5;\n cursor: pointer;\n\n &[data-grav-array-action=\"sort\"] {\n float: left;\n cursor: move;\n }\n }\n\n &.array-field-value_only {\n width: 100%;\n }\n }\n }\n\n [data-grav-field=\"multilevel\"] input:disabled {\n background-color: lightgray;\n }\n\n [data-grav-array-type=\"container\"].one-child {\n [data-grav-array-action=\"sort\"] {\n display: none;\n }\n }\n\n .button-bar {\n margin-top: 1rem;\n padding: 1.2rem 3rem;\n width: 100%;\n border-bottom-left-radius: 5px;\n border-bottom-right-radius: 5px;\n }\n\n .checkboxes {\n display: inline-block;\n padding: 5px 0;\n\n label {\n display: inline;\n cursor: pointer;\n position: relative;\n padding: 0 0 0 2rem;\n margin-right: 15px;\n\n }\n label:before {\n content:\"\";\n display: inline-block;\n width: 1.5rem;\n height: 1.5rem;\n top: 50%;\n left: 0;\n margin-top: -0.75rem;\n margin-right: 10px;\n position: absolute;\n border-radius: $form-border-radius;\n }\n input[type=checkbox] {\n display: none;\n }\n input[type=checkbox]:checked + label:before {\n content:\"\\f00c\";\n font-family: \"FontAwesome\";\n font-size: 1.2rem;\n line-height: 1;\n text-align: center;\n }\n\n &.toggleable label{\n margin-right: 0;\n }\n }\n}\n\n// Display\n.form-display-wrapper {\n p {\n padding-left: 0;\n padding-right: 0;\n\n &:first-child {\n margin-top: 0;\n }\n }\n}\n\n// Frontmatter Field\ndiv.frontmatter {\n margin-bottom: 3rem;\n}\n\ntextarea.frontmatter {\n height: 130px !important;\n\n & + .CodeMirror {\n height: 130px;\n }\n}\n\n// Sortables\n.form-order-wrapper {\n\n .notice {\n padding: 5px;\n }\n\n ul.orderable {\n\n list-style: none;\n margin: 0;\n padding: 0;\n\n li {\n padding: 0.2rem 1rem;\n border-radius: $form-border-radius;\n margin: 3px 0;\n position: relative;\n\n &.drag-handle {\n cursor: move;\n &::after {\n content: '\\f0c9';\n font-family: FontAwesome;\n position: absolute;\n right: 10px;\n }\n }\n\n i {\n font-size: 0.8rem;\n }\n }\n }\n}\n\n// Sortables\n.form-list-wrapper {\n ul[data-collection-holder] {\n\n list-style: none;\n margin: 0;\n padding: 0;\n\n > li {\n padding: 1rem;\n border-radius: $form-border-radius;\n margin: 3px 0;\n position: relative;\n\n .item-actions {\n position: absolute;\n right: 10px;\n top: 4px;\n\n .fa {\n cursor: pointer;\n }\n }\n\n &.collection-collapsed {\n height: 70px;\n overflow: hidden;\n @include transform(translateZ(0));\n\n @include breakpoint(mobile-only) {\n height: 120px;\n }\n }\n }\n\n &[data-collection-nosort] > li {\n cursor: default;\n }\n }\n\n .collection-actions {\n text-align: right;\n }\n\n .collection-sort {\n position: absolute;\n top: 0;\n left: 0;\n width: 32px;\n bottom: 0;\n cursor: move;\n\n @include breakpoint(mobile-only) {\n width: 20px;\n\n .fa {\n font-size: 0.8rem;\n }\n }\n\n .fa {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n }\n }\n\n [data-collection-nosort] .collection-sort {\n display: none;\n }\n}\n\n.form-label.block {\n position: relative;\n\n &:hover {\n z-index: 2;\n }\n label {\n z-index: 2;\n }\n}\n\n// Fieldset\n#admin-main .admin-block h2 {\n font-size: 1.25rem;\n margin: 0 0 .5rem;\n\n letter-spacing: normal;\n}\n.form-fieldset {\n margin: 1rem 1.5rem;\n}\n\n.form-fieldset--label {\n label {\n display: table;\n font-size: 1.25rem;\n padding: .5rem 1rem;\n width: 100%;\n }\n\n h2 {\n margin: 0 !important;\n }\n\n .actions {\n font-size: initial;\n display: table-cell;\n text-align: right;\n vertical-align: middle;\n }\n\n & + .form-data {\n margin-top: 1rem;\n padding: 0;\n }\n}\n.form-fieldset--cursor {\n cursor: pointer;\n}\n.form-fieldset--info {\n font-size: small;\n}\n.form-fieldset > input:checked ~ .form-data,\n.form-fieldset--collapsible .open,\n.form-fieldset input:checked ~ .form-label .form-fieldset--collapsible .close {\n display: block;\n}\n.form-fieldset > .form-data,\n.form-fieldset--collapsible .close,\n.form-fieldset input:checked ~ .form-label .form-fieldset--collapsible .open {\n display: none;\n}\n\n.filepicker-field-image {\n width: 30px;\n vertical-align: middle;\n margin-right: 5px;\n}\n\n.filepicker-field-name {\n vertical-align: middle;\n}\n\n.permissions-container {\n .permission-value {\n width: 50%;\n float: left;\n margin: 0 5px 5px 0;\n height: 39px;\n }\n\n .switch-toggle {\n line-height: 37px;\n margin: 0 5px 5px 0;\n\n }\n}\n\n[data-grav-field=\"cron\"] {\n display: flex;\n align-items: center;\n}\n\n.cron-install {\n\n margin: 1rem;\n padding: 0;\n border-radius: 4px;\n\n form & pre {\n padding: 1rem;\n margin: 0 1.5rem;\n line-height: 1;\n }\n .setup-status {\n font-weight: bold;\n }\n}\n\n.cron-status, .backups-history {\n\n margin-bottom: 4rem;\n\n .cron-at code {\n font-size: 120%;\n padding: 2px 10px;\n border-radius: 2px;\n }\n\n .badge {\n white-space: nowrap;\n }\n\n tr {\n\n &.deleting {\n background: #efefef;\n color: #bbb;\n text-shadow: 0 1px white;\n\n button, .button {\n background: #777;\n pointer-events: none;\n }\n }\n }\n}\n\nform .cron-job-list, form .backups-list {\n li {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n }\n\n .form-field.grid {\n width: calc(50% - 5px);\n\n @include breakpoint(mobile-only) {\n width: 100%;\n }\n }\n}\n","table,\ntbody,\nthead {\n display: inline-block;\n width: 100%;\n}\n\n.gpm-details {\n width: 100%;\n @include flex(auto);\n}\n\ntd {\n border: 0;\n}\n\ntr {\n @include transition(all 0.2s ease);\n\n @include display(flex);\n @include flex-wrap(wrap);\n @include align-items(center);\n\n th, td {\n display: block;\n @include flex(1);\n\n &.shrink {\n @include flex(0);\n }\n\n &.double {\n @include flex(2);\n }\n\n &.triple {\n @include flex(3);\n }\n\n &.quadruple {\n @include flex(4);\n }\n\n &.quintuple {\n @include flex(5);\n }\n\n &.sextuple {\n @include flex(6);\n }\n\n &.septuple {\n @include flex(7);\n }\n\n &.octuple {\n @include flex(8);\n }\n\n &.nontuple {\n @include flex(9);\n }\n\n &.dectuple {\n @include flex(10);\n }\n\n &:first-child {\n padding-left: $padding-default;\n\n @include breakpoint(mobile-only) {\n padding-left: 1rem;\n }\n }\n\n &:last-child {\n padding-right: $padding-default;\n\n @include breakpoint(mobile-only) {\n padding-right: 1rem;\n }\n }\n\n &.left {\n text-align: left;\n }\n\n &.center {\n text-align: center;\n }\n\n &.right {\n text-align: right;\n }\n\n &.right.pad {\n padding-right: $padding-default;\n .form-data {\n padding-right: 0;\n }\n }\n\n &.left.pad {\n padding-left: $padding-default;\n }\n\n &.ellipsis {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n &.rtl {\n text-align: left;\n }\n }\n\n &.rtl {\n direction: rtl;\n }\n }\n\n th {\n font-weight: bold;\n }\n\n td {\n\n &.gpm-actions {\n padding-right: $padding-default + 1rem;\n }\n\n &.gpm-actions {\n line-height: 1;\n text-align: right;\n position: relative;\n\n .gpm-details-expand {\n position: absolute;\n top: 12px;\n right: 12px;\n }\n }\n\n &.gpm-details {\n margin: 0;\n padding: 0;\n\n @include breakpoint(mobile-only){\n word-wrap: break-word;\n }\n\n > .table-wrapper {\n display: none;\n\n td {\n border-bottom: 0;\n }\n }\n\n tbody {\n width: 100%;\n }\n }\n }\n\n &:last-child {\n td {\n border-bottom: 0;\n }\n }\n}\n\ntable.noflex {\n display: table;\n table-layout: auto;\n\n thead {\n display: table-header-group;\n }\n\n tbody {\n display: table-row-group;\n }\n\n tr {\n display: table-row;\n\n td, th {\n flex: none;\n display: table-cell;\n }\n }\n}\n",".button {\n\t@extend %button;\n}\n\n.button-group {\n position: relative;\n display: inline-block;\n vertical-align: top;\n\n // real button\n > .button:first-child:not(:last-child):not(.dropdown-toggle) {\n border-top-right-radius: 0 !important;\n border-bottom-right-radius: 0 !important;\n }\n\n > .button:first-child {\n margin-left: 0 !important;\n }\n\n > .button {\n }\n\n // toggle caret\n > .button + .dropdown-toggle {\n text-align: center;\n padding-right: 8px;\n padding-left: 8px;\n\n i {\n margin: 0;\n }\n }\n\n > .button:last-child:not(:first-child), > .dropdown-toggle:not(:first-child) {\n border-top-left-radius: 0 !important;\n border-bottom-left-radius: 0 !important;\n }\n\n .button + .button, .button + .button-group, .button-group + .button, .button-group + .button-group {\n margin-left: -3px;\n }\n\n &:last-child {\n .dropdown-menu {\n left: inherit;\n right: 0;\n }\n }\n\n .dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 150px;\n padding: 5px 0;\n margin: 2px 0 0;\n font-size: 14px;\n text-align: left;\n list-style: none;\n -webkit-background-clip: padding-box;\n background-clip: padding-box;\n border-radius: 4px;\n\n .button {\n margin-right: 0 !important;\n }\n\n &.language-switcher {\n min-width: 50px;\n }\n\n &.lang-switcher {\n min-width: 150px;\n left: inherit;\n right: 0;\n\n button {\n width: 100%;\n }\n }\n\n .divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n }\n\n li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: 400;\n line-height: 1.42857143;\n\n &:focus, &:hover {\n text-decoration: none;\n }\n }\n }\n}\n\n.open > .dropdown-menu {\n display: block;\n}\n\n.dropdown-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 990;\n}\n\n// Override style for stubborn gumroad button\n#admin-main .admin-block a.gumroad-button {\n padding: 0.5rem 1rem !important;\n font-size: inherit !important;\n font-weight: 400 !important;\n line-height: inherit !important;\n font-family: inherit !important;\n border-radius: 4px !important;\n box-shadow: inherit !important;\n .gumroad-button-logo {\n display: none !important;\n background-image: none !important;\n }\n}\n","// Error specific styling\n#error {\n\ttext-align: center;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\theight: 100%;\n\tpadding-bottom: 6rem;\n\n\th1 {\n\t\tfont-size: $core-font-size + 4.0rem;\n\t}\n\n\tp {\n\t\tmargin: 1rem 0;\n\t}\n}\n","#admin-login {\n @extend %vertical-align;\n max-width: 24rem;\n margin: 0 auto;\n\n &.wide {\n max-width: 50rem;\n\n h1 {\n height: 100px;\n\n &.custom-logo img {\n height: 80px;\n }\n }\n\n form {\n > .padding {\n padding: 3rem 2rem 8rem 2rem;\n > div {\n width: 49%;\n display: inline-block;\n\n @include breakpoint(mobile-only) {\n width: 100%;\n margin-right: 0;\n }\n }\n\n .form-field {\n padding: 0 1rem;\n }\n }\n\n label {\n padding: 0;\n }\n\n input {\n margin-bottom: 1rem;\n text-align: left;\n\n @include placeholder {\n font-size: 1rem;\n line-height: 2rem;\n }\n }\n\n }\n\n .grid {\n display: block;\n }\n\n .form-label, .form-data {\n display: block;\n width: 100%;\n @include flex(1);\n }\n }\n\n .form-field {\n padding-left: 0;\n margin-bottom: 0;\n }\n\n .form-label {\n display: none;\n }\n\n .form-data {\n padding-right: 0;\n }\n\n .wrapper-spacer {\n width: 100% !important;\n display: block !important;\n padding: 0 1rem;\n h3 {\n padding-left: 1rem;\n }\n }\n\n .instructions {\n display: block;\n padding: 2rem 3rem 0;\n margin: 0;\n font-size: 1.3rem;\n p {\n margin: 0;\n }\n }\n\n h1 {\n font-size: 0;\n color: transparent;\n height: 216px;\n margin: 0;\n overflow: hidden;\n\n display: flex;\n align-items: center;\n justify-content: center;\n\n &.text-logo {\n text-align: center;\n font-size: 4rem;\n }\n\n &.custom-logo {\n img {\n max-height: 160px;\n }\n }\n\n &.default-logo {\n svg {\n height: 320px;\n }\n }\n }\n\n h5 {\n margin-top: 0;\n text-align: center;\n }\n\n form {\n position: relative;\n\n .padding {\n padding: 3rem 3rem 6rem 3rem;\n }\n\n input {\n margin-bottom: 2rem;\n font-size: 1.4rem;\n line-height: 1.5;\n text-align: center;\n font-weight: 300;\n -webkit-font-smoothing: auto;\n }\n\n .form-actions {\n display: block !important;\n width: 100% !important;\n text-align: center;\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 1.5rem 0;\n\n .button {\n margin-bottom: 2px;\n }\n\n button:first-child {\n margin-right: 1rem;\n }\n }\n }\n\n .alert {\n text-align: center;\n padding: 1rem 3rem;\n }\n}\n\n.twofa-secret-code {\n font-weight: bold;\n}\n\n.twofa-wrapper {\n margin-top: 5px;\n}\n\n\n.twofa-secret {\n position: absolute;\n opacity: 0;\n visibility: hidden;\n transition: opacity 600ms, visibility 600ms;\n\n &.show {\n position: static;\n visibility: visible;\n opacity: 1;\n }\n\n}\n","$topbar-height: 4.2rem;\n$update-height: 3rem;\n$sidebar-width-large: 240px;\n$sidebar-width-small: $topbar-height;\n$sidebar-padding: 2rem;\n$content-padding: 1.5rem;\n\n// Special cases\n.sidebar-closed:not(.sidebar-quickopen) {\n #admin-sidebar {\n float: left;\n visibility: visible;\n opacity: 1;\n width: $sidebar-width-small;\n\n #admin-logo {\n h3 {\n font-size: 0;\n &:first-letter {\n font-size: 2.5rem;\n }\n }\n }\n\n .custom-logo {\n margin-right: 0;\n padding-right: 15px;\n\n img {\n padding: 0 5px;\n }\n }\n\n #grav-logo-small {\n display: inline-block;\n }\n\n #grav-logo-large {\n display: none;\n }\n\n .front-end {\n display: none;\n }\n\n #admin-user-details {\n padding: 15px 0 15px 10px;\n text-align: left;\n }\n\n .admin-user-names {\n display: none;\n }\n\n #admin-menu {\n em {\n display: none;\n }\n\n li {\n .badges {\n .updates {\n border-radius: 100%;\n position: absolute;\n top: 4px;\n right: 10px;\n font-size: 9px;\n line-height: 12px;\n padding: 1px 3px;\n min-width: 15px;\n }\n .count {\n display: none;\n }\n }\n }\n }\n\n #open-handle {\n @include breakpoint(large-desktop-plus) {\n .fa-angle-right:before {\n content: \"\\f105\";\n }\n }\n }\n }\n #admin-main {\n margin-left: $sidebar-width-small;\n }\n}\n\n.sidebar-open {\n #admin-sidebar {\n z-index: 10001;\n float: left;\n visibility: visible;\n opacity: 1;\n width: $sidebar-width-large;\n\n h3 {\n font-size: 1.2rem;\n &:first-letter {\n font-size: 1.2rem;\n }\n }\n\n #grav-logo-small {\n display: none;\n }\n\n #grav-logo-large {\n display: inline-block;\n }\n\n #admin-user-details {\n padding: 15px 10px;\n text-align: left;\n }\n\n .admin-user-names {\n display: block;\n }\n\n .front-end {\n display: inline-block;\n }\n\n #admin-menu {\n em {\n display: inline;\n }\n\n li {\n .badges {\n .updates {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n position: relative;\n top: auto;\n right: auto;\n @extend .badge;\n }\n .count {\n display: inline-block;\n }\n }\n }\n }\n\n #open-handle {\n @include breakpoint(mini-nav-range) {\n .fa-angle-right:before {\n content: \"\\f104\";\n }\n }\n }\n }\n}\n\n#admin-sidebar {\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 100%;\n max-height: 100vh;\n opacity: 0;\n z-index: 3;\n //transition: opacity 0.2s 0s, visibility 0s 0.2s;\n\n #open-handle {\n display: block;\n position: absolute;\n cursor: pointer;\n right:0;\n top: 0;\n width: 15px;\n height: $topbar-height;\n text-align: center;\n line-height: $topbar-height;\n font-size: 12px;\n border-top-right-radius: 3px;\n border-bottom-right-radius: 3px;\n\n @include breakpoint(large-desktop-plus) {\n .fa-angle-right:before {\n content: \"\\f104\";\n }\n }\n }\n\n &.sidebar-is-visible {\n opacity: 1;\n visibility: visible;\n //transition: opacity 0.2s 0s, visibility 0s 0s\n }\n\n @include breakpoint(mobile-only) {\n z-index: 5;\n }\n\n @include breakpoint(tablet-plus) {\n float: left;\n visibility: visible;\n opacity: 1;\n width: $sidebar-width-small;\n }\n\n @include breakpoint(large-desktop-plus) {\n float: left;\n visibility: visible;\n opacity: 1;\n width: $sidebar-width-large;\n }\n\n}\n\n#admin-logo {\n height: $topbar-height;\n display: flex;\n align-items: center;\n justify-content: center;\n\n &.nav-hover {\n .admin-logo {\n margin-right: 0;\n }\n }\n\n .admin-logo {\n margin-right: -10px;\n\n @include breakpoint(tablet-plus) {\n margin-right: 0;\n &.custom-logo {\n\n padding-right: 15px;\n\n img {\n padding: 0 5px;\n }\n }\n }\n\n display: flex;\n align-items: center;\n\n a:not(.front-end) {\n display: inherit;\n }\n\n &.custom-logo {\n img {\n max-height: 30px;\n }\n }\n }\n\n .front-end {\n\n margin-left: 15px;\n opacity: 0.6;\n\n @include breakpoint(mobile-only) {\n display: none;\n }\n\n @include breakpoint(tablet-plus) {\n display: none;\n }\n\n @include breakpoint(large-desktop-plus) {\n display: inline-block;\n }\n\n }\n\n #grav-logo-small {\n height: 30px;\n display: none;\n\n @include breakpoint(tablet-plus) {\n display: inline-block;\n }\n\n @include breakpoint(large-desktop-plus) {\n display: none;\n }\n }\n\n #grav-logo-large {\n height: 30px;\n\n @include breakpoint(tablet-plus) {\n display: none;\n }\n\n @include breakpoint(large-desktop-plus) {\n display: inline-block;\n }\n }\n\n h3 {\n font-size: 1.5rem;\n margin: 0;\n\n @include breakpoint(tablet-plus) {\n font-size: 0;\n &:first-letter {\n font-size: 2.5rem;\n }\n }\n\n @include breakpoint(large-desktop-plus) {\n font-size: 1.5rem;\n &:first-letter {\n font-size: 1.5rem;\n }\n }\n\n i {\n font-size: 1rem;\n vertical-align: middle;\n margin-top: -1px;\n }\n }\n}\n\n.block-userinfo {\n img {\n width: 200px;\n height: 200px;\n object-fit: cover;\n }\n\n & ~ .block-file {\n margin-top: -5rem !important;\n }\n}\n\n#offline-status {\n padding: .2rem 1rem;\n text-align: center;\n display: none;\n\n .fa-plane {\n @include transform(rotate(45deg));\n }\n}\n\n#admin-user-details {\n img {\n width: 47px;\n height: 47px;\n object-fit: cover;\n }\n}\n\n#admin-user-details, .admin-user-details {\n padding: 15px 10px;\n min-height: 80px;\n overflow: hidden;\n text-align: left;\n\n @include breakpoint(mini-nav-range) {\n padding: 1rem 10px;\n text-align: center;\n }\n\n img {\n @include transition(all 0.5s ease);\n border-radius: 100%;\n float: left;\n }\n\n .admin-user-names {\n margin-left: 60px;\n margin-top: 5px;\n display: block;\n\n @include breakpoint(mini-nav-range) {\n display: none;\n }\n\n h4, h5 {\n margin: 0;\n font-size: 1rem;\n line-height: 1.3;\n }\n\n .badge {\n font-size: 0.8rem;\n }\n\n h5 {\n opacity: 0.7;\n font-size: 0.9rem;\n }\n }\n}\n\n#admin-nav-quick-tray {\n margin: 0;\n list-style: none;\n padding-left: 26px;\n padding-right: 10px;\n\n\n li {\n cursor: pointer;\n width: 24px;\n margin: 3px 0 5px;\n }\n\n i {\n transition: all .2s ease-in-out;\n\n &:hover {\n transform: scale(1.2);\n }\n }\n}\n\n.content-padding {\n #messages.default-box-shadow {\n margin-bottom: $content-padding;\n\n @include breakpoint(mobile-only) {\n margin-bottom: 0.5rem;\n }\n }\n}\n\n\n.admin-menu-wrapper {\n height: calc(100vh - 180px);\n overflow: hidden;\n\n @include breakpoint(mini-nav-range) {\n height: calc(100vh - 136px);\n }\n}\n\n#admin-menu {\n display: block;\n margin: 0;\n padding: 0;\n list-style: none;\n\n em {\n font-style: normal;\n\n @include breakpoint(mini-nav-range) {\n display: none;\n }\n }\n\n li {\n .badges {\n float: right;\n margin-right: 1rem;\n\n .badge {\n display: inline-block;\n margin-right: -5px;\n }\n\n .updates {\n display: none;\n }\n\n .count {\n @include breakpoint(mini-nav-range) {\n display: none;\n }\n }\n\n &.with-updates {\n .count {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n }\n\n .updates {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n display: inline-block;\n\n @include breakpoint(mini-nav-range) {\n border-radius: 100%;\n position: absolute;\n top: 4px;\n right: 10px;\n font-size: 9px;\n line-height: 12px;\n padding: 1px 3px;\n min-width: 15px;\n }\n }\n }\n }\n\n a {\n @include transition(all 0.2s ease);\n display: block;\n padding-left: 25px;\n padding-top: 0.7rem;\n padding-bottom: 0.7rem;\n position: relative;\n\n .fa {\n @include transition(all 0.2s ease);\n margin-right: 8px;\n }\n\n }\n\n &.selected {\n a {\n padding-left: 16px;\n }\n }\n }\n}\n\nbody.sidebar-quickopen #admin-main {\n @include breakpoint(large-desktop-plus) {\n margin-left: $sidebar-width-small;\n }\n}\n\n#admin-main {\n\n @include breakpoint(tablet-plus) {\n margin-left: $sidebar-width-small;\n }\n\n @include breakpoint(large-desktop-plus) {\n margin-left: $sidebar-width-large;\n }\n\n .form-label label {\n .hint:after, [data-hint]:after {\n max-width: 300px;\n min-width: 150px;\n display: inline-table;\n white-space: initial;\n line-height: 1.2;\n }\n }\n\n\n h1 {\n margin: 0;\n font-size: 1.3rem;\n text-align: left;\n letter-spacing: -1px;\n line-height: 1.5;\n }\n\n .padding {\n padding: $padding-default;\n }\n\n .lines-button {\n display: none;\n margin-left: $padding-default / 4;\n &:focus {\n outline:none;\n }\n\n @include breakpoint(mobile-only) {\n display: inline-block;\n\n }\n\n }\n\n .button {\n white-space: nowrap;\n }\n\n .titlebar {\n position: relative;\n height: $topbar-height;\n padding: 0 $padding-default;\n z-index: 4;\n\n box-shadow: 0px 3px 15px 0px rgba(0, 0, 0, 0.1);\n\n @include breakpoint(mobile-only) {\n padding: 0 ($padding-default / 4) 0 ($padding-default + 2.5);\n }\n\n h1 {\n @extend %vertical-align;\n display: block;\n white-space: nowrap;\n overflow: hidden;\n -ms-text-overflow: ellipsis;\n text-overflow: ellipsis;\n\n @include breakpoint(mobile-only) {\n font-size: 18px;\n i {\n display: none;\n }\n }\n }\n\n .button-bar {\n @extend %vertical-align;\n padding: 0;\n white-space: nowrap;\n z-index: 4;\n }\n\n .preview {\n font-size: 90%;\n }\n\n .button {\n padding: 0.5rem 0.4rem;\n vertical-align: top;\n margin-right: -2px;\n\n @include breakpoint(mobile-only) {\n font-size: 0;\n padding: 0.5rem 0.5rem;\n min-height: 36px;\n i {\n font-size: 20px;\n margin-right: 0;\n }\n }\n\n @media only all and (max-width: 20.875em) {\n padding: 0.5rem 0.2rem;\n }\n }\n\n\n\n @include breakpoint(mobile-only) {\n .dropdown-menu .button {\n font-size: 0.9rem;\n }\n }\n }\n\n .admin-block .alert {\n margin-top: -2rem;\n margin-bottom: 2rem;\n }\n\n .alert {\n &:before {\n font-family: FontAwesome;\n margin-right: 5px;\n }\n &.info:before {\n content: \"\\f0a1\";\n }\n &.warning:before {\n content: \"\\f071\";\n }\n &.note:before {\n content: \"\\f24a\";\n }\n\n &.grav:before {\n font-family: rockettheme-apps;\n content: \"\\62\";\n vertical-align: bottom;\n }\n &.grav {\n #grav-update-button {\n margin-top: -3px;\n float: right;\n }\n }\n }\n\n .grav-update {\n @include clearfix;\n padding: 0 $padding-default;\n margin-top: -2rem;\n margin-bottom: 1rem;\n display: none;\n\n @include breakpoint(mobile-only) {\n padding: 0 1rem;\n }\n\n .button {\n float: right;\n margin-top: 0.6rem;\n margin-left: 1rem;\n line-height: 1.5;\n }\n\n p {\n line-height: $update-height;\n margin: 0;\n display: block;\n white-space: nowrap;\n overflow: hidden;\n -ms-text-overflow: ellipsis;\n text-overflow: ellipsis;\n }\n\n span[class=\"hint--left\"]::after {\n line-height: 1.5;\n }\n\n i {\n padding-right: 0.5rem;\n }\n\n }\n\n .grav-update.grav + .content-wrapper {\n height: calc(100vh - #{$topbar-height} - #{$update-height});\n }\n\n .content-wrapper {\n position: relative;\n overflow-y: hidden;\n height: calc(100vh - #{$topbar-height});\n //z-index: 0;\n\n .gm-scrollbar.-vertical {\n right: 5px;\n top: 5px;\n width: 8px;\n border-radius: 4px;\n }\n }\n\n .content-padding {\n padding: $content-padding;\n\n @include breakpoint(mobile-only) {\n padding: 0;\n }\n }\n\n .admin-block {\n padding: 1rem 0;\n\n &:empty {\n padding: 0;\n }\n\n h1 {\n padding: 0 $padding-default 0.5rem;\n margin: 0 0 1rem;\n\n @include breakpoint(mobile-only) {\n padding: 0 1rem 0.5rem;\n }\n\n &.no_underline {\n border-bottom: 0;\n }\n }\n\n .button-bar {\n margin: 0 $padding-default;\n\n @include breakpoint(mobile-only) {\n margin: -1rem 0 1rem 0;\n padding: 0 1rem;\n text-align: center;\n\n .button {\n width: 100%;\n }\n }\n }\n }\n\n .flush-bottom {\n &.button-bar {\n margin: 1rem (-$padding-default) -1rem;\n padding: 1rem;\n float: none;\n display: flex;\n\n @include breakpoint(mobile-only) {\n &.stats-bar {\n font-size: 90%;\n b {\n font-size: 1.5rem;\n }\n }\n }\n\n .button {\n margin: 0;\n height: 37px;\n\n &.dropdown-toggle {\n margin-left: -4px;\n }\n }\n }\n }\n\n .danger, .success {\n position: relative;\n\n &.button-bar {\n margin: 2rem 0 -1rem;\n height: 70px;\n padding: 1rem;\n float: none;\n .button {\n @extend %vertical-align;\n }\n }\n }\n}\n\n.content-padding {\n #admin-dashboard {\n\n .dashboard-item-flex {\n &:nth-of-type(2n) .dashboard-item {\n margin-right: 0;\n margin-left: $content-padding / 2;\n }\n\n &:nth-of-type(2n+1) .dashboard-item {\n margin-left: 0;\n margin-right: $content-padding / 2;\n }\n }\n\n .dashboard-item {\n margin-bottom: $content-padding;\n }\n\n }\n}\n\n#admin-dashboard {\n\n @include display(flex);\n @include flex-flow(row wrap);\n\n .dashboard-item-flex {\n @include display(flex);\n @include flex-flow(column nowrap);\n\n width: 50%;\n\n\n\n @include breakpoint(mobile-only) {\n width: 100%;\n .dashboard-item {\n margin-right: 0px !important;\n margin-left: 0px !important;\n margin-bottom: 10px !important;\n }\n }\n }\n\n .dashboard-item {\n\n > div:not(.widget-content) {\n padding: 1rem $padding-default;\n }\n\n h1 .right {\n float: right;\n }\n\n &.admin-block {\n padding: 1rem 0;\n\n ul {\n margin: 0 0 1rem 0;\n padding: 0;\n list-style: none;\n }\n li {\n margin: 0;\n padding: 0.425rem $padding-default 0.2rem $padding-default;\n &:last-child {\n border-bottom: 0;\n }\n }\n }\n }\n\n .chart-loader {\n position: relative;\n\n .fa {\n display: block;\n position: absolute;\n width: 100%;\n text-align: center;\n font-size: 4rem;\n margin-top: -2rem;\n @extend %vertical-align;\n }\n\n text-align: center;\n }\n\n .widget-content {\n position: relative;\n min-height: 335px;\n padding: 0;\n }\n\n .widget-loader {\n position: absolute;\n text-align: center;\n top: 50%;\n left: 50%;\n width: 100%;\n transform: translate(-50%, -50%);\n\n .fa {\n font-size: 4rem;\n }\n }\n\n #updates {\n\n p {\n text-align: center;\n margin: 0;\n }\n\n .updates-chart {\n width: 50%;\n float: left;\n\n }\n\n .flush-bottom {\n &.button-bar {\n height: 70px;\n flex-direction: row-reverse;\n }\n }\n\n .chart-wrapper {\n position: relative;\n }\n\n .backups-chart {\n position: relative;\n width: 50%;\n float: left;\n }\n\n .ct-chart {\n min-height: 157px;\n }\n\n .numeric {\n display: block;\n position: absolute;\n width: 100%;\n text-align: center;\n font-size: 1.5rem;\n line-height: 1;\n @extend %vertical-align;\n\n em {\n display: block;\n font-style: normal;\n font-size: 1rem;\n }\n\n }\n\n .admin-update-charts {\n @include clearfix;\n min-height: 191px;\n }\n\n .button-bar > .button:not(.dropdown-toggle) {\n margin-left: 4px;\n height: 37px;\n }\n }\n\n #popularity {\n .ct-chart {\n min-height: 171px;\n }\n\n .button-bar {\n height: 100px;\n padding: 0 1rem;\n display: flex;\n }\n }\n\n .dashboard-item {\n\n .admin-statistics-chart {\n position: relative;\n }\n\n p {\n text-align: center;\n margin: 0;\n }\n\n .stat {\n width: 33%;\n text-align: center;\n\n display: flex;\n flex-direction: column;\n justify-content: center;\n\n b {\n display: block;\n font-size: 2.5rem;\n line-height: 1;\n font-weight: 300;\n }\n i {\n display: block;\n font-style: normal;\n }\n }\n }\n\n #news-feed {\n .date {\n float: right;\n z-index: 1;\n }\n li {\n\n\n a {\n display: block;\n white-space: nowrap;\n overflow: hidden;\n -ms-text-overflow: ellipsis;\n text-overflow: ellipsis;\n z-index: 0;\n\n &:before {\n font-family: FontAwesome;\n margin-right: 10px;\n content: \"\\f1ea\";\n }\n }\n\n\n }\n }\n}\n\n.no-flick {\n -webkit-transform: translate3d(0, 0, 0);\n}\n\n.card-row {\n @include justify-content(space-between);\n}\n\n.card-item {\n @extend .no-flick;\n overflow: hidden;\n padding: 1rem;\n margin: 0;\n position: relative;\n width: 31%;\n max-width: 21rem;\n\n @include breakpoint(tablet-range) {\n width: 48%;\n }\n @include breakpoint(mobile-only) {\n width: 100%;\n }\n\n margin-bottom: 2rem;\n\n h4 {\n font-size: 1.2rem;\n line-height: 1.2;\n }\n}\n\n.user-details {\n text-align: left;\n img {\n border-radius: 100%;\n\n }\n h2 {\n margin: 0;\n font-size: 2.1rem !important;\n margin: 1rem 0 0 !important;\n line-height: 1.5;\n\n span {\n font-size: 1.2rem;\n }\n }\n h5 {\n font-size: 1.1rem;\n margin: 0;\n linep-height: 1.2;\n }\n\n .gravatar {\n font-size: 0.9rem;\n padding: 0;\n }\n\n @include breakpoint(mobile-only) {\n text-align: center;\n }\n}\n\n#footer {\n text-align: center;\n padding: 3rem 0 1rem;\n\n p {\n margin: 0;\n }\n}\n\n// Chart overrides\n.ct-chart {\n .ct-series {\n .ct-bar {\n stroke-width: 20px;\n }\n }\n}\n\n#popularity .ct-chart {\n margin: 0 -10px -10px;\n .ct-chart-bar {\n padding: 4px 6px 1px 10px;\n }\n}\n\n#latest {\n .page-title, .page-route {\n overflow: auto;\n }\n}\n\n#overlay {\n position: fixed;\n width: 25%;\n height: 100%;\n z-index: 999999;\n left: 75%;\n top: 0;\n display: none;\n\n @include breakpoint(mobile-only) {\n display: none!important;\n }\n}\n\n// Fix tabs spacing in plugin / theme options\n.gpm-item-info + #blueprints .block-tabs {\n padding-top: 16px;\n}\n\n.plugins-notifications-container,\n.themes-notifications-container {\n margin-bottom: 2rem;\n}\n\n.package-dependencies-container {\n li {\n height: 35px;\n\n .button {\n height: 30px;\n float: right;\n margin-right: 10px;\n line-height: 20px;\n }\n }\n}\n\n.pointer-events-disabled {\n pointer-events: none;\n}\n\n// Reinstall packages\n.updates-checked {\n .button-reinstall-package {\n display: inline-block!important;\n }\n\n &.has-updates {\n .button-reinstall-package {\n display: none!important;\n }\n }\n}\n\n// Preview\n.preview-wrapper {\n .content-padding {\n position: absolute;\n top: 0;\n bottom: 0;\n right: 0;\n left: 0;\n }\n}\n\n// Config Side Tabs\n@include breakpoint(mobile-only) {\n #admin-main .config-wrapper-system .form-tabs.side-tabs {\n > .tabs-nav {\n display: none;\n width: 0;\n }\n > .tabs-content {\n width: 100%;\n .tab__content {\n display: block;\n }\n }\n }\n}\n\n// Log Viewer\n.logs-output {\n\n form {\n\n .form-data {\n display: flex;\n margin: 0 -0.5rem;\n\n @include breakpoint(small-mobile-range) {\n display: block;\n }\n }\n\n .forms-select-wrapper {\n display: block;\n flex: 1;\n margin: 0.5rem 0.5rem;\n }\n }\n\n h1 {\n margin-top: 2rem !important;\n }\n\n h3 {\n padding: 0.5rem 1.5rem;\n font-size: 1.1rem;\n\n @include breakpoint(mobile-only) {\n padding: 0.5rem 1rem;\n }\n }\n table.noflex {\n\n td {\n vertical-align: top;\n }\n\n td.date, td.level {\n white-space: nowrap;\n }\n\n td.date {\n opacity: 0.7;\n }\n\n th.level, td.level {\n padding: 0.375rem 30px;\n }\n\n td.level {\n font-weight: bold;\n\n span.badge {\n font-size: 0.7rem;\n }\n }\n\n td.message {\n width: 100%;\n word-break: break-all;\n }\n\n .trace {\n ol {\n margin: 10px 0;\n padding: 0;\n font-size: 0.7rem;\n\n code {\n font-size: 0.7rem;\n display: block;\n white-space: pre-wrap\n }\n }\n }\n }\n}\n\n\n",".pages-list {\n list-style: none;\n margin: 0;\n padding: 0;\n\n ul {\n list-style: none;\n margin: 0;\n padding: 0;\n }\n\n li {\n margin: 0;\n padding: 0;\n\n }\n\n .row {\n @include transition(all 0.2s ease);\n line-height: 2.5rem;\n padding-right: 3rem;\n\n @include breakpoint(mobile-only) {\n padding-right: 1rem;\n }\n [data-hint]:after {\n line-height: 1 !important;\n width: auto !important;\n white-space: nowrap !important;\n }\n\n p.page-route {\n display: block;\n margin: 0;\n line-height: 1;\n font-size: 0.9rem;\n word-break: break-all;\n\n .spacer {\n display: inline-block;\n margin: 0 0.3rem;\n }\n }\n\n // fix for hint alignment\n .hint--bottom:before, .hint--bottom:after {\n left: 4px;\n }\n .hint:after, [data-hint]:after {\n border-radius: 2px;\n }\n\n .badge.lang {\n margin-left: 8px;\n }\n }\n\n .page-home {\n font-size: 1.4rem;\n margin-left: 10px;\n vertical-align: middle;\n }\n\n .page-info {\n font-size: 1.1rem;\n margin-left: 10px;\n vertical-align: middle;\n }\n\n .page-edit {\n text-overflow: ellipsis;\n display: inline-block;\n max-width: 100%;\n white-space: nowrap;\n overflow-x: hidden;\n }\n\n .page-item__row {\n display: flex;\n }\n\n .page-item__toggle {\n width: 25px;\n line-height: 1;\n padding-top: 7px;\n }\n\n .page-item__content {\n padding: 5px 0;\n width: calc(100% - 50px);\n }\n\n .page-item__content-name {\n line-height: 1;\n }\n\n .page-item__content-hint {\n line-height: 1.3;\n vertical-align: middle;\n max-width: 100%;\n }\n\n .page-item__tools {\n width: 90px;\n text-align: right;\n font-size: 1.4rem;\n\n .page-view {\n margin-right: 4px;\n }\n }\n\n .page-icon {\n\n color: #0082BA;\n font-weight: 700;\n\n &.children-open:before {\n content: '\\f056';\n }\n\n &.children-closed:before{\n content: '\\f055';\n }\n\n &.not-routable {\n color: #CE431D;\n }\n\n &.not-visible {\n color: #999;\n }\n\n &.modular {\n color: #9055AF;\n }\n }\n\n #pages-legend {\n margin-top: 1.5rem;\n text-align: center;\n\n h2, ul, li {\n display: inline-block;\n }\n\n li {\n margin-right: 0.75rem;\n }\n }\n}\n\n#page-filtering {\n margin: 0 $padding-default 1rem;\n @include clearfix;\n\n @include breakpoint(mobile-only) {\n margin: -1rem 1rem 1rem;\n }\n\n .page-filters {\n width: 60%;\n float: left;\n\n @include breakpoint(mobile-only) {\n width: 100%;\n }\n\n }\n\n .page-search {\n position: relative;\n width: 40%;\n float: left;\n padding-left: 2rem;\n text-indent: 2.5rem;\n &:after {\n position: absolute;\n right: 15px;\n top: 10px;\n content: '\\f002';\n font-family: 'FontAwesome';\n }\n\n @include breakpoint(mobile-only) {\n width: 100%;\n padding-top: 1rem;\n padding-left: 0rem;\n\n &:after {\n top: 1.5rem;\n }\n }\n }\n\n .page-shortcuts {\n @include clearfix;\n clear: both;\n padding-top:5px;\n }\n\n .selectize-control.single.plugin-remove_button .selectize-input,\n .selectize-control.multi .selectize-input {\n\n padding: ($leading-margin / 4) ($leading-margin / 4);\n\n &.has-items {\n padding-top: 6px;\n padding-bottom: 4px;\n }\n\n input {\n font-size: $core-font-size;\n line-height: $core-line-height;\n }\n\n\n .item, .item.active {\n color: #777;\n background: #eee;\n\n padding: 2px 10px;\n &[data-value='Routable'] {\n background: #CE431D;\n color: $white;\n }\n &[data-value='NonRoutable'] {\n color: #CE431D;\n }\n &[data-value='Visible'] {\n background: #0082BA;\n color: $white;\n }\n &[data-value='NonVisible'] {\n color: #0082BA;\n }\n &[data-value='Modular'] {\n background: #9055AF;\n color: $white;\n }\n &[data-value='NonModular'] {\n color: #9055AF;\n }\n &[data-value='Published'] {\n background: #0093B8;\n color: $white;\n }\n &[data-value='NonPublished'] {\n color: #0093B8;\n }\n }\n\n }\n}\n\n.admin-form-wrapper {\n position: relative;\n}\n\n#admin-topbar {\n position: absolute;\n right: 5px;\n height: 50px;\n\n @include breakpoint(mobile-only) {\n position: relative;\n width: 100%;\n right: 0;\n padding: 0 .5rem;\n margin: -2rem 0 2rem 0;\n }\n\n #admin-mode-toggle, #admin-lang-toggle {\n @extend %vertical-align;\n height: 38px;\n display: inline-block;\n vertical-align: inherit;\n\n @include breakpoint(mobile-only) {\n float: right;\n }\n }\n\n #admin-lang-toggle {\n z-index: 3;\n float: left;\n margin-right: 5px;\n\n button {\n padding: 0.3rem 0.5rem;\n height: 100%;\n }\n\n .dropdown-menu {\n button {\n width: 100%;\n\n }\n }\n }\n\n .switch-grav {\n border: 0;\n line-height: 38px;\n\n }\n\n .switch-toggle {\n @include breakpoint(mobile-only) {\n width: 100%;\n }\n }\n}\n\n@for $i from 0 to 10 {\n .depth-#{$i} .row {\n padding-left: 3rem * ($i + 1);\n }\n\n @include breakpoint(mobile-only) {\n .depth-#{$i} .row {\n padding-left: 1rem * ($i + 1);\n }\n }\n}\n\n.selectize-route-option {\n display: flex;\n\n > :first-child {\n flex: 0 0 auto;\n width: auto;\n margin-right: 10px;\n }\n\n > :last-child {\n flex: 1 1 auto;\n\n > * {\n display: block;\n }\n }\n\n}\n\n.hidden {\n display: none!important;\n}\n\n.switch-toggle input[type=\"radio\"] {\n display: none!important;\n}\n","/*\n * Remodal - v0.2.0\n * Flat, responsive, lightweight, easy customizable modal window plugin with declarative state notation and hash tracking.\n * http://vodkabears.github.io/remodal/\n *\n * Made by Ilya Makarov\n * Under MIT License\n */\n\n/* ==========================================================================\n Remodal necessary styles\n ========================================================================== */\n\n/* Hide scroll bar */\n\nhtml.remodal-is-locked {\n overflow: hidden;\n\n touch-action: none;\n}\n\n/* Anti FOUC */\n\n.remodal, [data-remodal-id] {\n display: none;\n}\n\n/* Overlay necessary styles */\n\n.remodal-overlay {\n position: fixed;\n z-index: 99999;\n top: -5000px;\n right: -5000px;\n bottom: -5000px;\n left: -5000px;\n\n display: none;\n}\n\n/* Necessary styles of the wrapper */\n\n.remodal-wrapper {\n position: fixed;\n z-index: 100000;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n\n display: none;\n overflow: auto;\n\n text-align: center;\n\n -webkit-overflow-scrolling: touch;\n\n &:after {\n display: inline-block;\n\n height: 100%;\n margin-left: -0.05em;\n\n content: '';\n }\n}\n\n/* Fix iPad, iPhone glitches */\n\n.remodal-overlay,\n.remodal-wrapper {\n backface-visibility: hidden;\n}\n\n/* Modal dialog necessary styles */\n.remodal {\n position: relative;\n outline: none;\n text-size-adjust: 100%;\n}\n\n.remodal-is-initialized {\n /* Disable Anti-FOUC */\n display: inline-block;\n}\n\n/* ==========================================================================\n Remodal's default mobile first theme\n ========================================================================== */\n\n/* Default theme styles for the background */\n\n.remodal-bg.remodal-is-opening,\n.remodal-bg.remodal-is-opened {\n @include filter(blur(3px));\n}\n\n/* Default theme styles of the overlay */\n\n.remodal-overlay {\n background: rgba(43, 46, 56, 0.9);\n}\n\n.remodal-overlay.remodal-is-opening,\n.remodal-overlay.remodal-is-closing {\n animation-duration: 0.3s;\n animation-fill-mode: forwards;\n}\n\n.remodal-overlay.remodal-is-opening {\n animation-name: remodal-overlay-opening-keyframes;\n}\n\n.remodal-overlay.remodal-is-closing {\n animation-name: remodal-overlay-closing-keyframes;\n}\n\n/* Default theme styles of the wrapper */\n\n.remodal-wrapper {\n padding: 10px 10px 0;\n}\n\n/* Default theme styles of the modal dialog */\n\n.remodal {\n box-sizing: border-box;\n width: 100%;\n margin-bottom: 10px;\n padding: 35px;\n\n transform: translate3d(0, 0, 0);\n}\n\n.remodal.remodal-is-opening,\n.remodal.remodal-is-closing {\n animation-duration: 0.3s;\n animation-fill-mode: forwards;\n}\n\n.remodal.remodal-is-opening {\n animation-name: remodal-opening-keyframes;\n}\n\n.remodal.remodal-is-closing {\n animation-name: remodal-closing-keyframes;\n}\n\n/* Vertical align of the modal dialog */\n\n.remodal,\n.remodal-wrapper:after {\n vertical-align: middle;\n}\n\n/* Close button */\n\n.remodal-close {\n position: absolute;\n top: 0;\n left: 0;\n\n display: block;\n overflow: visible;\n\n width: 35px;\n height: 35px;\n margin: 0;\n padding: 0;\n\n cursor: pointer;\n transition: color 0.2s;\n text-decoration: none;\n\n border: 0;\n outline: 0;\n background: transparent;\n}\n\n.remodal-close:before {\n font-family: Arial, \"Helvetica CY\", \"Nimbus Sans L\", sans-serif !important;\n font-size: 25px;\n line-height: 35px;\n\n position: absolute;\n top: 0;\n left: 0;\n\n display: block;\n\n width: 35px;\n\n content: \"\\00d7\";\n text-align: center;\n}\n\n/* Dialog buttons */\n\n/*.remodal-confirm,\n.remodal-cancel {\n font: inherit;\n\n display: inline-block;\n overflow: visible;\n\n min-width: 110px;\n margin: 0;\n padding: 12px 0;\n\n cursor: pointer;\n transition: background 0.2s;\n text-align: center;\n vertical-align: middle;\n text-decoration: none;\n\n border: 0;\n outline: 0;\n}\n\n.remodal-confirm {\n color: #fff;\n background: #81c784;\n}\n\n.remodal-confirm:hover,\n.remodal-confirm:focus {\n background: #66bb6a;\n}\n\n.remodal-cancel {\n color: #fff;\n background: #e57373;\n}\n\n.remodal-cancel:hover,\n.remodal-cancel:focus {\n background: #ef5350;\n}\n\n!* Remove inner padding and border in Firefox 4+ for the button tag. *!\n\n.remodal-confirm::-moz-focus-inner,\n.remodal-cancel::-moz-focus-inner,\n.remodal-close::-moz-focus-inner {\n padding: 0;\n\n border: 0;\n}*/\n\n/* Keyframes\n ========================================================================== */\n\n@keyframes remodal-opening-keyframes {\n from {\n transform: scale(1.05);\n\n opacity: 0;\n }\n to {\n transform: none;\n\n opacity: 1;\n }\n}\n\n@keyframes remodal-closing-keyframes {\n from {\n transform: scale(1);\n\n opacity: 1;\n }\n to {\n transform: scale(0.95);\n\n opacity: 0;\n }\n}\n\n@keyframes remodal-overlay-opening-keyframes {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes remodal-overlay-closing-keyframes {\n from {\n opacity: 1;\n }\n to {\n opacity: 0;\n }\n}\n\n/* Media queries\n ========================================================================== */\n\n@media only screen and (min-width: 641px) {\n .remodal {\n max-width: 700px;\n }\n}\n\n/* IE8\n ========================================================================== */\n\n.lt-ie9 .remodal-overlay {\n background: #2b2e38;\n}\n\n.lt-ie9 .remodal {\n width: 700px;\n}\n\n/********* GRAV CUSTOM ********/\n\n.remodal {\n padding: 35px 0 0;\n text-align: left;\n box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);\n border-radius: 3px;\n}\n\n.remodal ul li {\n margin-left: 27px;\n list-style-type: square;\n}","$tab-label-height: 50px;\n\n// New JS powered tabs\n.form-tabs {\n\n .admin-pages &:first-child {\n margin-top: -1rem;\n }\n .admin-pages & {\n .tabs-nav {\n margin-right: 180px;\n\n @include breakpoint(mobile-only) {\n margin-right: 0 !important;\n }\n }\n }\n\n .tabs-nav {\n @include display(flex);\n padding-top: 1px;\n\n a {\n @include flex(1);\n transition: color 0.5s ease, background 0.5s ease;\n cursor: pointer;\n min-height: $tab-label-height;\n text-align:center;\n\n padding: 4px;\n\n display: flex;\n align-items: center;\n justify-content: center;\n\n }\n\n span {\n display: inline-block;\n line-height: 1.1;\n }\n\n\n }\n\n &.subtle .tabs-nav {\n margin-right: 0 !important;\n }\n\n .tabs-content {\n\n .tab__content {\n display: none;\n padding-top: 2rem;\n\n &.active {\n display: block;\n }\n }\n }\n\n &.side-tabs {\n display: flex;\n background: none;\n\n > .tabs-nav {\n width: 20%;\n display: block;\n padding-top: 1.5rem;\n\n a {\n text-align: left;\n flex: none;\n justify-content: left;\n padding-left: 15px;\n }\n }\n\n > .tabs-content {\n width: 80%;\n }\n }\n}\n\n\n// Old tabs here for posterity!\n//.tab-bar {\n// @include clearfix;\n// margin: 0;\n// padding: 0;\n// list-style: none;\n//\n// li {\n//\n// display: block;\n// float: left;\n// height: $tab-label-height;\n//\n// @include breakpoint(mobile-only) {\n// width: 100%;\n// span, a {\n// width: 100%;\n// text-align: center;\n// }\n// }\n// }\n//\n// span, a {\n// @extend .default-animation;\n// display: inline-block;\n// padding: 0 4rem;\n// line-height: $tab-label-height;\n// }\n//}\n\n\n\n\n","// Grav Editor\n.grav-editor-content {\n position: relative;\n}\n\n.grav-editor {\n [data-grav-editor] {\n height: 300px;\n\n &.code-mirrored {\n padding: 0;\n border: 0;\n position: absolute;\n z-index: -1;\n display: block !important;\n }\n }\n\n .CodeMirror {\n .CodeMirror-vscrollbar, .CodeMirror-hscrollbar {\n z-index: 1;\n }\n }\n\n .CodeMirror-scroll {\n //margin-right: -36px;\n position: static;\n\n // hide native scrollbar at the bottom\n padding-bottom: 15px;\n }\n\n .grav-editor-resizer {\n height: 6px;\n cursor: row-resize;\n border-radius: 0 0 3px 3px;\n margin-top: -2px;\n position: relative;\n z-index: 1;\n }\n .CodeMirror-selected {\n background: #D6E8FF;\n }\n\n .CodeMirror-gutters {\n height: 100%!important;\n }\n}\n\n.grav-editor-fullscreen {\n position: fixed;\n top: 4.2rem;\n left: 0;\n bottom: 0;\n right: 0;\n z-index: 3;\n padding: 0 !important;\n margin: 0 !important;\n display: flex;\n flex-direction: column;\n\n .grav-editor-content {\n display: flex;\n flex-direction: column;\n flex: 1 0 0;\n\n overflow-y: auto;\n }\n\n .grav-editor-content, .CodeMirror-wrap, .grav-editor-preview {\n height: 100% !important;\n }\n\n .grav-editor-toolbar {\n flex: 0 0 auto;\n }\n\n .grav-editor-toolbar, .grav-editor-toolbar ul li:first-child a, .grav-editor-toolbar-flip ul li:last-child a {\n border-radius: 0 !important;\n }\n\n .grav-editor-resizer {\n display: none;\n }\n\n .grav-editor-content.is-active {\n display: flex;\n }\n}\n\n.grav-editor-toolbar {\n @include align-items(stretch);\n @include display(flex);\n @include flex-direction(row);\n\n .grav-editor-actions {\n @include flex(1);\n }\n .grav-editor-modes {\n flex-shrink: 0;\n margin-right: 2px;\n ul {\n float: right;\n }\n }\n}\n\n.grav-editor-toolbar {\n @include clearfix;\n\n ul {\n list-style: none;\n margin: 0;\n padding: 0;\n\n li {\n &:first-child a {\n border-top-left-radius: 4px;\n }\n display: inline-block;\n }\n\n a {\n display: inline-block;\n cursor: pointer;\n background-color: transparent;\n border-radius: 0;\n padding: 0.4rem;\n margin-top: -1px;\n border: 1px solid transparent;\n\n &, &:hover, &:active, &:focus {\n outline: 0;\n }\n }\n }\n\n .dropdown-menu li {\n width: 50%;\n }\n}\n\n.grav-editor-hide-toolbar {\n display: none;\n}\n\n.grav-editor-toolbar .grav-editor-modes {\n li {\n &:first-child a {\n border-top-left-radius: 0;\n }\n &:last-child a {\n border-top-right-radius: 4px;\n }\n }\n\n a {\n &:hover {\n /*border: 1px solid transparent;\n border-bottom: 1px solid darken($content-bg, 20%);*/\n }\n }\n}\n\n.grav-editor-toolbar-flip {\n float: right;\n ul li:last-child a {\n border-top-right-radius: 4px;\n }\n}\n\n.grav-editor-content, .grav-editor-preview {\n @include clearfix;\n cursor: text;\n display: none;\n\n &.is-active {\n display: block;\n }\n}\n\n.grav-editor-content {\n /* PADDING */\n .CodeMirror-lines {\n padding: 10px 0; /* Vertical padding around content */\n }\n}\n\n#admin-main {\n .grav-editor-preview {\n padding: 20px;\n overflow-y: scroll;\n position: relative;\n background: #fff;\n line-height: 1.5;\n\n h1, h2, h3, h4, h5, h6 {\n padding: 0;\n margin: 2rem 0 1rem;\n border-bottom: 0 !important;\n }\n\n p {\n padding: 0;\n margin: 0.5rem 0 1rem;\n }\n\n pre {\n padding: 1rem;\n }\n\n }\n}\n\n\n.grav-editor-toolbar {\n [data-hint]:after {\n white-space: nowrap !important;\n max-width: 300px;\n width: auto !important;\n }\n\n p {\n margin-top: 10px;\n margin-bottom: 10px;\n padding-left: 20px;\n }\n}\n\n// CodeMirror Theme\n.cm-s-paper {\n $paper-fg: #666;\n $tag: darken($paper-fg, 10%);\n\n &.CodeMirror {\n color: $paper-fg;\n line-height: 1.4;\n font-size: 16px;\n }\n\n .cm-link {color: #0082ba;}\n .cm-comment {color: lighten($paper-fg,10%);}\n .cm-header {color: darken($paper-fg,5%);}\n .cm-strong {color: darken($paper-fg,10%);}\n .cm-em {color: darken($paper-fg, 10%);}\n .cm-string {color: #0082BA;}\n .cm-tag {color: $tag}\n .cm-bracket {color: lighten($tag, 10%);}\n .cm-hr {color: lighten($paper-fg,40%);font-weight: bold;}\n\n .cm-keyword {color: #0082ba;}\n .cm-atom {color: #9055af;}\n .cm-meta {color: $paper-fg;}\n\n .cm-number {color: #7f8c8d;}\n .cm-def {color: #00f;}\n .cm-variable {color: black;}\n //.cm-variable-2 {color: #555;}\n //.cm-variable-3 {color: #085;}\n .cm-property {color: black;}\n .cm-operator {color: black;}\n\n\n .cm-string-2 {color: #f50;}\n .cm-meta {color: #555;}\n .cm-error {color: #f00;}\n .cm-qualifier {color: #555;}\n .cm-builtin {color: #555;}\n\n\n .cm-attribute {color: #7f8c8d;}\n\n .cm-quote {color: #888;}\n\n\n .cm-header-1 {font-size: 140%;}\n .cm-header-2 {font-size: 120%;}\n .cm-header-3 {font-size: 110%;}\n\n .cm-negative {color: #d44;}\n .cm-positive {color: #292;}\n .cm-header, .cm-strong {font-weight: bold;}\n .cm-em {font-style: italic;}\n .cm-link {text-decoration: underline;}\n\n .cm-invalidchar {color: #f00;}\n}\n","/* The MIT License */\n\n$preview-width: 200px;\n$preview-height: 150px;\n\n.dropzone {\n position: relative;\n border-radius: $form-border-radius;\n min-height: 4rem;\n\n &.dz-clickable {\n cursor: pointer;\n }\n &.dz-drag-hover {\n border-color: rgba(0,0,0,0.15);\n background: rgba(0,0,0,0.04);\n }\n &.dz-started .dz-message {\n display: none;\n }\n .dz-message {\n opacity: 1;\n -ms-filter: none;\n filter: none;\n }\n .dz-preview {\n position: relative;\n display: inline-block;\n margin: 1rem 1rem 1.5rem 1rem;\n vertical-align: top;\n\n &.dz-file-preview [data-dz-thumbnail] {\n display: none;\n }\n\n &.dz-error .dz-error-mark {\n display: block;\n }\n\n &.dz-success .dz-success-mark {\n display: block;\n }\n\n .dz-success-mark, .dz-error-mark {\n font-family: FontAwesome;\n display: none;\n position: absolute;\n width: 22px;\n height: 22px;\n font-size: 18px;\n line-height: 25px;\n border-radius: 100%;\n text-align: center;\n right: 2px;\n top: 2px;\n span {\n display: none;\n }\n }\n &:hover {\n .dz-success-mark, .dz-error-mark {\n display: none;\n }\n }\n\n .dz-success-mark {\n &::after {\n content: '\\f00c';\n }\n }\n\n .dz-error-mark {\n &::after {\n content: '\\f12a';\n }\n }\n\n .dz-progress {\n position: absolute;\n top: $preview-height;\n left: 0px;\n right: 0px;\n height: 4px;\n display: none;\n\n .dz-upload {\n display: block;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 0%;\n }\n }\n\n .dz-error-message {\n display: none;\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n font-size: 0.9rem;\n line-height: 1.2;\n padding: 8px 10px;\n z-index: 500;\n }\n\n &.dz-processing .dz-progress {\n display: block;\n }\n\n &:hover {\n .dz-remove, .dz-unset, .dz-view, .dz-insert, .dz-metadata {\n display: block;\n }\n\n }\n\n &.dz-processing {\n &:hover {\n .dz-unset {\n display: none;\n }\n }\n }\n\n &:hover.dz-error .dz-error-message {\n display: block;\n }\n\n //.dz-remove, .dz-unset, .dz-insert {\n // display: none;\n //}\n\n .dz-filename {\n position: absolute;\n z-index: 1;\n bottom: 0;\n width: 100%;\n padding: 0 5px;\n background: rgba(0,0,0,0.3);\n color: #fff;\n text-align: center;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .dz-remove, .dz-unset, .dz-view, .dz-insert, .dz-metadata {\n display: none;\n position: absolute;\n width: 25px;\n height: 25px;\n right: -26px;\n font-size: 0;\n cursor: pointer;\n\n &:hover {\n &:after {\n color: #fff;\n }\n }\n\n &:after {\n text-align: center;\n display: block;\n color: #999;\n font-family: FontAwesome;\n font-size: 18px;\n line-height: 25px;\n }\n }\n\n .dz-insert {\n top: -1px;\n border-top-right-radius: 4px;\n &:after {\n content: '\\f055';\n }\n }\n\n .dz-view {\n top: 24px;\n &:after {\n content: '\\f06e';\n }\n }\n\n .dz-remove {\n top: 72px;\n &:after {\n content: '\\f1f8';\n }\n }\n\n .dz-unset {\n top: 96px;\n border-bottom-right-radius: 4px;\n &:after {\n content: '\\f00d';\n }\n }\n \n .dz-metadata {\n top: 49px;\n &:after {\n content: '\\f05a';\n }\n }\n\n &.dz-no-editor {\n .dz-view {\n top: -1px;\n border-top-right-radius: 4px;\n }\n .dz-remove {\n top: 24px;\n }\n .dz-unset {\n top: 72px;\n }\n }\n\n &:hover .dz-remove, &:hover .dz-unset {\n &.file-thumbnail-remove {\n display: block;\n }\n\n &:not(.file-thumbnail-remove) {\n border-left: 0;\n }\n }\n\n &.dz-processing {\n .dz-details {\n overflow: hidden;\n }\n }\n\n .dz-details {\n width: $preview-width;\n height: $preview-height;\n position: relative;\n font-size: 0.8rem;\n\n background-image: url('../images/transparent.png');\n\n img {\n position: absolute;\n object-fit: contain;\n top: 0;\n left: 0;\n width: $preview-width;\n height: $preview-height;\n }\n\n .dz-size {\n position: absolute;\n bottom: -28px;\n left: 0;\n right: 0;\n text-align: center;\n font-size: 0.8rem;\n height: 28px;\n line-height: 28px;\n }\n }\n\n\n }\n\n\n &.dz-clickable {\n .dz-message, .dz-message span {\n cursor: pointer;\n text-align: center;\n font-size: 1.2rem;\n line-height: 1.4;\n margin: 1rem 0;\n }\n }\n * {\n cursor: default;\n }\n}\n",".toast-title {\n font-weight: bold;\n}\n.toast-message {\n -ms-word-wrap: break-word;\n word-wrap: break-word;\n}\n.toast-message a,\n.toast-message label {\n color: #ffffff;\n}\n.toast-message a:hover {\n color: #cccccc;\n text-decoration: none;\n}\n.toast-close-button {\n position: relative;\n right: -0.3em;\n top: -0.3em;\n float: right;\n font-size: 20px;\n font-weight: bold;\n color: #ffffff;\n -webkit-text-shadow: 0 1px 0 #ffffff;\n text-shadow: 0 1px 0 #ffffff;\n opacity: 0.8;\n -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);\n filter: alpha(opacity=80);\n}\n.toast-close-button:hover,\n.toast-close-button:focus {\n color: #000000;\n text-decoration: none;\n cursor: pointer;\n opacity: 0.4;\n -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);\n filter: alpha(opacity=40);\n}\n/*Additional properties for button version\n iOS requires the button element instead of an anchor tag.\n If you want the anchor version, it requires `href=\"#\"`.*/\nbutton.toast-close-button {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n}\n.toast-top-center {\n top: 0;\n right: 0;\n width: 100%;\n}\n.toast-bottom-center {\n bottom: 0;\n right: 0;\n width: 100%;\n}\n.toast-top-full-width {\n top: 0;\n right: 0;\n width: 100%;\n}\n.toast-bottom-full-width {\n bottom: 0;\n right: 0;\n width: 100%;\n}\n.toast-top-left {\n top: 12px;\n left: 12px;\n}\n.toast-top-right {\n top: 5rem;\n right: 1.5rem;\n}\n.toast-bottom-right {\n right: 12px;\n bottom: 12px;\n}\n.toast-bottom-left {\n bottom: 12px;\n left: 12px;\n}\n#toast-container {\n position: fixed;\n z-index: 999999;\n /*overrides*/\n\n}\n#toast-container * {\n -moz-box-sizing: border-box;\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n}\n#toast-container > div {\n position: relative;\n overflow: hidden;\n margin: 0 0 6px;\n padding: 15px 15px 15px 50px;\n width: 300px;\n border-radius: 3px;\n background-position: 15px center;\n background-repeat: no-repeat;\n color: #ffffff;\n opacity: 0.9;\n -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=90);\n filter: alpha(opacity=90);\n}\n#toast-container > :hover {\n opacity: 1;\n -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);\n filter: alpha(opacity=100);\n cursor: pointer;\n}\n#toast-container > .toast-info {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=\") !important;\n}\n#toast-container > .toast-error {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=\") !important;\n}\n#toast-container > .toast-success {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==\") !important;\n}\n#toast-container > .toast-warning {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=\") !important;\n}\n#toast-container.toast-top-center > div,\n#toast-container.toast-bottom-center > div {\n width: 300px;\n margin: auto;\n}\n#toast-container.toast-top-full-width > div,\n#toast-container.toast-bottom-full-width > div {\n width: 96%;\n margin: auto;\n}\n\n.toast-warning {\n background-color: #f89406;\n\n .button {\n background-color: darken(#f89406, 10);\n @include button-color(darken(#f89406, 20));\n }\n}\n.toast-progress {\n position: absolute;\n left: 0;\n bottom: 0;\n height: 4px;\n background-color: #000000;\n opacity: 0.4;\n -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);\n filter: alpha(opacity=40);\n}\n/*Responsive Design*/\n@media all and (max-width: 240px) {\n #toast-container > div {\n padding: 8px 8px 8px 50px;\n width: 11em;\n }\n #toast-container .toast-close-button {\n right: -0.2em;\n top: -0.2em;\n }\n}\n@media all and (min-width: 241px) and (max-width: 480px) {\n #toast-container > div {\n padding: 8px 8px 8px 50px;\n width: 18em;\n }\n #toast-container .toast-close-button {\n right: -0.2em;\n top: -0.2em;\n }\n}\n@media all and (min-width: 481px) and (max-width: 768px) {\n #toast-container > div {\n padding: 15px 15px 15px 50px;\n width: 25em;\n }\n}\n",".gpm {\n td {\n border: 0;\n }\n\n .gpm-search {\n position: relative;\n padding: 0 3rem 0.5rem $padding-default;\n text-indent: 2.5rem;\n &:after {\n position: absolute;\n right: 60px;\n top: 10px;\n content: '\\f002';\n font-family: 'FontAwesome';\n }\n\n @include breakpoint(mobile-only) {\n padding-left: 1rem;\n\n }\n }\n\n .sort-actions {\n float: right;\n margin: 0 24px;\n\n @include breakpoint(mobile-only) {\n float: none;\n }\n\n > * {\n font-size: 1rem;\n display: inline-block;\n vertical-align: middle;\n }\n\n .sort-icon {\n cursor: pointer;\n }\n\n select {\n margin: 0;\n display: none;\n }\n\n .selectize-input {\n width: 9rem;\n vertical-align: middle;\n line-height: 21px;\n\n input {\n line-height: 21px;\n }\n }\n }\n\n .gpm-name {\n white-space: nowrap;\n\n @include breakpoint(mobile-only) {\n white-space: normal;\n }\n }\n\n .gpm-version {\n padding-left:0.5rem;\n font-size: 0.9rem;\n }\n\n .gpm-testing {\n @extend .badge;\n border-radius: 3px;\n padding: 0 4px;\n font-size: 0.75rem;\n }\n\n .theme .gpm-testing {\n line-height: 1rem;\n position: absolute;\n top: 0;\n right: 0;\n border-radius: 0 0 0 3px;\n line-height: 1.3;\n }\n\n .gpm-actions {\n\n .enabled, .disabled {\n font-size: 1.6rem;\n }\n }\n\n .gpm-item-info {\n position: relative;\n padding-bottom: 1rem;\n margin-bottom: 3rem;\n overflow: hidden;\n\n\n\n .gpm-item-icon {\n position: absolute;\n right: $padding-default;\n font-size: 20rem;\n }\n table {\n position: relative;\n }\n td {\n border: 0;\n text-align: left !important;\n\n &.double {\n padding-left: 2rem;\n }\n\n &:first-child {\n white-space: nowrap;\n }\n\n @include breakpoint(mobile-only) {\n word-wrap: break-word;\n word-break: break-word;\n @include hyphens(auto);\n }\n }\n tr:hover {\n background: inherit;\n }\n\n\n }\n\n .badge.update {\n display: inline-block;\n border-radius: 4px;\n padding: 2px 10px;\n margin-left: 1rem;\n }\n\n .gpm-ribbon {\n overflow: hidden;\n white-space: nowrap;\n position: absolute;\n top: 1rem;\n right: -2rem;\n @include transform(rotate(45deg));\n\n a {\n display: block;\n font-weight: bold;\n font-size: 0.9rem;\n padding: 5px 40px;\n text-align: center;\n }\n }\n\n .themes {\n padding: $padding-default;\n\n .card-item {\n padding: 1rem 1rem 4rem;\n }\n\n .gpm-screenshot {\n text-align: center;\n img {\n margin: 0 auto;\n max-height: 300px;\n }\n }\n\n .gpm-name {\n margin-bottom: 0.5rem;\n white-space: inherit;\n }\n\n .gpm-actions {\n margin: 1rem -1rem -1rem -1rem;\n height: 4rem;\n text-align: center;\n padding: 1rem;\n font-size: 1rem;\n font-weight: bold;\n\n position: absolute;\n left: 0;\n right: 0;\n bottom: 1rem;\n }\n\n .active-theme .gpm-actions, &.inactive-theme .gpm-actions {\n line-height: 2rem;\n }\n\n .inactive-theme .gpm-actions {\n display: block;\n font-weight: normal;\n }\n }\n\n #gpm-release-toggle {\n float: right;\n margin-right: 3rem;\n }\n}\n","#phpinfo {\n img { display: none;}\n\n table {\n margin: 1rem 0 0;\n }\n \n tr {\n &:hover {\n background: transparent;\n }\n }\n\n td {\n word-wrap: break-word;\n\t overflow: hidden;\n\n h1 {\n margin: 0rem (- $padding-default) 0rem !important;\n }\n }\n\n hr {\n border-bottom: 0;\n }\n\n h1 {\n font-size: 2.3rem;\n }\n\n h2 {\n font-size: 1.7rem;\n margin: 3rem ($padding-default) 0rem !important;\n }\n}\n","$button-size : 2rem;\n$transition: .3s; // increase this to see the transformations in slow-motion\n\n@mixin line {\n display: inline-block;\n width: $button-size;\n height: $button-size / 7;\n border-radius: $button-size / 14;\n transition: $transition;\n}\n\n.lines-button {\n display: inline-block;\n padding: $button-size / 4 $button-size / 4 $button-size / 2;\n transition: .3s;\n cursor: pointer;\n user-select: none;\n border-radius: $button-size / 5;\n position: absolute;\n z-index: 100002;\n top: 10px;\n\n background: transparent;\n\n &:hover {\n opacity: 1;\n }\n\n &:active {\n transition: none;\n background: rgba(0, 0, 0, .1);\n }\n}\n\n.lines {\n position: relative;\n @include line;\n\n /*create the upper and lower lines as pseudo-elements of the middle line*/\n &:before, &:after {\n\n @include line;\n position: absolute;\n left:0;\n content: '';\n transform-origin: $button-size / 14 center;\n }\n &:before { top: $button-size / 4; }\n &:after { top: -$button-size / 4; }\n}\n\n.sidebar-mobile-open .lines-button.x .lines {\n\n /*hide the middle line*/\n background: transparent !important;\n\n /*overlay the lines by setting both their top values to 0*/\n &:before, &:after{\n transform-origin: 50% 50%;\n top:0;\n width: $button-size;\n }\n\n // rotate the lines to form the x shape\n &:before{\n transform: rotate3d(0, 0, 1, 45deg);\n }\n &:after{\n transform: rotate3d(0, 0, 1, -45deg);\n }\n}\n",".single-notification.alert {\n position: relative;\n}\n\n.hide-notification {\n\tright: 1rem;\n position: absolute;\n}\n\n.alert.raw {\n padding: 0 !important;\n}\n\n#admin-dashboard {\n #notifications {\n .admin-block {\n padding: 1rem 0;\n }\n\n li {\n white-space: nowrap;\n overflow: hidden;\n -ms-text-overflow: ellipsis;\n text-overflow: ellipsis;\n }\n }\n}\n\n.dashboard-notifications-container, .plugins-notifications-container, .themes-notifications-container {\n\n margin-bottom: 1rem;\n\n .raw {\n position: relative;\n\n .hide-notification {\n position: absolute;\n top: 5px;\n right: 5px;\n line-height: 1;\n font-size: 1.2rem;\n color: #333;\n text-shadow:\n -1px -1px 0 #fff,\n 1px -1px 0 #fff,\n -1px 1px 0 #fff,\n 1px 1px 0 #fff;\n }\n }\n\n &:empty {\n margin: 0;\n }\n}\n\n#admin-main {\n .content-padding {\n\n div[class*=notifications-container] .alert {\n a:hover {\n text-decoration: underline;\n }\n\n a.button, span.button, button.button {\n float: right;\n text-decoration: none;\n }\n\n }\n\n\n\n .top-notifications-container {\n\n margin-bottom: 1rem;\n\n .alert {\n a {\n cursor: pointer;\n }\n }\n\n @include breakpoint(mobile-only) {\n padding-bottom: 20px;\n }\n }\n }\n\n #notifications {\n td:last-child {\n position: relative;\n span {\n position: absolute;\n display: block;\n right: 2rem;\n top: 6px;\n }\n }\n .badge.alert {\n margin: 0;\n font-size: 0.7rem;\n text-transform: uppercase;\n margin-right: 5px;\n border-radius: 4px;\n vertical-align: middle;\n line-height: 1.7;\n\n i {\n margin-right: 3px;\n }\n }\n }\n}\n\n\n\n","/* Media */\n$dark-text: #333;\n\n.pages-list-container {\n width: 30%;\n flex: none!important;\n padding: 0!important;\n\n .pages-list {\n width: 100%;\n\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n\n &.depth-0 .row {\n padding-left: .5rem;\n }\n\n .depth-1 .row {\n padding-left: 1rem;\n }\n\n .page-item {\n .page-link {\n cursor: pointer;\n }\n }\n\n .row.active {\n background-color: rgba(#00a6cf, 0.1);\n }\n }\n}\n\n.media-page-dropzone {\n width: 100%;\n margin-bottom: 20px;\n}\n\n.media-container {\n &.size-2-3 {\n flex: none!important;\n width: 70%;\n }\n\n\n .filters {\n padding-bottom: 30px;\n width: 100%;\n\n\n .filter-wrapper {\n margin-left: 10px;\n margin-right: 10px;\n\n &:after {\n margin-top: -25px;\n border: 0px none;\n content: \"\\f078\";\n font-family: \"FontAwesome\";\n right: 12px;\n top: 50%;\n line-height: 0;\n color: #9BA2A6;\n pointer-events: none;\n float: right;\n padding-right: 10px;\n }\n }\n\n .filter {\n option {\n padding: 8px;\n }\n }\n }\n\n .files {\n padding-left: 30px;\n padding-right: 30px;\n\n }\n\n .admin-media-title {\n\n color: $dark-text;\n margin: 0px;\n font-size: .8rem;\n line-height: 1.3;\n\n }\n\n .card-item {\n width: 49%;\n\n &.empty-space {\n\n background: rgba(255,255,255,0.4);\n border: 1px solid rgba(0,0,0,0.1) !important;\n\n color: rgba(0,0,0,0.3);\n\n max-width: none;\n display: block;\n text-align: center;\n\n top: 20%;\n height: 10rem !important;\n width: 50% !important;\n margin: 0 auto !important;\n font-size: 2rem;\n line-height: 8rem;\n transform: translateY(-50%);\n\n\n }\n }\n\n .media-details {\n text-align: center;\n img {\n width: 50%;\n }\n h2 {\n margin: 0;\n font-size: 1.8rem;\n }\n h5 {\n color: #9BA2A6;\n font-size: 1.1rem;\n margin: 0;\n }\n }\n\n\n h1 {\n padding: 0 2rem 0.5rem!important;\n }\n\n .admin-form-wrapper {\n width: 100%;\n padding-bottom: 20px;\n }\n}\n\n.spinning-wheel {\n text-align: center;\n}\n\n/* Media in-page modal */\n\n.media-container.in-modal {\n h1 {\n font-size: 30px!important;\n margin-top: 0px;\n }\n\n a {\n color: #333;\n }\n\n .pages-list li {\n list-style-type: none;\n margin-left: 0;\n }\n}\n\n//Mediapicker field\n.remodal.remodal-mediapicker {\n max-width: 70vw;\n padding: 0;\n}\n\n.media-container {\n height: 70vh;\n\n\n .grid {\n justify-content: flex-start;\n }\n\n\n &.in-modal {\n\n .gm-scrollbar {\n background: rgba(255,255,255,0.2);\n\n .thumb {\n background-color: rgba(0,0,0,0.2) !important;\n &:hover, &.active {\n background-color: rgba(0,0,0,0.3) !important;\n }\n }\n }\n }\n\n h5 {\n border-bottom: 1px solid #ddd;\n padding: 0.5rem 15px;\n margin: 0;\n height: 55px;\n }\n\n .filter-wrapper {\n float: right;\n margin-right: 5px;\n margin-top: 10px;\n }\n\n .filter-date {\n width: 150px;\n\n }\n\n .filter-type {\n width: 100px;\n margin-right: 15px;\n }\n\n .pages-list-container {\n position: relative;\n\n .pages-list {\n border-top: 0;\n padding-right: 10px;\n font-size: 90%;\n\n .row {\n transition: none !important;\n line-height: 2rem;\n }\n\n @for $i from 1 to 10 {\n .depth-#{$i} .row {\n padding-left: 1rem * ($i + 1);\n }\n\n }\n\n }\n\n .mediapicker-scroll {\n position: absolute;\n top: 55px;\n bottom: 3px;\n height: inherit;\n }\n }\n\n .thumbs-list-container {\n position: relative;\n\n .media-range {\n position: absolute;\n right: 15px;\n bottom: 5px;\n }\n\n .mediapicker-scroll {\n position: absolute;\n top: 55px;\n bottom: 30px;\n height: inherit;\n }\n\n .gm-scrollbar.-horizontal {\n display: none;\n }\n\n }\n\n\n #admin-media {\n\n padding: 15px;\n height: 100%;\n\n align-items: flex-start;\n align-content: flex-start;\n\n .card-item {\n width: 100px;\n padding: 2px;\n margin: 0 5px 5px 0;\n border: 0;\n\n img {\n display: block;\n }\n }\n\n .admin-media-details {\n position: relative;\n\n .admin-media-title {\n position: absolute;\n width: 100%;\n bottom: 0;\n background: rgba(0,0,0,0.3);\n color: #fff;\n font-size: 10px;\n overflow: hidden;\n line-height: 2;\n text-indent: 2px;\n white-space: nowrap;\n }\n }\n }\n}\n\n[data-remodal-id=\"metadata\"] {\n h1 > * {\n vertical-align: middle;\n }\n h1 strong {\n @include ellipsis(75%);\n }\n .metadata-preview {\n display: flex;\n margin: 0 3rem;\n\n .meta-preview {\n max-width: 200px;\n }\n\n .meta-content ul {\n margin: 0;\n }\n }\n}\n","\n/*\n * This file is part of the Arnapou jqCron package.\n *\n * (c) Arnaud Buathier \n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\n.jqCron-selector {\n position: relative;\n}\n.jqCron-cross,\n.jqCron-selector-title {\n cursor: pointer;\n border-radius: 3px;\n border: 1px solid #ddd;\n margin: 0 0.2em;\n padding: 0 0.5em;\n}\n.jqCron-container.disable .jqCron-cross:hover,\n.jqCron-container.disable .jqCron-selector-title:hover,\n.jqCron-cross,\n.jqCron-selector-title {\n background: #eee;\n border-color: #ddd;\n}\n.jqCron-cross:hover,\n.jqCron-selector-title:hover {\n background-color: #ddd;\n border-color: #aaa;\n}\n.jqCron-cross {\n border-radius: 1em;\n font-size: 80%;\n padding: 0 0.3em;\n}\n.jqCron-selector-list {\n background: #eee;\n border: 1px solid #aaa;\n -webkit-box-shadow: 2px 2px 3px #ccc;\n box-shadow: 2px 2px 3px #ccc;\n left: 0.2em;\n list-style: none;\n margin: 0;\n padding: 0;\n position: absolute;\n top: 1.5em;\n z-index: 5;\n}\n.jqCron-selector-list li {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n -ms-box-sizing: border-box;\n box-sizing: border-box;\n cursor: default;\n display: inline-block !important;\n margin: 0;\n padding: 0.1em 0.4em;\n width: 100%;\n}\n.jqCron-selector-list li.selected {\n background: #0088cc;\n color: white;\n}\n.jqCron-selector-list li:hover {\n background: #5fb9e7;\n color: white;\n}\n.jqCron-selector-list.cols2 {\n width: 4em;\n}\n.jqCron-selector-list.cols2 li {\n width: 50%;\n}\n.jqCron-selector-list.cols3 {\n width: 6em;\n}\n.jqCron-selector-list.cols3 li {\n width: 33%;\n}\n.jqCron-selector-list.cols4 {\n width: 8em;\n}\n.jqCron-selector-list.cols4 li {\n width: 25%;\n}\n.jqCron-selector-list.cols5 {\n width: 10em;\n}\n.jqCron-selector-list.cols5 li {\n width: 20%;\n}\n.jqCron-error .jqCron-selector-title {\n background: #fee;\n border: 1px solid #fdd;\n color: red;\n}\n.jqCron-container.disable * {\n color: #888;\n}\n.jqCron-container.disable .jqCron-selector-title {\n background: #eee !important;\n}\n","// Backups\n#backups-stats {\n #backups-usage {\n position: relative;\n margin-bottom: 1.5rem;\n\n h1 {\n position: absolute;\n right: 1rem;\n top: 5px;\n }\n }\n\n .backups-usage-wrapper {\n height: 40px;\n\n background: linear-gradient(90deg, $color-green 0%, $color-lime 20%, $color-yellow 40%, $color-orange 60%, $color-red 80%, $color-maroon 100%);\n\n > div {\n float: right;\n height: 40px;\n &.full {\n width: 100%;\n }\n }\n }\n}\n\n.backups-content {\n #admin-dashboard {\n display: block;\n #backups-stats {\n margin-left: 1rem;\n margin-right: 1rem;\n\n h1 {\n padding: 0rem;\n @include breakpoint(mobile-only) {\n font-size: 1.1rem;\n }\n }\n }\n }\n}\n\n// Scheduler\n.scheduler-content {\n\n #admin-main .admin-block & .alert {\n margin-top: -1rem;\n margin-bottom: 2rem;\n }\n\n .secondary-accent {\n .button {\n float: right;\n margin-top: -3px;\n }\n }\n\n #cron-install {\n @extend .default-animation;\n padding: 0 1.5rem;\n\n pre {\n padding: 0.5rem;\n }\n\n &.hide {\n display: none;\n }\n }\n}\n\n// Reports\n.report-output {\n #admin-main .admin-block & .alert {\n margin-top: 0;\n margin-bottom: 0;\n }\n\n td {\n .key {\n font-weight: bold;\n }\n\n .value {\n\n }\n }\n}\n\n// Direct install\n.direct-install-content {\n padding: 30px;\n\n .button {\n margin-top: 10px;\n margin-bottom: 50px;\n }\n}\n","// Your custom SCSS should be written here...\n\n"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/user/plugins/admin/themes/grav/css/chartist.min.css b/user/plugins/admin/themes/grav/css/chartist.min.css
new file mode 100755
index 0000000..f348f0a
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css/chartist.min.css
@@ -0,0 +1 @@
+.ct-double-octave:after,.ct-major-eleventh:after,.ct-major-second:after,.ct-major-seventh:after,.ct-major-sixth:after,.ct-major-tenth:after,.ct-major-third:after,.ct-major-twelfth:after,.ct-minor-second:after,.ct-minor-seventh:after,.ct-minor-sixth:after,.ct-minor-third:after,.ct-octave:after,.ct-perfect-fifth:after,.ct-perfect-fourth:after,.ct-square:after{content:"";clear:both}.ct-double-octave:after,.ct-double-octave:before,.ct-golden-section:after,.ct-major-eleventh:after,.ct-major-eleventh:before,.ct-major-second:after,.ct-major-second:before,.ct-major-seventh:after,.ct-major-seventh:before,.ct-major-sixth:after,.ct-major-sixth:before,.ct-major-tenth:after,.ct-major-tenth:before,.ct-major-third:after,.ct-major-third:before,.ct-major-twelfth:after,.ct-major-twelfth:before,.ct-minor-second:after,.ct-minor-second:before,.ct-minor-seventh:after,.ct-minor-seventh:before,.ct-minor-sixth:after,.ct-minor-sixth:before,.ct-minor-third:after,.ct-minor-third:before,.ct-octave:after,.ct-octave:before,.ct-perfect-fifth:after,.ct-perfect-fifth:before,.ct-perfect-fourth:after,.ct-perfect-fourth:before,.ct-square:after,.ct-square:before{content:""}.ct-label{fill:rgba(0,0,0,.4);color:rgba(0,0,0,.4);font-size:.75rem;line-height:1}.ct-chart-bar .ct-label,.ct-chart-line .ct-label{display:block;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}.ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label.ct-vertical.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-end;-webkit-justify-content:flex-end;-ms-flex-pack:flex-end;justify-content:flex-end;text-align:right;text-anchor:end}.ct-label.ct-vertical.ct-end{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar .ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;text-anchor:start}.ct-chart-bar .ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-start{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:flex-end;-webkit-justify-content:flex-end;-ms-flex-pack:flex-end;justify-content:flex-end;text-align:right;text-anchor:end}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-end{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:end}.ct-grid{stroke:rgba(0,0,0,.2);stroke-width:1px;stroke-dasharray:2px}.ct-point{stroke-width:10px;stroke-linecap:round}.ct-line{fill:none;stroke-width:4px}.ct-area{stroke:none;fill-opacity:.1}.ct-bar{fill:none;stroke-width:10px}.ct-slice-donut{fill:none;stroke-width:60px}.ct-series-a .ct-bar,.ct-series-a .ct-line,.ct-series-a .ct-point,.ct-series-a .ct-slice-donut{stroke:#d70206}.ct-series-a .ct-area,.ct-series-a .ct-slice-pie{fill:#d70206}.ct-series-b .ct-bar,.ct-series-b .ct-line,.ct-series-b .ct-point,.ct-series-b .ct-slice-donut{stroke:#f05b4f}.ct-series-b .ct-area,.ct-series-b .ct-slice-pie{fill:#f05b4f}.ct-series-c .ct-bar,.ct-series-c .ct-line,.ct-series-c .ct-point,.ct-series-c .ct-slice-donut{stroke:#f4c63d}.ct-series-c .ct-area,.ct-series-c .ct-slice-pie{fill:#f4c63d}.ct-series-d .ct-bar,.ct-series-d .ct-line,.ct-series-d .ct-point,.ct-series-d .ct-slice-donut{stroke:#d17905}.ct-series-d .ct-area,.ct-series-d .ct-slice-pie{fill:#d17905}.ct-series-e .ct-bar,.ct-series-e .ct-line,.ct-series-e .ct-point,.ct-series-e .ct-slice-donut{stroke:#453d3f}.ct-series-e .ct-area,.ct-series-e .ct-slice-pie{fill:#453d3f}.ct-series-f .ct-bar,.ct-series-f .ct-line,.ct-series-f .ct-point,.ct-series-f .ct-slice-donut{stroke:#59922b}.ct-series-f .ct-area,.ct-series-f .ct-slice-pie{fill:#59922b}.ct-series-g .ct-bar,.ct-series-g .ct-line,.ct-series-g .ct-point,.ct-series-g .ct-slice-donut{stroke:#0544d3}.ct-series-g .ct-area,.ct-series-g .ct-slice-pie{fill:#0544d3}.ct-series-h .ct-bar,.ct-series-h .ct-line,.ct-series-h .ct-point,.ct-series-h .ct-slice-donut{stroke:#6b0392}.ct-series-h .ct-area,.ct-series-h .ct-slice-pie{fill:#6b0392}.ct-series-i .ct-bar,.ct-series-i .ct-line,.ct-series-i .ct-point,.ct-series-i .ct-slice-donut{stroke:#f05b4f}.ct-series-i .ct-area,.ct-series-i .ct-slice-pie{fill:#f05b4f}.ct-series-j .ct-bar,.ct-series-j .ct-line,.ct-series-j .ct-point,.ct-series-j .ct-slice-donut{stroke:#dda458}.ct-series-j .ct-area,.ct-series-j .ct-slice-pie{fill:#dda458}.ct-series-k .ct-bar,.ct-series-k .ct-line,.ct-series-k .ct-point,.ct-series-k .ct-slice-donut{stroke:#eacf7d}.ct-series-k .ct-area,.ct-series-k .ct-slice-pie{fill:#eacf7d}.ct-series-l .ct-bar,.ct-series-l .ct-line,.ct-series-l .ct-point,.ct-series-l .ct-slice-donut{stroke:#86797d}.ct-series-l .ct-area,.ct-series-l .ct-slice-pie{fill:#86797d}.ct-series-m .ct-bar,.ct-series-m .ct-line,.ct-series-m .ct-point,.ct-series-m .ct-slice-donut{stroke:#b2c326}.ct-series-m .ct-area,.ct-series-m .ct-slice-pie{fill:#b2c326}.ct-series-n .ct-bar,.ct-series-n .ct-line,.ct-series-n .ct-point,.ct-series-n .ct-slice-donut{stroke:#6188e2}.ct-series-n .ct-area,.ct-series-n .ct-slice-pie{fill:#6188e2}.ct-series-o .ct-bar,.ct-series-o .ct-line,.ct-series-o .ct-point,.ct-series-o .ct-slice-donut{stroke:#a748ca}.ct-series-o .ct-area,.ct-series-o .ct-slice-pie{fill:#a748ca}.ct-square{display:block;position:relative;width:100%}.ct-square:before{display:block;float:left;width:0;height:0;padding-bottom:100%}.ct-square:after{display:table}.ct-square>svg{display:block;position:absolute;top:0;left:0}.ct-minor-second{display:block;position:relative;width:100%}.ct-minor-second:before{display:block;float:left;width:0;height:0;padding-bottom:93.75%}.ct-minor-second:after{display:table}.ct-minor-second>svg{display:block;position:absolute;top:0;left:0}.ct-major-second{display:block;position:relative;width:100%}.ct-major-second:before{display:block;float:left;width:0;height:0;padding-bottom:88.8888888889%}.ct-major-second:after{display:table}.ct-major-second>svg{display:block;position:absolute;top:0;left:0}.ct-minor-third{display:block;position:relative;width:100%}.ct-minor-third:before{display:block;float:left;width:0;height:0;padding-bottom:83.3333333333%}.ct-minor-third:after{display:table}.ct-minor-third>svg{display:block;position:absolute;top:0;left:0}.ct-major-third{display:block;position:relative;width:100%}.ct-major-third:before{display:block;float:left;width:0;height:0;padding-bottom:80%}.ct-major-third:after{display:table}.ct-major-third>svg{display:block;position:absolute;top:0;left:0}.ct-perfect-fourth{display:block;position:relative;width:100%}.ct-perfect-fourth:before{display:block;float:left;width:0;height:0;padding-bottom:75%}.ct-perfect-fourth:after{display:table}.ct-perfect-fourth>svg{display:block;position:absolute;top:0;left:0}.ct-perfect-fifth{display:block;position:relative;width:100%}.ct-perfect-fifth:before{display:block;float:left;width:0;height:0;padding-bottom:66.6666666667%}.ct-perfect-fifth:after{display:table}.ct-perfect-fifth>svg{display:block;position:absolute;top:0;left:0}.ct-minor-sixth{display:block;position:relative;width:100%}.ct-minor-sixth:before{display:block;float:left;width:0;height:0;padding-bottom:62.5%}.ct-minor-sixth:after{display:table}.ct-minor-sixth>svg{display:block;position:absolute;top:0;left:0}.ct-golden-section{display:block;position:relative;width:100%}.ct-golden-section:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:61.804697157%}.ct-golden-section:after{display:table;clear:both}.ct-golden-section>svg{display:block;position:absolute;top:0;left:0}.ct-major-sixth{display:block;position:relative;width:100%}.ct-major-sixth:before{display:block;float:left;width:0;height:0;padding-bottom:60%}.ct-major-sixth:after{display:table}.ct-major-sixth>svg{display:block;position:absolute;top:0;left:0}.ct-minor-seventh{display:block;position:relative;width:100%}.ct-minor-seventh:before{display:block;float:left;width:0;height:0;padding-bottom:56.25%}.ct-minor-seventh:after{display:table}.ct-minor-seventh>svg{display:block;position:absolute;top:0;left:0}.ct-major-seventh{display:block;position:relative;width:100%}.ct-major-seventh:before{display:block;float:left;width:0;height:0;padding-bottom:53.3333333333%}.ct-major-seventh:after{display:table}.ct-major-seventh>svg{display:block;position:absolute;top:0;left:0}.ct-octave{display:block;position:relative;width:100%}.ct-octave:before{display:block;float:left;width:0;height:0;padding-bottom:50%}.ct-octave:after{display:table}.ct-octave>svg{display:block;position:absolute;top:0;left:0}.ct-major-tenth{display:block;position:relative;width:100%}.ct-major-tenth:before{display:block;float:left;width:0;height:0;padding-bottom:40%}.ct-major-tenth:after{display:table}.ct-major-tenth>svg{display:block;position:absolute;top:0;left:0}.ct-major-eleventh{display:block;position:relative;width:100%}.ct-major-eleventh:before{display:block;float:left;width:0;height:0;padding-bottom:37.5%}.ct-major-eleventh:after{display:table}.ct-major-eleventh>svg{display:block;position:absolute;top:0;left:0}.ct-major-twelfth{display:block;position:relative;width:100%}.ct-major-twelfth:before{display:block;float:left;width:0;height:0;padding-bottom:33.3333333333%}.ct-major-twelfth:after{display:table}.ct-major-twelfth>svg{display:block;position:absolute;top:0;left:0}.ct-double-octave{display:block;position:relative;width:100%}.ct-double-octave:before{display:block;float:left;width:0;height:0;padding-bottom:25%}.ct-double-octave:after{display:table}.ct-double-octave>svg{display:block;position:absolute;top:0;left:0}
\ No newline at end of file
diff --git a/user/plugins/admin/themes/grav/css/codemirror/base16-light.css b/user/plugins/admin/themes/grav/css/codemirror/base16-light.css
new file mode 100644
index 0000000..12ff2eb
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css/codemirror/base16-light.css
@@ -0,0 +1,36 @@
+/*
+
+ Name: Base16 Default Light
+ Author: Chris Kempson (http://chriskempson.com)
+
+ CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-chrome-devtools)
+ Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
+
+*/
+
+.cm-s-base16-light.CodeMirror {background: #f5f5f5; color: #202020;}
+.cm-s-base16-light div.CodeMirror-selected {background: #e0e0e0 !important;}
+.cm-s-base16-light .CodeMirror-gutters {background: #f5f5f5; border-right: 0px;}
+.cm-s-base16-light .CodeMirror-guttermarker { color: #ac4142; }
+.cm-s-base16-light .CodeMirror-guttermarker-subtle { color: #b0b0b0; }
+.cm-s-base16-light .CodeMirror-linenumber {color: #b0b0b0;}
+.cm-s-base16-light .CodeMirror-cursor {border-left: 1px solid #505050 !important;}
+
+.cm-s-base16-light span.cm-comment {color: #8f5536;}
+.cm-s-base16-light span.cm-atom {color: #aa759f;}
+.cm-s-base16-light span.cm-number {color: #aa759f;}
+
+.cm-s-base16-light span.cm-property, .cm-s-base16-light span.cm-attribute {color: #90a959;}
+.cm-s-base16-light span.cm-keyword {color: #ac4142;}
+.cm-s-base16-light span.cm-string {color: #f4bf75;}
+
+.cm-s-base16-light span.cm-variable {color: #90a959;}
+.cm-s-base16-light span.cm-variable-2 {color: #6a9fb5;}
+.cm-s-base16-light span.cm-def {color: #d28445;}
+.cm-s-base16-light span.cm-bracket {color: #202020;}
+.cm-s-base16-light span.cm-tag {color: #ac4142;}
+.cm-s-base16-light span.cm-link {color: #aa759f;}
+.cm-s-base16-light span.cm-error {background: #ac4142; color: #505050;}
+
+.cm-s-base16-light .CodeMirror-activeline-background {background: #DDDCDC !important;}
+.cm-s-base16-light .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;}
diff --git a/user/plugins/admin/themes/grav/css/codemirror/codemirror.css b/user/plugins/admin/themes/grav/css/codemirror/codemirror.css
new file mode 100644
index 0000000..318d25f
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css/codemirror/codemirror.css
@@ -0,0 +1,332 @@
+/* BASICS */
+
+.CodeMirror {
+ /* Set height, width, borders, and global font properties here */
+ font-family: monospace;
+ height: 300px;
+ color: black;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+ padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+ padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+ border-right: 1px solid #ddd;
+ background-color: #f7f7f7;
+ white-space: nowrap;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+ padding: 0 3px 0 5px;
+ min-width: 20px;
+ text-align: right;
+ color: #999;
+ white-space: nowrap;
+}
+
+.CodeMirror-guttermarker { color: black; }
+.CodeMirror-guttermarker-subtle { color: #999; }
+
+/* CURSOR */
+
+.CodeMirror div.CodeMirror-cursor {
+ border-left: 1px solid black;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+ border-left: 1px solid silver;
+}
+.CodeMirror.cm-fat-cursor div.CodeMirror-cursor {
+ width: auto;
+ border: 0;
+ background: #7e7;
+}
+.CodeMirror.cm-fat-cursor div.CodeMirror-cursors {
+ z-index: 1;
+}
+
+.cm-animate-fat-cursor {
+ width: auto;
+ border: 0;
+ -webkit-animation: blink 1.06s steps(1) infinite;
+ -moz-animation: blink 1.06s steps(1) infinite;
+ animation: blink 1.06s steps(1) infinite;
+ background-color: #7e7;
+}
+@-moz-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@-webkit-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+
+/* Can style cursor different in overwrite (non-insert) mode */
+div.CodeMirror-overwrite div.CodeMirror-cursor {}
+
+.cm-tab { display: inline-block; text-decoration: inherit; }
+
+.CodeMirror-ruler {
+ border-left: 1px solid #ccc;
+ position: absolute;
+}
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+.cm-strikethrough {text-decoration: line-through;}
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable,
+.cm-s-default .cm-punctuation,
+.cm-s-default .cm-property,
+.cm-s-default .cm-operator {}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3 {color: #085;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-s-default .cm-error {color: #f00;}
+.cm-invalidchar {color: #f00;}
+
+.CodeMirror-composing { border-bottom: 2px solid; }
+
+/* Default styles for common addons */
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
+.CodeMirror-activeline-background {background: #e8f2ff;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+ the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+ position: relative;
+ overflow: hidden;
+ background: white;
+}
+
+.CodeMirror-scroll {
+ overflow: scroll !important; /* Things will break if this is overridden */
+ /* 30px is the magic margin used to hide the element's real scrollbars */
+ /* See overflow: hidden in .CodeMirror */
+ margin-bottom: -30px; margin-right: -30px;
+ padding-bottom: 30px;
+ height: 100%;
+ outline: none; /* Prevent dragging from highlighting the element */
+ position: relative;
+}
+.CodeMirror-sizer {
+ position: relative;
+ border-right: 30px solid transparent;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+ before actuall scrolling happens, thus preventing shaking and
+ flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ position: absolute;
+ z-index: 6;
+ display: none;
+}
+.CodeMirror-vscrollbar {
+ right: 0; top: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+.CodeMirror-hscrollbar {
+ bottom: 0; left: 0;
+ overflow-y: hidden;
+ overflow-x: scroll;
+}
+.CodeMirror-scrollbar-filler {
+ right: 0; bottom: 0;
+}
+.CodeMirror-gutter-filler {
+ left: 0; bottom: 0;
+}
+
+.CodeMirror-gutters {
+ position: absolute; left: 0; top: 0;
+ z-index: 3;
+}
+.CodeMirror-gutter {
+ white-space: normal;
+ height: 100%;
+ display: inline-block;
+ margin-bottom: -30px;
+ /* Hack to make IE7 behave */
+ *zoom:1;
+ *display:inline;
+}
+.CodeMirror-gutter-wrapper {
+ position: absolute;
+ z-index: 4;
+ background: none !important;
+ border: none !important;
+}
+.CodeMirror-gutter-background {
+ position: absolute;
+ top: 0; bottom: 0;
+ z-index: 4;
+}
+.CodeMirror-gutter-elt {
+ position: absolute;
+ cursor: default;
+ z-index: 4;
+}
+.CodeMirror-gutter-wrapper {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.CodeMirror-lines {
+ cursor: text;
+ min-height: 1px; /* prevents collapsing before first draw */
+}
+.CodeMirror pre {
+ /* Reset some styles that the rest of the page might have set */
+ -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
+ border-width: 0;
+ background: transparent;
+ font-family: inherit;
+ font-size: inherit;
+ margin: 0;
+ white-space: pre;
+ word-wrap: normal;
+ line-height: inherit;
+ color: inherit;
+ z-index: 2;
+ position: relative;
+ overflow: visible;
+ -webkit-tap-highlight-color: transparent;
+}
+.CodeMirror-wrap pre {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ word-break: normal;
+}
+
+.CodeMirror-linebackground {
+ position: absolute;
+ left: 0; right: 0; top: 0; bottom: 0;
+ z-index: 0;
+}
+
+.CodeMirror-linewidget {
+ position: relative;
+ z-index: 2;
+ overflow: auto;
+}
+
+.CodeMirror-widget {}
+
+.CodeMirror-code {
+ outline: none;
+}
+
+/* Force content-box sizing for the elements where we expect it */
+.CodeMirror-scroll,
+.CodeMirror-sizer,
+.CodeMirror-gutter,
+.CodeMirror-gutters,
+.CodeMirror-linenumber {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+.CodeMirror-measure {
+ position: absolute;
+ width: 100%;
+ height: 0;
+ overflow: hidden;
+ visibility: hidden;
+}
+.CodeMirror-measure pre { position: static; }
+
+.CodeMirror div.CodeMirror-cursor {
+ position: absolute;
+ border-right: none;
+ width: 0;
+}
+
+div.CodeMirror-cursors {
+ visibility: hidden;
+ position: relative;
+ z-index: 3;
+}
+.CodeMirror-focused div.CodeMirror-cursors {
+ visibility: visible;
+}
+
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+.CodeMirror-crosshair { cursor: crosshair; }
+.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
+.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
+
+.cm-searching {
+ background: #ffa;
+ background: rgba(255, 255, 0, .4);
+}
+
+/* IE7 hack to prevent it from returning funny offsetTops on the spans */
+.CodeMirror span { *vertical-align: text-bottom; }
+
+/* Used to force a border model for a node */
+.cm-force-border { padding-right: .1px; }
+
+@media print {
+ /* Hide the cursor when printing */
+ .CodeMirror div.CodeMirror-cursors {
+ visibility: hidden;
+ }
+}
+
+/* See issue #2901 */
+.cm-tab-wrap-hack:after { content: ''; }
+
+/* Help users use markselection to safely style text background */
+span.CodeMirror-selectedtext { background: none; }
diff --git a/user/plugins/admin/themes/grav/css/codemirror/paper.css b/user/plugins/admin/themes/grav/css/codemirror/paper.css
new file mode 100644
index 0000000..8305176
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css/codemirror/paper.css
@@ -0,0 +1,39 @@
+.cm-s-paper .cm-keyword {color: #555;}
+.cm-s-paper .cm-atom {color: #7f8c8d;}
+.cm-s-paper .cm-number {color: #7f8c8d;}
+.cm-s-paper .cm-def {color: #00f;}
+.cm-s-paper .cm-variable {color: black;}
+.cm-s-paper .cm-variable-2 {color: #555;}
+.cm-s-paper .cm-variable-3 {color: #085;}
+.cm-s-paper .cm-property {color: black;}
+.cm-s-paper .cm-operator {color: black;}
+.cm-s-paper .cm-comment {color: #959595;}
+.cm-s-paper .cm-string {color: #7f8c8d;}
+.cm-s-paper .cm-string-2 {color: #f50;}
+.cm-s-paper .cm-meta {color: #555;}
+.cm-s-paper .cm-error {color: #f00;}
+.cm-s-paper .cm-qualifier {color: #555;}
+.cm-s-paper .cm-builtin {color: #555;}
+.cm-s-paper .cm-bracket {color: #997;}
+.cm-s-paper .cm-tag {color: #7f8c8d;}
+.cm-s-paper .cm-attribute {color: #7f8c8d;}
+.cm-s-paper .cm-header {color: #000;}
+.cm-s-paper .cm-quote {color: #888;}
+.cm-s-paper .cm-hr {color: #999;}
+.cm-s-paper .cm-link {color: #7f8c8d;}
+
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+
+.cm-invalidchar {color: #f00;}
+
+.cm-s-paper .cm-header-1 {font-size: 140%;}
+.cm-s-paper .cm-header-2 {font-size: 120%;}
+.cm-s-paper .cm-header-3 {font-size: 110%;}
+
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
diff --git a/user/plugins/admin/themes/grav/css/featherlight.min.css b/user/plugins/admin/themes/grav/css/featherlight.min.css
new file mode 100644
index 0000000..bf553f6
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css/featherlight.min.css
@@ -0,0 +1,8 @@
+/**
+ * Featherlight - ultra slim jQuery lightbox
+ * Version 0.4.9 - http://noelboss.github.io/featherlight/
+ *
+ * Copyright 2014, Noël Raoul Bossart (http://www.noelboss.com)
+ * MIT Licensed.
+**/
+@media all{.featherlight{display:none;position:fixed;top:0;right:0;bottom:0;left:0;z-index:2;text-align:center;white-space:nowrap;cursor:pointer;background:#333;background:rgba(0,0,0,0)}.featherlight:last-of-type{background:rgba(0,0,0,.8)}.featherlight:before{content:'';display:inline-block;height:100%;vertical-align:middle;margin-right:-.25em}.featherlight .featherlight-content{position:relative;text-align:left;vertical-align:middle;display:inline-block;overflow:auto;padding:25px 25px 0;border-bottom:25px solid transparent;min-width:30%;margin-left:5%;margin-right:5%;max-height:95%;background:#fff;cursor:auto;white-space:normal}.featherlight .featherlight-inner{display:block}.featherlight .featherlight-close-icon{position:absolute;z-index:9999;top:0;right:0;line-height:25px;width:25px;cursor:pointer;text-align:center;font:Arial,sans-serif;background:#fff;background:rgba(255,255,255,.3);color:#000}.featherlight .featherlight-image{width:100%}.featherlight-iframe .featherlight-content{border-bottom:0;padding:0}.featherlight iframe{border:0}}@media only screen and (max-width:1024px){.featherlight .featherlight-content{margin-left:10px;margin-right:10px;max-height:98%;padding:10px 10px 0;border-bottom:10px solid transparent}}
\ No newline at end of file
diff --git a/user/plugins/admin/themes/grav/css/fork-awesome.min.css b/user/plugins/admin/themes/grav/css/fork-awesome.min.css
new file mode 100755
index 0000000..56c7242
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css/fork-awesome.min.css
@@ -0,0 +1,12 @@
+/*!
+Fork Awesome 1.1.5
+License - http://forkawesome.github.io/Fork-Awesome/license
+
+Copyright 2018 Dave Gandy & Fork Awesome
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */@font-face{font-family:FontAwesome;src:url(../fonts/forkawesome-webfont.eot?v=1.1.5);src:url(../fonts/forkawesome-webfont.eot?#iefix&v=1.1.5) format('embedded-opentype'),url(../fonts/forkawesome-webfont.woff2?v=1.1.5) format('woff2'),url(../fonts/forkawesome-webfont.woff?v=1.1.5) format('woff'),url(../fonts/forkawesome-webfont.ttf?v=1.1.5) format('truetype'),url(../fonts/forkawesome-webfont.svg?v=1.1.5#forkawesomeregular) format('svg');font-weight:400;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1,-1);-ms-transform:scale(1,-1);transform:scale(1,-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-rotate-90{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-close:before,.fa-remove:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-repeat:before,.fa-rotate-right:before{content:"\f01e"}.fa-refresh:before,.fa-sync:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before,.fa-video:before{content:"\f03d"}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-exclamation-triangle:before,.fa-warning:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell-o:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-globe-e:before{content:"\f304"}.fa-globe-w:before{content:"\f305"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-community:before,.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-floppy-o:before,.fa-save:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus-g:before,.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-bolt:before,.fa-flash:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before,.fa-utensils:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-chain-broken:before,.fa-unlink:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:"\f150"}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:"\f151"}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:"\f152"}.fa-eur:before,.fa-euro:before{content:"\f153"}.fa-gbp:before,.fa-pound:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-inr:before,.fa-rupee:before{content:"\f156"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:"\f158"}.fa-krw:before,.fa-won:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before,.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-desc:before,.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-amount-asc:before,.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-desc:before,.fa-sort-amount-up:before{content:"\f161"}.fa-sort-numeric-asc:before,.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-desc:before,.fa-sort-numeric-up:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-try:before,.fa-turkish-lira:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-bank:before,.fa-institution:before,.fa-university:before{content:"\f19c"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before,.fa-utensil-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:"\f1c5"}.fa-file-archive-o:before,.fa-file-zip-o:before{content:"\f1c6"}.fa-file-audio-o:before,.fa-file-sound-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:"\f1d0"}.fa-empire:before,.fa-ge:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-paper-plane:before,.fa-send:before{content:"\f1d8"}.fa-paper-plane-o:before,.fa-send-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before,.fa-heading:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before,.fa-closed-captioning:before{content:"\f20a"}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before,.fa-gem:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-bed:before,.fa-hotel:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-medium-square:before{content:"\f2f8"}.fa-y-combinator:before,.fa-yc:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-television:before,.fa-tv:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo-v:before,.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-phone-volume:before,.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:"\f2a3"}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-sign-language:before,.fa-signing:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-address-card:before,.fa-vcard:before{content:"\f2bb"}.fa-address-card-o:before,.fa-vcard-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.fa-mastodon:before{content:"\f2e1"}.fa-mastodon-alt:before{content:"\f2e2"}.fa-fork-awesome:before,.fa-fork-circle:before{content:"\f2e3"}.fa-peertube:before{content:"\f2e4"}.fa-diaspora:before{content:"\f2e5"}.fa-friendica:before{content:"\f2e6"}.fa-gnu-social:before{content:"\f2e7"}.fa-liberapay-square:before{content:"\f2e8"}.fa-liberapay:before{content:"\f2e9"}.fa-scuttlebutt:before,.fa-ssb:before{content:"\f2ea"}.fa-hubzilla:before{content:"\f2eb"}.fa-social-home:before{content:"\f2ec"}.fa-artstation:before{content:"\f2ed"}.fa-discord:before{content:"\f2ee"}.fa-discord-alt:before{content:"\f2ef"}.fa-patreon:before{content:"\f2f0"}.fa-snowdrift:before{content:"\f2f1"}.fa-activitypub:before{content:"\f2f2"}.fa-ethereum:before{content:"\f2f3"}.fa-keybase:before{content:"\f2f4"}.fa-shaarli:before{content:"\f2f5"}.fa-shaarli-o:before{content:"\f2f6"}.fa-cut-key:before,.fa-key-modern:before{content:"\f2f7"}.fa-xmpp:before{content:"\f2f9"}.fa-archive-org:before{content:"\f2fc"}.fa-freedombox:before{content:"\f2fd"}.fa-facebook-messenger:before{content:"\f2fe"}.fa-debian:before{content:"\f2ff"}.fa-mastodon-square:before{content:"\f300"}.fa-tipeee:before{content:"\f301"}.fa-react:before{content:"\f302"}.fa-dogmazic:before{content:"\f303"}.fa-zotero:before{content:"\f309"}.fa-nodejs:before{content:"\f308"}.fa-nextcloud:before{content:"\f306"}.fa-nextcloud-square:before{content:"\f307"}.fa-hackaday:before{content:"\f30a"}.fa-laravel:before{content:"\f30b"}.fa-signalapp:before{content:"\f30c"}.fa-gnupg:before{content:"\f30d"}.fa-php:before{content:"\f30e"}.fa-ffmpeg:before{content:"\f30f"}.fa-joplin:before{content:"\f310"}.fa-syncthing:before{content:"\f311"}.fa-inkscape:before{content:"\f312"}.fa-matrix-org:before{content:"\f313"}.fa-pixelfed:before{content:"\f314"}.fa-bootstrap:before{content:"\f315"}.fa-dev-to:before{content:"\f316"}.fa-hashnode:before{content:"\f317"}.fa-jirafeau:before{content:"\f318"}.fa-emby:before{content:"\f319"}.fa-wikidata:before{content:"\f31a"}.fa-gimp:before{content:"\f31b"}.fa-c:before{content:"\f31c"}.fa-digitalocean:before{content:"\f31d"}.fa-att:before{content:"\f31e"}.fa-gitea:before{content:"\f31f"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}/*# sourceMappingURL=fork-awesome.min.css.map */
diff --git a/user/plugins/admin/themes/grav/css/hint.base.min.css b/user/plugins/admin/themes/grav/css/hint.base.min.css
new file mode 100644
index 0000000..c39c097
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css/hint.base.min.css
@@ -0,0 +1,5 @@
+/*! Hint.css (base version) - v1.3.5 - 2015-06-16
+* http://kushagragour.in/lab/hint/
+* Copyright (c) 2015 Kushagra Gour; Licensed MIT */
+
+.hint--bottom:after,.hint--top:after{margin-left:-18px}.hint,[data-hint]{position:relative;display:inline-block}.hint:after,.hint:before,[data-hint]:after,[data-hint]:before{position:absolute;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);transform:translate3d(0,0,0);visibility:hidden;opacity:0;z-index:1000000;pointer-events:none;-webkit-transition:.3s ease;-moz-transition:.3s ease;transition:.3s ease;-webkit-transition-delay:0ms;-moz-transition-delay:0ms;transition-delay:0ms}.hint--always:after,.hint--always:before,.hint:focus:after,.hint:focus:before,.hint:hover:after,.hint:hover:before,[data-hint]:focus:after,[data-hint]:focus:before,[data-hint]:hover:after,[data-hint]:hover:before{opacity:1;visibility:visible}.hint:hover:after,.hint:hover:before,[data-hint]:hover:after,[data-hint]:hover:before{-webkit-transition-delay:100ms;-moz-transition-delay:100ms;transition-delay:100ms}.hint:before,[data-hint]:before{content:'';position:absolute;background:0 0;border:6px solid transparent;z-index:1000001}.hint:after,[data-hint]:after{content:attr(data-hint);background:#383838;color:#fff;padding:8px 10px;font-size:12px;line-height:12px;white-space:nowrap}.hint--top:before{border-top-color:#383838;margin-bottom:-12px}.hint--bottom-left:before,.hint--bottom-right:before,.hint--bottom:before{border-bottom-color:#383838;margin-top:-12px}.hint--top:after,.hint--top:before{bottom:100%;left:50%}.hint--top:focus:after,.hint--top:focus:before,.hint--top:hover:after,.hint--top:hover:before{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.hint--bottom:after,.hint--bottom:before{top:100%;left:50%}.hint--bottom:focus:after,.hint--bottom:focus:before,.hint--bottom:hover:after,.hint--bottom:hover:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.hint--right:before{border-right-color:#383838;margin-left:-12px;margin-bottom:-6px}.hint--right:after{margin-bottom:-14px}.hint--right:after,.hint--right:before{left:100%;bottom:50%}.hint--right:focus:after,.hint--right:focus:before,.hint--right:hover:after,.hint--right:hover:before{-webkit-transform:translateX(8px);-moz-transform:translateX(8px);transform:translateX(8px)}.hint--left:before{border-left-color:#383838;margin-right:-12px;margin-bottom:-6px}.hint--left:after{margin-bottom:-14px}.hint--left:after,.hint--left:before{right:100%;bottom:50%}.hint--left:focus:after,.hint--left:focus:before,.hint--left:hover:after,.hint--left:hover:before{-webkit-transform:translateX(-8px);-moz-transform:translateX(-8px);transform:translateX(-8px)}.hint--bottom-left:after,.hint--bottom-left:before{top:100%;left:50%}.hint--bottom-left:before{margin-top:-11px;left:calc(50% - 6px)}.hint--bottom-left:after{-webkit-transform:translateX(-100%);-moz-transform:translateX(-100%);transform:translateX(-100%);margin-left:12px}.hint--bottom-left:hover:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.hint--bottom-left:hover:after{-webkit-transform:translateX(-100%) translateY(8px);-moz-transform:translateX(-100%) translateY(8px);transform:translateX(-100%) translateY(8px)}.hint--bottom-right:after,.hint--bottom-right:before{top:100%;left:50%}.hint--bottom-right:before{margin-top:-11px;left:calc(50% - 6px)}.hint--bottom-right:after{-webkit-transform:translateX(0);-moz-transform:translateX(0);transform:translateX(0);margin-left:-12px}.hint--bottom-right:hover:after,.hint--bottom-right:hover:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.hint--always.hint--top:after,.hint--always.hint--top:before{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.hint--always.hint--bottom:after,.hint--always.hint--bottom:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.hint--always.hint--left:after,.hint--always.hint--left:before{-webkit-transform:translateX(-8px);-moz-transform:translateX(-8px);transform:translateX(-8px)}.hint--always.hint--right:after,.hint--always.hint--right:before{-webkit-transform:translateX(8px);-moz-transform:translateX(8px);transform:translateX(8px)}
diff --git a/user/plugins/admin/themes/grav/css/iconpicker.css b/user/plugins/admin/themes/grav/css/iconpicker.css
new file mode 100755
index 0000000..0346d8b
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css/iconpicker.css
@@ -0,0 +1,223 @@
+/* Icon Picker by QueryLoop
+ * Author: @eliorivero
+ * URL: http://queryloop.com/
+ * License: GPLv2
+ */
+
+.icon-set {
+ display: none;
+ background: #fff;
+ overflow-y: auto;
+ -webkit-box-shadow: 0 1px 5px rgba(0, 0, 0, 0.15);
+ box-shadow: 0 1px 5px rgba(0, 0, 0, 0.15);
+}
+.icon-set ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+.icon-set li {
+ display: inline-block;
+ cursor: pointer;
+ padding: 8px;
+ margin: 0 0 5px;
+ width: 48px;
+ height: 48px;
+ font-size: 24px;
+ text-align: center;
+}
+.icon-set li:hover:before, .icon-set .icon-selected:before {
+ font-size: 32px;
+ position: relative;
+ top: 5px;
+ line-height: 1px;
+ width: 32px;
+ display: inline-block;
+}
+.icon-set.large li {
+ width: 32px;
+ height: 32px;
+ font-size: 32px;
+}
+.icon-set.large li:hover:before, .icon-set.large .icon-selected:before {
+ font-size: 48px;
+ width: 48px;
+ line-height: 24px;
+}
+.icon-highlight {
+ background: #00a6cf;
+ color: #fff;
+}
+.dialog {
+ display: block;
+ margin: 20px 0 0;
+ position: fixed;
+ right: 30%;
+ bottom: 30%;
+ width: 40%;
+ height: 40%;
+ max-height: 315px;
+ z-index: 19998;
+ visibility:hidden;
+ opacity: 0;
+ -webkit-box-shadow: 0 2px 4px rgba(0,0,0,0.25);
+ box-shadow: 0 2px 4px rgba(0,0,0,0.25);
+ -webkit-transform: scale(0.7) translateZ(0);
+ -ms-transform: scale(0.7) translateZ(0);
+ transform: scale(0.7) translateZ(0);
+ -webkit-transition: all 0.2s;
+ transition: all 0.2s;
+}
+.dialog-open {
+ padding: 10px;
+ visibility:visible;
+ -webkit-transform: scale(1);
+ -ms-transform: scale(1);
+ transform: scale(1);
+ opacity: 1;
+}
+.icon-set-wrap {
+ position: relative;
+}
+.ip-inline {
+ display: block;
+ width: 276px;
+ height: 0;
+ opacity: 0;
+ visibility:hidden;
+ -webkit-transform: scaleX(0.5) rotateX(90deg);
+ -ms-transform: scaleX(0.5) rotateX(90deg);
+ transform: scaleX(0.5) rotateX(90deg);
+ -webkit-transform-style: preserve-3d;
+ transform-style: preserve-3d;
+ -webkit-transform-origin: 50% 0;
+ transform-origin: 50% 0;
+ -webkit-transition: transform 0.3s ease-out;
+ transition: transform 0.3s ease-out;
+}
+.ip-inline-open {
+ padding: 10px;
+ border: 1px solid #eee;
+ height: auto;
+ visibility:visible;
+ -webkit-transform: scaleX(1) rotateY(0deg);
+ -ms-transform: scaleX(1) rotateY(0deg);
+ transform: scaleX(1) rotateY(0deg);
+ opacity: 1;
+}
+.dialog, .icon-picker-close {
+ top: 30%;
+ left: 30%;
+}
+.icon-picker-close {
+ opacity: 0;
+ visibility: hidden;
+ display: block;
+ width: 20px;
+ height: 20px;
+ color: #fff;
+ background: #333;
+ font-size: 19px;
+ margin: 30px 0 0 20px;
+ position: fixed;
+ z-index: 19999;
+ -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.25);
+ box-shadow: 0 1px 3px rgba(0,0,0,0.25);
+ -webkit-transition: all 0.2s;
+ transition: all 0.2s;
+}
+.icon-picker-close:after {
+ content: "\00D7";
+ width: 20px;
+ display: block;
+ position: absolute;
+ text-align: center;
+}
+.icon-picker-overlay {
+ opacity: 0;
+ visibility: hidden;
+ background: #222;
+ background: rgba(0, 0, 0, 0.5);
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 19997;
+ -webkit-transition: all 0.2s;
+ transition: all 0.2s;
+}
+.icon-picker-close.make-visible {
+ margin: 10px 0 0 -10px;
+}
+.ip-inline .icon-picker-close {
+ position: absolute;
+ z-index: 8;
+ top: 0;
+ right: 0;
+ left: auto;
+ opacity: 1;
+ visibility: visible;
+ margin: 0;
+}
+.make-visible {
+ visibility: visible;
+ opacity: 1;
+}
+.icon-selected {
+ background: #349886;
+ color: #fff;
+}
+.icon-preview {
+ display: none;
+ margin: 10px 0;
+ position: relative;
+}
+.icon-preview-on {
+ display: inline-block;
+}
+.icon-preview i {
+ width: 48px;
+ height: 48px;
+ text-align: center;
+ border: 1px solid #d5d5d5;
+ border-radius: 3px;
+ background: #fff;
+}
+.icon-preview i:before {
+ font-size: 32px;
+ line-height: 48px;
+}
+.remove-icon {
+ background-color: #00a6cf;
+ color: #fff !important;
+ border-radius: 3px;
+ transition: background .2s ease-out;
+ position: absolute;
+ top: -10px;
+ right: -10px;
+}
+.remove-icon:hover {
+ color: #fff !important;
+ background-color: #00a6cf;
+ cursor: pointer;
+}
+.remove-times {
+ width: 20px;
+ height: 20px;
+ font-size: 21px;
+ text-align: center;
+ line-height: 99%;
+}
+.remove-times:after {
+ content: "\00D7";
+ display: block;
+ line-height: 15px;
+}
+.launch-icons {
+ margin: 5px 3px 0 0;
+}
+.launch-icons:active {
+ margin: 5px 3px 0 0;
+}
+
diff --git a/user/plugins/admin/themes/grav/css/nucleus-ie10.css b/user/plugins/admin/themes/grav/css/nucleus-ie10.css
new file mode 100644
index 0000000..c1d2f2a
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css/nucleus-ie10.css
@@ -0,0 +1,24 @@
+button {
+ overflow: visible;
+}
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box;
+ padding: 0;
+}
+
+.fixed-blocks {
+ display: block;
+}
+
+.feature-items {
+ overflow: hidden;
+}
+
+.modular .features .feature {
+ margin: 1%;
+ display: inline-block;
+ vertical-align: top;
+ float: none;
+}
\ No newline at end of file
diff --git a/user/plugins/admin/themes/grav/css/nucleus-ie9.css b/user/plugins/admin/themes/grav/css/nucleus-ie9.css
new file mode 100644
index 0000000..46df376
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css/nucleus-ie9.css
@@ -0,0 +1,62 @@
+/* IE9 Resets and Normalization */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+ display: block;
+}
+
+audio,
+canvas,
+progress,
+video {
+ display: inline-block;
+}
+
+[hidden],
+template {
+ display: none;
+}
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+img {
+ border: 0;
+}
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+figure {
+ margin: 1em 40px;
+}
+
+button {
+ overflow: visible;
+}
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box;
+ padding: 0;
+}
+
+legend {
+ border: 0;
+ padding: 0;
+}
+
+textarea {
+ overflow: auto;
+}
\ No newline at end of file
diff --git a/user/plugins/admin/themes/grav/css/prism.css b/user/plugins/admin/themes/grav/css/prism.css
new file mode 100644
index 0000000..00005b3
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css/prism.css
@@ -0,0 +1,121 @@
+/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript */
+/**
+ * prism.js default theme for JavaScript, CSS and HTML
+ * Based on dabblet (http://dabblet.com)
+ * @author Lea Verou
+ */
+
+code[class*="language-"],
+pre[class*="language-"] {
+ color: black;
+ text-shadow: 0 1px white;
+ direction: ltr;
+ text-align: left;
+ white-space: pre;
+ word-spacing: normal;
+ word-break: normal;
+
+
+ -moz-tab-size: 4;
+ -o-tab-size: 4;
+ tab-size: 4;
+
+ -webkit-hyphens: none;
+ -moz-hyphens: none;
+ -ms-hyphens: none;
+ hyphens: none;
+}
+
+pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
+code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
+ text-shadow: none;
+ background: #b3d4fc;
+}
+
+pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
+code[class*="language-"]::selection, code[class*="language-"] ::selection {
+ text-shadow: none;
+ background: #b3d4fc;
+}
+
+@media print {
+ code[class*="language-"],
+ pre[class*="language-"] {
+ text-shadow: none;
+ }
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+ padding: 1em;
+ margin: .5em 0;
+ overflow: auto;
+}
+
+/* Inline code */
+:not(pre) > code[class*="language-"] {
+ padding: .1em;
+ border-radius: .3em;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+ color: slategray;
+}
+
+.token.punctuation {
+ color: #999;
+}
+
+.namespace {
+ opacity: .7;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol {
+ color: #905;
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.builtin {
+ color: #690;
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string,
+.token.variable {
+ color: #a67f59;
+ background: hsla(0,0%,100%,.5);
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+ color: #07a;
+}
+
+
+.token.regex,
+.token.important {
+ color: #e90;
+}
+
+.token.important {
+ font-weight: bold;
+}
+
+.token.entity {
+ cursor: help;
+}
+
diff --git a/user/plugins/admin/themes/grav/css/pure-0.5.0/grids-min.css b/user/plugins/admin/themes/grav/css/pure-0.5.0/grids-min.css
new file mode 100644
index 0000000..4b66e7e
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css/pure-0.5.0/grids-min.css
@@ -0,0 +1,15 @@
+/*!
+Pure v0.5.0
+Copyright 2014 Yahoo! Inc. All rights reserved.
+Licensed under the BSD License.
+https://github.com/yui/pure/blob/master/LICENSE.md
+*/
+.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}
+
+/* Custom */
+[class *="pure-u"] {display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;}
+.pure-u-1-7 {width: 14.285%;}.pure-u-2-7 {width: 28.571%;}.pure-u-3-7 {width: 42.857%;}.pure-u-4-7 {width: 57.142%;}.pure-u-5-7 {width: 71.428%;}.pure-u-6-7 {width: 85.714%;}
+.pure-u-1-9 {width: 11.111%;}.pure-u-2-9 {width: 22.222%;}.pure-u-3-9 {width: 33.333%;}.pure-u-4-9 {width: 44.444%;}.pure-u-5-9 {width: 55.555%;}.pure-u-6-9 {width: 66.666%;}.pure-u-7-9 {width: 77.777%;}.pure-u-8-9 {width: 88.888%;}
+.pure-u-1-10 {width: 10%;}.pure-u-2-10 {width: 20%;}.pure-u-3-10 {width: 30%;}.pure-u-4-10 {width: 40%;}.pure-u-5-10 {width: 50%;}.pure-u-6-10 {width: 60%;}.pure-u-7-10 {width: 70%;}.pure-u-8-10 {width: 80%;}.pure-u-9-10 {width: 90%;}
+
+.pure-u-1-11 {width: 9.090%;}.pure-u-2-11 {width: 18.181%;}.pure-u-3-11 {width: 27.272%;}.pure-u-4-11 {width: 36.363%;}.pure-u-5-11 {width: 45.454%;}.pure-u-6-11 {width: 54.545%;}.pure-u-7-11 {width: 63.636%;}.pure-u-8-11 {width: 72.727%;}.pure-u-9-11 {width: 81.818%;}.pure-u-10-11 {width: 90.909%;}
diff --git a/user/plugins/admin/themes/grav/css/selectize.min.css b/user/plugins/admin/themes/grav/css/selectize.min.css
new file mode 100644
index 0000000..fbf7424
--- /dev/null
+++ b/user/plugins/admin/themes/grav/css/selectize.min.css
@@ -0,0 +1 @@
+.selectize-control.plugin-drag_drop.multi > .selectize-input > div.ui-sortable-placeholder{visibility:visible!important;background:#f2f2f2!important;background:rgba(0,0,0,0.06)!important;border:0 none!important;-webkit-box-shadow:inset 0 0 12px 4px #fff;box-shadow:inset 0 0 12px 4px #fff}.selectize-control.plugin-drag_drop .ui-sortable-placeholder::after{content:'!';visibility:hidden}.selectize-control.plugin-drag_drop .ui-sortable-helper{-webkit-box-shadow:0 2px 5px rgba(0,0,0,0.2);box-shadow:0 2px 5px rgba(0,0,0,0.2)}.selectize-dropdown-header{position:relative;padding:5px 8px;border-bottom:1px solid #d0d0d0;background:#f8f8f8;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0}.selectize-dropdown-header-close{position:absolute;right:8px;top:50%;color:#303030;opacity:.4;margin-top:-12px;line-height:20px;font-size:20px!important}.selectize-dropdown-header-close:hover{color:#000}.selectize-dropdown.plugin-optgroup_columns .optgroup{border-right:1px solid #f2f2f2;border-top:0 none;float:left;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child{border-right:0 none}.selectize-dropdown.plugin-optgroup_columns .optgroup:before{display:none}.selectize-dropdown.plugin-optgroup_columns .optgroup-header{border-top:0 none}.selectize-control.plugin-remove_button [data-value]{position:relative;padding-right:24px!important}.selectize-control.plugin-remove_button [data-value] .remove{z-index:1;position:absolute;top:0;right:0;bottom:0;width:17px;text-align:center;font-weight:700;font-size:12px;color:inherit;text-decoration:none;vertical-align:middle;display:inline-block;padding:2px 0 0;border-left:1px solid #d0d0d0;-webkit-border-radius:0 2px 2px 0;-moz-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.selectize-control.plugin-remove_button [data-value] .remove:hover{background:rgba(0,0,0,0.05)}.selectize-control.plugin-remove_button [data-value].active .remove{border-left-color:#cacaca}.selectize-control.plugin-remove_button .disabled [data-value] .remove:hover{background:none}.selectize-control.plugin-remove_button .disabled [data-value] .remove{border-left-color:#fff}.selectize-control{position:relative}.selectize-dropdown,.selectize-input,.selectize-input input{color:#303030;font-family:inherit;font-size:13px;line-height:18px;-webkit-font-smoothing:inherit}.selectize-input,.selectize-control.single .selectize-input.input-active{background:#fff;cursor:text;display:inline-block}.selectize-input{border:1px solid #d0d0d0;padding:8px;display:inline-block;width:100%;overflow:hidden;position:relative;z-index:1;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.1);box-shadow:inset 0 1px 1px rgba(0,0,0,0.1);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.selectize-control.multi .selectize-input.has-items{padding:6px 8px 3px}.selectize-input.full{background-color:#fff}.selectize-input.disabled,.selectize-input.disabled *{cursor:default!important}.selectize-input.focus{-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.15)}.selectize-input.dropdown-active{-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0}.selectize-input > *{vertical-align:baseline;display:-moz-inline-stack;display:inline-block;zoom:1;*display:inline}.selectize-control.multi .selectize-input > div{cursor:pointer;margin:0 3px 3px 0;padding:2px 6px;background:#f2f2f2;color:#303030;border:0 solid #d0d0d0}.selectize-control.multi .selectize-input > div.active{background:#e8e8e8;color:#303030;border:0 solid #cacaca}.selectize-control.multi .selectize-input.disabled > div,.selectize-control.multi .selectize-input.disabled > div.active{color:#7d7d7d;background:#fff;border:0 solid #fff}.selectize-input > input{padding:0!important;min-height:0!important;max-height:none!important;max-width:100%!important;margin:0 2px 0 0 !important;text-indent:0!important;border:0 none!important;background:none!important;line-height:inherit!important;-webkit-user-select:auto!important;-webkit-box-shadow:none!important;box-shadow:none!important}.selectize-input > input::-ms-clear{display:none}.selectize-input > input:focus{outline:none!important}.selectize-input::after{content:' ';display:block;clear:left}.selectize-input.dropdown-active::before{content:' ';display:block;position:absolute;background:#f0f0f0;height:1px;bottom:0;left:0;right:0}.selectize-dropdown{position:absolute;z-index:10;border:1px solid #d0d0d0;background:#fff;margin:-1px 0 0;border-top:0 none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1);-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.selectize-dropdown [data-selectable]{cursor:pointer;overflow:hidden}.selectize-dropdown [data-selectable] .highlight{background:rgba(125,168,208,0.2);-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px}.selectize-dropdown [data-selectable],.selectize-dropdown .optgroup-header{padding:5px 8px}.selectize-dropdown .optgroup:first-child .optgroup-header{border-top:0 none}.selectize-dropdown .optgroup-header{color:#303030;background:#fff;cursor:default}.selectize-dropdown .active{background-color:#f5fafd;color:#495c68}.selectize-dropdown .active.create{color:#495c68}.selectize-dropdown .create{color:rgba(48,48,48,0.5)}.selectize-dropdown-content{overflow-y:auto;overflow-x:hidden;max-height:350px}.selectize-control.single .selectize-input,.selectize-control.single .selectize-input input{cursor:pointer}.selectize-control.single .selectize-input.input-active,.selectize-control.single .selectize-input.input-active input{cursor:text}.selectize-control.single .selectize-input:after{content:' ';display:block;position:absolute;top:50%;right:15px;margin-top:-3px;width:0;height:0;border-style:solid;border-width:5px 5px 0;border-color:gray transparent transparent}.selectize-control.single .selectize-input.dropdown-active:after{margin-top:-4px;border-width:0 5px 5px;border-color:transparent transparent gray}.selectize-control.rtl.single .selectize-input:after{left:15px;right:auto}.selectize-control.rtl .selectize-input > input{margin:0 4px 0 -2px !important}.selectize-control .selectize-input.disabled{opacity:.5;background-color:#fafafa}
diff --git a/user/plugins/admin/themes/grav/fonts/forkawesome-webfont.eot b/user/plugins/admin/themes/grav/fonts/forkawesome-webfont.eot
new file mode 100755
index 0000000..23b4f41
Binary files /dev/null and b/user/plugins/admin/themes/grav/fonts/forkawesome-webfont.eot differ
diff --git a/user/plugins/admin/themes/grav/fonts/forkawesome-webfont.svg b/user/plugins/admin/themes/grav/fonts/forkawesome-webfont.svg
new file mode 100755
index 0000000..874a3a1
--- /dev/null
+++ b/user/plugins/admin/themes/grav/fonts/forkawesome-webfont.svg
@@ -0,0 +1,2810 @@
+
+
+
+
+
+Created by FontForge 20180321 at Mon Oct 15 10:55:59 2018
+ By Julien Deswaef
+The Fork Awesome font is licensed under the SIL OFL 1.1 (http://scripts.sil.org/OFL). Fork Awesome is a fork based of off Font Awesome 4.7.0 by Dave Gandy. More info on licenses at https://forkawesome.github.io
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/user/plugins/admin/themes/grav/fonts/forkawesome-webfont.ttf b/user/plugins/admin/themes/grav/fonts/forkawesome-webfont.ttf
new file mode 100755
index 0000000..c8a6626
Binary files /dev/null and b/user/plugins/admin/themes/grav/fonts/forkawesome-webfont.ttf differ
diff --git a/user/plugins/admin/themes/grav/fonts/forkawesome-webfont.woff b/user/plugins/admin/themes/grav/fonts/forkawesome-webfont.woff
new file mode 100755
index 0000000..8f8204e
Binary files /dev/null and b/user/plugins/admin/themes/grav/fonts/forkawesome-webfont.woff differ
diff --git a/user/plugins/admin/themes/grav/fonts/forkawesome-webfont.woff2 b/user/plugins/admin/themes/grav/fonts/forkawesome-webfont.woff2
new file mode 100755
index 0000000..50e0522
Binary files /dev/null and b/user/plugins/admin/themes/grav/fonts/forkawesome-webfont.woff2 differ
diff --git a/user/plugins/admin/themes/grav/fonts/rockettheme-apps/rockettheme-apps.eot b/user/plugins/admin/themes/grav/fonts/rockettheme-apps/rockettheme-apps.eot
new file mode 100644
index 0000000..c52df41
Binary files /dev/null and b/user/plugins/admin/themes/grav/fonts/rockettheme-apps/rockettheme-apps.eot differ
diff --git a/user/plugins/admin/themes/grav/fonts/rockettheme-apps/rockettheme-apps.svg b/user/plugins/admin/themes/grav/fonts/rockettheme-apps/rockettheme-apps.svg
new file mode 100644
index 0000000..f338187
--- /dev/null
+++ b/user/plugins/admin/themes/grav/fonts/rockettheme-apps/rockettheme-apps.svg
@@ -0,0 +1,16 @@
+
+
+
+Generated by Fontastic.me
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/user/plugins/admin/themes/grav/fonts/rockettheme-apps/rockettheme-apps.ttf b/user/plugins/admin/themes/grav/fonts/rockettheme-apps/rockettheme-apps.ttf
new file mode 100644
index 0000000..53a6e37
Binary files /dev/null and b/user/plugins/admin/themes/grav/fonts/rockettheme-apps/rockettheme-apps.ttf differ
diff --git a/user/plugins/admin/themes/grav/fonts/rockettheme-apps/rockettheme-apps.woff b/user/plugins/admin/themes/grav/fonts/rockettheme-apps/rockettheme-apps.woff
new file mode 100644
index 0000000..0fd4941
Binary files /dev/null and b/user/plugins/admin/themes/grav/fonts/rockettheme-apps/rockettheme-apps.woff differ
diff --git a/user/plugins/admin/themes/grav/gulpfile.js b/user/plugins/admin/themes/grav/gulpfile.js
new file mode 100644
index 0000000..02d1cba
--- /dev/null
+++ b/user/plugins/admin/themes/grav/gulpfile.js
@@ -0,0 +1,110 @@
+'use strict';
+
+var gulp = require('gulp'),
+ util = require('util'),
+ path = require('path'),
+ gutil = require('gulp-util'),
+ path = require('path'),
+ immutable = require('immutable'),
+ merge = require('merge-stream'),
+ gulpWebpack = require('gulp-webpack'),
+ webpack = require('webpack'),
+ sass = require('gulp-sass'),
+ sourcemaps = require('gulp-sourcemaps');
+
+var plugins = {
+ 'Promise': 'imports?this=>global!exports?global.Promise!babel-polyfill',
+ 'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch'
+ },
+ base = immutable.fromJS(require('./webpack.conf.js')),
+ options = {
+ dev: base.mergeDeep({
+ devtool: 'source-map',
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': { NODE_ENV: '"development"' }
+ }),
+ new webpack.ProvidePlugin(plugins),
+ new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity)
+ ],
+ output: {
+ filename: 'admin.js'
+ }
+ }),
+
+ prod: base.mergeDeep({
+ devtool: 'source-map',
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': { NODE_ENV: '"production"' }
+ }),
+ new webpack.optimize.UglifyJsPlugin({
+ sourceMap: true,
+ compress: {
+ warnings: false
+ }
+ }),
+ new webpack.ProvidePlugin(plugins),
+ new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.min.js", Infinity)
+ ],
+ output: {
+ filename: 'admin.min.js'
+ }
+ })
+ };
+
+var compileJS = function(watch) {
+ var devOpts = options.dev.set('watch', watch),
+ prodOpts = options.prod.set('watch', watch);
+
+ var prod = gulp.src('app/main.js')
+ .pipe(gulpWebpack(prodOpts.toJS()))
+ .pipe(gulp.dest('js/'));
+
+ /*var dev = gulp.src('app/main.js')
+ .pipe(gulpWebpack(devOpts.toJS()))
+ .pipe(gulp.dest('js/'));*/
+
+ // return merge(prod, dev);
+ return prod;
+};
+
+var compileCSS = function(event) {
+ return gulp.src('./scss/**/*.scss')
+ .on('end', function() {
+ // console.log(util.inspect(event));
+ if (event && event.path) {
+ gutil.log(gutil.colors.green('√'), 'Saved change for "' + event.path.replace(__dirname, '') + '"');
+ }
+ })
+ .on('error', gutil.log)
+ .pipe(sourcemaps.init())
+ .pipe(sass({outputStyle: 'compressed'}).on('error', sass.logError))
+ .pipe(sourcemaps.write('./'))
+ .pipe(gulp.dest('./css-compiled'));
+};
+
+gulp.task('js', function() {
+ compileJS(false);
+});
+
+gulp.task('css', function() {
+ compileCSS();
+});
+
+gulp.task('watch', function() {
+ compileJS(true);
+ gulp.watch('./scss/**/*.scss', compileCSS);
+});
+
+gulp.task('watch-js', function() {
+ compileJS(true);
+});
+
+gulp.task('watch-css', function() {
+ compileCSS();
+ gulp.watch('./scss/**/*.scss', compileCSS);
+});
+
+gulp.task('all', ['css', 'js']);
+gulp.task('default', ['all']);
diff --git a/user/plugins/admin/themes/grav/images/favicon.png b/user/plugins/admin/themes/grav/images/favicon.png
new file mode 100644
index 0000000..85526cf
Binary files /dev/null and b/user/plugins/admin/themes/grav/images/favicon.png differ
diff --git a/user/plugins/admin/themes/grav/images/grav-regular.svg b/user/plugins/admin/themes/grav/images/grav-regular.svg
new file mode 100644
index 0000000..3533910
--- /dev/null
+++ b/user/plugins/admin/themes/grav/images/grav-regular.svg
@@ -0,0 +1 @@
+
diff --git a/user/plugins/admin/themes/grav/images/grav-small.svg b/user/plugins/admin/themes/grav/images/grav-small.svg
new file mode 100644
index 0000000..6d31cd7
--- /dev/null
+++ b/user/plugins/admin/themes/grav/images/grav-small.svg
@@ -0,0 +1 @@
+
diff --git a/user/plugins/admin/themes/grav/images/logo.svg b/user/plugins/admin/themes/grav/images/logo.svg
new file mode 100644
index 0000000..6edd841
--- /dev/null
+++ b/user/plugins/admin/themes/grav/images/logo.svg
@@ -0,0 +1 @@
+
diff --git a/user/plugins/admin/themes/grav/images/transparent.png b/user/plugins/admin/themes/grav/images/transparent.png
new file mode 100644
index 0000000..5fcd5d4
Binary files /dev/null and b/user/plugins/admin/themes/grav/images/transparent.png differ
diff --git a/user/plugins/admin/themes/grav/js/admin.min.js b/user/plugins/admin/themes/grav/js/admin.min.js
new file mode 100644
index 0000000..4e4243a
--- /dev/null
+++ b/user/plugins/admin/themes/grav/js/admin.min.js
@@ -0,0 +1,31 @@
+var Grav=webpackJsonpGrav([0],[function(e,t,n){(function(e){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(1),a=r(i),o=n(2),s=r(o),l=n(342),u=r(l),c=n(343),f=r(c),d=n(363),p=r(d),h=n(368),m=r(h),v=n(457),g=r(v),y=n(604),_=r(y);n(720),n(721);var b=n(621),w=n(331),x=r(w),k=n(358),S=r(k);n(622),n(623),n(635),n(636),n(637);var C=n(639),E=r(C);u.default.start(),e.setInterval(function(){y.Instance.update(),C.Instance.scroller.update()},150),(0,a.default)(e).on("sidebar_state._grav",function(){Object.keys(p.default.Chart.Instances).forEach(function(e){setTimeout(function(){return p.default.Chart.Instances[e].chart.update()},10)})}),t.default={GPM:{GPM:s.default,Instance:o.Instance},KeepAlive:u.default,Dashboard:p.default,Pages:m.default,Forms:g.default,Scrollbar:{Scrollbar:_.default,Instance:y.Instance},Updates:{Updates:f.default,Notifications:c.Notifications,Feed:c.Feed,Instance:c.Instance},Sidebar:{Sidebar:E.default,Instance:C.Instance},MediaFilter:{MediaFilter:b.Filter,Instance:b.Instance},Utils:{request:S.default,toastr:x.default}}}).call(t,function(){return this}())},,function(e,t,n){(function(e){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0}),t.Instance=void 0;var o=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:"getUpdates";r(this,n);var t=i(this,(n.__proto__||Object.getPrototypeOf(n)).call(this));return t.payload={},t.raw={},t.action=e,t}return a(n,t),o(n,[{key:"setPayload",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return this.payload=e,this.emit("payload",e),this}},{key:"setAction",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"getUpdates";return this.action=e,this.emit("action",e),this}},{key:"fetch",value:function(e){function t(){return e.apply(this,arguments)}return t.toString=function(){return e.toString()},t}(function(){var t=this,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!0},r=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=new FormData;i.append("admin-nonce",l.config.admin_nonce),r&&i.append("flush",!0),this.emit("fetching",this),e(l.config.base_url_relative+"/update.json/task"+l.config.param_sep+"getUpdates",{credentials:"same-origin",method:"post",body:i}).then(function(e){return t.raw=e,e}).then(s.parseStatus).then(s.parseJSON).then(function(e){return t.response(e)}).then(function(e){return n(e,t.raw)}).then(function(e){return t.emit("fetched",t.payload,t.raw,t)}).catch(s.userFeedbackError)})},{key:"response",value:function(e){return this.payload=e,e}}]),n}(u.EventEmitter);t.default=c;t.Instance=new c}).call(t,n(3))},function(e,t,n){(function(t,n){(function(){!function(e){"use strict";function n(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(e))throw new TypeError("Invalid character in header field name");return e.toLowerCase()}function r(e){return"string"!=typeof e&&(e=String(e)),e}function i(e){var t={next:function(){var t=e.shift();return{done:void 0===t,value:t}}};return y.iterable&&(t[Symbol.iterator]=function(){return t}),t}function a(e){this.map={},e instanceof a?e.forEach(function(e,t){this.append(t,e)},this):Array.isArray(e)?e.forEach(function(e){this.append(e[0],e[1])},this):e&&Object.getOwnPropertyNames(e).forEach(function(t){this.append(t,e[t])},this)}function o(e){return e.bodyUsed?t.reject(new TypeError("Already read")):void(e.bodyUsed=!0)}function s(e){return new t(function(t,n){e.onload=function(){t(e.result)},e.onerror=function(){n(e.error)}})}function l(e){var t=new FileReader,n=s(t);return t.readAsArrayBuffer(e),n}function u(e){var t=new FileReader,n=s(t);return t.readAsText(e),n}function c(e){for(var t=new Uint8Array(e),n=new Array(t.length),r=0;r-1?t:e}function h(e,t){t=t||{};var n=t.body;if(e instanceof h){if(e.bodyUsed)throw new TypeError("Already read");this.url=e.url,this.credentials=e.credentials,t.headers||(this.headers=new a(e.headers)),this.method=e.method,this.mode=e.mode,n||null==e._bodyInit||(n=e._bodyInit,e.bodyUsed=!0)}else this.url=String(e);if(this.credentials=t.credentials||this.credentials||"omit",!t.headers&&this.headers||(this.headers=new a(t.headers)),this.method=p(t.method||this.method||"GET"),this.mode=t.mode||this.mode||null,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&n)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(n)}function m(e){var t=new FormData;return e.trim().split("&").forEach(function(e){if(e){var n=e.split("="),r=n.shift().replace(/\+/g," "),i=n.join("=").replace(/\+/g," ");t.append(decodeURIComponent(r),decodeURIComponent(i))}}),t}function v(e){var t=new a,n=e.replace(/\r?\n[\t ]+/g," ");return n.split(/\r?\n/).forEach(function(e){var n=e.split(":"),r=n.shift().trim();if(r){var i=n.join(":").trim();t.append(r,i)}}),t}function g(e,t){t||(t={}),this.type="default",this.status=void 0===t.status?200:t.status,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in t?t.statusText:"OK",this.headers=new a(t.headers),this.url=t.url||"",this._initBody(e)}if(!e.fetch){var y={searchParams:"URLSearchParams"in e,iterable:"Symbol"in e&&"iterator"in Symbol,blob:"FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(e){return!1}}(),formData:"FormData"in e,arrayBuffer:"ArrayBuffer"in e};if(y.arrayBuffer)var _=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],b=function(e){return e&&DataView.prototype.isPrototypeOf(e)},w=ArrayBuffer.isView||function(e){return e&&_.indexOf(Object.prototype.toString.call(e))>-1};a.prototype.append=function(e,t){e=n(e),t=r(t);var i=this.map[e];this.map[e]=i?i+","+t:t},a.prototype.delete=function(e){delete this.map[n(e)]},a.prototype.get=function(e){return e=n(e),this.has(e)?this.map[e]:null},a.prototype.has=function(e){return this.map.hasOwnProperty(n(e))},a.prototype.set=function(e,t){this.map[n(e)]=r(t)},a.prototype.forEach=function(e,t){for(var n in this.map)this.map.hasOwnProperty(n)&&e.call(t,this.map[n],n,this)},a.prototype.keys=function(){var e=[];return this.forEach(function(t,n){e.push(n)}),i(e)},a.prototype.values=function(){var e=[];return this.forEach(function(t){e.push(t)}),i(e)},a.prototype.entries=function(){var e=[];return this.forEach(function(t,n){e.push([n,t])}),i(e)},y.iterable&&(a.prototype[Symbol.iterator]=a.prototype.entries);var x=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];h.prototype.clone=function(){return new h(this,{body:this._bodyInit})},d.call(h.prototype),d.call(g.prototype),g.prototype.clone=function(){return new g(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new a(this.headers),url:this.url})},g.error=function(){var e=new g(null,{status:0,statusText:""});return e.type="error",e};var k=[301,302,303,307,308];g.redirect=function(e,t){if(k.indexOf(t)===-1)throw new RangeError("Invalid status code");return new g(null,{status:t,headers:{location:e}})},e.Headers=a,e.Request=h,e.Response=g,e.fetch=function(e,n){return new t(function(t,r){var i=new h(e,n),a=new XMLHttpRequest;a.onload=function(){var e={status:a.status,statusText:a.statusText,headers:v(a.getAllResponseHeaders()||"")};e.url="responseURL"in a?a.responseURL:e.headers.get("X-Request-URL");var n="response"in a?a.response:a.responseText;t(new g(n,e))},a.onerror=function(){r(new TypeError("Network request failed"))},a.ontimeout=function(){r(new TypeError("Network request failed"))},a.open(i.method,i.url,!0),"include"===i.credentials?a.withCredentials=!0:"omit"===i.credentials&&(a.withCredentials=!1),"responseType"in a&&y.blob&&(a.responseType="blob"),i.headers.forEach(function(e,t){a.setRequestHeader(t,e)}),a.send("undefined"==typeof i._bodyInit?null:i._bodyInit)})},e.fetch.polyfill=!0}}("undefined"!=typeof self?self:this),e.exports=n.fetch}).call(n)}).call(t,n(4),function(){return this}())},function(e,t,n){(function(t){(function(){"use strict";function r(e,t,n){e[t]||Object[i](e,t,{writable:!0,configurable:!0,value:n})}if(n(5),n(326),n(327),t._babelPolyfill)throw new Error("only one instance of babel-polyfill is allowed");t._babelPolyfill=!0;var i="defineProperty";r(String.prototype,"padLeft","".padStart),r(String.prototype,"padRight","".padEnd),"pop,reverse,shift,keys,values,entries,indexOf,every,some,forEach,map,filter,find,findIndex,includes,join,slice,concat,push,splice,unshift,sort,lastIndexOf,reduce,reduceRight,copyWithin,fill".split(",").forEach(function(e){[][e]&&r(Array,e,Function.call.bind([][e]))}),e.exports=t.Promise}).call(t)}).call(t,function(){return this}())},function(e,t,n){n(6),n(54),n(55),n(56),n(57),n(59),n(62),n(63),n(64),n(65),n(66),n(67),n(68),n(69),n(70),n(72),n(74),n(76),n(78),n(81),n(82),n(83),n(87),n(89),n(91),n(94),n(95),n(96),n(97),n(99),n(100),n(101),n(102),n(103),n(104),n(105),n(107),n(108),n(109),n(111),n(112),n(113),n(115),n(117),n(118),n(119),n(120),n(121),n(122),n(123),n(124),n(125),n(126),n(127),n(128),n(129),n(134),n(135),n(139),n(140),n(141),n(142),n(144),n(145),n(146),n(147),n(148),n(149),n(150),n(151),n(152),n(153),n(154),n(155),n(156),n(157),n(158),n(160),n(161),n(163),n(164),n(170),n(171),n(173),n(174),n(175),n(179),n(180),n(181),n(182),n(183),n(185),n(186),n(187),n(188),n(191),n(193),n(194),n(195),n(197),n(199),n(201),n(202),n(203),n(205),n(206),n(207),n(208),n(219),n(223),n(224),n(226),n(227),n(231),n(232),n(234),n(235),n(236),n(237),n(238),n(239),n(240),n(241),n(242),n(243),n(244),n(245),n(246),n(247),n(248),n(249),n(250),n(251),n(252),n(254),n(255),n(256),n(257),n(258),n(260),n(261),n(262),n(264),n(265),n(266),n(267),n(268),n(269),n(270),n(271),n(273),n(274),n(276),n(277),n(278),n(279),n(282),n(283),n(285),n(286),n(287),n(288),n(290),n(291),n(292),n(293),n(294),n(295),n(296),n(297),n(298),n(299),n(301),n(302),n(303),n(304),n(305),n(306),n(307),n(308),n(309),n(310),n(311),n(313),n(314),n(315),n(316),n(317),n(318),n(319),n(320),n(321),n(322),n(323),n(324),n(325),e.exports=n(12)},function(e,t,n){"use strict";var r=n(7),i=n(8),a=n(9),o=n(11),s=n(21),l=n(25).KEY,u=n(10),c=n(26),f=n(28),d=n(22),p=n(29),h=n(30),m=n(31),v=n(32),g=n(47),y=n(15),_=n(16),b=n(35),w=n(19),x=n(20),k=n(48),S=n(51),C=n(53),E=n(14),A=n(33),I=C.f,M=E.f,O=S.f,T=r.Symbol,P=r.JSON,j=P&&P.stringify,D="prototype",z=p("_hidden"),L=p("toPrimitive"),N={}.propertyIsEnumerable,F=c("symbol-registry"),R=c("symbols"),q=c("op-symbols"),U=Object[D],V="function"==typeof T,B=r.QObject,G=!B||!B[D]||!B[D].findChild,H=a&&u(function(){return 7!=k(M({},"a",{get:function(){return M(this,"a",{value:7}).a}})).a})?function(e,t,n){var r=I(U,t);r&&delete U[t],M(e,t,n),r&&e!==U&&M(U,t,r)}:M,$=function(e){var t=R[e]=k(T[D]);return t._k=e,t},W=V&&"symbol"==typeof T.iterator?function(e){return"symbol"==typeof e}:function(e){return e instanceof T},K=function(e,t,n){return e===U&&K(q,t,n),y(e),t=w(t,!0),y(n),i(R,t)?(n.enumerable?(i(e,z)&&e[z][t]&&(e[z][t]=!1),n=k(n,{enumerable:x(0,!1)})):(i(e,z)||M(e,z,x(1,{})),e[z][t]=!0),H(e,t,n)):M(e,t,n)},Y=function(e,t){y(e);for(var n,r=v(t=b(t)),i=0,a=r.length;a>i;)K(e,n=r[i++],t[n]);return e},J=function(e,t){return void 0===t?k(e):Y(k(e),t)},X=function(e){var t=N.call(this,e=w(e,!0));return!(this===U&&i(R,e)&&!i(q,e))&&(!(t||!i(this,e)||!i(R,e)||i(this,z)&&this[z][e])||t)},Q=function(e,t){if(e=b(e),t=w(t,!0),e!==U||!i(R,t)||i(q,t)){var n=I(e,t);return!n||!i(R,t)||i(e,z)&&e[z][t]||(n.enumerable=!0),n}},Z=function(e){for(var t,n=O(b(e)),r=[],a=0;n.length>a;)i(R,t=n[a++])||t==z||t==l||r.push(t);return r},ee=function(e){for(var t,n=e===U,r=O(n?q:b(e)),a=[],o=0;r.length>o;)!i(R,t=r[o++])||n&&!i(U,t)||a.push(R[t]);return a};V||(T=function(){if(this instanceof T)throw TypeError("Symbol is not a constructor!");var e=d(arguments.length>0?arguments[0]:void 0),t=function(n){this===U&&t.call(q,n),i(this,z)&&i(this[z],e)&&(this[z][e]=!1),H(this,e,x(1,n))};return a&&G&&H(U,e,{configurable:!0,set:t}),$(e)},s(T[D],"toString",function(){return this._k}),C.f=Q,E.f=K,n(52).f=S.f=Z,n(46).f=X,n(45).f=ee,a&&!n(27)&&s(U,"propertyIsEnumerable",X,!0),h.f=function(e){return $(p(e))}),o(o.G+o.W+o.F*!V,{Symbol:T});for(var te="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),ne=0;te.length>ne;)p(te[ne++]);for(var re=A(p.store),ie=0;re.length>ie;)m(re[ie++]);o(o.S+o.F*!V,"Symbol",{for:function(e){return i(F,e+="")?F[e]:F[e]=T(e)},keyFor:function(e){if(!W(e))throw TypeError(e+" is not a symbol!");for(var t in F)if(F[t]===e)return t},useSetter:function(){G=!0},useSimple:function(){G=!1}}),o(o.S+o.F*!V,"Object",{create:J,defineProperty:K,defineProperties:Y,getOwnPropertyDescriptor:Q,getOwnPropertyNames:Z,getOwnPropertySymbols:ee}),P&&o(o.S+o.F*(!V||u(function(){var e=T();return"[null]"!=j([e])||"{}"!=j({a:e})||"{}"!=j(Object(e))})),"JSON",{stringify:function(e){for(var t,n,r=[e],i=1;arguments.length>i;)r.push(arguments[i++]);if(n=t=r[1],(_(t)||void 0!==e)&&!W(e))return g(t)||(t=function(e,t){if("function"==typeof n&&(t=n.call(this,e,t)),!W(t))return t}),r[1]=t,j.apply(P,r)}}),T[D][L]||n(13)(T[D],L,T[D].valueOf),f(T,"Symbol"),f(Math,"Math",!0),f(r.JSON,"JSON",!0)},function(e,t){var n=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t,n){e.exports=!n(10)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t,n){var r=n(7),i=n(12),a=n(13),o=n(21),s=n(23),l="prototype",u=function(e,t,n){var c,f,d,p,h=e&u.F,m=e&u.G,v=e&u.S,g=e&u.P,y=e&u.B,_=m?r:v?r[t]||(r[t]={}):(r[t]||{})[l],b=m?i:i[t]||(i[t]={}),w=b[l]||(b[l]={});m&&(n=t);for(c in n)f=!h&&_&&void 0!==_[c],d=(f?_:n)[c],p=y&&f?s(d,r):g&&"function"==typeof d?s(Function.call,d):d,_&&o(_,c,d,e&u.U),b[c]!=d&&a(b,c,p),g&&w[c]!=d&&(w[c]=d)};r.core=i,u.F=1,u.G=2,u.S=4,u.P=8,u.B=16,u.W=32,u.U=64,u.R=128,e.exports=u},function(e,t){var n=e.exports={version:"2.5.7"};"number"==typeof __e&&(__e=n)},function(e,t,n){var r=n(14),i=n(20);e.exports=n(9)?function(e,t,n){return r.f(e,t,i(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){var r=n(15),i=n(17),a=n(19),o=Object.defineProperty;t.f=n(9)?Object.defineProperty:function(e,t,n){if(r(e),t=a(t,!0),r(n),i)try{return o(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(e[t]=n.value),e}},function(e,t,n){var r=n(16);e.exports=function(e){if(!r(e))throw TypeError(e+" is not an object!");return e}},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t,n){e.exports=!n(9)&&!n(10)(function(){return 7!=Object.defineProperty(n(18)("div"),"a",{get:function(){return 7}}).a})},function(e,t,n){var r=n(16),i=n(7).document,a=r(i)&&r(i.createElement);e.exports=function(e){return a?i.createElement(e):{}}},function(e,t,n){var r=n(16);e.exports=function(e,t){if(!r(e))return e;var n,i;if(t&&"function"==typeof(n=e.toString)&&!r(i=n.call(e)))return i;if("function"==typeof(n=e.valueOf)&&!r(i=n.call(e)))return i;if(!t&&"function"==typeof(n=e.toString)&&!r(i=n.call(e)))return i;throw TypeError("Can't convert object to primitive value")}},function(e,t){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},function(e,t,n){var r=n(7),i=n(13),a=n(8),o=n(22)("src"),s="toString",l=Function[s],u=(""+l).split(s);n(12).inspectSource=function(e){return l.call(e)},(e.exports=function(e,t,n,s){var l="function"==typeof n;l&&(a(n,"name")||i(n,"name",t)),e[t]!==n&&(l&&(a(n,o)||i(n,o,e[t]?""+e[t]:u.join(String(t)))),e===r?e[t]=n:s?e[t]?e[t]=n:i(e,t,n):(delete e[t],i(e,t,n)))})(Function.prototype,s,function(){return"function"==typeof this&&this[o]||l.call(this)})},function(e,t){var n=0,r=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+r).toString(36))}},function(e,t,n){var r=n(24);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,i){return e.call(t,n,r,i)}}return function(){return e.apply(t,arguments)}}},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!");return e}},function(e,t,n){var r=n(22)("meta"),i=n(16),a=n(8),o=n(14).f,s=0,l=Object.isExtensible||function(){return!0},u=!n(10)(function(){return l(Object.preventExtensions({}))}),c=function(e){o(e,r,{value:{i:"O"+ ++s,w:{}}})},f=function(e,t){if(!i(e))return"symbol"==typeof e?e:("string"==typeof e?"S":"P")+e;if(!a(e,r)){if(!l(e))return"F";if(!t)return"E";c(e)}return e[r].i},d=function(e,t){if(!a(e,r)){if(!l(e))return!0;if(!t)return!1;c(e)}return e[r].w},p=function(e){return u&&h.NEED&&l(e)&&!a(e,r)&&c(e),e},h=e.exports={KEY:r,NEED:!1,fastKey:f,getWeak:d,onFreeze:p}},function(e,t,n){var r=n(12),i=n(7),a="__core-js_shared__",o=i[a]||(i[a]={});(e.exports=function(e,t){return o[e]||(o[e]=void 0!==t?t:{})})("versions",[]).push({version:r.version,mode:n(27)?"pure":"global",copyright:"© 2018 Denis Pushkarev (zloirock.ru)"})},function(e,t){e.exports=!1},function(e,t,n){var r=n(14).f,i=n(8),a=n(29)("toStringTag");e.exports=function(e,t,n){e&&!i(e=n?e:e.prototype,a)&&r(e,a,{configurable:!0,value:t})}},function(e,t,n){var r=n(26)("wks"),i=n(22),a=n(7).Symbol,o="function"==typeof a,s=e.exports=function(e){return r[e]||(r[e]=o&&a[e]||(o?a:i)("Symbol."+e))};s.store=r},function(e,t,n){t.f=n(29)},function(e,t,n){var r=n(7),i=n(12),a=n(27),o=n(30),s=n(14).f;e.exports=function(e){var t=i.Symbol||(i.Symbol=a?{}:r.Symbol||{});"_"==e.charAt(0)||e in t||s(t,e,{value:o.f(e)})}},function(e,t,n){var r=n(33),i=n(45),a=n(46);e.exports=function(e){var t=r(e),n=i.f;if(n)for(var o,s=n(e),l=a.f,u=0;s.length>u;)l.call(e,o=s[u++])&&t.push(o);return t}},function(e,t,n){var r=n(34),i=n(44);e.exports=Object.keys||function(e){return r(e,i)}},function(e,t,n){var r=n(8),i=n(35),a=n(39)(!1),o=n(43)("IE_PROTO");e.exports=function(e,t){var n,s=i(e),l=0,u=[];for(n in s)n!=o&&r(s,n)&&u.push(n);for(;t.length>l;)r(s,n=t[l++])&&(~a(u,n)||u.push(n));return u}},function(e,t,n){var r=n(36),i=n(38);e.exports=function(e){return r(i(e))}},function(e,t,n){var r=n(37);e.exports=Object("z").propertyIsEnumerable(0)?Object:function(e){return"String"==r(e)?e.split(""):Object(e)}},function(e,t){var n={}.toString;e.exports=function(e){return n.call(e).slice(8,-1)}},function(e,t){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,t,n){var r=n(35),i=n(40),a=n(42);e.exports=function(e){return function(t,n,o){var s,l=r(t),u=i(l.length),c=a(o,u);if(e&&n!=n){for(;u>c;)if(s=l[c++],s!=s)return!0}else for(;u>c;c++)if((e||c in l)&&l[c]===n)return e||c||0;return!e&&-1}}},function(e,t,n){var r=n(41),i=Math.min;e.exports=function(e){return e>0?i(r(e),9007199254740991):0}},function(e,t){var n=Math.ceil,r=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?r:n)(e)}},function(e,t,n){var r=n(41),i=Math.max,a=Math.min;e.exports=function(e,t){return e=r(e),e<0?i(e+t,0):a(e,t)}},function(e,t,n){var r=n(26)("keys"),i=n(22);e.exports=function(e){return r[e]||(r[e]=i(e))}},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t){t.f={}.propertyIsEnumerable},function(e,t,n){var r=n(37);e.exports=Array.isArray||function(e){return"Array"==r(e)}},function(e,t,n){var r=n(15),i=n(49),a=n(44),o=n(43)("IE_PROTO"),s=function(){},l="prototype",u=function(){var e,t=n(18)("iframe"),r=a.length,i="<",o=">";for(t.style.display="none",n(50).appendChild(t),t.src="javascript:",e=t.contentWindow.document,e.open(),e.write(i+"script"+o+"document.F=Object"+i+"/script"+o),e.close(),u=e.F;r--;)delete u[l][a[r]];return u()};e.exports=Object.create||function(e,t){var n;return null!==e?(s[l]=r(e),n=new s,s[l]=null,n[o]=e):n=u(),void 0===t?n:i(n,t)}},function(e,t,n){var r=n(14),i=n(15),a=n(33);e.exports=n(9)?Object.defineProperties:function(e,t){i(e);for(var n,o=a(t),s=o.length,l=0;s>l;)r.f(e,n=o[l++],t[n]);return e}},function(e,t,n){var r=n(7).document;e.exports=r&&r.documentElement},function(e,t,n){var r=n(35),i=n(52).f,a={}.toString,o="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],s=function(e){try{return i(e)}catch(e){return o.slice()}};e.exports.f=function(e){return o&&"[object Window]"==a.call(e)?s(e):i(r(e))}},function(e,t,n){var r=n(34),i=n(44).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return r(e,i)}},function(e,t,n){var r=n(46),i=n(20),a=n(35),o=n(19),s=n(8),l=n(17),u=Object.getOwnPropertyDescriptor;t.f=n(9)?u:function(e,t){if(e=a(e),t=o(t,!0),l)try{return u(e,t)}catch(e){}if(s(e,t))return i(!r.f.call(e,t),e[t])}},function(e,t,n){var r=n(11);r(r.S,"Object",{create:n(48)})},function(e,t,n){var r=n(11);r(r.S+r.F*!n(9),"Object",{defineProperty:n(14).f})},function(e,t,n){var r=n(11);r(r.S+r.F*!n(9),"Object",{defineProperties:n(49)})},function(e,t,n){var r=n(35),i=n(53).f;n(58)("getOwnPropertyDescriptor",function(){return function(e,t){return i(r(e),t)}})},function(e,t,n){var r=n(11),i=n(12),a=n(10);e.exports=function(e,t){var n=(i.Object||{})[e]||Object[e],o={};o[e]=t(n),r(r.S+r.F*a(function(){n(1)}),"Object",o)}},function(e,t,n){var r=n(60),i=n(61);n(58)("getPrototypeOf",function(){return function(e){return i(r(e))}})},function(e,t,n){var r=n(38);e.exports=function(e){return Object(r(e))}},function(e,t,n){var r=n(8),i=n(60),a=n(43)("IE_PROTO"),o=Object.prototype;e.exports=Object.getPrototypeOf||function(e){return e=i(e),r(e,a)?e[a]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?o:null}},function(e,t,n){var r=n(60),i=n(33);n(58)("keys",function(){return function(e){return i(r(e))}})},function(e,t,n){n(58)("getOwnPropertyNames",function(){return n(51).f})},function(e,t,n){var r=n(16),i=n(25).onFreeze;n(58)("freeze",function(e){return function(t){return e&&r(t)?e(i(t)):t}})},function(e,t,n){var r=n(16),i=n(25).onFreeze;n(58)("seal",function(e){return function(t){return e&&r(t)?e(i(t)):t}})},function(e,t,n){var r=n(16),i=n(25).onFreeze;n(58)("preventExtensions",function(e){return function(t){return e&&r(t)?e(i(t)):t}})},function(e,t,n){var r=n(16);n(58)("isFrozen",function(e){return function(t){return!r(t)||!!e&&e(t)}})},function(e,t,n){var r=n(16);n(58)("isSealed",function(e){return function(t){return!r(t)||!!e&&e(t)}})},function(e,t,n){var r=n(16);n(58)("isExtensible",function(e){return function(t){return!!r(t)&&(!e||e(t))}})},function(e,t,n){var r=n(11);r(r.S+r.F,"Object",{assign:n(71)})},function(e,t,n){"use strict";var r=n(33),i=n(45),a=n(46),o=n(60),s=n(36),l=Object.assign;e.exports=!l||n(10)(function(){var e={},t={},n=Symbol(),r="abcdefghijklmnopqrst";return e[n]=7,r.split("").forEach(function(e){t[e]=e}),7!=l({},e)[n]||Object.keys(l({},t)).join("")!=r})?function(e,t){for(var n=o(e),l=arguments.length,u=1,c=i.f,f=a.f;l>u;)for(var d,p=s(arguments[u++]),h=c?r(p).concat(c(p)):r(p),m=h.length,v=0;m>v;)f.call(p,d=h[v++])&&(n[d]=p[d]);return n}:l},function(e,t,n){var r=n(11);r(r.S,"Object",{is:n(73)})},function(e,t){e.exports=Object.is||function(e,t){return e===t?0!==e||1/e===1/t:e!=e&&t!=t}},function(e,t,n){var r=n(11);r(r.S,"Object",{setPrototypeOf:n(75).set})},function(e,t,n){var r=n(16),i=n(15),a=function(e,t){if(i(e),!r(t)&&null!==t)throw TypeError(t+": can't set as prototype!")};e.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(e,t,r){try{r=n(23)(Function.call,n(53).f(Object.prototype,"__proto__").set,2),r(e,[]),t=!(e instanceof Array)}catch(e){t=!0}return function(e,n){return a(e,n),t?e.__proto__=n:r(e,n),e}}({},!1):void 0),check:a}},function(e,t,n){"use strict";var r=n(77),i={};i[n(29)("toStringTag")]="z",i+""!="[object z]"&&n(21)(Object.prototype,"toString",function(){return"[object "+r(this)+"]"},!0)},function(e,t,n){var r=n(37),i=n(29)("toStringTag"),a="Arguments"==r(function(){return arguments}()),o=function(e,t){try{return e[t]}catch(e){}};e.exports=function(e){var t,n,s;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=o(t=Object(e),i))?n:a?r(t):"Object"==(s=r(t))&&"function"==typeof t.callee?"Arguments":s}},function(e,t,n){var r=n(11);r(r.P,"Function",{bind:n(79)})},function(e,t,n){"use strict";var r=n(24),i=n(16),a=n(80),o=[].slice,s={},l=function(e,t,n){if(!(t in s)){for(var r=[],i=0;i>>0||(o.test(n)?16:10))}:r},function(e,t,n){var r=n(11),i=n(38),a=n(10),o=n(86),s="["+o+"]",l="
",u=RegExp("^"+s+s+"*"),c=RegExp(s+s+"*$"),f=function(e,t,n){var i={},s=a(function(){return!!o[e]()||l[e]()!=l}),u=i[e]=s?t(d):o[e];n&&(i[n]=u),r(r.P+r.F*s,"String",i)},d=f.trim=function(e,t){return e=String(i(e)),1&t&&(e=e.replace(u,"")),2&t&&(e=e.replace(c,"")),e};e.exports=f},function(e,t){e.exports="\t\n\v\f\r \u2028\u2029\ufeff"},function(e,t,n){var r=n(11),i=n(88);r(r.G+r.F*(parseFloat!=i),{parseFloat:i})},function(e,t,n){var r=n(7).parseFloat,i=n(85).trim;e.exports=1/r(n(86)+"-0")!==-(1/0)?function(e){var t=i(String(e),3),n=r(t);return 0===n&&"-"==t.charAt(0)?-0:n}:r},function(e,t,n){"use strict";var r=n(7),i=n(8),a=n(37),o=n(90),s=n(19),l=n(10),u=n(52).f,c=n(53).f,f=n(14).f,d=n(85).trim,p="Number",h=r[p],m=h,v=h.prototype,g=a(n(48)(v))==p,y="trim"in String.prototype,_=function(e){var t=s(e,!1);if("string"==typeof t&&t.length>2){t=y?t.trim():d(t,3);var n,r,i,a=t.charCodeAt(0);if(43===a||45===a){if(n=t.charCodeAt(2),88===n||120===n)return NaN}else if(48===a){switch(t.charCodeAt(1)){case 66:case 98:r=2,i=49;break;case 79:case 111:r=8,i=55;break;default:return+t}for(var o,l=t.slice(2),u=0,c=l.length;ui)return NaN;return parseInt(l,r)}}return+t};if(!h(" 0o1")||!h("0b1")||h("+0x1")){h=function(e){var t=arguments.length<1?0:e,n=this;return n instanceof h&&(g?l(function(){v.valueOf.call(n)}):a(n)!=p)?o(new m(_(t)),n,h):_(t)};for(var b,w=n(9)?u(m):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger".split(","),x=0;w.length>x;x++)i(m,b=w[x])&&!i(h,b)&&f(h,b,c(m,b));h.prototype=v,v.constructor=h,n(21)(r,p,h)}},function(e,t,n){var r=n(16),i=n(75).set;e.exports=function(e,t,n){var a,o=t.constructor;return o!==n&&"function"==typeof o&&(a=o.prototype)!==n.prototype&&r(a)&&i&&i(e,a),e}},function(e,t,n){"use strict";var r=n(11),i=n(41),a=n(92),o=n(93),s=1..toFixed,l=Math.floor,u=[0,0,0,0,0,0],c="Number.toFixed: incorrect invocation!",f="0",d=function(e,t){for(var n=-1,r=t;++n<6;)r+=e*u[n],u[n]=r%1e7,r=l(r/1e7)},p=function(e){for(var t=6,n=0;--t>=0;)n+=u[t],u[t]=l(n/e),n=n%e*1e7},h=function(){for(var e=6,t="";--e>=0;)if(""!==t||0===e||0!==u[e]){var n=String(u[e]);t=""===t?n:t+o.call(f,7-n.length)+n}return t},m=function(e,t,n){return 0===t?n:t%2===1?m(e,t-1,n*e):m(e*e,t/2,n)},v=function(e){for(var t=0,n=e;n>=4096;)t+=12,n/=4096;for(;n>=2;)t+=1,n/=2;return t};r(r.P+r.F*(!!s&&("0.000"!==8e-5.toFixed(3)||"1"!==.9.toFixed(0)||"1.25"!==1.255.toFixed(2)||"1000000000000000128"!==(0xde0b6b3a7640080).toFixed(0))||!n(10)(function(){s.call({})})),"Number",{toFixed:function(e){var t,n,r,s,l=a(this,c),u=i(e),g="",y=f;if(u<0||u>20)throw RangeError(c);if(l!=l)return"NaN";if(l<=-1e21||l>=1e21)return String(l);if(l<0&&(g="-",l=-l),l>1e-21)if(t=v(l*m(2,69,1))-69,n=t<0?l*m(2,-t,1):l/m(2,t,1),
+n*=4503599627370496,t=52-t,t>0){for(d(0,n),r=u;r>=7;)d(1e7,0),r-=7;for(d(m(10,r,1),0),r=t-1;r>=23;)p(1<<23),r-=23;p(1<0?(s=y.length,y=g+(s<=u?"0."+o.call(f,u-s)+y:y.slice(0,s-u)+"."+y.slice(s-u))):y=g+y,y}})},function(e,t,n){var r=n(37);e.exports=function(e,t){if("number"!=typeof e&&"Number"!=r(e))throw TypeError(t);return+e}},function(e,t,n){"use strict";var r=n(41),i=n(38);e.exports=function(e){var t=String(i(this)),n="",a=r(e);if(a<0||a==1/0)throw RangeError("Count can't be negative");for(;a>0;(a>>>=1)&&(t+=t))1&a&&(n+=t);return n}},function(e,t,n){"use strict";var r=n(11),i=n(10),a=n(92),o=1..toPrecision;r(r.P+r.F*(i(function(){return"1"!==o.call(1,void 0)})||!i(function(){o.call({})})),"Number",{toPrecision:function(e){var t=a(this,"Number#toPrecision: incorrect invocation!");return void 0===e?o.call(t):o.call(t,e)}})},function(e,t,n){var r=n(11);r(r.S,"Number",{EPSILON:Math.pow(2,-52)})},function(e,t,n){var r=n(11),i=n(7).isFinite;r(r.S,"Number",{isFinite:function(e){return"number"==typeof e&&i(e)}})},function(e,t,n){var r=n(11);r(r.S,"Number",{isInteger:n(98)})},function(e,t,n){var r=n(16),i=Math.floor;e.exports=function(e){return!r(e)&&isFinite(e)&&i(e)===e}},function(e,t,n){var r=n(11);r(r.S,"Number",{isNaN:function(e){return e!=e}})},function(e,t,n){var r=n(11),i=n(98),a=Math.abs;r(r.S,"Number",{isSafeInteger:function(e){return i(e)&&a(e)<=9007199254740991}})},function(e,t,n){var r=n(11);r(r.S,"Number",{MAX_SAFE_INTEGER:9007199254740991})},function(e,t,n){var r=n(11);r(r.S,"Number",{MIN_SAFE_INTEGER:-9007199254740991})},function(e,t,n){var r=n(11),i=n(88);r(r.S+r.F*(Number.parseFloat!=i),"Number",{parseFloat:i})},function(e,t,n){var r=n(11),i=n(84);r(r.S+r.F*(Number.parseInt!=i),"Number",{parseInt:i})},function(e,t,n){var r=n(11),i=n(106),a=Math.sqrt,o=Math.acosh;r(r.S+r.F*!(o&&710==Math.floor(o(Number.MAX_VALUE))&&o(1/0)==1/0),"Math",{acosh:function(e){return(e=+e)<1?NaN:e>94906265.62425156?Math.log(e)+Math.LN2:i(e-1+a(e-1)*a(e+1))}})},function(e,t){e.exports=Math.log1p||function(e){return(e=+e)>-1e-8&&e<1e-8?e-e*e/2:Math.log(1+e)}},function(e,t,n){function r(e){return isFinite(e=+e)&&0!=e?e<0?-r(-e):Math.log(e+Math.sqrt(e*e+1)):e}var i=n(11),a=Math.asinh;i(i.S+i.F*!(a&&1/a(0)>0),"Math",{asinh:r})},function(e,t,n){var r=n(11),i=Math.atanh;r(r.S+r.F*!(i&&1/i(-0)<0),"Math",{atanh:function(e){return 0==(e=+e)?e:Math.log((1+e)/(1-e))/2}})},function(e,t,n){var r=n(11),i=n(110);r(r.S,"Math",{cbrt:function(e){return i(e=+e)*Math.pow(Math.abs(e),1/3)}})},function(e,t){e.exports=Math.sign||function(e){return 0==(e=+e)||e!=e?e:e<0?-1:1}},function(e,t,n){var r=n(11);r(r.S,"Math",{clz32:function(e){return(e>>>=0)?31-Math.floor(Math.log(e+.5)*Math.LOG2E):32}})},function(e,t,n){var r=n(11),i=Math.exp;r(r.S,"Math",{cosh:function(e){return(i(e=+e)+i(-e))/2}})},function(e,t,n){var r=n(11),i=n(114);r(r.S+r.F*(i!=Math.expm1),"Math",{expm1:i})},function(e,t){var n=Math.expm1;e.exports=!n||n(10)>22025.465794806718||n(10)<22025.465794806718||n(-2e-17)!=-2e-17?function(e){return 0==(e=+e)?e:e>-1e-6&&e<1e-6?e+e*e/2:Math.exp(e)-1}:n},function(e,t,n){var r=n(11);r(r.S,"Math",{fround:n(116)})},function(e,t,n){var r=n(110),i=Math.pow,a=i(2,-52),o=i(2,-23),s=i(2,127)*(2-o),l=i(2,-126),u=function(e){return e+1/a-1/a};e.exports=Math.fround||function(e){var t,n,i=Math.abs(e),c=r(e);return is||n!=n?c*(1/0):c*n)}},function(e,t,n){var r=n(11),i=Math.abs;r(r.S,"Math",{hypot:function(e,t){for(var n,r,a=0,o=0,s=arguments.length,l=0;o0?(r=n/l,a+=r*r):a+=n;return l===1/0?1/0:l*Math.sqrt(a)}})},function(e,t,n){var r=n(11),i=Math.imul;r(r.S+r.F*n(10)(function(){return i(4294967295,5)!=-5||2!=i.length}),"Math",{imul:function(e,t){var n=65535,r=+e,i=+t,a=n&r,o=n&i;return 0|a*o+((n&r>>>16)*o+a*(n&i>>>16)<<16>>>0)}})},function(e,t,n){var r=n(11);r(r.S,"Math",{log10:function(e){return Math.log(e)*Math.LOG10E}})},function(e,t,n){var r=n(11);r(r.S,"Math",{log1p:n(106)})},function(e,t,n){var r=n(11);r(r.S,"Math",{log2:function(e){return Math.log(e)/Math.LN2}})},function(e,t,n){var r=n(11);r(r.S,"Math",{sign:n(110)})},function(e,t,n){var r=n(11),i=n(114),a=Math.exp;r(r.S+r.F*n(10)(function(){return!Math.sinh(-2e-17)!=-2e-17}),"Math",{sinh:function(e){return Math.abs(e=+e)<1?(i(e)-i(-e))/2:(a(e-1)-a(-e-1))*(Math.E/2)}})},function(e,t,n){var r=n(11),i=n(114),a=Math.exp;r(r.S,"Math",{tanh:function(e){var t=i(e=+e),n=i(-e);return t==1/0?1:n==1/0?-1:(t-n)/(a(e)+a(-e))}})},function(e,t,n){var r=n(11);r(r.S,"Math",{trunc:function(e){return(e>0?Math.floor:Math.ceil)(e)}})},function(e,t,n){var r=n(11),i=n(42),a=String.fromCharCode,o=String.fromCodePoint;r(r.S+r.F*(!!o&&1!=o.length),"String",{fromCodePoint:function(e){for(var t,n=[],r=arguments.length,o=0;r>o;){if(t=+arguments[o++],i(t,1114111)!==t)throw RangeError(t+" is not a valid code point");n.push(t<65536?a(t):a(((t-=65536)>>10)+55296,t%1024+56320))}return n.join("")}})},function(e,t,n){var r=n(11),i=n(35),a=n(40);r(r.S,"String",{raw:function(e){for(var t=i(e.raw),n=a(t.length),r=arguments.length,o=[],s=0;n>s;)o.push(String(t[s++])),s=t.length?{value:void 0,done:!0}:(e=r(t,n),this._i+=e.length,{value:e,done:!1})})},function(e,t,n){var r=n(41),i=n(38);e.exports=function(e){return function(t,n){var a,o,s=String(i(t)),l=r(n),u=s.length;return l<0||l>=u?e?"":void 0:(a=s.charCodeAt(l),a<55296||a>56319||l+1===u||(o=s.charCodeAt(l+1))<56320||o>57343?e?s.charAt(l):a:e?s.slice(l,l+2):(a-55296<<10)+(o-56320)+65536)}}},function(e,t,n){"use strict";var r=n(27),i=n(11),a=n(21),o=n(13),s=n(132),l=n(133),u=n(28),c=n(61),f=n(29)("iterator"),d=!([].keys&&"next"in[].keys()),p="@@iterator",h="keys",m="values",v=function(){return this};e.exports=function(e,t,n,g,y,_,b){l(n,t,g);var w,x,k,S=function(e){if(!d&&e in I)return I[e];switch(e){case h:return function(){return new n(this,e)};case m:return function(){return new n(this,e)}}return function(){return new n(this,e)}},C=t+" Iterator",E=y==m,A=!1,I=e.prototype,M=I[f]||I[p]||y&&I[y],O=M||S(y),T=y?E?S("entries"):O:void 0,P="Array"==t?I.entries||M:M;if(P&&(k=c(P.call(new e)),k!==Object.prototype&&k.next&&(u(k,C,!0),r||"function"==typeof k[f]||o(k,f,v))),E&&M&&M.name!==m&&(A=!0,O=function(){return M.call(this)}),r&&!b||!d&&!A&&I[f]||o(I,f,O),s[t]=O,s[C]=v,y)if(w={values:E?O:S(m),keys:_?O:S(h),entries:T},b)for(x in w)x in I||a(I,x,w[x]);else i(i.P+i.F*(d||A),t,w);return w}},function(e,t){e.exports={}},function(e,t,n){"use strict";var r=n(48),i=n(20),a=n(28),o={};n(13)(o,n(29)("iterator"),function(){return this}),e.exports=function(e,t,n){e.prototype=r(o,{next:i(1,n)}),a(e,t+" Iterator")}},function(e,t,n){"use strict";var r=n(11),i=n(130)(!1);r(r.P,"String",{codePointAt:function(e){return i(this,e)}})},function(e,t,n){"use strict";var r=n(11),i=n(40),a=n(136),o="endsWith",s=""[o];r(r.P+r.F*n(138)(o),"String",{endsWith:function(e){var t=a(this,e,o),n=arguments.length>1?arguments[1]:void 0,r=i(t.length),l=void 0===n?r:Math.min(i(n),r),u=String(e);return s?s.call(t,u,l):t.slice(l-u.length,l)===u}})},function(e,t,n){var r=n(137),i=n(38);e.exports=function(e,t,n){if(r(t))throw TypeError("String#"+n+" doesn't accept regex!");return String(i(e))}},function(e,t,n){var r=n(16),i=n(37),a=n(29)("match");e.exports=function(e){var t;return r(e)&&(void 0!==(t=e[a])?!!t:"RegExp"==i(e))}},function(e,t,n){var r=n(29)("match");e.exports=function(e){var t=/./;try{"/./"[e](t)}catch(n){try{return t[r]=!1,!"/./"[e](t)}catch(e){}}return!0}},function(e,t,n){"use strict";var r=n(11),i=n(136),a="includes";r(r.P+r.F*n(138)(a),"String",{includes:function(e){return!!~i(this,e,a).indexOf(e,arguments.length>1?arguments[1]:void 0)}})},function(e,t,n){var r=n(11);r(r.P,"String",{repeat:n(93)})},function(e,t,n){"use strict";var r=n(11),i=n(40),a=n(136),o="startsWith",s=""[o];r(r.P+r.F*n(138)(o),"String",{startsWith:function(e){var t=a(this,e,o),n=i(Math.min(arguments.length>1?arguments[1]:void 0,t.length)),r=String(e);return s?s.call(t,r,n):t.slice(n,n+r.length)===r}})},function(e,t,n){"use strict";n(143)("anchor",function(e){return function(t){return e(this,"a","name",t)}})},function(e,t,n){var r=n(11),i=n(10),a=n(38),o=/"/g,s=function(e,t,n,r){var i=String(a(e)),s="<"+t;return""!==n&&(s+=" "+n+'="'+String(r).replace(o,""")+'"'),s+">"+i+""+t+">"};e.exports=function(e,t){var n={};n[e]=t(s),r(r.P+r.F*i(function(){var t=""[e]('"');return t!==t.toLowerCase()||t.split('"').length>3}),"String",n)}},function(e,t,n){"use strict";n(143)("big",function(e){return function(){return e(this,"big","","")}})},function(e,t,n){"use strict";n(143)("blink",function(e){return function(){return e(this,"blink","","")}})},function(e,t,n){"use strict";n(143)("bold",function(e){return function(){return e(this,"b","","")}})},function(e,t,n){"use strict";n(143)("fixed",function(e){return function(){return e(this,"tt","","")}})},function(e,t,n){"use strict";n(143)("fontcolor",function(e){return function(t){return e(this,"font","color",t)}})},function(e,t,n){"use strict";n(143)("fontsize",function(e){return function(t){return e(this,"font","size",t)}})},function(e,t,n){"use strict";n(143)("italics",function(e){return function(){return e(this,"i","","")}})},function(e,t,n){"use strict";n(143)("link",function(e){return function(t){return e(this,"a","href",t)}})},function(e,t,n){"use strict";n(143)("small",function(e){return function(){return e(this,"small","","")}})},function(e,t,n){"use strict";n(143)("strike",function(e){return function(){return e(this,"strike","","")}})},function(e,t,n){"use strict";n(143)("sub",function(e){return function(){return e(this,"sub","","")}})},function(e,t,n){"use strict";n(143)("sup",function(e){return function(){return e(this,"sup","","")}})},function(e,t,n){var r=n(11);r(r.S,"Date",{now:function(){return(new Date).getTime()}})},function(e,t,n){"use strict";var r=n(11),i=n(60),a=n(19);r(r.P+r.F*n(10)(function(){return null!==new Date(NaN).toJSON()||1!==Date.prototype.toJSON.call({toISOString:function(){return 1}})}),"Date",{toJSON:function(e){var t=i(this),n=a(t);return"number"!=typeof n||isFinite(n)?t.toISOString():null}})},function(e,t,n){var r=n(11),i=n(159);r(r.P+r.F*(Date.prototype.toISOString!==i),"Date",{toISOString:i})},function(e,t,n){"use strict";var r=n(10),i=Date.prototype.getTime,a=Date.prototype.toISOString,o=function(e){return e>9?e:"0"+e};e.exports=r(function(){return"0385-07-25T07:06:39.999Z"!=a.call(new Date(-5e13-1))})||!r(function(){a.call(new Date(NaN))})?function(){if(!isFinite(i.call(this)))throw RangeError("Invalid time value");var e=this,t=e.getUTCFullYear(),n=e.getUTCMilliseconds(),r=t<0?"-":t>9999?"+":"";return r+("00000"+Math.abs(t)).slice(r?-6:-4)+"-"+o(e.getUTCMonth()+1)+"-"+o(e.getUTCDate())+"T"+o(e.getUTCHours())+":"+o(e.getUTCMinutes())+":"+o(e.getUTCSeconds())+"."+(n>99?n:"0"+o(n))+"Z"}:a},function(e,t,n){var r=Date.prototype,i="Invalid Date",a="toString",o=r[a],s=r.getTime;new Date(NaN)+""!=i&&n(21)(r,a,function(){var e=s.call(this);return e===e?o.call(this):i})},function(e,t,n){var r=n(29)("toPrimitive"),i=Date.prototype;r in i||n(13)(i,r,n(162))},function(e,t,n){"use strict";var r=n(15),i=n(19),a="number";e.exports=function(e){if("string"!==e&&e!==a&&"default"!==e)throw TypeError("Incorrect hint");return i(r(this),e!=a)}},function(e,t,n){var r=n(11);r(r.S,"Array",{isArray:n(47)})},function(e,t,n){"use strict";var r=n(23),i=n(11),a=n(60),o=n(165),s=n(166),l=n(40),u=n(167),c=n(168);i(i.S+i.F*!n(169)(function(e){Array.from(e)}),"Array",{from:function(e){var t,n,i,f,d=a(e),p="function"==typeof this?this:Array,h=arguments.length,m=h>1?arguments[1]:void 0,v=void 0!==m,g=0,y=c(d);if(v&&(m=r(m,h>2?arguments[2]:void 0,2)),void 0==y||p==Array&&s(y))for(t=l(d.length),n=new p(t);t>g;g++)u(n,g,v?m(d[g],g):d[g]);else for(f=y.call(d),n=new p;!(i=f.next()).done;g++)u(n,g,v?o(f,m,[i.value,g],!0):i.value);return n.length=g,n}})},function(e,t,n){var r=n(15);e.exports=function(e,t,n,i){try{return i?t(r(n)[0],n[1]):t(n)}catch(t){var a=e.return;throw void 0!==a&&r(a.call(e)),t}}},function(e,t,n){var r=n(132),i=n(29)("iterator"),a=Array.prototype;e.exports=function(e){return void 0!==e&&(r.Array===e||a[i]===e)}},function(e,t,n){"use strict";var r=n(14),i=n(20);e.exports=function(e,t,n){t in e?r.f(e,t,i(0,n)):e[t]=n}},function(e,t,n){var r=n(77),i=n(29)("iterator"),a=n(132);e.exports=n(12).getIteratorMethod=function(e){if(void 0!=e)return e[i]||e["@@iterator"]||a[r(e)]}},function(e,t,n){var r=n(29)("iterator"),i=!1;try{var a=[7][r]();a.return=function(){i=!0},Array.from(a,function(){throw 2})}catch(e){}e.exports=function(e,t){if(!t&&!i)return!1;var n=!1;try{var a=[7],o=a[r]();o.next=function(){return{done:n=!0}},a[r]=function(){return o},e(a)}catch(e){}return n}},function(e,t,n){"use strict";var r=n(11),i=n(167);r(r.S+r.F*n(10)(function(){function e(){}return!(Array.of.call(e)instanceof e)}),"Array",{of:function(){for(var e=0,t=arguments.length,n=new("function"==typeof this?this:Array)(t);t>e;)i(n,e,arguments[e++]);return n.length=t,n}})},function(e,t,n){"use strict";var r=n(11),i=n(35),a=[].join;r(r.P+r.F*(n(36)!=Object||!n(172)(a)),"Array",{join:function(e){return a.call(i(this),void 0===e?",":e)}})},function(e,t,n){"use strict";var r=n(10);e.exports=function(e,t){return!!e&&r(function(){t?e.call(null,function(){},1):e.call(null)})}},function(e,t,n){"use strict";var r=n(11),i=n(50),a=n(37),o=n(42),s=n(40),l=[].slice;r(r.P+r.F*n(10)(function(){i&&l.call(i)}),"Array",{slice:function(e,t){var n=s(this.length),r=a(this);if(t=void 0===t?n:t,"Array"==r)return l.call(this,e,t);for(var i=o(e,n),u=o(t,n),c=s(u-i),f=new Array(c),d=0;dw;w++)if((d||w in y)&&(m=y[w],v=_(m,w,g),e))if(n)x[w]=v;else if(v)switch(e){case 3:return!0;case 5:return m;case 6:return w;case 2:x.push(m)}else if(c)return!1;return f?-1:u||c?c:x}}},function(e,t,n){var r=n(178);e.exports=function(e,t){return new(r(e))(t)}},function(e,t,n){var r=n(16),i=n(47),a=n(29)("species");e.exports=function(e){var t;return i(e)&&(t=e.constructor,"function"!=typeof t||t!==Array&&!i(t.prototype)||(t=void 0),r(t)&&(t=t[a],null===t&&(t=void 0))),void 0===t?Array:t}},function(e,t,n){"use strict";var r=n(11),i=n(176)(1);r(r.P+r.F*!n(172)([].map,!0),"Array",{map:function(e){return i(this,e,arguments[1])}})},function(e,t,n){"use strict";var r=n(11),i=n(176)(2);r(r.P+r.F*!n(172)([].filter,!0),"Array",{filter:function(e){return i(this,e,arguments[1])}})},function(e,t,n){"use strict";var r=n(11),i=n(176)(3);r(r.P+r.F*!n(172)([].some,!0),"Array",{some:function(e){return i(this,e,arguments[1])}})},function(e,t,n){"use strict";var r=n(11),i=n(176)(4);r(r.P+r.F*!n(172)([].every,!0),"Array",{every:function(e){return i(this,e,arguments[1])}})},function(e,t,n){"use strict";var r=n(11),i=n(184);r(r.P+r.F*!n(172)([].reduce,!0),"Array",{reduce:function(e){return i(this,e,arguments.length,arguments[1],!1)}})},function(e,t,n){var r=n(24),i=n(60),a=n(36),o=n(40);e.exports=function(e,t,n,s,l){r(t);var u=i(e),c=a(u),f=o(u.length),d=l?f-1:0,p=l?-1:1;if(n<2)for(;;){if(d in c){s=c[d],d+=p;break}if(d+=p,l?d<0:f<=d)throw TypeError("Reduce of empty array with no initial value")}for(;l?d>=0:f>d;d+=p)d in c&&(s=t(s,c[d],d,u));return s}},function(e,t,n){"use strict";var r=n(11),i=n(184);r(r.P+r.F*!n(172)([].reduceRight,!0),"Array",{reduceRight:function(e){return i(this,e,arguments.length,arguments[1],!0)}})},function(e,t,n){"use strict";var r=n(11),i=n(39)(!1),a=[].indexOf,o=!!a&&1/[1].indexOf(1,-0)<0;r(r.P+r.F*(o||!n(172)(a)),"Array",{indexOf:function(e){return o?a.apply(this,arguments)||0:i(this,e,arguments[1])}})},function(e,t,n){"use strict";var r=n(11),i=n(35),a=n(41),o=n(40),s=[].lastIndexOf,l=!!s&&1/[1].lastIndexOf(1,-0)<0;r(r.P+r.F*(l||!n(172)(s)),"Array",{lastIndexOf:function(e){if(l)return s.apply(this,arguments)||0;var t=i(this),n=o(t.length),r=n-1;for(arguments.length>1&&(r=Math.min(r,a(arguments[1]))),r<0&&(r=n+r);r>=0;r--)if(r in t&&t[r]===e)return r||0;return-1}})},function(e,t,n){var r=n(11);r(r.P,"Array",{copyWithin:n(189)}),n(190)("copyWithin")},function(e,t,n){"use strict";var r=n(60),i=n(42),a=n(40);e.exports=[].copyWithin||function(e,t){var n=r(this),o=a(n.length),s=i(e,o),l=i(t,o),u=arguments.length>2?arguments[2]:void 0,c=Math.min((void 0===u?o:i(u,o))-l,o-s),f=1;for(l0;)l in n?n[s]=n[l]:delete n[s],s+=f,l+=f;return n}},function(e,t,n){var r=n(29)("unscopables"),i=Array.prototype;void 0==i[r]&&n(13)(i,r,{}),e.exports=function(e){i[r][e]=!0}},function(e,t,n){var r=n(11);r(r.P,"Array",{fill:n(192)}),n(190)("fill")},function(e,t,n){"use strict";var r=n(60),i=n(42),a=n(40);e.exports=function(e){for(var t=r(this),n=a(t.length),o=arguments.length,s=i(o>1?arguments[1]:void 0,n),l=o>2?arguments[2]:void 0,u=void 0===l?n:i(l,n);u>s;)t[s++]=e;return t}},function(e,t,n){"use strict";var r=n(11),i=n(176)(5),a="find",o=!0;a in[]&&Array(1)[a](function(){o=!1}),r(r.P+r.F*o,"Array",{find:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0)}}),n(190)(a)},function(e,t,n){"use strict";var r=n(11),i=n(176)(6),a="findIndex",o=!0;a in[]&&Array(1)[a](function(){o=!1}),r(r.P+r.F*o,"Array",{findIndex:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0)}}),n(190)(a)},function(e,t,n){n(196)("Array")},function(e,t,n){"use strict";var r=n(7),i=n(14),a=n(9),o=n(29)("species");e.exports=function(e){var t=r[e];a&&t&&!t[o]&&i.f(t,o,{configurable:!0,get:function(){return this}})}},function(e,t,n){"use strict";var r=n(190),i=n(198),a=n(132),o=n(35);e.exports=n(131)(Array,"Array",function(e,t){this._t=o(e),this._i=0,this._k=t},function(){var e=this._t,t=this._k,n=this._i++;return!e||n>=e.length?(this._t=void 0,i(1)):"keys"==t?i(0,n):"values"==t?i(0,e[n]):i(0,[n,e[n]])},"values"),a.Arguments=a.Array,r("keys"),r("values"),r("entries")},function(e,t){e.exports=function(e,t){return{value:t,done:!!e}}},function(e,t,n){var r=n(7),i=n(90),a=n(14).f,o=n(52).f,s=n(137),l=n(200),u=r.RegExp,c=u,f=u.prototype,d=/a/g,p=/a/g,h=new u(d)!==d;if(n(9)&&(!h||n(10)(function(){return p[n(29)("match")]=!1,u(d)!=d||u(p)==p||"/a/i"!=u(d,"i")}))){u=function(e,t){var n=this instanceof u,r=s(e),a=void 0===t;return!n&&r&&e.constructor===u&&a?e:i(h?new c(r&&!a?e.source:e,t):c((r=e instanceof u)?e.source:e,r&&a?l.call(e):t),n?this:f,u)};for(var m=(function(e){e in u||a(u,e,{configurable:!0,get:function(){return c[e]},set:function(t){c[e]=t}})}),v=o(c),g=0;v.length>g;)m(v[g++]);f.constructor=u,u.prototype=f,n(21)(r,"RegExp",u)}n(196)("RegExp")},function(e,t,n){"use strict";var r=n(15);e.exports=function(){var e=r(this),t="";return e.global&&(t+="g"),e.ignoreCase&&(t+="i"),e.multiline&&(t+="m"),e.unicode&&(t+="u"),e.sticky&&(t+="y"),t}},function(e,t,n){"use strict";n(202);var r=n(15),i=n(200),a=n(9),o="toString",s=/./[o],l=function(e){n(21)(RegExp.prototype,o,e,!0)};n(10)(function(){return"/a/b"!=s.call({source:"a",flags:"b"})})?l(function(){var e=r(this);return"/".concat(e.source,"/","flags"in e?e.flags:!a&&e instanceof RegExp?i.call(e):void 0)}):s.name!=o&&l(function(){return s.call(this)})},function(e,t,n){n(9)&&"g"!=/./g.flags&&n(14).f(RegExp.prototype,"flags",{configurable:!0,get:n(200)})},function(e,t,n){n(204)("match",1,function(e,t,n){return[function(n){"use strict";var r=e(this),i=void 0==n?void 0:n[t];return void 0!==i?i.call(n,r):new RegExp(n)[t](String(r))},n]})},function(e,t,n){"use strict";var r=n(13),i=n(21),a=n(10),o=n(38),s=n(29);e.exports=function(e,t,n){var l=s(e),u=n(o,l,""[e]),c=u[0],f=u[1];a(function(){var t={};return t[l]=function(){return 7},7!=""[e](t)})&&(i(String.prototype,e,c),r(RegExp.prototype,l,2==t?function(e,t){return f.call(e,this,t)}:function(e){return f.call(e,this)}))}},function(e,t,n){n(204)("replace",2,function(e,t,n){return[function(r,i){"use strict";var a=e(this),o=void 0==r?void 0:r[t];return void 0!==o?o.call(r,a,i):n.call(String(a),r,i)},n]})},function(e,t,n){n(204)("search",1,function(e,t,n){return[function(n){"use strict";var r=e(this),i=void 0==n?void 0:n[t];return void 0!==i?i.call(n,r):new RegExp(n)[t](String(r))},n]})},function(e,t,n){n(204)("split",2,function(e,t,r){"use strict";var i=n(137),a=r,o=[].push,s="split",l="length",u="lastIndex";if("c"=="abbc"[s](/(b)*/)[1]||4!="test"[s](/(?:)/,-1)[l]||2!="ab"[s](/(?:ab)*/)[l]||4!="."[s](/(.?)(.?)/)[l]||"."[s](/()()/)[l]>1||""[s](/.?/)[l]){var c=void 0===/()??/.exec("")[1];r=function(e,t){var n=String(this);if(void 0===e&&0===t)return[];if(!i(e))return a.call(n,e,t);var r,s,f,d,p,h=[],m=(e.ignoreCase?"i":"")+(e.multiline?"m":"")+(e.unicode?"u":"")+(e.sticky?"y":""),v=0,g=void 0===t?4294967295:t>>>0,y=new RegExp(e.source,m+"g");for(c||(r=new RegExp("^"+y.source+"$(?!\\s)",m));(s=y.exec(n))&&(f=s.index+s[0][l],!(f>v&&(h.push(n.slice(v,s.index)),!c&&s[l]>1&&s[0].replace(r,function(){for(p=1;p1&&s.index=g)));)y[u]===s.index&&y[u]++;return v===n[l]?!d&&y.test("")||h.push(""):h.push(n.slice(v)),h[l]>g?h.slice(0,g):h}}else"0"[s](void 0,0)[l]&&(r=function(e,t){return void 0===e&&0===t?[]:a.call(this,e,t)});return[function(n,i){var a=e(this),o=void 0==n?void 0:n[t];return void 0!==o?o.call(n,a,i):r.call(String(a),n,i)},r]})},function(e,t,n){"use strict";var r,i,a,o,s=n(27),l=n(7),u=n(23),c=n(77),f=n(11),d=n(16),p=n(24),h=n(209),m=n(210),v=n(211),g=n(212).set,y=n(213)(),_=n(214),b=n(215),w=n(216),x=n(217),k="Promise",S=l.TypeError,C=l.process,E=C&&C.versions,A=E&&E.v8||"",I=l[k],M="process"==c(C),O=function(){},T=i=_.f,P=!!function(){try{var e=I.resolve(1),t=(e.constructor={})[n(29)("species")]=function(e){e(O,O)};return(M||"function"==typeof PromiseRejectionEvent)&&e.then(O)instanceof t&&0!==A.indexOf("6.6")&&w.indexOf("Chrome/66")===-1}catch(e){}}(),j=function(e){var t;return!(!d(e)||"function"!=typeof(t=e.then))&&t},D=function(e,t){if(!e._n){e._n=!0;var n=e._c;y(function(){for(var r=e._v,i=1==e._s,a=0,o=function(t){var n,a,o,s=i?t.ok:t.fail,l=t.resolve,u=t.reject,c=t.domain;try{s?(i||(2==e._h&&N(e),e._h=1),s===!0?n=r:(c&&c.enter(),n=s(r),c&&(c.exit(),o=!0)),n===t.promise?u(S("Promise-chain cycle")):(a=j(n))?a.call(n,l,u):l(n)):u(r)}catch(e){c&&!o&&c.exit(),u(e)}};n.length>a;)o(n[a++]);e._c=[],e._n=!1,t&&!e._h&&z(e)})}},z=function(e){g.call(l,function(){var t,n,r,i=e._v,a=L(e);if(a&&(t=b(function(){M?C.emit("unhandledRejection",i,e):(n=l.onunhandledrejection)?n({promise:e,reason:i}):(r=l.console)&&r.error&&r.error("Unhandled promise rejection",i)}),e._h=M||L(e)?2:1),e._a=void 0,a&&t.e)throw t.v})},L=function(e){return 1!==e._h&&0===(e._a||e._c).length},N=function(e){g.call(l,function(){var t;M?C.emit("rejectionHandled",e):(t=l.onrejectionhandled)&&t({promise:e,reason:e._v})})},F=function(e){var t=this;t._d||(t._d=!0,t=t._w||t,t._v=e,t._s=2,t._a||(t._a=t._c.slice()),D(t,!0))},R=function(e){var t,n=this;if(!n._d){n._d=!0,n=n._w||n;try{if(n===e)throw S("Promise can't be resolved itself");(t=j(e))?y(function(){var r={_w:n,_d:!1};try{t.call(e,u(R,r,1),u(F,r,1))}catch(e){F.call(r,e)}}):(n._v=e,n._s=1,D(n,!1))}catch(e){F.call({_w:n,_d:!1},e)}}};P||(I=function(e){h(this,I,k,"_h"),p(e),r.call(this);try{e(u(R,this,1),u(F,this,1))}catch(e){F.call(this,e)}},r=function(e){this._c=[],this._a=void 0,this._s=0,this._d=!1,this._v=void 0,this._h=0,this._n=!1},r.prototype=n(218)(I.prototype,{then:function(e,t){var n=T(v(this,I));return n.ok="function"!=typeof e||e,n.fail="function"==typeof t&&t,n.domain=M?C.domain:void 0,this._c.push(n),this._a&&this._a.push(n),this._s&&D(this,!1),n.promise},catch:function(e){return this.then(void 0,e)}}),a=function(){var e=new r;this.promise=e,this.resolve=u(R,e,1),this.reject=u(F,e,1)},_.f=T=function(e){return e===I||e===o?new a(e):i(e)}),f(f.G+f.W+f.F*!P,{Promise:I}),n(28)(I,k),n(196)(k),o=n(12)[k],f(f.S+f.F*!P,k,{reject:function(e){var t=T(this),n=t.reject;return n(e),t.promise}}),f(f.S+f.F*(s||!P),k,{resolve:function(e){return x(s&&this===o?I:this,e)}}),f(f.S+f.F*!(P&&n(169)(function(e){I.all(e).catch(O)})),k,{all:function(e){var t=this,n=T(t),r=n.resolve,i=n.reject,a=b(function(){var n=[],a=0,o=1;m(e,!1,function(e){var s=a++,l=!1;n.push(void 0),o++,t.resolve(e).then(function(e){l||(l=!0,n[s]=e,--o||r(n))},i)}),--o||r(n)});return a.e&&i(a.v),n.promise},race:function(e){var t=this,n=T(t),r=n.reject,i=b(function(){m(e,!1,function(e){t.resolve(e).then(n.resolve,r)})});return i.e&&r(i.v),n.promise}})},function(e,t){e.exports=function(e,t,n,r){if(!(e instanceof t)||void 0!==r&&r in e)throw TypeError(n+": incorrect invocation!");return e}},function(e,t,n){var r=n(23),i=n(165),a=n(166),o=n(15),s=n(40),l=n(168),u={},c={},t=e.exports=function(e,t,n,f,d){var p,h,m,v,g=d?function(){return e}:l(e),y=r(n,f,t?2:1),_=0;if("function"!=typeof g)throw TypeError(e+" is not iterable!");if(a(g)){for(p=s(e.length);p>_;_++)if(v=t?y(o(h=e[_])[0],h[1]):y(e[_]),v===u||v===c)return v}else for(m=g.call(e);!(h=m.next()).done;)if(v=i(m,y,h.value,t),v===u||v===c)return v};t.BREAK=u,t.RETURN=c},function(e,t,n){var r=n(15),i=n(24),a=n(29)("species");e.exports=function(e,t){var n,o=r(e).constructor;return void 0===o||void 0==(n=r(o)[a])?t:i(n)}},function(e,t,n){var r,i,a,o=n(23),s=n(80),l=n(50),u=n(18),c=n(7),f=c.process,d=c.setImmediate,p=c.clearImmediate,h=c.MessageChannel,m=c.Dispatch,v=0,g={},y="onreadystatechange",_=function(){var e=+this;if(g.hasOwnProperty(e)){var t=g[e];delete g[e],t()}},b=function(e){_.call(e.data)};d&&p||(d=function(e){for(var t=[],n=1;arguments.length>n;)t.push(arguments[n++]);return g[++v]=function(){s("function"==typeof e?e:Function(e),t)},r(v),v},p=function(e){delete g[e]},"process"==n(37)(f)?r=function(e){f.nextTick(o(_,e,1))}:m&&m.now?r=function(e){m.now(o(_,e,1))}:h?(i=new h,a=i.port2,i.port1.onmessage=b,r=o(a.postMessage,a,1)):c.addEventListener&&"function"==typeof postMessage&&!c.importScripts?(r=function(e){c.postMessage(e+"","*")},c.addEventListener("message",b,!1)):r=y in u("script")?function(e){l.appendChild(u("script"))[y]=function(){l.removeChild(this),_.call(e)}}:function(e){setTimeout(o(_,e,1),0)}),e.exports={set:d,clear:p}},function(e,t,n){var r=n(7),i=n(212).set,a=r.MutationObserver||r.WebKitMutationObserver,o=r.process,s=r.Promise,l="process"==n(37)(o);e.exports=function(){var e,t,n,u=function(){var r,i;for(l&&(r=o.domain)&&r.exit();e;){i=e.fn,e=e.next;try{i()}catch(r){throw e?n():t=void 0,r}}t=void 0,r&&r.enter()};if(l)n=function(){o.nextTick(u)};else if(!a||r.navigator&&r.navigator.standalone)if(s&&s.resolve){var c=s.resolve(void 0);n=function(){c.then(u)}}else n=function(){i.call(r,u)};else{var f=!0,d=document.createTextNode("");new a(u).observe(d,{characterData:!0}),n=function(){d.data=f=!f}}return function(r){var i={fn:r,next:void 0};t&&(t.next=i),e||(e=i,n()),t=i}}},function(e,t,n){"use strict";function r(e){var t,n;this.promise=new e(function(e,r){if(void 0!==t||void 0!==n)throw TypeError("Bad Promise constructor");t=e,n=r}),this.resolve=i(t),this.reject=i(n)}var i=n(24);e.exports.f=function(e){return new r(e)}},function(e,t){e.exports=function(e){try{return{e:!1,v:e()}}catch(e){return{e:!0,v:e}}}},function(e,t,n){var r=n(7),i=r.navigator;e.exports=i&&i.userAgent||""},function(e,t,n){var r=n(15),i=n(16),a=n(214);e.exports=function(e,t){if(r(e),i(t)&&t.constructor===e)return t;var n=a.f(e),o=n.resolve;return o(t),n.promise}},function(e,t,n){var r=n(21);e.exports=function(e,t,n){for(var i in t)r(e,i,t[i],n);return e}},function(e,t,n){"use strict";var r=n(220),i=n(221),a="Map";e.exports=n(222)(a,function(e){return function(){return e(this,arguments.length>0?arguments[0]:void 0)}},{get:function(e){var t=r.getEntry(i(this,a),e);return t&&t.v},set:function(e,t){return r.def(i(this,a),0===e?0:e,t)}},r,!0)},function(e,t,n){"use strict";var r=n(14).f,i=n(48),a=n(218),o=n(23),s=n(209),l=n(210),u=n(131),c=n(198),f=n(196),d=n(9),p=n(25).fastKey,h=n(221),m=d?"_s":"size",v=function(e,t){var n,r=p(t);if("F"!==r)return e._i[r];for(n=e._f;n;n=n.n)if(n.k==t)return n};e.exports={getConstructor:function(e,t,n,u){var c=e(function(e,r){s(e,c,t,"_i"),e._t=t,e._i=i(null),e._f=void 0,e._l=void 0,e[m]=0,void 0!=r&&l(r,n,e[u],e)});return a(c.prototype,{clear:function(){for(var e=h(this,t),n=e._i,r=e._f;r;r=r.n)r.r=!0,r.p&&(r.p=r.p.n=void 0),delete n[r.i];e._f=e._l=void 0,e[m]=0},delete:function(e){var n=h(this,t),r=v(n,e);if(r){var i=r.n,a=r.p;delete n._i[r.i],r.r=!0,a&&(a.n=i),i&&(i.p=a),n._f==r&&(n._f=i),n._l==r&&(n._l=a),n[m]--}return!!r},forEach:function(e){h(this,t);for(var n,r=o(e,arguments.length>1?arguments[1]:void 0,3);n=n?n.n:this._f;)for(r(n.v,n.k,this);n&&n.r;)n=n.p},has:function(e){return!!v(h(this,t),e)}}),d&&r(c.prototype,"size",{get:function(){return h(this,t)[m]}}),c},def:function(e,t,n){var r,i,a=v(e,t);return a?a.v=n:(e._l=a={i:i=p(t,!0),k:t,v:n,p:r=e._l,n:void 0,r:!1},e._f||(e._f=a),r&&(r.n=a),e[m]++,"F"!==i&&(e._i[i]=a)),e},getEntry:v,setStrong:function(e,t,n){u(e,t,function(e,n){this._t=h(e,t),this._k=n,this._l=void 0},function(){for(var e=this,t=e._k,n=e._l;n&&n.r;)n=n.p;return e._t&&(e._l=n=n?n.n:e._t._f)?"keys"==t?c(0,n.k):"values"==t?c(0,n.v):c(0,[n.k,n.v]):(e._t=void 0,c(1))},n?"entries":"values",!n,!0),f(t)}}},function(e,t,n){var r=n(16);e.exports=function(e,t){if(!r(e)||e._t!==t)throw TypeError("Incompatible receiver, "+t+" required!");return e}},function(e,t,n){"use strict";var r=n(7),i=n(11),a=n(21),o=n(218),s=n(25),l=n(210),u=n(209),c=n(16),f=n(10),d=n(169),p=n(28),h=n(90);e.exports=function(e,t,n,m,v,g){var y=r[e],_=y,b=v?"set":"add",w=_&&_.prototype,x={},k=function(e){var t=w[e];a(w,e,"delete"==e?function(e){return!(g&&!c(e))&&t.call(this,0===e?0:e)}:"has"==e?function(e){return!(g&&!c(e))&&t.call(this,0===e?0:e)}:"get"==e?function(e){return g&&!c(e)?void 0:t.call(this,0===e?0:e)}:"add"==e?function(e){return t.call(this,0===e?0:e),this}:function(e,n){return t.call(this,0===e?0:e,n),this})};if("function"==typeof _&&(g||w.forEach&&!f(function(){(new _).entries().next()}))){var S=new _,C=S[b](g?{}:-0,1)!=S,E=f(function(){S.has(1)}),A=d(function(e){new _(e)}),I=!g&&f(function(){for(var e=new _,t=5;t--;)e[b](t,t);return!e.has(-0)});A||(_=t(function(t,n){u(t,_,e);var r=h(new y,t,_);return void 0!=n&&l(n,v,r[b],r),r}),_.prototype=w,w.constructor=_),(E||I)&&(k("delete"),k("has"),v&&k("get")),(I||C)&&k(b),g&&w.clear&&delete w.clear}else _=m.getConstructor(t,e,v,b),o(_.prototype,n),s.NEED=!0;return p(_,e),x[e]=_,i(i.G+i.W+i.F*(_!=y),x),g||m.setStrong(_,e,v),_}},function(e,t,n){"use strict";var r=n(220),i=n(221),a="Set";e.exports=n(222)(a,function(e){return function(){return e(this,arguments.length>0?arguments[0]:void 0)}},{add:function(e){return r.def(i(this,a),e=0===e?0:e,e)}},r)},function(e,t,n){"use strict";var r,i=n(176)(0),a=n(21),o=n(25),s=n(71),l=n(225),u=n(16),c=n(10),f=n(221),d="WeakMap",p=o.getWeak,h=Object.isExtensible,m=l.ufstore,v={},g=function(e){return function(){return e(this,arguments.length>0?arguments[0]:void 0)}},y={get:function(e){if(u(e)){var t=p(e);return t===!0?m(f(this,d)).get(e):t?t[this._i]:void 0}},set:function(e,t){
+return l.def(f(this,d),e,t)}},_=e.exports=n(222)(d,g,y,l,!0,!0);c(function(){return 7!=(new _).set((Object.freeze||Object)(v),7).get(v)})&&(r=l.getConstructor(g,d),s(r.prototype,y),o.NEED=!0,i(["delete","has","get","set"],function(e){var t=_.prototype,n=t[e];a(t,e,function(t,i){if(u(t)&&!h(t)){this._f||(this._f=new r);var a=this._f[e](t,i);return"set"==e?this:a}return n.call(this,t,i)})}))},function(e,t,n){"use strict";var r=n(218),i=n(25).getWeak,a=n(15),o=n(16),s=n(209),l=n(210),u=n(176),c=n(8),f=n(221),d=u(5),p=u(6),h=0,m=function(e){return e._l||(e._l=new v)},v=function(){this.a=[]},g=function(e,t){return d(e.a,function(e){return e[0]===t})};v.prototype={get:function(e){var t=g(this,e);if(t)return t[1]},has:function(e){return!!g(this,e)},set:function(e,t){var n=g(this,e);n?n[1]=t:this.a.push([e,t])},delete:function(e){var t=p(this.a,function(t){return t[0]===e});return~t&&this.a.splice(t,1),!!~t}},e.exports={getConstructor:function(e,t,n,a){var u=e(function(e,r){s(e,u,t,"_i"),e._t=t,e._i=h++,e._l=void 0,void 0!=r&&l(r,n,e[a],e)});return r(u.prototype,{delete:function(e){if(!o(e))return!1;var n=i(e);return n===!0?m(f(this,t)).delete(e):n&&c(n,this._i)&&delete n[this._i]},has:function(e){if(!o(e))return!1;var n=i(e);return n===!0?m(f(this,t)).has(e):n&&c(n,this._i)}}),u},def:function(e,t,n){var r=i(a(t),!0);return r===!0?m(e).set(t,n):r[e._i]=n,e},ufstore:m}},function(e,t,n){"use strict";var r=n(225),i=n(221),a="WeakSet";n(222)(a,function(e){return function(){return e(this,arguments.length>0?arguments[0]:void 0)}},{add:function(e){return r.def(i(this,a),e,!0)}},r,!1,!0)},function(e,t,n){"use strict";var r=n(11),i=n(228),a=n(229),o=n(15),s=n(42),l=n(40),u=n(16),c=n(7).ArrayBuffer,f=n(211),d=a.ArrayBuffer,p=a.DataView,h=i.ABV&&c.isView,m=d.prototype.slice,v=i.VIEW,g="ArrayBuffer";r(r.G+r.W+r.F*(c!==d),{ArrayBuffer:d}),r(r.S+r.F*!i.CONSTR,g,{isView:function(e){return h&&h(e)||u(e)&&v in e}}),r(r.P+r.U+r.F*n(10)(function(){return!new d(2).slice(1,void 0).byteLength}),g,{slice:function(e,t){if(void 0!==m&&void 0===t)return m.call(o(this),e);for(var n=o(this).byteLength,r=s(e,n),i=s(void 0===t?n:t,n),a=new(f(this,d))(l(i-r)),u=new p(this),c=new p(a),h=0;r>1,c=23===t?U(2,-24)-U(2,-77):0,f=0,d=e<0||0===e&&1/e<0?1:0;for(e=q(e),e!=e||e===F?(i=e!=e?1:0,r=l):(r=V(B(e)/G),e*(a=U(2,-r))<1&&(r--,a*=2),e+=r+u>=1?c/a:c*U(2,1-u),e*a>=2&&(r++,a/=2),r+u>=l?(i=0,r=l):r+u>=1?(i=(e*a-1)*U(2,t),r+=u):(i=e*U(2,u-1)*U(2,t),r=0));t>=8;o[f++]=255&i,i/=256,t-=8);for(r=r<0;o[f++]=255&r,r/=256,s-=8);return o[--f]|=128*d,o}function i(e,t,n){var r,i=8*n-t-1,a=(1<>1,s=i-7,l=n-1,u=e[l--],c=127&u;for(u>>=7;s>0;c=256*c+e[l],l--,s-=8);for(r=c&(1<<-s)-1,c>>=-s,s+=t;s>0;r=256*r+e[l],l--,s-=8);if(0===c)c=1-o;else{if(c===a)return r?NaN:u?-F:F;r+=U(2,t),c-=o}return(u?-1:1)*r*U(2,c-t)}function a(e){return e[3]<<24|e[2]<<16|e[1]<<8|e[0]}function o(e){return[255&e]}function s(e){return[255&e,e>>8&255]}function l(e){return[255&e,e>>8&255,e>>16&255,e>>24&255]}function u(e){return r(e,52,8)}function c(e){return r(e,23,4)}function f(e,t,n){E(e[T],t,{get:function(){return this[n]}})}function d(e,t,n,r){var i=+n,a=S(i);if(a+t>e[Y])throw N(j);var o=e[K]._b,s=a+e[J],l=o.slice(s,s+t);return r?l:l.reverse()}function p(e,t,n,r,i,a){var o=+n,s=S(o);if(s+t>e[Y])throw N(j);for(var l=e[K]._b,u=s+e[J],c=r(+i),f=0;fee;)(X=Z[ee++])in D||y(D,X,R[X]);v||(Q.constructor=D)}var te=new z(new D(2)),ne=z[T].setInt8;te.setInt8(0,2147483648),te.setInt8(1,2147483649),!te.getInt8(0)&&te.getInt8(1)||_(z[T],{setInt8:function(e,t){ne.call(this,e,t<<24>>24)},setUint8:function(e,t){ne.call(this,e,t<<24>>24)}},!0)}else D=function(e){w(this,D,M);var t=S(e);this._b=A.call(new Array(t),0),this[Y]=t},z=function(e,t,n){w(this,z,O),w(e,D,O);var r=e[Y],i=x(t);if(i<0||i>r)throw N("Wrong offset!");if(n=void 0===n?r-i:k(n),i+n>r)throw N(P);this[K]=e,this[J]=i,this[Y]=n},m&&(f(D,$,"_l"),f(z,H,"_b"),f(z,$,"_l"),f(z,W,"_o")),_(z[T],{getInt8:function(e){return d(this,1,e)[0]<<24>>24},getUint8:function(e){return d(this,1,e)[0]},getInt16:function(e){var t=d(this,2,e,arguments[1]);return(t[1]<<8|t[0])<<16>>16},getUint16:function(e){var t=d(this,2,e,arguments[1]);return t[1]<<8|t[0]},getInt32:function(e){return a(d(this,4,e,arguments[1]))},getUint32:function(e){return a(d(this,4,e,arguments[1]))>>>0},getFloat32:function(e){return i(d(this,4,e,arguments[1]),23,4)},getFloat64:function(e){return i(d(this,8,e,arguments[1]),52,8)},setInt8:function(e,t){p(this,1,e,o,t)},setUint8:function(e,t){p(this,1,e,o,t)},setInt16:function(e,t){p(this,2,e,s,t,arguments[2])},setUint16:function(e,t){p(this,2,e,s,t,arguments[2])},setInt32:function(e,t){p(this,4,e,l,t,arguments[2])},setUint32:function(e,t){p(this,4,e,l,t,arguments[2])},setFloat32:function(e,t){p(this,4,e,c,t,arguments[2])},setFloat64:function(e,t){p(this,8,e,u,t,arguments[2])}});I(D,M),I(z,O),y(z[T],g.VIEW,!0),t[M]=D,t[O]=z},function(e,t,n){var r=n(41),i=n(40);e.exports=function(e){if(void 0===e)return 0;var t=r(e),n=i(t);if(t!==n)throw RangeError("Wrong length!");return n}},function(e,t,n){var r=n(11);r(r.G+r.W+r.F*!n(228).ABV,{DataView:n(229).DataView})},function(e,t,n){n(233)("Int8",1,function(e){return function(t,n,r){return e(this,t,n,r)}})},function(e,t,n){"use strict";if(n(9)){var r=n(27),i=n(7),a=n(10),o=n(11),s=n(228),l=n(229),u=n(23),c=n(209),f=n(20),d=n(13),p=n(218),h=n(41),m=n(40),v=n(230),g=n(42),y=n(19),_=n(8),b=n(77),w=n(16),x=n(60),k=n(166),S=n(48),C=n(61),E=n(52).f,A=n(168),I=n(22),M=n(29),O=n(176),T=n(39),P=n(211),j=n(197),D=n(132),z=n(169),L=n(196),N=n(192),F=n(189),R=n(14),q=n(53),U=R.f,V=q.f,B=i.RangeError,G=i.TypeError,H=i.Uint8Array,$="ArrayBuffer",W="Shared"+$,K="BYTES_PER_ELEMENT",Y="prototype",J=Array[Y],X=l.ArrayBuffer,Q=l.DataView,Z=O(0),ee=O(2),te=O(3),ne=O(4),re=O(5),ie=O(6),ae=T(!0),oe=T(!1),se=j.values,le=j.keys,ue=j.entries,ce=J.lastIndexOf,fe=J.reduce,de=J.reduceRight,pe=J.join,he=J.sort,me=J.slice,ve=J.toString,ge=J.toLocaleString,ye=M("iterator"),_e=M("toStringTag"),be=I("typed_constructor"),we=I("def_constructor"),xe=s.CONSTR,ke=s.TYPED,Se=s.VIEW,Ce="Wrong length!",Ee=O(1,function(e,t){return Te(P(e,e[we]),t)}),Ae=a(function(){return 1===new H(new Uint16Array([1]).buffer)[0]}),Ie=!!H&&!!H[Y].set&&a(function(){new H(1).set({})}),Me=function(e,t){var n=h(e);if(n<0||n%t)throw B("Wrong offset!");return n},Oe=function(e){if(w(e)&&ke in e)return e;throw G(e+" is not a typed array!")},Te=function(e,t){if(!(w(e)&&be in e))throw G("It is not a typed array constructor!");return new e(t)},Pe=function(e,t){return je(P(e,e[we]),t)},je=function(e,t){for(var n=0,r=t.length,i=Te(e,r);r>n;)i[n]=t[n++];return i},De=function(e,t,n){U(e,t,{get:function(){return this._d[n]}})},ze=function(e){var t,n,r,i,a,o,s=x(e),l=arguments.length,c=l>1?arguments[1]:void 0,f=void 0!==c,d=A(s);if(void 0!=d&&!k(d)){for(o=d.call(s),r=[],t=0;!(a=o.next()).done;t++)r.push(a.value);s=r}for(f&&l>2&&(c=u(c,arguments[2],2)),t=0,n=m(s.length),i=Te(this,n);n>t;t++)i[t]=f?c(s[t],t):s[t];return i},Le=function(){for(var e=0,t=arguments.length,n=Te(this,t);t>e;)n[e]=arguments[e++];return n},Ne=!!H&&a(function(){ge.call(new H(1))}),Fe=function(){return ge.apply(Ne?me.call(Oe(this)):Oe(this),arguments)},Re={copyWithin:function(e,t){return F.call(Oe(this),e,t,arguments.length>2?arguments[2]:void 0)},every:function(e){return ne(Oe(this),e,arguments.length>1?arguments[1]:void 0)},fill:function(e){return N.apply(Oe(this),arguments)},filter:function(e){return Pe(this,ee(Oe(this),e,arguments.length>1?arguments[1]:void 0))},find:function(e){return re(Oe(this),e,arguments.length>1?arguments[1]:void 0)},findIndex:function(e){return ie(Oe(this),e,arguments.length>1?arguments[1]:void 0)},forEach:function(e){Z(Oe(this),e,arguments.length>1?arguments[1]:void 0)},indexOf:function(e){return oe(Oe(this),e,arguments.length>1?arguments[1]:void 0)},includes:function(e){return ae(Oe(this),e,arguments.length>1?arguments[1]:void 0)},join:function(e){return pe.apply(Oe(this),arguments)},lastIndexOf:function(e){return ce.apply(Oe(this),arguments)},map:function(e){return Ee(Oe(this),e,arguments.length>1?arguments[1]:void 0)},reduce:function(e){return fe.apply(Oe(this),arguments)},reduceRight:function(e){return de.apply(Oe(this),arguments)},reverse:function(){for(var e,t=this,n=Oe(t).length,r=Math.floor(n/2),i=0;i1?arguments[1]:void 0)},sort:function(e){return he.call(Oe(this),e)},subarray:function(e,t){var n=Oe(this),r=n.length,i=g(e,r);return new(P(n,n[we]))(n.buffer,n.byteOffset+i*n.BYTES_PER_ELEMENT,m((void 0===t?r:g(t,r))-i))}},qe=function(e,t){return Pe(this,me.call(Oe(this),e,t))},Ue=function(e){Oe(this);var t=Me(arguments[1],1),n=this.length,r=x(e),i=m(r.length),a=0;if(i+t>n)throw B(Ce);for(;a255?255:255&r),i.v[p](n*t+i.o,r,Ae)},M=function(e,t){U(e,t,{get:function(){return A(this,t)},set:function(e){return I(this,t,e)},enumerable:!0})};_?(h=n(function(e,n,r,i){c(e,h,u,"_d");var a,o,s,l,f=0,p=0;if(w(n)){if(!(n instanceof X||(l=b(n))==$||l==W))return ke in n?je(h,n):ze.call(h,n);a=n,p=Me(r,t);var g=n.byteLength;if(void 0===i){if(g%t)throw B(Ce);if(o=g-p,o<0)throw B(Ce)}else if(o=m(i)*t,o+p>g)throw B(Ce);s=o/t}else s=v(n),o=s*t,a=new X(o);for(d(e,"_d",{b:a,o:p,l:o,e:s,v:new Q(a)});f=n.length)return{value:void 0,done:!0};while(!((e=n[t._i++])in t._t));return{value:e,done:!1}}),r(r.S,"Reflect",{enumerate:function(e){return new a(e)}})},function(e,t,n){function r(e,t){var n,s,c=arguments.length<3?e:arguments[2];return u(e)===c?e[t]:(n=i.f(e,t))?o(n,"value")?n.value:void 0!==n.get?n.get.call(c):void 0:l(s=a(e))?r(s,t,c):void 0}var i=n(53),a=n(61),o=n(8),s=n(11),l=n(16),u=n(15);s(s.S,"Reflect",{get:r})},function(e,t,n){var r=n(53),i=n(11),a=n(15);i(i.S,"Reflect",{getOwnPropertyDescriptor:function(e,t){return r.f(a(e),t)}})},function(e,t,n){var r=n(11),i=n(61),a=n(15);r(r.S,"Reflect",{getPrototypeOf:function(e){return i(a(e))}})},function(e,t,n){var r=n(11);r(r.S,"Reflect",{has:function(e,t){return t in e}})},function(e,t,n){var r=n(11),i=n(15),a=Object.isExtensible;r(r.S,"Reflect",{isExtensible:function(e){return i(e),!a||a(e)}})},function(e,t,n){var r=n(11);r(r.S,"Reflect",{ownKeys:n(253)})},function(e,t,n){var r=n(52),i=n(45),a=n(15),o=n(7).Reflect;e.exports=o&&o.ownKeys||function(e){var t=r.f(a(e)),n=i.f;return n?t.concat(n(e)):t}},function(e,t,n){var r=n(11),i=n(15),a=Object.preventExtensions;r(r.S,"Reflect",{preventExtensions:function(e){i(e);try{return a&&a(e),!0}catch(e){return!1}}})},function(e,t,n){function r(e,t,n){var l,d,p=arguments.length<4?e:arguments[3],h=a.f(c(e),t);if(!h){if(f(d=o(e)))return r(d,t,n,p);h=u(0)}if(s(h,"value")){if(h.writable===!1||!f(p))return!1;if(l=a.f(p,t)){if(l.get||l.set||l.writable===!1)return!1;l.value=n,i.f(p,t,l)}else i.f(p,t,u(0,n));return!0}return void 0!==h.set&&(h.set.call(p,n),!0)}var i=n(14),a=n(53),o=n(61),s=n(8),l=n(11),u=n(20),c=n(15),f=n(16);l(l.S,"Reflect",{set:r})},function(e,t,n){var r=n(11),i=n(75);i&&r(r.S,"Reflect",{setPrototypeOf:function(e,t){i.check(e,t);try{return i.set(e,t),!0}catch(e){return!1}}})},function(e,t,n){"use strict";var r=n(11),i=n(39)(!0);r(r.P,"Array",{includes:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0)}}),n(190)("includes")},function(e,t,n){"use strict";var r=n(11),i=n(259),a=n(60),o=n(40),s=n(24),l=n(177);r(r.P,"Array",{flatMap:function(e){var t,n,r=a(this);return s(e),t=o(r.length),n=l(r,0),i(n,r,r,t,0,1,e,arguments[1]),n}}),n(190)("flatMap")},function(e,t,n){"use strict";function r(e,t,n,u,c,f,d,p){for(var h,m,v=c,g=0,y=!!d&&s(d,p,3);g0)v=r(e,t,h,o(h.length),v,f-1)-1;else{if(v>=9007199254740991)throw TypeError();e[v]=h}v++}g++}return v}var i=n(47),a=n(16),o=n(40),s=n(23),l=n(29)("isConcatSpreadable");e.exports=r},function(e,t,n){"use strict";var r=n(11),i=n(259),a=n(60),o=n(40),s=n(41),l=n(177);r(r.P,"Array",{flatten:function(){var e=arguments[0],t=a(this),n=o(t.length),r=l(t,0);return i(r,t,t,n,0,void 0===e?1:s(e)),r}}),n(190)("flatten")},function(e,t,n){"use strict";var r=n(11),i=n(130)(!0);r(r.P,"String",{at:function(e){return i(this,e)}})},function(e,t,n){"use strict";var r=n(11),i=n(263),a=n(216);r(r.P+r.F*/Version\/10\.\d+(\.\d+)? Safari\//.test(a),"String",{padStart:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0,!0)}})},function(e,t,n){var r=n(40),i=n(93),a=n(38);e.exports=function(e,t,n,o){var s=String(a(e)),l=s.length,u=void 0===n?" ":String(n),c=r(t);if(c<=l||""==u)return s;var f=c-l,d=i.call(u,Math.ceil(f/u.length));return d.length>f&&(d=d.slice(0,f)),o?d+s:s+d}},function(e,t,n){"use strict";var r=n(11),i=n(263),a=n(216);r(r.P+r.F*/Version\/10\.\d+(\.\d+)? Safari\//.test(a),"String",{padEnd:function(e){return i(this,e,arguments.length>1?arguments[1]:void 0,!1)}})},function(e,t,n){"use strict";n(85)("trimLeft",function(e){return function(){return e(this,1)}},"trimStart")},function(e,t,n){"use strict";n(85)("trimRight",function(e){return function(){return e(this,2)}},"trimEnd")},function(e,t,n){"use strict";var r=n(11),i=n(38),a=n(40),o=n(137),s=n(200),l=RegExp.prototype,u=function(e,t){this._r=e,this._s=t};n(133)(u,"RegExp String",function(){var e=this._r.exec(this._s);return{value:e,done:null===e}}),r(r.P,"String",{matchAll:function(e){if(i(this),!o(e))throw TypeError(e+" is not a regexp!");var t=String(this),n="flags"in l?String(e.flags):s.call(e),r=new RegExp(e.source,~n.indexOf("g")?n:"g"+n);return r.lastIndex=a(e.lastIndex),new u(r,t)}})},function(e,t,n){n(31)("asyncIterator")},function(e,t,n){n(31)("observable")},function(e,t,n){var r=n(11),i=n(253),a=n(35),o=n(53),s=n(167);r(r.S,"Object",{getOwnPropertyDescriptors:function(e){for(var t,n,r=a(e),l=o.f,u=i(r),c={},f=0;u.length>f;)n=l(r,t=u[f++]),void 0!==n&&s(c,t,n);return c}})},function(e,t,n){var r=n(11),i=n(272)(!1);r(r.S,"Object",{values:function(e){return i(e)}})},function(e,t,n){var r=n(33),i=n(35),a=n(46).f;e.exports=function(e){return function(t){for(var n,o=i(t),s=r(o),l=s.length,u=0,c=[];l>u;)a.call(o,n=s[u++])&&c.push(e?[n,o[n]]:o[n]);return c}}},function(e,t,n){var r=n(11),i=n(272)(!0);r(r.S,"Object",{entries:function(e){return i(e)}})},function(e,t,n){"use strict";var r=n(11),i=n(60),a=n(24),o=n(14);n(9)&&r(r.P+n(275),"Object",{__defineGetter__:function(e,t){o.f(i(this),e,{get:a(t),enumerable:!0,configurable:!0})}})},function(e,t,n){"use strict";e.exports=n(27)||!n(10)(function(){var e=Math.random();__defineSetter__.call(null,e,function(){}),delete n(7)[e]})},function(e,t,n){"use strict";var r=n(11),i=n(60),a=n(24),o=n(14);n(9)&&r(r.P+n(275),"Object",{__defineSetter__:function(e,t){o.f(i(this),e,{set:a(t),enumerable:!0,configurable:!0})}})},function(e,t,n){"use strict";var r=n(11),i=n(60),a=n(19),o=n(61),s=n(53).f;n(9)&&r(r.P+n(275),"Object",{__lookupGetter__:function(e){var t,n=i(this),r=a(e,!0);do if(t=s(n,r))return t.get;while(n=o(n))}})},function(e,t,n){"use strict";var r=n(11),i=n(60),a=n(19),o=n(61),s=n(53).f;n(9)&&r(r.P+n(275),"Object",{__lookupSetter__:function(e){var t,n=i(this),r=a(e,!0);do if(t=s(n,r))return t.set;while(n=o(n))}})},function(e,t,n){var r=n(11);r(r.P+r.R,"Map",{toJSON:n(280)("Map")})},function(e,t,n){var r=n(77),i=n(281);e.exports=function(e){return function(){if(r(this)!=e)throw TypeError(e+"#toJSON isn't generic");return i(this)}}},function(e,t,n){var r=n(210);e.exports=function(e,t){var n=[];return r(e,!1,n.push,n,t),n}},function(e,t,n){var r=n(11);r(r.P+r.R,"Set",{toJSON:n(280)("Set")})},function(e,t,n){n(284)("Map")},function(e,t,n){"use strict";var r=n(11);e.exports=function(e){r(r.S,e,{of:function(){for(var e=arguments.length,t=new Array(e);e--;)t[e]=arguments[e];return new this(t)}})}},function(e,t,n){n(284)("Set")},function(e,t,n){n(284)("WeakMap")},function(e,t,n){n(284)("WeakSet")},function(e,t,n){n(289)("Map")},function(e,t,n){"use strict";var r=n(11),i=n(24),a=n(23),o=n(210);e.exports=function(e){r(r.S,e,{from:function(e){var t,n,r,s,l=arguments[1];return i(this),t=void 0!==l,t&&i(l),void 0==e?new this:(n=[],t?(r=0,s=a(l,arguments[2],2),o(e,!1,function(e){n.push(s(e,r++))})):o(e,!1,n.push,n),new this(n))}})}},function(e,t,n){n(289)("Set")},function(e,t,n){n(289)("WeakMap")},function(e,t,n){n(289)("WeakSet")},function(e,t,n){var r=n(11);r(r.G,{global:n(7)})},function(e,t,n){var r=n(11);r(r.S,"System",{global:n(7)})},function(e,t,n){var r=n(11),i=n(37);r(r.S,"Error",{isError:function(e){return"Error"===i(e)}})},function(e,t,n){var r=n(11);r(r.S,"Math",{clamp:function(e,t,n){return Math.min(n,Math.max(t,e))}})},function(e,t,n){var r=n(11);r(r.S,"Math",{DEG_PER_RAD:Math.PI/180})},function(e,t,n){var r=n(11),i=180/Math.PI;r(r.S,"Math",{degrees:function(e){return e*i}})},function(e,t,n){var r=n(11),i=n(300),a=n(116);r(r.S,"Math",{fscale:function(e,t,n,r,o){return a(i(e,t,n,r,o))}})},function(e,t){e.exports=Math.scale||function(e,t,n,r,i){return 0===arguments.length||e!=e||t!=t||n!=n||r!=r||i!=i?NaN:e===1/0||e===-(1/0)?e:(e-t)*(i-r)/(n-t)+r}},function(e,t,n){var r=n(11);r(r.S,"Math",{iaddh:function(e,t,n,r){var i=e>>>0,a=t>>>0,o=n>>>0;return a+(r>>>0)+((i&o|(i|o)&~(i+o>>>0))>>>31)|0}})},function(e,t,n){var r=n(11);r(r.S,"Math",{isubh:function(e,t,n,r){var i=e>>>0,a=t>>>0,o=n>>>0;return a-(r>>>0)-((~i&o|~(i^o)&i-o>>>0)>>>31)|0}})},function(e,t,n){var r=n(11);r(r.S,"Math",{imulh:function(e,t){var n=65535,r=+e,i=+t,a=r&n,o=i&n,s=r>>16,l=i>>16,u=(s*o>>>0)+(a*o>>>16);return s*l+(u>>16)+((a*l>>>0)+(u&n)>>16)}})},function(e,t,n){var r=n(11);r(r.S,"Math",{RAD_PER_DEG:180/Math.PI})},function(e,t,n){var r=n(11),i=Math.PI/180;r(r.S,"Math",{radians:function(e){return e*i}})},function(e,t,n){var r=n(11);r(r.S,"Math",{scale:n(300)})},function(e,t,n){var r=n(11);r(r.S,"Math",{umulh:function(e,t){var n=65535,r=+e,i=+t,a=r&n,o=i&n,s=r>>>16,l=i>>>16,u=(s*o>>>0)+(a*o>>>16);return s*l+(u>>>16)+((a*l>>>0)+(u&n)>>>16)}})},function(e,t,n){var r=n(11);r(r.S,"Math",{signbit:function(e){return(e=+e)!=e?e:0==e?1/e==1/0:e>0}})},function(e,t,n){"use strict";var r=n(11),i=n(12),a=n(7),o=n(211),s=n(217);r(r.P+r.R,"Promise",{finally:function(e){var t=o(this,i.Promise||a.Promise),n="function"==typeof e;return this.then(n?function(n){return s(t,e()).then(function(){return n})}:e,n?function(n){return s(t,e()).then(function(){throw n})}:e)}})},function(e,t,n){"use strict";var r=n(11),i=n(214),a=n(215);r(r.S,"Promise",{try:function(e){var t=i.f(this),n=a(e);return(n.e?t.reject:t.resolve)(n.v),t.promise}})},function(e,t,n){var r=n(312),i=n(15),a=r.key,o=r.set;r.exp({defineMetadata:function(e,t,n,r){o(e,t,i(n),a(r))}})},function(e,t,n){var r=n(219),i=n(11),a=n(26)("metadata"),o=a.store||(a.store=new(n(224))),s=function(e,t,n){var i=o.get(e);if(!i){if(!n)return;o.set(e,i=new r)}var a=i.get(t);if(!a){if(!n)return;i.set(t,a=new r)}return a},l=function(e,t,n){var r=s(t,n,!1);return void 0!==r&&r.has(e)},u=function(e,t,n){var r=s(t,n,!1);return void 0===r?void 0:r.get(e)},c=function(e,t,n,r){s(n,r,!0).set(e,t)},f=function(e,t){var n=s(e,t,!1),r=[];return n&&n.forEach(function(e,t){r.push(t)}),r},d=function(e){return void 0===e||"symbol"==typeof e?e:String(e)},p=function(e){i(i.S,"Reflect",e)};e.exports={store:o,map:s,has:l,get:u,set:c,keys:f,key:d,exp:p}},function(e,t,n){var r=n(312),i=n(15),a=r.key,o=r.map,s=r.store;r.exp({deleteMetadata:function(e,t){var n=arguments.length<3?void 0:a(arguments[2]),r=o(i(t),n,!1);if(void 0===r||!r.delete(e))return!1;if(r.size)return!0;var l=s.get(t);return l.delete(n),!!l.size||s.delete(t)}})},function(e,t,n){var r=n(312),i=n(15),a=n(61),o=r.has,s=r.get,l=r.key,u=function(e,t,n){var r=o(e,t,n);if(r)return s(e,t,n);var i=a(t);return null!==i?u(e,i,n):void 0};r.exp({getMetadata:function(e,t){return u(e,i(t),arguments.length<3?void 0:l(arguments[2]))}})},function(e,t,n){var r=n(223),i=n(281),a=n(312),o=n(15),s=n(61),l=a.keys,u=a.key,c=function(e,t){var n=l(e,t),a=s(e);if(null===a)return n;var o=c(a,t);return o.length?n.length?i(new r(n.concat(o))):o:n};a.exp({getMetadataKeys:function(e){return c(o(e),arguments.length<2?void 0:u(arguments[1]))}})},function(e,t,n){var r=n(312),i=n(15),a=r.get,o=r.key;r.exp({getOwnMetadata:function(e,t){return a(e,i(t),arguments.length<3?void 0:o(arguments[2]))}})},function(e,t,n){var r=n(312),i=n(15),a=r.keys,o=r.key;r.exp({getOwnMetadataKeys:function(e){return a(i(e),arguments.length<2?void 0:o(arguments[1]))}})},function(e,t,n){var r=n(312),i=n(15),a=n(61),o=r.has,s=r.key,l=function(e,t,n){var r=o(e,t,n);if(r)return!0;var i=a(t);return null!==i&&l(e,i,n)};r.exp({hasMetadata:function(e,t){return l(e,i(t),arguments.length<3?void 0:s(arguments[2]))}})},function(e,t,n){var r=n(312),i=n(15),a=r.has,o=r.key;r.exp({hasOwnMetadata:function(e,t){return a(e,i(t),arguments.length<3?void 0:o(arguments[2]))}})},function(e,t,n){var r=n(312),i=n(15),a=n(24),o=r.key,s=r.set;r.exp({metadata:function(e,t){return function(n,r){s(e,t,(void 0!==r?i:a)(n),o(r))}}})},function(e,t,n){var r=n(11),i=n(213)(),a=n(7).process,o="process"==n(37)(a);r(r.G,{asap:function(e){var t=o&&a.domain;i(t?t.bind(e):e)}})},function(e,t,n){"use strict";var r=n(11),i=n(7),a=n(12),o=n(213)(),s=n(29)("observable"),l=n(24),u=n(15),c=n(209),f=n(218),d=n(13),p=n(210),h=p.RETURN,m=function(e){return null==e?void 0:l(e)},v=function(e){var t=e._c;t&&(e._c=void 0,t())},g=function(e){return void 0===e._o},y=function(e){g(e)||(e._o=void 0,v(e))},_=function(e,t){u(e),this._c=void 0,this._o=e,e=new b(this);try{var n=t(e),r=n;null!=n&&("function"==typeof n.unsubscribe?n=function(){r.unsubscribe()}:l(n),this._c=n)}catch(t){return void e.error(t)}g(this)&&v(this)};_.prototype=f({},{unsubscribe:function(){y(this)}});var b=function(e){this._s=e};b.prototype=f({},{next:function(e){var t=this._s;if(!g(t)){var n=t._o;try{var r=m(n.next);if(r)return r.call(n,e)}catch(e){try{y(t)}finally{throw e}}}},error:function(e){var t=this._s;if(g(t))throw e;var n=t._o;t._o=void 0;try{var r=m(n.error);if(!r)throw e;e=r.call(n,e)}catch(e){try{v(t)}finally{throw e}}return v(t),e},complete:function(e){var t=this._s;if(!g(t)){var n=t._o;t._o=void 0;try{var r=m(n.complete);e=r?r.call(n,e):void 0}catch(e){try{v(t)}finally{throw e}}return v(t),e}}});var w=function(e){c(this,w,"Observable","_f")._f=l(e)};f(w.prototype,{subscribe:function(e){return new _(e,this._f)},forEach:function(e){var t=this;return new(a.Promise||i.Promise)(function(n,r){l(e);var i=t.subscribe({next:function(t){try{return e(t)}catch(e){r(e),i.unsubscribe()}},error:r,complete:n})})}}),f(w,{from:function(e){var t="function"==typeof this?this:w,n=m(u(e)[s]);if(n){var r=u(n.call(e));return r.constructor===t?r:new t(function(e){return r.subscribe(e)})}return new t(function(t){var n=!1;return o(function(){if(!n){try{if(p(e,!1,function(e){if(t.next(e),n)return h})===h)return}catch(e){if(n)throw e;return void t.error(e)}t.complete()}}),function(){n=!0}})},of:function(){for(var e=0,t=arguments.length,n=new Array(t);e2,i=!!r&&o.call(arguments,2);return e(r?function(){("function"==typeof t?t:Function(t)).apply(this,i)}:t,n)}};i(i.G+i.B+i.F*s,{setTimeout:l(r.setTimeout),setInterval:l(r.setInterval)})},function(e,t,n){var r=n(11),i=n(212);r(r.G+r.B,{setImmediate:i.set,clearImmediate:i.clear})},function(e,t,n){for(var r=n(197),i=n(33),a=n(21),o=n(7),s=n(13),l=n(132),u=n(29),c=u("iterator"),f=u("toStringTag"),d=l.Array,p={CSSRuleList:!0,CSSStyleDeclaration:!1,CSSValueList:!1,ClientRectList:!1,DOMRectList:!1,DOMStringList:!1,DOMTokenList:!0,DataTransferItemList:!1,FileList:!1,HTMLAllCollection:!1,HTMLCollection:!1,HTMLFormElement:!1,HTMLSelectElement:!1,MediaList:!0,MimeTypeArray:!1,NamedNodeMap:!1,NodeList:!0,PaintRequestList:!1,Plugin:!1,PluginArray:!1,SVGLengthList:!1,SVGNumberList:!1,SVGPathSegList:!1,SVGPointList:!1,SVGStringList:!1,SVGTransformList:!1,SourceBufferList:!1,StyleSheetList:!0,TextTrackCueList:!1,TextTrackList:!1,TouchList:!1},h=i(p),m=0;m=0;--r){var i=this.tryEntries[r],a=i.completion;if("root"===i.tryLoc)return t("end");if(i.tryLoc<=this.prev){var o=_.call(i,"catchLoc"),s=_.call(i,"finallyLoc");if(o&&s){if(this.prev=0;--n){var r=this.tryEntries[n];if(r.tryLoc<=this.prev&&_.call(r,"finallyLoc")&&this.prev=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),p(n),O}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var r=n.completion;if("throw"===r.type){var i=r.arg;p(n)}return i}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,n){return this.delegate={iterator:m(e),resultName:t,nextLoc:n},"next"===this.method&&(this.arg=g),O}}}("object"==typeof t?t:"object"==typeof window?window:"object"==typeof self?self:this)}).call(t,function(){return this}(),n(4))},function(e,t,n){n(328),e.exports=n(12).RegExp.escape},function(e,t,n){var r=n(11),i=n(329)(/[\\^$*+?.()|[\]{}]/g,"\\$&");r(r.S,"RegExp",{escape:function(e){return i(e)}})},function(e,t){e.exports=function(e,t){var n=t===Object(t)?function(e){return t[e]}:t;return function(t){return String(t).replace(e,n)}}},function(e,t,n){(function(e){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e){return e}function a(e){return e.text().then(function(e){var t=e;try{t=JSON.parse(e)}catch(t){var n=document.createElement("div");n.innerHTML=e;var r=new Error;throw r.stack=(0,v.default)(n.innerText),r}return t})}function o(e){if(g)return!0;var t=e.status||(e.error?"error":""),n=e.message||(e.error?e.error.message:null),r=e.toastr||null,i=void 0;switch(t){case"unauthenticated":throw document.location.href=h.config.base_url_relative,y("Logged out");case"unauthorized":t="error",n=n||"Unauthorized.";break;case"error":t="error",n=n||"Unknown error.";break;case"success":t="success",n=n||"";break;default:t="error",n=n||"Invalid AJAX response."}return r&&(i=Object.assign({},f.default.options),Object.keys(r).forEach(function(e){f.default.options[e]=r[e]})),n&&(p.default||!p.default&&"error"!==t)&&f.default["success"===t?"success":"error"](n),r&&(f.default.options=i),e}function s(e){if(g)return!0;var t=e.stack?""+e.stack+" ":"";f.default.error("Fetch Failed: "+e.message+" "+t),console.error(e.message+" at "+e.stack)}Object.defineProperty(t,"__esModule",{value:!0}),t.parseStatus=i,t.parseJSON=a,t.userFeedback=o,t.userFeedbackError=s;var l=n(1),u=r(l),c=n(331),f=r(c),d=n(334),p=r(d),h=n(335),m=n(336),v=r(m),g=!1,y=function e(t){var e=new Error(t.statusText||t||"");return e.response=t,e};(0,u.default)(e).on("beforeunload._ajax",function(){g=!0})}).call(t,function(){return this}())},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(332),a=r(i);a.default.options.positionClass="toast-top-right",a.default.options.preventDuplicates=!0,t.default=a.default},,,function(e,t,n){(function(e){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(1),a=r(i),o=n(334),s=r(o),l=(0,a.default)("#offline-status");(0,a.default)(window).on("offline",function(){l.slideDown()}),(0,a.default)(window).on("online",function(){l.slideUp()}),(0,a.default)(document).ready(function(){s.default||l.slideDown()}),t.default="undefined"==typeof e.navigator.onLine||e.navigator.onLine}).call(t,function(){return this}())},function(e,t){e.exports=GravAdmin},function(e,t,n){function r(e,t){return e=i(e),t=t||a,o(s(e,t),t)}var i=n(337),a=n(338),o=n(339),s=n(340);e.exports=r},function(e,t){function n(e){return null==e?"":e.toString()}e.exports=n},function(e,t){e.exports=[" ","\n","\r","\t","\f","\v"," "," ",""," "," "," "," "," "," "," "," "," "," "," ","\u2028","\u2029"," "," "," "]},function(e,t,n){function r(e,t){e=i(e),t=t||a;for(var n,r,o=0,s=e.length,l=t.length,u=!0;u&&o=s?"":e.substr(o,s)}var i=n(337),a=n(338);e.exports=r},function(e,t,n){function r(e,t){e=i(e),t=t||a;for(var n,r,o=e.length-1,s=t.length,l=!0;l&&o>=0;)for(l=!1,n=-1,r=e.charAt(o);++n=0?e.substring(0,o+1):""}var i=n(337),a=n(338);e.exports=r},function(e,t){function n(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function r(e){return"function"==typeof e}function i(e){return"number"==typeof e}function a(e){return"object"==typeof e&&null!==e}function o(e){return void 0===e}e.exports=n,n.EventEmitter=n,n.prototype._events=void 0,n.prototype._maxListeners=void 0,n.defaultMaxListeners=10,n.prototype.setMaxListeners=function(e){if(!i(e)||e<0||isNaN(e))throw TypeError("n must be a positive number");return this._maxListeners=e,this},n.prototype.emit=function(e){var t,n,i,s,l,u;if(this._events||(this._events={}),"error"===e&&(!this._events.error||a(this._events.error)&&!this._events.error.length)){if(t=arguments[1],t instanceof Error)throw t;var c=new Error('Uncaught, unspecified "error" event. ('+t+")");throw c.context=t,c}if(n=this._events[e],o(n))return!1;if(r(n))switch(arguments.length){case 1:n.call(this);break;case 2:n.call(this,arguments[1]);break;case 3:n.call(this,arguments[1],arguments[2]);break;default:s=Array.prototype.slice.call(arguments,1),n.apply(this,s)}else if(a(n))for(s=Array.prototype.slice.call(arguments,1),u=n.slice(),i=u.length,l=0;l0&&this._events[e].length>i&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace())),this},n.prototype.on=n.prototype.addListener,n.prototype.once=function(e,t){function n(){this.removeListener(e,n),i||(i=!0,t.apply(this,arguments))}if(!r(t))throw TypeError("listener must be a function");var i=!1;return n.listener=t,this.on(e,n),this},n.prototype.removeListener=function(e,t){var n,i,o,s;if(!r(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(n=this._events[e],o=n.length,i=-1,n===t||r(n.listener)&&n.listener===t)delete this._events[e],this._events.removeListener&&this.emit("removeListener",e,t);else if(a(n)){for(s=o;s-- >0;)if(n[s]===t||n[s].listener&&n[s].listener===t){i=s;break}if(i<0)return this;1===n.length?(n.length=0,delete this._events[e]):n.splice(i,1),this._events.removeListener&&this.emit("removeListener",e,t)}return this},n.prototype.removeAllListeners=function(e){var t,n;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)"removeListener"!==t&&this.removeAllListeners(t);return this.removeAllListeners("removeListener"),this._events={},this}if(n=this._events[e],r(n))this.removeListener(e,n);else if(n)for(;n.length;)this.removeListener(e,n[n.length-1]);return delete this._events[e],this},n.prototype.listeners=function(e){var t;return t=this._events&&this._events[e]?r(this._events[e])?[this._events[e]]:this._events[e].slice():[]},n.prototype.listenerCount=function(e){if(this._events){var t=this._events[e];if(r(t))return 1;if(t)return t.length}return 0},n.listenerCount=function(e,t){return e.listenerCount(t)}},function(e,t,n){(function(e){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var i=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};i(this,e),this.setPayload(t),this.task="task"+c.config.param_sep}return a(e,[{key:"setPayload",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return this.payload=e,this}},{key:"fetch",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]&&arguments[0];return p.Instance.fetch(function(t){return e.setPayload(t)},t),this}},{key:"maintenance",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"hide",t=(0,s.default)("#updates [data-update-packages]");return t["show"===e?"fadeIn":"fadeOut"](),"hide"===e&&(0,s.default)(".badges.with-updates").removeClass("with-updates").find(".badge.updates").remove(),this}},{key:"grav",value:function(){var e=this.payload.grav;if(e&&e.isUpdatable){var t=this.task,n="";n+=e.isSymlink?' ':''+c.translations.PLUGIN_ADMIN.UPDATE_GRAV_NOW+" ",n+="\n Grav v"+e.available+" "+c.translations.PLUGIN_ADMIN.IS_NOW_AVAILABLE+'! ('+c.translations.PLUGIN_ADMIN.CURRENT+" v"+e.version+") \n ";var r=(0,s.default)("[data-gpm-grav]").removeClass("hidden");r.is(":empty")&&r.hide(),r.addClass("grav").html(""+n).slideDown(150).parent("#messages").addClass("default-box-shadow")}return(0,s.default)("#grav-update-button").on("click",function(){(0,s.default)(this).html(c.translations.PLUGIN_ADMIN.UPDATING_PLEASE_WAIT+" "+(0,d.default)(e.assets["grav-update"].size)+"..")}),this}},{key:"resources",value:function(){if(!this.payload||!this.payload.resources||!this.payload.resources.total)return this.maintenance("hide");var e=!0,t=["plugins","themes"],n=["plugin","theme"],r=this.payload.resources,i=r.plugins,a=r.themes;return this.payload.resources.total?([i,a].forEach(function(r,i){if(r&&!Array.isArray(r)){var a=Object.keys(r).length,o=t[i];(0,s.default)('#admin-menu a[href$="/'+t[i]+'"]').find(".badges").addClass("with-updates").find(".badge.updates").text(a);var l="";l="plugins"===o?c.translations.PLUGIN_ADMIN.PLUGINS:c.translations.PLUGIN_ADMIN.THEMES;var f=(0,s.default)(".grav-update."+o);f.css("display","block").html('\n \n '+c.translations.PLUGIN_ADMIN.UPDATE+" "+c.translations.PLUGIN_ADMIN.ALL+" "+l+' \n \n '+a+" "+c.translations.PLUGIN_ADMIN.OF_YOUR+" "+l.toLowerCase()+" "+c.translations.PLUGIN_ADMIN.HAVE_AN_UPDATE_AVAILABLE+"\n
\n ");var d=(0,s.default)("[data-update-packages]").attr("data-packages-slugs")||"";d=d?d.split(","):[];var p=(0,u.default)(d.concat(Object.keys(r))).join();(0,s.default)("[data-update-packages]").attr("data-packages-slugs",""+p),Object.keys(r).forEach(function(t){var a=(0,s.default)("[data-gpm-"+n[i]+'="'+t+'"]'),l=a.find(".gpm-name"),u=l.find("a"),f=a.parents(".content-wrapper");if("plugins"!==o||l.find(".badge.update").length?"themes"===o&&(l.append('"),f.addClass("has-updates")):(l.append(''+c.translations.PLUGIN_ADMIN.UPDATE_AVAILABLE+"! "),f.addClass("has-updates")),a.length){var d=(0,s.default)(".grav-update."+n[i]);if(d.length){var p="testing"===r[t].type?'test release ':"";d.html('\n \n '+c.translations.PLUGIN_ADMIN.UPDATE+" "+(n[i].charAt(0).toUpperCase()+n[i].substr(1).toLowerCase())+' \n \n v'+r[t].available+" "+p+" "+c.translations.PLUGIN_ADMIN.OF_THIS+" "+n[i]+" "+c.translations.PLUGIN_ADMIN.IS_NOW_AVAILABLE+"!\n
\n ").css("display","block"),e=!1}}}),(0,s.default)("[data-update-packages]").removeClass("hidden")}}),(0,s.default)(".content-wrapper").addClass("updates-checked"),void(e||(0,s.default)(".warning-reinstall-not-latest-release").removeClass("hidden"))):this}}]),e}();t.default=y;var _=new y;t.Instance=_,t.Notifications=m.default,t.Feed=g.default,p.Instance.on("fetched",function(e,t){_.setPayload(e.payload||{}),_.grav().resources()}),"1"===c.config.enable_auto_updates_check&&p.Instance.fetch()},function(e,t,n){function r(e,t){return t=t||i,a(e,function(e,n,r){for(var i=r.length;++n0&&void 0!==arguments[0]?arguments[0]:{},n=t.filter,r=void 0===n?d():n,i=t.refresh,a=void 0!==i&&i;if(!f())return!1;var o=(0,s.default)("#notifications"),u=o.find(".widget-loader"),p=o.find(".widget-content > ul");u.find("div").remove(),u.find(".fa-warning").removeClass("fa-warning").addClass("fa-refresh fa-spin"),u.show(),p.hide();var h=function(t){var n=t.notifications;(0,s.default)("#notifications").find(".widget-content > ul").empty(),n&&Object.keys(n).forEach(function(t){return e.processLocation(t,n[t])})};(0,c.default)(l.config.base_url_relative+"/task"+l.config.param_sep+"getNotifications",{method:"post",body:{refresh:a,filter:r}},function(e){h(e)}).catch(function(){var e=(0,s.default)("#notifications .widget-content");e.find(".widget-loader").find("div").remove(),e.find(".widget-loader").append(" Failed to retrieve notifications
").find(".fa-spin").removeClass("fa-spin fa-refresh").addClass("fa-warning")})}}],[{key:"addShowAllInFeed",value:function(){(0,s.default)("#notifications ul").append('Show all ')}},{key:"showNotificationInFeed",value:function(t){var n=(0,s.default)("#notifications").removeClass("hidden"),r=n.find(".widget-loader").hide(),i=n.find(".widget-content > ul").show();r.find("div").remove(),r.find(".fa-warning").removeClass("fa-warning").addClass("fa-refresh fa-spin"),i.append(t).find("li:nth-child(n+11)").addClass("hidden"),i.find("li.hidden").length&&e.addShowAllInFeed()}},{key:"showNotificationInTop",value:function(e){var t=(0,s.default)(".top-notifications-container"),n=(0,s.default)("
").html(e);t.removeClass("hidden").append(n.children()),n.children().slideDown(150)}},{key:"showNotificationInDashboard",value:function(e){var t=(0,s.default)(".dashboard-notifications-container"),n=(0,s.default)("
").html(e);t.removeClass("hidden").append(n.children()),n.children().slideDown(150)}},{key:"showNotificationInPlugins",value:function(e){var t=(0,s.default)(".plugins-notifications-container"),n=(0,s.default)("
").html(e);t.removeClass("hidden").append(n.children()),n.children().slideDown(150)}},{key:"showNotificationInThemes",value:function(e){var t=(0,s.default)(".themes-notifications-container"),n=(0,s.default)("
").html(e);t.removeClass("hidden").append(n.children()),n.children().slideDown(150)}},{key:"processLocation",value:function(t,n){switch(t){case"feed":e.showNotificationInFeed(n);break;case"top":n.read||e.showNotificationInTop(n);break;case"dashboard":n.read||e.showNotificationInDashboard(n);break;case"plugins":n.read||e.showNotificationInPlugins(n);break;case"themes":n.read||e.showNotificationInThemes(n)}}}]),e}(),h=new p;t.default=h,f()&&(h.fetch(),(0,s.default)(document).on("click",'[data-notification-action="hide-notification"]',function(e){var t=(0,s.default)(e.currentTarget),n=t.parent();n.slideUp(function(){return n.remove()})}),(0,s.default)(document).on("click",'[data-notification-action="show-all-notifications"]',function(e){(0,s.default)("#notifications .show-all").hide(),(0,s.default)("#notifications .hidden").removeClass("hidden")}),(0,s.default)(document).on("click",'[data-refresh="notifications"]',function(e){e.preventDefault(),h.fetch({filter:["feed"],refresh:!0})}))},function(e,t,n){(function(e){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(330),i=n(335),a=void 0,o=function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){return!0};if("function"==typeof n&&(o=n,n={}),n.method&&"post"===n.method){var s=new FormData;n.body=Object.assign({"admin-nonce":i.config.admin_nonce},n.body||{}),Object.keys(n.body).map(function(e){return s.append(e,n.body[e])}),n.body=s}return n=Object.assign({credentials:"same-origin",headers:{Accept:"application/json"}},n),e(t,n).then(function(e){return a=e,e}).then(r.parseStatus).then(r.parseJSON).then(r.userFeedback).then(function(e){return o(e,a)}).catch(r.userFeedbackError)};t.default=o}).call(t,n(3))},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var a=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]&&arguments[0],n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:function(){};(0,c.default)(f,{method:"post",body:{refresh:t}},function(t){e.data=t,n(t)})}},{key:"refresh",value:function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=(0,s.default)("#news-feed .widget-content");if(t.length){var n=t.find(".widget-loader");n.find("div").remove(),n.find(".fa-warning").removeClass("fa-warning").addClass("fa-refresh fa-spin"),n.show(),t.find("> ul").hide(),!this.data||this.data.error||e?this.fetch(e,this.updateContent.bind(this)):this.updateContent()}}},{key:"updateContent",value:function(){var e=(0,s.default)("#news-feed .widget-content");if(e.length){var t=e.find(".widget-loader").hide(),n=e.find("> ul").empty().show();return this.data.error||"error"===this.data.status?(t.show().find("div").remove(),t.find(".fa-refresh").removeClass("fa-refresh fa-spin").addClass("fa-warning"),void t.append(""+(this.data.error?this.data.error.message:this.data.message||"Unable to download news feed")+"
")):void(this.data&&this.data.feed_data&&n.append(this.data.feed_data))}}}]),e}(),p=new d;(0,s.default)(document).ready(function(){return p.refresh()}),(0,s.default)(document).on("click",'[data-refresh="feed"]',function(e){e.preventDefault(),p.refresh(!0)}),t.default=p},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var i=n(1),a=r(i),o=n(2),s=n(335),l=n(331),u=r(l);(0,a.default)("[data-gpm-checkupdates]").on("click",function(){var e=(0,a.default)(this);e.find("i").addClass("fa-spin"),o.Instance.fetch(function(t){e.find("i").removeClass("fa-spin");var n=t.payload;if(n)if(n.grav.isUpdatable||n.resources.total){var r=n.grav.isUpdatable?"Grav v"+n.grav.available:"",i=n.resources.total?n.resources.total+" "+s.translations.PLUGIN_ADMIN.UPDATES_ARE_AVAILABLE:"";i||(r+=" "+s.translations.PLUGIN_ADMIN.IS_AVAILABLE_FOR_UPDATE),u.default.info(r+(r&&i?" "+s.translations.PLUGIN_ADMIN.AND+" ":"")+i)}else u.default.success(s.translations.PLUGIN_ADMIN.EVERYTHING_UP_TO_DATE)},!0)})},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var i=n(1),a=r(i),o=n(358),s=r(o);(0,a.default)("body").on("click","[data-maintenance-update]",function(){var e=(0,a.default)(this),t=e.data("maintenanceUpdate");e.attr("disabled","disabled").find("> .fa").removeClass("fa-cloud-download").addClass("fa-refresh fa-spin"),(0,s.default)(t,function(t){"updategrav"===t.type&&((0,a.default)("[data-gpm-grav]").remove(),(0,a.default)("#footer .grav-version").html(t.version)),e.removeAttr("disabled").find("> .fa").removeClass("fa-refresh fa-spin").addClass("fa-cloud-download")})})},function(e,t,n){(function(e){"use strict";function t(e){return e&&e.__esModule?e:{default:e}}var r=n(1),i=t(r),a=n(358),o=t(a),s=(0,i.default)('input[type="radio"][name="channel-switch"]');s&&s.on("change",function(t){var n=(0,i.default)(t.target),r=""+n.parent("[data-url]").data("url");(0,o.default)(r,{method:"post",body:{task:"gpmRelease",release:n.val()}},function(t){t.reload&&e.location.reload()})})}).call(t,function(){return this}())},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(364),a=r(i),o=n(366);n(367),t.default={Chart:{Chart:a.default,UpdatesChart:i.UpdatesChart,Instances:i.Instances},Cache:o.Instance}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0}),t.Instances=t.UpdatesChart=t.defaults=void 0;var s=function e(t,n,r){null===t&&(t=Function.prototype);var i=Object.getOwnPropertyDescriptor(t,n);if(void 0===i){var a=Object.getPrototypeOf(t);return null===a?void 0:e(a,n,r)}if("value"in i)return i.value;var o=i.get;if(void 0!==o)return o.call(r)},l=function(){function e(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{},i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(o(this,e),this.element=(0,c.default)(t)||[],this.element[0]){var a=(this.element.data("chart-type")||"pie").toLowerCase();this.type=a.charAt(0).toUpperCase()+a.substr(1).toLowerCase(),r=Object.assign({},v.options[this.type],r),i=Object.assign({},v.data,i),Object.assign(this,{options:r,data:i}),this.chart=d.default[this.type](this.element.find(".ct-chart").empty()[0],this.data,this.options),this.chart.on("created",function(){n.element.find(".hidden").removeClass("hidden"),n.element.find(".ct-label").each(function(e,t){t=(0,c.default)(t);var n=t.html().replace("<","<").replace(">",">");
+t.html(n)})})}}return l(e,[{key:"updateData",value:function(e){Object.assign(this.data,e),this.chart.update(this.data)}}]),e}();t.default=g;var y=t.UpdatesChart=function(e){function t(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};o(this,t);var a=i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,n,r));return a.chart.on("draw",function(e){return a.draw(e)}),h.Instance.on("fetched",function(e){if(e.payload){var t=e.payload.grav,n=100*(e.payload.resources.total+(t.isUpdatable?1:0))/(e.payload.installed+(t.isUpdatable?1:0)),r=100-n;a.updateData({series:[r,n]}),e.payload.resources.total&&m.Instance.maintenance("show")}}),a}return a(t,e),l(t,[{key:"draw",value:function(e){if(!e.index){var t=p.translations.PLUGIN_ADMIN[100===e.value?"FULLY_UPDATED":"UPDATES_AVAILABLE"];this.element.find(".numeric span").text(Math.round(e.value)+"%"),this.element.find(".js__updates-available-description").html(t),this.element.find(".hidden").removeClass("hidden")}}},{key:"updateData",value:function(e){s(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"updateData",this).call(this,e),this.data.series[0]<100&&this.element.closest("#updates").find("[data-update-packages]").fadeIn()}}]),t}(g),_={};(0,c.default)("[data-chart-name]").each(function(){var e=(0,c.default)(this),t=e.data("chart-name")||"",n=e.data("chart-options")||{},r=e.data("chart-data")||{};"updates"===t?_[t]=new y(e,n,r):_[t]=new g(e,n,r)});t.Instances=_},,function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0}),t.Instance=void 0;var a=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:"";return e&&(e="cleartype:"+e+"/"),l.config.base_url_relative+"/cache.json/task"+l.config.param_sep+"clearCache/"+e+"admin-nonce"+l.config.param_sep+l.config.admin_nonce},d=function(){function e(){var t=this;i(this,e),this.element=(0,s.default)("[data-clear-cache]"),(0,s.default)("body").on("click","[data-clear-cache]",function(e){return t.clear(e,e.target)})}return a(e,[{key:"clear",value:function(e,t){var n=this,r="";e&&e.preventDefault&&e.preventDefault(),"string"==typeof e&&(r=e),t=t?(0,s.default)(t):(0,s.default)('[data-clear-cache-type="'+r+'"]'),r=r||(0,s.default)(t).data("clear-cache-type")||"";var i=t.data("clearCache")||f(r);this.disable(),(0,c.default)(i,function(){return n.enable()})}},{key:"enable",value:function(){this.element.removeAttr("disabled").find("> .fa").removeClass("fa-refresh fa-spin").addClass("fa-trash")}},{key:"disable",value:function(){this.element.attr("disabled","disabled").find("> .fa").removeClass("fa-trash").addClass("fa-refresh fa-spin")}}]),e}();t.default=d;var p=new d;t.Instance=p},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var i=n(1),a=r(i),o=n(335),s=n(358),l=r(s),u=n(364);(0,a.default)('[data-backup][data-ajax*="backup/"]').on("click",function(){var e=(0,a.default)(this),t=e.data("ajax"),n=e.closest(".dropdown-menu");(n.length?n:e).closest(".button-group").find("> button:first").attr("disabled","disabled").find("> .fa").removeClass("fa-life-ring").addClass("fa-spin fa-refresh"),(0,l.default)(t,function(){u.Instances&&u.Instances.backups&&(u.Instances.backups.updateData({series:[0,100]}),u.Instances.backups.element.find(".numeric").html("0 "+o.translations.PLUGIN_ADMIN.DAYS.toLowerCase()+" ")),(n.length?n:e).closest(".button-group").find("> button:first").removeAttr("disabled").find("> .fa").removeClass("fa-spin fa-refresh").addClass("fa-life-ring")})}),(0,a.default)('[data-backup][data-ajax*="backupDelete"]').on("click",function(){var e=(0,a.default)(this),t=e.data("ajax"),n=e.closest("tr");n.addClass("deleting"),(0,l.default)(t,function(e){"success"===e.status?n.remove():n.removeClass("deleting")})})},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(1),a=r(i),o=n(369),s=r(o),l=n(370),u=r(l),c=n(378),f=r(c),d=function(e,t){return("000"+e).substr(-t)},p=null,h=(0,a.default)("#ordering");h.length&&(p=new s.default(h.get(0),{filter:".ignore",onUpdate:function(){var e=[],t=h.children(),n=(t.length+"").split("").length;t.each(function(t,r){r=(0,a.default)(r),e.push(r.data("id")),r.find(".page-order").text(d(t+1,n)+".")}),(0,a.default)("[data-order]").val(e.join(","))}}),(0,a.default)(document).on("input",'[name="data[folder]"]',function(e){var t=(0,a.default)(e.currentTarget),n=(0,a.default)("[data-id][data-active-id]");n.data("id",t.val()),p.options.onUpdate()})),t.default={Ordering:p,Page:f.default,PageFilters:{PageFilters:u.default,Instance:l.Instance}}},,function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0}),t.Instance=void 0;var a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},o=function(){function e(e,t){for(var n=0;n=0?i=setTimeout(r,t-u):(i=null,n||(l=e.apply(o,a),o=a=null))}var i,a,o,s,l;null==t&&(t=100);var u=function(){o=this,a=arguments,s=Date.now();var u=n&&!i;return i||(i=setTimeout(r,t)),u&&(l=e.apply(o,a),o=a=null),l};return u.clear=function(){i&&(clearTimeout(i),i=null)},u.flush=function(){i&&(l=e.apply(o,a),o=a=null,clearTimeout(i),i=null)},u}n.debounce=n,e.exports=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0}),t.Instance=void 0;var a=function(){function e(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:void 0;i(this,e),this.query=t,this.elements=(0,s.default)(void 0!==r?r:this.query),this.session=JSON.parse(sessionStorage.getItem(l)||"{}"),this.elements.length&&(this.restore(),this.elements.find(".page-icon").on("click",function(e){return n.toggle(e.target)}),this.elements.data("tree_init",1),(0,s.default)("[data-page-toggleall]").on("click",function(e){var t=(0,s.default)(e.target).closest("[data-page-toggleall]"),r=t.data("page-toggleall");n[r]()}))}return a(e,[{key:"reload",value:function(){var e=(0,s.default)(this.query).filter(function(e,t){return!(0,s.default)(t).data("tree_init")});e.length&&this.constructor(this.query,e)}},{key:"toggle",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]&&arguments[1];"string"==typeof e&&(e=(0,s.default)('[data-nav-id="'+e+'"]').find('[data-toggle="children"]')),e=(0,s.default)(e||this.elements),e.each(function(e,r){r=(0,s.default)(r);var i=t.getState(r.closest('[data-toggle="children"]'));t[i.isOpen?"collapse":"expand"](i.id,n)})}},{key:"collapse",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]&&arguments[1];"string"==typeof e&&(e=(0,s.default)('[data-nav-id="'+e+'"]').find('[data-toggle="children"]')),e=(0,s.default)(e||this.elements),e.each(function(e,r){r=(0,s.default)(r);var i=t.getState(r);i.isOpen&&(i.children.hide(),i.icon.removeClass("children-open").addClass("children-closed"),n||delete t.session[i.id])});var r=e.closest(".mediapicker-scroll");r.length&&r.data("scrollbar")&&r.data("scrollbar").update(),n||this.save()}},{key:"expand",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]&&arguments[1];if("string"==typeof e){var r=(0,s.default)('[data-nav-id="'+e+'"]'),i=r.parents("[data-nav-id]");if(i.length)return i=i.find('[data-toggle="children"]:first'),i=i.add(r.find('[data-toggle="children"]:first')),this.expand(i,n);e=r.find('[data-toggle="children"]:first')}e=(0,s.default)(e||this.elements),e.each(function(e,r){r=(0,s.default)(r);var i=t.getState(r);i.isOpen||(i.children.show(),i.icon.removeClass("children-closed").addClass("children-open"),n||(t.session[i.id]=1))});var a=e.closest(".mediapicker-scroll");a.length&&a.data("scrollbar")&&a.data("scrollbar").update(),n||this.save()}},{key:"restore",value:function(){var e=this;this.collapse(null,!0),Object.keys(this.session).forEach(function(t){e.expand(t,"no-store")})}},{key:"save",value:function(){return sessionStorage.setItem(l,JSON.stringify(this.session))}},{key:"getState",value:function(e){return e=(0,s.default)(e),{id:e.closest("[data-nav-id]").data("nav-id"),children:e.closest("li.page-item").find("ul:first"),icon:e.find(".page-icon"),get isOpen(){return this.icon.hasClass("children-open")}}}}]),e}();t.default=u;var c=new u('[data-toggle="children"]');t.Instance=c},function(e,t){"use strict";!function(){function e(){var e="localStoragePollyfill";try{return localStorage.setItem(e,e),localStorage.removeItem(e),sessionStorage.setItem(e,e),sessionStorage.removeItem(e),!0}catch(e){return!1}}if(!e())try{Storage.prototype._data={},Storage.prototype.setItem=function(e,t){return this._data[e]=String(t),this._data[e]},Storage.prototype.getItem=function(e){return this._data.hasOwnProperty(e)?this._data[e]:void 0},Storage.prototype.removeItem=function(e){return delete this._data[e]},Storage.prototype.clear=function(){return this._data={},this._data}}catch(e){console.error("localStorage pollyfill error: ",e)}}()},,,,function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var i=n(374),a=r(i);a.default.define("required-fix",function(e){var t=this;this.refreshValidityState=function(){if(!t.isRequired)return!1;var e=!t.items.length;t.isInvalid=e,e?(t.$control_input.attr("required",""),t.$input.removeAttr("required")):(t.$control_input.removeAttr("required"),t.$input.attr("required"))}})},function(e,t,n){(function(e){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(1),a=r(i);n(379),n(382),n(383),n(384),n(385);var o=n(386),s=r(o);n(456);var l=(0,a.default)('input[type="radio"][name="mode-switch"]');if(l){var u=l.closest(":checked").data("leave-url"),c=(0,a.default)(' ');l.parent().append(c),l.siblings("label").on("mousedown touchdown",function(t){t.preventDefault();var n=(0,a.default)('[data-remodal-id="changes"] [data-leave-action="continue"]');n.one("click",function(){(0,a.default)(e).on("beforeunload._grav"),c.off("click._grav"),(0,a.default)(t.target).trigger("click")}),c.trigger("click._grav")}),l.on("change",function(e){var t=(0,a.default)(e.target);u=t.data("leave-url"),setTimeout(function(){return c.attr("href",u).get(0).click()},5)})}t.default={Media:{PageMedia:s.default,PageMediaInstances:o.Instance}}}).call(t,function(){return this}())},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var i=n(1),a=r(i);n(380);var o=n(358),s=r(o),l=n(335),u=!1,c=(0,a.default)('[data-remodal-id="modal"] input[name="data[folder]"], [data-remodal-id="modular"] input[name="data[folder]"]'),f=(0,a.default)('[data-remodal-id="modal"] input[name="data[title]"], [data-remodal-id="modular"] input[name="data[title]"]'),d=function(e,t){t=(0,a.default)(t);var n='[data-remodal-id="'+t.closest("[data-remodal-id]").data("remodal-id")+'"]';return{title:"title"===e?(0,a.default)(t):(0,a.default)(n+' input[name="data[title]"]'),folder:"folder"===e?(0,a.default)(t):(0,a.default)(n+' input[name="data[folder]"]')}};f.on("input focus blur",function(e){if(u)return!0;var t=d("title",e.currentTarget),n=a.default.slugify(t.title.val(),{custom:{"'":""}});t.folder.val(n)}),c.on("input",function(e){var t=d("folder",e.currentTarget),n=t.folder.get(0),r=t.folder.val(),i={start:n.selectionStart,end:n.selectionEnd};r=r.toLowerCase().replace(/\s/g,"-").replace(/[^a-z0-9_\-]/g,""),t.folder.val(r),u=!!r,n.setSelectionRange(i.start,i.end)}),c.on("focus blur",function(e){return d("title",e.currentTarget).title.trigger("input")}),(0,a.default)(document).on("change",'[name="data[route]"]',function(e){var t=(0,a.default)(e.currentTarget).val(),n=(0,a.default)('[name="data[name]"]'),r=l.config.base_url_relative+"/ajax.json/task"+l.config.param_sep+"getChildTypes";0!==n.length&&(0,s.default)(r,{method:"post",body:{rawroute:t}},function(e){var t=e.child_type;""!==t&&"default"!==t&&(n.val(t),n.data("selectize").setValue(t))})})},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var i=n(1),a=r(i),o=n(381),s=r(o);a.default.expr[":"].noparents=a.default.expr.createPseudo(function(e){return function(t){return(0,a.default)(t).parents(e).length<1}}),a.default.fn.slugify=function(e,t){return(void 0).each(function(e){var n=(0,a.default)(e),r=(0,a.default)(r);n.on("keyup change",function(){n.data("locked",""!==n.val()&&void 0!==n.val())}),r.on("keyup change",function(){if(n.data("locked")===!0)return!0;var e=n.is("input")||n.is("textarea");n[e?"val":"text"](a.default.slugify(r.val(),t))})})},a.default.slugify=function(e,t){return t=a.default.extend({},a.default.slugify.options,t),t.lang=t.lang||(0,a.default)("html").prop("lang"),"function"==typeof t.preSlug&&(e=t.preSlug(e)),e=t.slugFunc(e,t),"function"==typeof t.postSlug&&(e=t.postSlug(e)),e},a.default.slugify.options={preSlug:null,postSlug:null,slugFunc:function(e,t){return(0,s.default)(e,t)}}},,function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var i=n(1),a=r(i);(0,a.default)('[data-page-move] button[name="task"][value="save"]').on("click",function(){var e=(0,a.default)('form#blueprints:first select[name="data[route]"]'),t=(0,a.default)("[data-page-move] select").val();if(e.length&&e.val()!==t){var n=e.data("selectize");e.val(t),n&&n.setValue(t)}})},function(e,t,n){(function(e){"use strict";function t(e){return e&&e.__esModule?e:{default:e}}var r=n(1),i=t(r);(0,i.default)(document).on("click",'[data-remodal-target="delete"]',function(){var e=(0,i.default)('[data-remodal-id="delete"] [data-delete-action]'),t=(0,i.default)(this).data("delete-url");e.data("delete-action",t)}),(0,i.default)(document).on("click","[data-delete-action]",function(){var t=i.default.remodal.lookup[(0,i.default)('[data-remodal-id="delete"]').data("remodal")];e.location.href=(0,i.default)(this).data("delete-action"),t.close()})}).call(t,function(){return this}())},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var i=n(1),a=r(i);(0,a.default)(document).on("click",".dz-unset",function(){var e=(0,a.default)(this).closest(".files-upload");(0,a.default)(this).closest(".dz-image-preview").remove();var t=(0,a.default)(this).closest(".dz-image-preview").find("[data-dz-name]").text().trim(),n=JSON.parse(e.find('input[data-grav-field="hidden"]').val())||{},r={};a.default.each(n,function(e,n){e.endsWith(t)||(r[e]=n)}),e.find('input[data-grav-field="hidden"]').val(JSON.stringify(r))})},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var i=n(1),a=r(i);(0,a.default)(".disable-after-click").on("click",function(){(0,a.default)(this).addClass("pointer-events-disabled")})},function(e,t,n){(function(e){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function o(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0}),t.Instance=void 0;var s=function(){function e(e,t){for(var n=0;n\n \n
\n
\n
\n
\n
\n ✔
\n ✘
\n
\n '+m.translations.PLUGIN_ADMIN.DELETE+' \n '+m.translations.PLUGIN_ADMIN.METADATA+' \n '+m.translations.PLUGIN_ADMIN.VIEW+' \n '+m.translations.PLUGIN_ADMIN.INSERT+" \n ").trim(),b=function(t){function n(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.container,r=void 0===t?"#grav-dropzone":t,o=e.options,s=void 0===o?{}:o;i(this,n),s=Object.assign(s,{previewTemplate:_});var l=a(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,{container:r,options:s}));if(!l.container.length)return a(l);l.urls={fetch:l.container.data("media-url")+"/task"+m.config.param_sep+"listmedia",add:l.container.data("media-url")+"/task"+m.config.param_sep+"addmedia",delete:l.container.data("media-url")+"/task"+m.config.param_sep+"delmedia"},l.dropzone.options.url=l.urls.add,("undefined"==typeof l.options.fetchMedia||l.options.fetchMedia)&&l.fetchMedia(),("undefined"==typeof l.options.attachDragDrop||l.options.attachDragDrop)&&l.attachDragDrop();var u=(0,c.default)('[name="'+l.container.data("dropzone-field")+'"]');return u.length&&(l.sortable=new y.default(l.container.get(0),{animation:150,setData:function(e,t){t=(0,c.default)(t);var n=encodeURI(t.find(".dz-filename").text()),r=(0,p.UriToMarkdown)(n);l.dropzone.disable(),t.addClass("hide-backface"),e.effectAllowed="copy",e.setData("text",r)},onSort:function(){var e=[];l.container.find("[data-dz-name]").each(function(t,n){n=(0,c.default)(n);var r=n.text().trim();e.push(r)}),u.val(e.join(","))}})),l}return o(n,t),s(n,[{key:"fetchMedia",value:function(){var e=this,t=this.container.closest(".form-field").find('[name="data[header][media_order]"]').val(),n={uri:this.getURI(),order:t},r=this.urls.fetch;(0,d.default)(r,{method:"post",body:n},function(t){var n=t.results;Object.keys(n).forEach(function(t){var r=n[t],i={name:t,size:r.size,accepted:!0,extras:r};e.dropzone.files.push(i),e.dropzone.options.addedfile.call(e.dropzone,i),e.dropzone.options.thumbnail.call(e.dropzone,i,r.url)}),e.container.find(".dz-preview").prop("draggable","true")})}},{key:"onDropzoneSending",value:function(e,t,n){n.append("name",this.options.dotNotation||e.name),n.append("admin-nonce",m.config.admin_nonce),n.append("uri",this.getURI())}},{key:"onDropzoneComplete",value:function(e){l(n.prototype.__proto__||Object.getPrototypeOf(n.prototype),"onDropzoneComplete",this).call(this,e),this.sortable.options.onSort(),(0,c.default)(".dz-preview").prop("draggable","true")}},{key:"onDropzoneRemovedFile",value:function(e){for(var t,r=arguments.length,i=Array(r>1?r-1:0),a=1;a ');var u=s.find(".meta-content").html("").find("ul");Object.keys(o.metadata).forEach(function(e){u.append(""+(e?e+":":"")+" "+o.metadata[e]+" ")}),l.open()}),this.container.delegate(".dz-preview","dragstart",function(e){var n=(0,c.default)(e.currentTarget),r=encodeURI(n.find(".dz-filename").text()),i=(0,p.UriToMarkdown)(r);t.dropzone.disable(),n.addClass("hide-backface"),e.originalEvent.dataTransfer.effectAllowed="copy",e.originalEvent.dataTransfer.setData("text",i)}),this.container.delegate(".dz-preview","dragend",function(e){var n=(0,c.default)(e.currentTarget);t.dropzone.enable(),n.removeClass("hide-backface")})}}]),n}(h.default);t.default=b;t.Instance=new b}).call(t,function(){return this}())},function(e,t,n){(function(e){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e){return e=e.replace(/@3x|@2x|@1x/,""),e=e.replace(/\(/g,"%28"),e=e.replace(/\)/g,"%29"),e.match(/\.(jpe?g|png|gif|svg|mp4|webm|ogv|mov)$/i)?"":"["+decodeURI(e)+"]("+e+")"}Object.defineProperty(t,"__esModule",{value:!0}),t.Instances=void 0;var o=function(){function e(e,t){for(var n=0;n"),dictRemoveFileConfirmation:"[placeholder]",previewTemplate:('\n ").trim()};e.EXIF=d.default;var y=function(e,t,n){var r=n.resolution;if(!r)return t();var i=new FileReader,a="",o=r.min&&(r.min.width||r.min.height),s=r.max&&(r.max.width||r.max.height);return o||!n.resizeWidth&&!n.resizeHeight&&s?(i.onload=function(e){var i=new Image;i.src=e.target.result,i.onload=function(){var e=this;return r.min&&Object.keys(r.min).forEach(function(t){r.min[t]&&e[t]r.max[t]&&(a+=m.translations.PLUGIN_FORM.RESOLUTION_MAX.replace(/{{attr}}/g,t).replace(/{{max}}/g,r.max[t]))}),a?t(a):t()}},void i.readAsDataURL(e)):a?t(a):t()},_=function(){function t(){var e=this,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=n.container,a=void 0===r?".dropzone.files-upload":r,o=n.options,s=void 0===o?{}:o;i(this,t),this.container=(0,l.default)(a),this.container.length&&(this.urls={},this.customPost=this.container.data("filePostAdd")||{},this.options=Object.assign({},v,g,{klass:this,url:this.container.data("file-url-add")||m.config.current_url,acceptedFiles:this.container.data("media-types"),init:this.initDropzone},this.container.data("dropzone-options"),s),this.options=Object.assign({},this.options,{accept:function(e,t){y(e,t,this.options)}}),this.dropzone=new c.default(a,this.options),this.dropzone.on("complete",this.onDropzoneComplete.bind(this)),this.dropzone.on("success",this.onDropzoneSuccess.bind(this)),this.dropzone.on("removedfile",this.onDropzoneRemovedFile.bind(this)),this.dropzone.on("sending",this.onDropzoneSending.bind(this)),this.dropzone.on("error",this.onDropzoneError.bind(this)),this.container.on("mouseenter","[data-dz-view]",function(t){var n=JSON.parse(e.container.find('[name][type="hidden"]').val()||"{}"),r=(0,l.default)(t.currentTarget),i=r.parent(".dz-preview").find(".dz-filename"),a=encodeURI(i.text()),o=Object.keys(n).filter(function(e){
+return n[e].name===a}).shift();r.attr("href",m.config.base_url_simple+"/"+o)}))}return o(t,[{key:"initDropzone",value:function(){var e=this,t=this.options.klass.container.find("[data-file]"),n=this;t.length&&t.each(function(t,r){r=(0,l.default)(r);var i=r.data("file"),a={name:i.name,size:i.size,type:i.type,status:c.default.ADDED,accepted:!0,url:e.options.url,removeUrl:i.remove};n.files.push(a),n.options.addedfile.call(n,a),a.type.match(/^image\//)&&(n.options.thumbnail.call(n,a,i.path),n.createThumbnailFromUrl(a,i.path)),r.remove()})}},{key:"getURI",value:function(){return this.container.data("mediaUri")||""}},{key:"onDropzoneSending",value:function(e,t,n){var r=this;Object.keys(this.customPost).length?Object.keys(this.customPost).forEach(function(e){n.append(e,r.customPost[e])}):(n.append("name",this.options.dotNotation),n.append("task","filesupload"),n.append("uri",this.getURI())),n.append("admin-nonce",m.config.admin_nonce)}},{key:"onDropzoneSuccess",value:function(t,n,r){if(n="string"==typeof n?JSON.parse(n):n,this.options.reloadPage&&e.location.reload(),n.session){t.sessionParams=n.session,t.removeUrl=this.options.url;var i=this.container.find('[name][type="hidden"]'),a=i.val();i.val(a+" ")}return this.handleError({file:t,data:n,mode:"removeFile",msg:""+m.translations.PLUGIN_ADMIN.FILE_ERROR_UPLOAD+" "+t.name+"
\n "+n.message+" "})}},{key:"onDropzoneComplete",value:function(t){if(!t.accepted&&!t.rejected){var n={status:"error",message:m.translations.PLUGIN_ADMIN.FILE_UNSUPPORTED+": "+t.name.match(/\..+/).join("")};return this.handleError({file:t,data:n,mode:"removeFile",msg:""+m.translations.PLUGIN_ADMIN.FILE_ERROR_ADD+" "+t.name+"
\n "+n.message+" "})}this.options.reloadPage&&e.location.reload()}},{key:"b64_to_utf8",value:function(e){return e=e.replace(/\s/g,""),decodeURIComponent(escape(window.atob(e)))}},{key:"onDropzoneRemovedFile",value:function(e){var t=this;if(e.accepted&&!e.rejected){var n=e.removeUrl||this.urls.delete||this.options.url,r=(n||"").match(/path:(.*)\//),i={filename:e.name,uri:this.getURI()};e.sessionParams&&(i.task="filessessionremove",i.session=e.sessionParams);var a=this.container.data("filePostRemove")||{};Object.keys(a).length&&(i={},Object.keys(a).forEach(function(e){i[e]=a[e]})),i.filename=e.name,i["admin-nonce"]=m.config.admin_nonce,(0,h.default)(n,{method:"post",body:i},function(){if(r){r=t.b64_to_utf8(r[1]);var e=t.container.find('[name][type="hidden"]'),n=JSON.parse(e.val()||"{}");delete n[r],e.val(JSON.stringify(n))}})}}},{key:"onDropzoneError",value:function(e,t,n){var r=n?t.error.message:t;return(0,l.default)(e.previewElement).find("[data-dz-errormessage]").html(r),this.handleError({file:e,data:{status:"error"},msg:""+r+" "})}},{key:"handleError",value:function(e){var t=e.file,n=e.data,r=e.mode,i=e.msg;if("error"===n.status||"unauthorized"===n.status){switch(r){case"addBack":t instanceof File?this.dropzone.addFile.call(this.dropzone,t):(this.dropzone.files.push(t),this.dropzone.options.addedfile.call(this.dropzone,t),this.dropzone.options.thumbnail.call(this.dropzone,t,t.extras.url));break;case"removeFile":default:~this.dropzone.files.indexOf(t)&&(t.rejected=!0,this.dropzone.removeFile.call(this.dropzone,t,{silent:!0}))}var a=(0,l.default)('[data-remodal-id="generic"]');a.find(".error-content").html(i),l.default.remodal.lookup[a.data("remodal")].open()}}}]),t}();t.default=_;var b=[],w=(0,l.default)(),x=function(e,t){var n=(0,l.default)(t).find(".dropzone.files-upload");n.length&&n.each(function(e,t){t=(0,l.default)(t),~w.index(t)||k(t)})},k=function(e){e=(0,l.default)(e);var t=e.find('input[type="file"]'),n=e.data("grav-file-settings")||{};n.accept&&~n.accept.indexOf("*")&&(n.accept=[""]);var r={url:e.data("file-url-add")||(e.closest("form").attr("action")||m.config.current_url)+".json",paramName:n.paramName||"file",dotNotation:n.name||"file",acceptedFiles:n.accept?n.accept.join(","):t.attr("accept")||e.data("media-types"),maxFilesize:"undefined"!=typeof n.filesize?n.filesize:256,maxFiles:n.limit||null,resizeWidth:n.resizeWidth||null,resizeHeight:n.resizeHeight||null,resizeQuality:n.resizeQuality||null,accept:function(e,t){y(e,t,n)}};w=w.add(e),e=e[0],b.push(new _({container:e,options:r}))};t.Instances=function(){return(0,l.default)(".dropzone.files-upload").each(function(e,t){return k(t)}),(0,l.default)("body").on("mutation._grav",x),b}()}).call(t,function(){return this}())},,,function(e,t,r){var i,a;(function(){function r(e){return!!e.exifdata}function o(e,t){t=t||e.match(/^data\:([^\;]+)\;base64,/im)[1]||"",e=e.replace(/^data\:([^\;]+)\;base64,/gim,"");for(var n=atob(e),r=n.length,i=new ArrayBuffer(r),a=new Uint8Array(i),o=0;o4?p:t+8,o=[],l=0;l4?p:t+8,v(e,a,d-1);case 3:if(1==d)return e.getUint16(t+8,!i);for(a=d>2?p:t+8,o=[],l=0;le.byteLength)return{};var a=d(e,t,t+i,E,r);if(a.Compression)switch(a.Compression){case 6:if(a.JpegIFOffset&&a.JpegIFByteCount){var o=t+a.JpegIFOffset,s=a.JpegIFByteCount;a.blob=new Blob([new Uint8Array(e.buffer,o,s)],{type:"image/jpeg"})}break;case 1:console.log("Thumbnail image format is TIFF, which is not implemented.");break;default:console.log("Unknown thumbnail image format '%s'",a.Compression)}else 2==a.PhotometricInterpretation&&console.log("Thumbnail image format is RGB, which is not implemented.");return a}function v(e,t,r){var i="";for(n=t;n")+8;s=s.substring(s.indexOf("0){t["@attributes"]={};for(var n=0;n0)for(var n=0;n ').attr("href",r))}"yaml"===t.mode&&Object.assign(t.extraKeys,{Tab:function(e){e.replaceSelection(" ","end")}});var i=f.default.fromTextArea(e.get(0),t);e.data("codemirror",i),e.data("toolbar",new _(e)),e.addClass("code-mirrored"),t.toolbar===!1&&e.data("toolbar").ui.navigation.addClass("grav-editor-hide-toolbar"),i.on("change",function(){return i.save()})}},{key:"_onAddedNodes",value:function(e,t){var n=this,r=(0,s.default)(t).find("[data-grav-editor]");r.length&&r.each(function(e,t){t=(0,s.default)(t),~n.editors.index(t)||n.addEditor(t)})}}]),e}();t.default=y;var _=t.Toolbar=function(){function e(t){i(this,e),this.editor=(0,s.default)(t),this.codemirror=this.editor.data("codemirror"),this.buttons=u.default.navigation,this.ui={navigation:(0,s.default)(e.templates().navigation)},this.editor.parent(".grav-editor-content").before(this.ui.navigation).after(this.ui.states),this.renderButtons()}return a(e,null,[{key:"templates",value:function(){return{navigation:'\n \n '}}}]),a(e,[{key:"renderButtons",value:function(){var e=this,t={actions:"navigation",modes:"states"};["actions","modes"].forEach(function(n){e.ui.navigation.find(".grav-editor-"+n).empty().append(""),u.default[t[n]].forEach(function(t){return e.renderButton(t,n)})})}},{key:"renderButton",value:function(e,t){var n=this,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;Object.keys(e).forEach(function(i){var a=e[i];if(a.modes||(a.modes=[]),!~n.codemirror.options.ignore.indexOf(i)&&(!a.modes.length||a.modes.indexOf(n.codemirror.options.mode)>-1)){var o=a.title?'data-hint="'+a.title+'"':"",l=(0,s.default)('"+a.label+" ");if((r||n.ui.navigation.find(".grav-editor-"+t+" ul:not(.dropdown-menu)")).append(l),a.shortcut&&n.addShortcut(a.identifier,a.shortcut,l),a.action&&a.action.call(a.action,{codemirror:n.codemirror,button:l,textarea:n.editor,ui:n.ui}),a.children){var u=(0,s.default)('');l.addClass("button-group").find("a").wrap('
'),l.find("a").append(' '),l.append(u),a.children.forEach(function(e){return n.renderButton(e,t,u)})}}})}},{key:"addShortcut",value:function(e,t,n){var r=this,i={};Array.isArray(t)||(t=[t]),t.forEach(function(t){i[t]=function(){n.trigger("click.editor."+e,[r.codemirror])}}),this.codemirror.addKeyMap(i)}}]),e}();t.Instance=new y}).call(t,function(){return this}())},function(e,t,n){(function(e){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0}),t.strategies=void 0;var i=n(1),a=r(i),o=n(335),s=n(358),l=r(s),u=function(e){var t=e.name,n=e.replace,r=e.codemirror,i=e.button,a=e.mode,o=void 0===a?"replaceSelections":a,s=e.runner;i.on("click.editor."+t,function(){c[o]({token:"$1",template:n,codemirror:r,runner:s})})},c=t.strategies={replaceSelections:function(e){var t=e.template,n=e.token,r=e.codemirror,i=e.runner,a=[],o=[],s=r.getSelections(),l=r.listSelections(),u={};s.forEach(function(e,r){var s=t.replace(n,e),c=s.indexOf("$cur"),f=l[r].anchor,d=f.line,p=f.ch;s=s.replace("$cur",""),s=i?i(e,s,l):s,a.push(s),u[d]||(u[d]=0),p+=u[d]+(c===-1?s.length:c);var h={ch:p,line:d};o.push({anchor:h,head:h}),u[d]+=s.length-e.length}),r.replaceSelections(a),r.setSelections(o),r.focus()},replaceLine:function(e){var t=e.template,n=e.token,r=e.codemirror,i=e.runner,a=r.listSelections(),o=void 0;a.forEach(function(e){var a={min:Math.min(e.anchor.line,e.head.line),max:Math.max(e.anchor.line,e.head.line)};r.eachLine(a.min,a.max+1,function(e){var a=t.replace(n,e.text),s=r.getLineNumber(e);a=i?i(e,a):a,r.replaceRange(a,{line:s,ch:0},{line:s,ch:a.length}),o={line:s,ch:a.length}})}),r.setSelection(o,o,"end"),r.focus()},replaceRange:function(){}},f=function(e,t,n){var r=e.historySize()[n],i=t.find("a");t[r?"removeClass":"addClass"]("button-disabled"),r?(i.attr("title",i.attr("title-disabled")),i.attr("data-hint",i.attr("data-hint-disabled")),i.removeAttr("title-disabled").removeAttr("data-hint-disabled")):(i.attr("title-disabled",i.attr("title")),i.attr("data-hint-disabled",i.attr("data-hint")),i.removeAttr("title").removeAttr("data-hint"))};t.default={navigation:[{undo:{identifier:"undo",title:o.translations.PLUGIN_ADMIN.UNDO,label:' ',modes:[],action:function(e){var t=e.codemirror,n=e.button;e.textarea;n.addClass("button-disabled"),t.on("change",function(){return f(t,n,"undo")}),n.on("click.editor.undo",function(){t.undo()})}}},{redo:{identifier:"redo",title:o.translations.PLUGIN_ADMIN.REDO,label:' ',modes:[],action:function(e){var t=e.codemirror,n=e.button;e.textarea;n.addClass("button-disabled"),t.on("change",function(){return f(t,n,"redo")}),n.on("click.editor.redo",function(){t.redo()})}}},{headers:{identifier:"headers",title:o.translations.PLUGIN_ADMIN.HEADERS,label:'',modes:["gfm","markdown"],children:[{h1:{identifier:"h1",label:'1',modes:["gfm","markdown"],action:function(e){var t=e.codemirror,n=e.button;e.textarea;u({name:"h1",replace:"# $1",codemirror:t,button:n,mode:"replaceLine"})}}},{h2:{identifier:"h2",label:'2',modes:["gfm","markdown"],action:function(e){var t=e.codemirror,n=e.button;e.textarea;u({name:"h2",replace:"## $1",codemirror:t,button:n,mode:"replaceLine"})}}},{h3:{identifier:"h3",label:'3',modes:["gfm","markdown"],action:function(e){var t=e.codemirror,n=e.button;e.textarea;u({name:"h3",replace:"### $1",codemirror:t,button:n,mode:"replaceLine"})}}},{h4:{identifier:"h4",label:'4',modes:["gfm","markdown"],action:function(e){var t=e.codemirror,n=e.button;e.textarea;u({name:"h4",replace:"#### $1",codemirror:t,button:n,mode:"replaceLine"})}}},{h5:{identifier:"h5",label:'5',modes:["gfm","markdown"],action:function(e){var t=e.codemirror,n=e.button;e.textarea;u({name:"h5",replace:"##### $1",codemirror:t,button:n,mode:"replaceLine"})}}},{h6:{identifier:"h6",label:'6',modes:["gfm","markdown"],action:function(e){var t=e.codemirror,n=e.button;e.textarea;u({name:"h6",replace:"###### $1",codemirror:t,button:n,mode:"replaceLine"})}}}]}},{bold:{identifier:"bold",title:o.translations.PLUGIN_ADMIN.BOLD,label:' ',modes:["gfm","markdown"],shortcut:["Ctrl-B","Cmd-B"],action:function(e){var t=e.codemirror,n=e.button;e.textarea;u({name:"bold",replace:"**$1$cur**",codemirror:t,button:n})}}},{italic:{identifier:"italic",title:o.translations.PLUGIN_ADMIN.ITALIC,label:' ',modes:["gfm","markdown"],shortcut:["Ctrl-I","Cmd-I"],action:function(e){var t=e.codemirror,n=e.button;e.textarea;u({name:"italic",replace:"_$1$cur_",codemirror:t,button:n})}}},{strike:{identifier:"strike",title:o.translations.PLUGIN_ADMIN.STRIKETHROUGH,label:' ',modes:["gfm","markdown"],action:function(e){var t=e.codemirror,n=e.button;e.textarea;u({name:"strike",replace:"~~$1$cur~~",codemirror:t,button:n})}}},{delimiter:{identifier:"delimiter",title:o.translations.PLUGIN_ADMIN.SUMMARY_DELIMITER,label:' ',modes:["gfm","markdown"],action:function(e){var t=e.codemirror,n=e.button;e.textarea;u({name:"delimiter",replace:o.config.site.delimiter+"$1",codemirror:t,button:n,mode:"replaceLine"})}}},{link:{identifier:"link",title:o.translations.PLUGIN_ADMIN.LINK,label:' ',modes:["gfm","markdown"],shortcut:["Ctrl-K","Cmd-K"],action:function(e){var t=e.codemirror,n=e.button;e.textarea;u({name:"link",replace:"[$1]($cur)",codemirror:t,button:n})}}},{image:{identifier:"image",title:o.translations.PLUGIN_ADMIN.IMAGE,label:' ',modes:["gfm","markdown"],action:function(e){var t=e.codemirror,n=e.button;e.textarea;u({name:"image",replace:"",codemirror:t,button:n})}}},{blockquote:{identifier:"blockquote",title:o.translations.PLUGIN_ADMIN.BLOCKQUOTE,label:' ',modes:["gfm","markdown"],action:function(e){var t=e.codemirror,n=e.button;e.textarea;u({name:"blockquote",replace:"> $1",codemirror:t,button:n,mode:"replaceLine"})}}},{listUl:{identifier:"listUl",title:o.translations.PLUGIN_ADMIN.UNORDERED_LIST,label:' ',modes:["gfm","markdown"],action:function(e){var t=e.codemirror,n=e.button;e.textarea;u({name:"listUl",replace:"* $1",codemirror:t,button:n,mode:"replaceLine"})}}},{listOl:{identifier:"listOl",title:o.translations.PLUGIN_ADMIN.ORDERED_LIST,label:' ',modes:["gfm","markdown"],action:function(e){var t=e.codemirror,n=e.button;e.textarea;u({name:"listOl",replace:". $1",codemirror:t,button:n,mode:"replaceLine",runner:function(e,n){var r=t.getLineNumber(e),i=t.getLine(r-1)||"",a=i.match(/^(\d+)\./),o=1+(a?Number(a[1]):0);return""+o+n}})}}}],states:[{code:{identifier:"editor",title:o.translations.PLUGIN_ADMIN.EDITOR,label:' ',action:function(e){var t=(e.codemirror,e.button),n=e.textarea,r=e.ui;"editor"===n.data("grav-editor-mode")&&t.addClass("editor-active"),t.on("click.states.editor",function(){t.siblings().removeClass("editor-active"),t.addClass("editor-active"),n.data("grav-editor-mode","editor");var e=n.data("grav-editor-preview-container"),i=n.parent(".grav-editor-content");i.addClass("is-active"),r.navigation.find(".grav-editor-actions").css("visibility","visible"),e&&e.removeClass("is-active")})}}},{preview:{identifier:"preview",title:o.translations.PLUGIN_ADMIN.PREVIEW,label:' ',
+modes:["gfm","markdown"],action:function(e){var t=(e.codemirror,e.button),n=e.textarea,r=e.ui;"preview"===n.data("grav-editor-mode")&&t.addClass("editor-active"),t.on("click.states.preview",function(){var e=n.data("grav-editor-preview-container"),i=n.parent(".grav-editor-content");t.siblings().removeClass("editor-active"),t.addClass("editor-active"),n.data("grav-editor-mode","preview"),e||(e=(0,a.default)('
'),i.after(e),n.data("grav-editor-preview-container",e)),e.css({height:i.height()}),e.addClass("is-active"),i.removeClass("is-active"),r.navigation.find(".grav-editor-actions").css("visibility","hidden");var s=n.data("grav-urlpreview")+"/task"+o.config.param_sep+"processmarkdown",u=n.closest("form").serializeArray(),c={};u.map(function(e){c[e.name]=e.value}),(0,l.default)(s,{method:"post",body:c},function(t){return e.html(t.preview)})})}}},{fullscreen:{identifier:"fullscreen",title:o.translations.PLUGIN_ADMIN.FULLSCREEN,label:' ',action:function(t){var n=t.codemirror,r=t.button,i=t.textarea;r.on("click.editor.fullscreen",function(){var t=i.closest(".grav-editor"),r=n.getWrapperElement(),o=(0,a.default)(".content-wrapper");if(t.hasClass("grav-editor-fullscreen")){e.document.documentElement.style.overflow="";var s=i.data("fullScreenRestore");r.style.width=s.width,r.style.height=s.height,e.scrollTo(s.scrollLeft,s.scrollTop);var l=t.find(".grav-editor-toolbar .hint--bottom");l&&(l.removeClass("hint--bottom").addClass("hint--top"),(0,a.default)(l[l.length-1]).removeClass("hint--bottom-left")),o&&o.css("overflow","auto")}else{i.data("fullScreenRestore",{scrollTop:e.pageYOffset,scrollLeft:e.pageXOffset,width:r.style.width,height:r.style.height}),r.style.width="",r.style.height=i.parent(".grav-editor-content").height()+"px",e.document.documentElement.style.overflow="hidden";var u=t.find(".grav-editor-toolbar .hint--top");u&&(u.removeClass("hint--top").addClass("hint--bottom"),(0,a.default)(u[u.length-1]).addClass("hint--bottom-left")),o&&o.css("overflow","visible")}t.toggleClass("grav-editor-fullscreen"),setTimeout(function(){n.refresh(),(0,a.default)(e).trigger("resize")},5)})}}}]}}).call(t,function(){return this}())},,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,function(e,t,n){!function(e){e(n(393))}(function(e){"use strict";function t(e){for(var t={},n=0;n*\/]/.test(n)?r(null,"select-op"):"."==n&&e.match(/^-?[_a-z][_a-z0-9-]*/i)?r("qualifier","qualifier"):/[:;{}\[\]\(\)]/.test(n)?r(null,n):("u"==n||"U"==n)&&e.match(/rl(-prefix)?\(/i)||("d"==n||"D"==n)&&e.match("omain(",!0,!0)||("r"==n||"R"==n)&&e.match("egexp(",!0,!0)?(e.backUp(1),t.tokenize=o,r("property","word")):/[\w\\\-]/.test(n)?(e.eatWhile(/[\w\\\-]/),r("property","word")):r(null,null):/[\d.]/.test(e.peek())?(e.eatWhile(/[\w.%]/),r("number","unit")):e.match(/^-[\w\\\-]+/)?(e.eatWhile(/[\w\\\-]/),e.match(/^\s*:/,!1)?r("variable-2","variable-definition"):r("variable-2","variable")):e.match(/^\w+-/)?r("meta","meta"):void 0}function a(e){return function(t,n){for(var i,a=!1;null!=(i=t.next());){if(i==e&&!a){")"==e&&t.backUp(1);break}a=!a&&"\\"==i}return(i==e||!a&&")"!=e)&&(n.tokenize=null),r("string","string")}}function o(e,t){return e.next(),e.match(/\s*[\"\')]/,!1)?t.tokenize=null:t.tokenize=a(")"),r(null,"(")}function s(e,t,n){this.type=e,this.indent=t,this.prev=n}function l(e,t,n,r){return e.context=new s(n,t.indentation()+(r===!1?0:v),e.context),n}function u(e){return e.context.prev&&(e.context=e.context.prev),e.context.type}function c(e,t,n){return T[n.context.type](e,t,n)}function f(e,t,n,r){for(var i=r||1;i>0;i--)n.context=n.context.prev;return c(e,t,n)}function d(e){var t=e.current().toLowerCase();m=A.hasOwnProperty(t)?"atom":E.hasOwnProperty(t)?"keyword":"variable"}var p=n.inline;n.propertyKeywords||(n=e.resolveMode("text/css"));var h,m,v=t.indentUnit,g=n.tokenHooks,y=n.documentTypes||{},_=n.mediaTypes||{},b=n.mediaFeatures||{},w=n.mediaValueKeywords||{},x=n.propertyKeywords||{},k=n.nonStandardPropertyKeywords||{},S=n.fontProperties||{},C=n.counterDescriptors||{},E=n.colorKeywords||{},A=n.valueKeywords||{},I=n.allowNested,M=n.lineComment,O=n.supportsAtComponent===!0,T={};return T.top=function(e,t,n){if("{"==e)return l(n,t,"block");if("}"==e&&n.context.prev)return u(n);if(O&&/@component/i.test(e))return l(n,t,"atComponentBlock");if(/^@(-moz-)?document$/i.test(e))return l(n,t,"documentTypes");if(/^@(media|supports|(-moz-)?document|import)$/i.test(e))return l(n,t,"atBlock");if(/^@(font-face|counter-style)/i.test(e))return n.stateArg=e,"restricted_atBlock_before";if(/^@(-(moz|ms|o|webkit)-)?keyframes$/i.test(e))return"keyframes";if(e&&"@"==e.charAt(0))return l(n,t,"at");if("hash"==e)m="builtin";else if("word"==e)m="tag";else{if("variable-definition"==e)return"maybeprop";if("interpolation"==e)return l(n,t,"interpolation");if(":"==e)return"pseudo";if(I&&"("==e)return l(n,t,"parens")}return n.context.type},T.block=function(e,t,n){if("word"==e){var r=t.current().toLowerCase();return x.hasOwnProperty(r)?(m="property","maybeprop"):k.hasOwnProperty(r)?(m="string-2","maybeprop"):I?(m=t.match(/^\s*:(?:\s|$)/,!1)?"property":"tag","block"):(m+=" error","maybeprop")}return"meta"==e?"block":I||"hash"!=e&&"qualifier"!=e?T.top(e,t,n):(m="error","block")},T.maybeprop=function(e,t,n){return":"==e?l(n,t,"prop"):c(e,t,n)},T.prop=function(e,t,n){if(";"==e)return u(n);if("{"==e&&I)return l(n,t,"propBlock");if("}"==e||"{"==e)return f(e,t,n);if("("==e)return l(n,t,"parens");if("hash"!=e||/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(t.current())){if("word"==e)d(t);else if("interpolation"==e)return l(n,t,"interpolation")}else m+=" error";return"prop"},T.propBlock=function(e,t,n){return"}"==e?u(n):"word"==e?(m="property","maybeprop"):n.context.type},T.parens=function(e,t,n){return"{"==e||"}"==e?f(e,t,n):")"==e?u(n):"("==e?l(n,t,"parens"):"interpolation"==e?l(n,t,"interpolation"):("word"==e&&d(t),"parens")},T.pseudo=function(e,t,n){return"meta"==e?"pseudo":"word"==e?(m="variable-3",n.context.type):c(e,t,n)},T.documentTypes=function(e,t,n){return"word"==e&&y.hasOwnProperty(t.current())?(m="tag",n.context.type):T.atBlock(e,t,n)},T.atBlock=function(e,t,n){if("("==e)return l(n,t,"atBlock_parens");if("}"==e||";"==e)return f(e,t,n);if("{"==e)return u(n)&&l(n,t,I?"block":"top");if("interpolation"==e)return l(n,t,"interpolation");if("word"==e){var r=t.current().toLowerCase();m="only"==r||"not"==r||"and"==r||"or"==r?"keyword":_.hasOwnProperty(r)?"attribute":b.hasOwnProperty(r)?"property":w.hasOwnProperty(r)?"keyword":x.hasOwnProperty(r)?"property":k.hasOwnProperty(r)?"string-2":A.hasOwnProperty(r)?"atom":E.hasOwnProperty(r)?"keyword":"error"}return n.context.type},T.atComponentBlock=function(e,t,n){return"}"==e?f(e,t,n):"{"==e?u(n)&&l(n,t,I?"block":"top",!1):("word"==e&&(m="error"),n.context.type)},T.atBlock_parens=function(e,t,n){return")"==e?u(n):"{"==e||"}"==e?f(e,t,n,2):T.atBlock(e,t,n)},T.restricted_atBlock_before=function(e,t,n){return"{"==e?l(n,t,"restricted_atBlock"):"word"==e&&"@counter-style"==n.stateArg?(m="variable","restricted_atBlock_before"):c(e,t,n)},T.restricted_atBlock=function(e,t,n){return"}"==e?(n.stateArg=null,u(n)):"word"==e?(m="@font-face"==n.stateArg&&!S.hasOwnProperty(t.current().toLowerCase())||"@counter-style"==n.stateArg&&!C.hasOwnProperty(t.current().toLowerCase())?"error":"property","maybeprop"):"restricted_atBlock"},T.keyframes=function(e,t,n){return"word"==e?(m="variable","keyframes"):"{"==e?l(n,t,"top"):c(e,t,n)},T.at=function(e,t,n){return";"==e?u(n):"{"==e||"}"==e?f(e,t,n):("word"==e?m="tag":"hash"==e&&(m="builtin"),"at")},T.interpolation=function(e,t,n){return"}"==e?u(n):"{"==e||";"==e?f(e,t,n):("word"==e?m="variable":"variable"!=e&&"("!=e&&")"!=e&&(m="error"),"interpolation")},{startState:function(e){return{tokenize:null,state:p?"block":"top",stateArg:null,context:new s(p?"block":"top",e||0,null)}},token:function(e,t){if(!t.tokenize&&e.eatSpace())return null;var n=(t.tokenize||i)(e,t);return n&&"object"==typeof n&&(h=n[1],n=n[0]),m=n,"comment"!=h&&(t.state=T[t.state](h,e,t)),m},indent:function(e,t){var n=e.context,r=t&&t.charAt(0),i=n.indent;return"prop"!=n.type||"}"!=r&&")"!=r||(n=n.prev),n.prev&&("}"!=r||"block"!=n.type&&"top"!=n.type&&"interpolation"!=n.type&&"restricted_atBlock"!=n.type?(")"!=r||"parens"!=n.type&&"atBlock_parens"!=n.type)&&("{"!=r||"at"!=n.type&&"atBlock"!=n.type)||(i=Math.max(0,n.indent-v)):(n=n.prev,i=n.indent)),i},electricChars:"}",blockCommentStart:"/*",blockCommentEnd:"*/",blockCommentContinue:" * ",lineComment:M,fold:"brace"}});var r=["domain","regexp","url","url-prefix"],i=t(r),a=["all","aural","braille","handheld","print","projection","screen","tty","tv","embossed"],o=t(a),s=["width","min-width","max-width","height","min-height","max-height","device-width","min-device-width","max-device-width","device-height","min-device-height","max-device-height","aspect-ratio","min-aspect-ratio","max-aspect-ratio","device-aspect-ratio","min-device-aspect-ratio","max-device-aspect-ratio","color","min-color","max-color","color-index","min-color-index","max-color-index","monochrome","min-monochrome","max-monochrome","resolution","min-resolution","max-resolution","scan","grid","orientation","device-pixel-ratio","min-device-pixel-ratio","max-device-pixel-ratio","pointer","any-pointer","hover","any-hover"],l=t(s),u=["landscape","portrait","none","coarse","fine","on-demand","hover","interlace","progressive"],c=t(u),f=["align-content","align-items","align-self","alignment-adjust","alignment-baseline","anchor-point","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","appearance","azimuth","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","baseline-shift","binding","bleed","bookmark-label","bookmark-level","bookmark-state","bookmark-target","border","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","color","color-profile","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","content","counter-increment","counter-reset","crop","cue","cue-after","cue-before","cursor","direction","display","dominant-baseline","drop-initial-after-adjust","drop-initial-after-align","drop-initial-before-adjust","drop-initial-before-align","drop-initial-size","drop-initial-value","elevation","empty-cells","fit","fit-position","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","float-offset","flow-from","flow-into","font","font-feature-settings","font-family","font-kerning","font-language-override","font-size","font-size-adjust","font-stretch","font-style","font-synthesis","font-variant","font-variant-alternates","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-weight","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-gap","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-gap","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","inline-box-align","justify-content","justify-items","justify-self","left","letter-spacing","line-break","line-height","line-stacking","line-stacking-ruby","line-stacking-shift","line-stacking-strategy","list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom","margin-left","margin-right","margin-top","marks","marquee-direction","marquee-loop","marquee-play-count","marquee-speed","marquee-style","max-height","max-width","min-height","min-width","move-to","nav-down","nav-index","nav-left","nav-right","nav-up","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-style","overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right","padding-top","page","page-break-after","page-break-before","page-break-inside","page-policy","pause","pause-after","pause-before","perspective","perspective-origin","pitch","pitch-range","place-content","place-items","place-self","play-during","position","presentation-level","punctuation-trim","quotes","region-break-after","region-break-before","region-break-inside","region-fragment","rendering-intent","resize","rest","rest-after","rest-before","richness","right","rotation","rotation-point","ruby-align","ruby-overhang","ruby-position","ruby-span","shape-image-threshold","shape-inside","shape-margin","shape-outside","size","speak","speak-as","speak-header","speak-numeral","speak-punctuation","speech-rate","stress","string-set","tab-size","table-layout","target","target-name","target-new","target-position","text-align","text-align-last","text-decoration","text-decoration-color","text-decoration-line","text-decoration-skip","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-height","text-indent","text-justify","text-outline","text-overflow","text-shadow","text-size-adjust","text-space-collapse","text-transform","text-underline-position","text-wrap","top","transform","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","user-select","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","z-index","clip-path","clip-rule","mask","enable-background","filter","flood-color","flood-opacity","lighting-color","stop-color","stop-opacity","pointer-events","color-interpolation","color-interpolation-filters","color-rendering","fill","fill-opacity","fill-rule","image-rendering","marker","marker-end","marker-mid","marker-start","shape-rendering","stroke","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","text-rendering","baseline-shift","dominant-baseline","glyph-orientation-horizontal","glyph-orientation-vertical","text-anchor","writing-mode"],d=t(f),p=["scrollbar-arrow-color","scrollbar-base-color","scrollbar-dark-shadow-color","scrollbar-face-color","scrollbar-highlight-color","scrollbar-shadow-color","scrollbar-3d-light-color","scrollbar-track-color","shape-inside","searchfield-cancel-button","searchfield-decoration","searchfield-results-button","searchfield-results-decoration","zoom"],h=t(p),m=["font-family","src","unicode-range","font-variant","font-feature-settings","font-stretch","font-weight","font-style"],v=t(m),g=["additive-symbols","fallback","negative","pad","prefix","range","speak-as","suffix","symbols","system"],y=t(g),_=["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","indianred","indigo","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","snow","springgreen","steelblue","tan","teal","thistle","tomato","turquoise","violet","wheat","white","whitesmoke","yellow","yellowgreen"],b=t(_),w=["above","absolute","activeborder","additive","activecaption","afar","after-white-space","ahead","alias","all","all-scroll","alphabetic","alternate","always","amharic","amharic-abegede","antialiased","appworkspace","arabic-indic","armenian","asterisks","attr","auto","auto-flow","avoid","avoid-column","avoid-page","avoid-region","background","backwards","baseline","below","bidi-override","binary","bengali","blink","block","block-axis","bold","bolder","border","border-box","both","bottom","break","break-all","break-word","bullets","button","button-bevel","buttonface","buttonhighlight","buttonshadow","buttontext","calc","cambodian","capitalize","caps-lock-indicator","caption","captiontext","caret","cell","center","checkbox","circle","cjk-decimal","cjk-earthly-branch","cjk-heavenly-stem","cjk-ideographic","clear","clip","close-quote","col-resize","collapse","color","color-burn","color-dodge","column","column-reverse","compact","condensed","contain","content","contents","content-box","context-menu","continuous","copy","counter","counters","cover","crop","cross","crosshair","currentcolor","cursive","cyclic","darken","dashed","decimal","decimal-leading-zero","default","default-button","dense","destination-atop","destination-in","destination-out","destination-over","devanagari","difference","disc","discard","disclosure-closed","disclosure-open","document","dot-dash","dot-dot-dash","dotted","double","down","e-resize","ease","ease-in","ease-in-out","ease-out","element","ellipse","ellipsis","embed","end","ethiopic","ethiopic-abegede","ethiopic-abegede-am-et","ethiopic-abegede-gez","ethiopic-abegede-ti-er","ethiopic-abegede-ti-et","ethiopic-halehame-aa-er","ethiopic-halehame-aa-et","ethiopic-halehame-am-et","ethiopic-halehame-gez","ethiopic-halehame-om-et","ethiopic-halehame-sid-et","ethiopic-halehame-so-et","ethiopic-halehame-ti-er","ethiopic-halehame-ti-et","ethiopic-halehame-tig","ethiopic-numeric","ew-resize","exclusion","expanded","extends","extra-condensed","extra-expanded","fantasy","fast","fill","fixed","flat","flex","flex-end","flex-start","footnotes","forwards","from","geometricPrecision","georgian","graytext","grid","groove","gujarati","gurmukhi","hand","hangul","hangul-consonant","hard-light","hebrew","help","hidden","hide","higher","highlight","highlighttext","hiragana","hiragana-iroha","horizontal","hsl","hsla","hue","icon","ignore","inactiveborder","inactivecaption","inactivecaptiontext","infinite","infobackground","infotext","inherit","initial","inline","inline-axis","inline-block","inline-flex","inline-grid","inline-table","inset","inside","intrinsic","invert","italic","japanese-formal","japanese-informal","justify","kannada","katakana","katakana-iroha","keep-all","khmer","korean-hangul-formal","korean-hanja-formal","korean-hanja-informal","landscape","lao","large","larger","left","level","lighter","lighten","line-through","linear","linear-gradient","lines","list-item","listbox","listitem","local","logical","loud","lower","lower-alpha","lower-armenian","lower-greek","lower-hexadecimal","lower-latin","lower-norwegian","lower-roman","lowercase","ltr","luminosity","malayalam","match","matrix","matrix3d","media-controls-background","media-current-time-display","media-fullscreen-button","media-mute-button","media-play-button","media-return-to-realtime-button","media-rewind-button","media-seek-back-button","media-seek-forward-button","media-slider","media-sliderthumb","media-time-remaining-display","media-volume-slider","media-volume-slider-container","media-volume-sliderthumb","medium","menu","menulist","menulist-button","menulist-text","menulist-textfield","menutext","message-box","middle","min-intrinsic","mix","mongolian","monospace","move","multiple","multiply","myanmar","n-resize","narrower","ne-resize","nesw-resize","no-close-quote","no-drop","no-open-quote","no-repeat","none","normal","not-allowed","nowrap","ns-resize","numbers","numeric","nw-resize","nwse-resize","oblique","octal","opacity","open-quote","optimizeLegibility","optimizeSpeed","oriya","oromo","outset","outside","outside-shape","overlay","overline","padding","padding-box","painted","page","paused","persian","perspective","plus-darker","plus-lighter","pointer","polygon","portrait","pre","pre-line","pre-wrap","preserve-3d","progress","push-button","radial-gradient","radio","read-only","read-write","read-write-plaintext-only","rectangle","region","relative","repeat","repeating-linear-gradient","repeating-radial-gradient","repeat-x","repeat-y","reset","reverse","rgb","rgba","ridge","right","rotate","rotate3d","rotateX","rotateY","rotateZ","round","row","row-resize","row-reverse","rtl","run-in","running","s-resize","sans-serif","saturation","scale","scale3d","scaleX","scaleY","scaleZ","screen","scroll","scrollbar","scroll-position","se-resize","searchfield","searchfield-cancel-button","searchfield-decoration","searchfield-results-button","searchfield-results-decoration","self-start","self-end","semi-condensed","semi-expanded","separate","serif","show","sidama","simp-chinese-formal","simp-chinese-informal","single","skew","skewX","skewY","skip-white-space","slide","slider-horizontal","slider-vertical","sliderthumb-horizontal","sliderthumb-vertical","slow","small","small-caps","small-caption","smaller","soft-light","solid","somali","source-atop","source-in","source-out","source-over","space","space-around","space-between","space-evenly","spell-out","square","square-button","start","static","status-bar","stretch","stroke","sub","subpixel-antialiased","super","sw-resize","symbolic","symbols","system-ui","table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row","table-row-group","tamil","telugu","text","text-bottom","text-top","textarea","textfield","thai","thick","thin","threeddarkshadow","threedface","threedhighlight","threedlightshadow","threedshadow","tibetan","tigre","tigrinya-er","tigrinya-er-abegede","tigrinya-et","tigrinya-et-abegede","to","top","trad-chinese-formal","trad-chinese-informal","transform","translate","translate3d","translateX","translateY","translateZ","transparent","ultra-condensed","ultra-expanded","underline","unset","up","upper-alpha","upper-armenian","upper-greek","upper-hexadecimal","upper-latin","upper-norwegian","upper-roman","uppercase","urdu","url","var","vertical","vertical-text","visible","visibleFill","visiblePainted","visibleStroke","visual","w-resize","wait","wave","wider","window","windowframe","windowtext","words","wrap","wrap-reverse","x-large","x-small","xor","xx-large","xx-small"],x=t(w),k=r.concat(a).concat(s).concat(u).concat(f).concat(p).concat(_).concat(w);e.registerHelper("hintWords","css",k),e.defineMIME("text/css",{documentTypes:i,mediaTypes:o,mediaFeatures:l,mediaValueKeywords:c,propertyKeywords:d,nonStandardPropertyKeywords:h,fontProperties:v,counterDescriptors:y,colorKeywords:b,valueKeywords:x,tokenHooks:{"/":function(e,t){return!!e.eat("*")&&(t.tokenize=n,n(e,t))}},name:"css"}),e.defineMIME("text/x-scss",{mediaTypes:o,mediaFeatures:l,mediaValueKeywords:c,propertyKeywords:d,nonStandardPropertyKeywords:h,colorKeywords:b,valueKeywords:x,fontProperties:v,allowNested:!0,lineComment:"//",tokenHooks:{"/":function(e,t){return e.eat("/")?(e.skipToEnd(),["comment","comment"]):e.eat("*")?(t.tokenize=n,n(e,t)):["operator","operator"]},":":function(e){return!!e.match(/\s*\{/,!1)&&[null,null]},$:function(e){return e.match(/^[\w-]+/),e.match(/^\s*:/,!1)?["variable-2","variable-definition"]:["variable-2","variable"]},"#":function(e){return!!e.eat("{")&&[null,"interpolation"]}},name:"css",helperType:"scss"}),e.defineMIME("text/x-less",{mediaTypes:o,mediaFeatures:l,mediaValueKeywords:c,propertyKeywords:d,nonStandardPropertyKeywords:h,colorKeywords:b,valueKeywords:x,fontProperties:v,allowNested:!0,lineComment:"//",tokenHooks:{"/":function(e,t){return e.eat("/")?(e.skipToEnd(),["comment","comment"]):e.eat("*")?(t.tokenize=n,n(e,t)):["operator","operator"]},"@":function(e){return e.eat("{")?[null,"interpolation"]:!e.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/i,!1)&&(e.eatWhile(/[\w\\\-]/),e.match(/^\s*:/,!1)?["variable-2","variable-definition"]:["variable-2","variable"])},"&":function(){return["atom","atom"]}},name:"css",helperType:"less"}),e.defineMIME("text/x-gss",{documentTypes:i,mediaTypes:o,mediaFeatures:l,propertyKeywords:d,nonStandardPropertyKeywords:h,fontProperties:v,counterDescriptors:y,colorKeywords:b,valueKeywords:x,supportsAtComponent:!0,tokenHooks:{"/":function(e,t){return!!e.eat("*")&&(t.tokenize=n,n(e,t))}},name:"css",helperType:"gss"})})},function(e,t,n){!function(e){e(n(393),n(432),n(435))}(function(e){"use strict";var t=/^((?:(?:aaas?|about|acap|adiumxtra|af[ps]|aim|apt|attachment|aw|beshare|bitcoin|bolo|callto|cap|chrome(?:-extension)?|cid|coap|com-eventbrite-attendee|content|crid|cvs|data|dav|dict|dlna-(?:playcontainer|playsingle)|dns|doi|dtn|dvb|ed2k|facetime|feed|file|finger|fish|ftp|geo|gg|git|gizmoproject|go|gopher|gtalk|h323|hcp|https?|iax|icap|icon|im|imap|info|ipn|ipp|irc[6s]?|iris(?:\.beep|\.lwz|\.xpc|\.xpcs)?|itms|jar|javascript|jms|keyparc|lastfm|ldaps?|magnet|mailto|maps|market|message|mid|mms|ms-help|msnim|msrps?|mtqp|mumble|mupdate|mvn|news|nfs|nih?|nntp|notes|oid|opaquelocktoken|palm|paparazzi|platform|pop|pres|proxy|psyc|query|res(?:ource)?|rmi|rsync|rtmp|rtsp|secondlife|service|session|sftp|sgn|shttp|sieve|sips?|skype|sm[bs]|snmp|soap\.beeps?|soldat|spotify|ssh|steam|svn|tag|teamspeak|tel(?:net)?|tftp|things|thismessage|tip|tn3270|tv|udp|unreal|urn|ut2004|vemmi|ventrilo|view-source|webcal|wss?|wtai|wyciwyg|xcon(?:-userid)?|xfire|xmlrpc\.beeps?|xmpp|xri|ymsgr|z39\.50[rs]?):(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`*!()\[\]{};:'".,<>?«»“”‘’]))/i;e.defineMode("gfm",function(n,r){function i(e){return e.code=!1,null}var a=0,o={startState:function(){return{code:!1,codeBlock:!1,ateSpace:!1}},copyState:function(e){return{code:e.code,codeBlock:e.codeBlock,ateSpace:e.ateSpace}},token:function(e,n){if(n.combineTokens=null,n.codeBlock)return e.match(/^```+/)?(n.codeBlock=!1,null):(e.skipToEnd(),null);if(e.sol()&&(n.code=!1),e.sol()&&e.match(/^```+/))return e.skipToEnd(),n.codeBlock=!0,null;if("`"===e.peek()){e.next();var i=e.pos;e.eatWhile("`");var o=1+e.pos-i;return n.code?o===a&&(n.code=!1):(a=o,n.code=!0),null}if(n.code)return e.next(),null;if(e.eatSpace())return n.ateSpace=!0,null;if((e.sol()||n.ateSpace)&&(n.ateSpace=!1,r.gitHubSpice!==!1)){if(e.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?=.{0,6}\d)(?:[a-f0-9]{7,40}\b)/))return n.combineTokens=!0,"link";if(e.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/))return n.combineTokens=!0,"link"}return e.match(t)&&"]("!=e.string.slice(e.start-2,e.start)&&(0==e.start||/\W/.test(e.string.charAt(e.start-1)))?(n.combineTokens=!0,"link"):(e.next(),null)},blankLine:i},s={taskLists:!0,strikethrough:!0,emoji:!0};for(var l in r)s[l]=r[l];return s.name="markdown",e.overlayMode(e.getMode(n,s),o)},"markdown"),e.defineMIME("text/x-gfm","gfm")})},function(e,t,n){!function(e){e(n(393),n(433),n(434))}(function(e){"use strict";e.defineMode("markdown",function(t,n){function r(n){if(e.findModeByName){var r=e.findModeByName(n);r&&(n=r.mime||r.mimes[0])}var i=e.getMode(t,n);return"null"==i.name?null:i}function i(e,t,n){return t.f=t.inline=n,n(e,t)}function a(e,t,n){return t.f=t.block=n,n(e,t)}function o(e){return!e||!/\S/.test(e.string)}function s(t){if(t.linkTitle=!1,t.linkHref=!1,t.linkText=!1,t.em=!1,t.strong=!1,t.strikethrough=!1,t.quote=0,t.indentedCode=!1,t.f==u){var n=w;if(!n){var r=e.innerMode(b,t.htmlState);n="xml"==r.mode.name&&null===r.state.tagStart&&!r.state.context&&r.state.tokenize.isInText}n&&(t.f=p,t.block=l,t.htmlState=null)}return t.trailingSpace=0,t.trailingSpaceNewLine=!1,t.prevLine=t.thisLine,t.thisLine={stream:null},null}function l(t,a){var s=t.column()===a.indentation,l=o(a.prevLine.stream),u=a.indentedCode,d=a.prevLine.hr,p=a.list!==!1,h=(a.listStack[a.listStack.length-1]||0)+3;a.indentedCode=!1;var m=a.indentation;if(null===a.indentationDiff&&(a.indentationDiff=a.indentation,p)){for(a.em=!1,a.strong=!1,a.code=!1,a.strikethrough=!1,a.list=null;m=4&&(u||a.prevLine.fencedCodeEnd||a.prevLine.header||l))return t.skipToEnd(),a.indentedCode=!0,x.code;if(t.eatSpace())return null;if(s&&a.indentation<=h&&(_=t.match(A))&&_[1].length<=6)return a.quote=0,a.header=_[1].length,a.thisLine.header=!0,n.highlightFormatting&&(a.formatting="header"),a.f=a.inline,f(a);if(a.indentation<=h&&t.eat(">"))return a.quote=s?1:a.quote+1,n.highlightFormatting&&(a.formatting="quote"),t.eatSpace(),f(a);if(!y&&!a.setext&&s&&a.indentation<=h&&(_=t.match(C))){var b=_[1]?"ol":"ul";return a.indentation=m+t.current().length,a.list=!0,a.quote=0,a.listStack.push(a.indentation),n.taskLists&&t.match(E,!1)&&(a.taskList=!0),a.f=a.inline,n.highlightFormatting&&(a.formatting=["list","list-"+b]),f(a)}return s&&a.indentation<=h&&(_=t.match(O,!0))?(a.quote=0,a.fencedEndRE=new RegExp(_[1]+"+ *$"),a.localMode=n.fencedCodeBlockHighlighting&&r(_[2]),a.localMode&&(a.localState=e.startState(a.localMode)),a.f=a.block=c,n.highlightFormatting&&(a.formatting="code-block"),a.code=-1,f(a)):a.setext||!(v&&p||a.quote||a.list!==!1||a.code||y||T.test(t.string))&&(_=t.lookAhead(1))&&(_=_.match(I))?(a.setext?(a.header=a.setext,
+a.setext=0,t.skipToEnd(),n.highlightFormatting&&(a.formatting="header")):(a.header="="==_[0].charAt(0)?1:2,a.setext=a.header),a.thisLine.header=!0,a.f=a.inline,f(a)):y?(t.skipToEnd(),a.hr=!0,a.thisLine.hr=!0,x.hr):"["===t.peek()?i(t,a,g):i(t,a,a.inline)}function u(t,n){var r=b.token(t,n.htmlState);if(!w){var i=e.innerMode(b,n.htmlState);("xml"==i.mode.name&&null===i.state.tagStart&&!i.state.context&&i.state.tokenize.isInText||n.md_inside&&t.current().indexOf(">")>-1)&&(n.f=p,n.block=l,n.htmlState=null)}return r}function c(e,t){var r=t.listStack[t.listStack.length-1]||0,i=t.indentation=e.quote?t.push(x.formatting+"-"+e.formatting[r]+"-"+e.quote):t.push("error"))}if(e.taskOpen)return t.push("meta"),t.length?t.join(" "):null;if(e.taskClosed)return t.push("property"),t.length?t.join(" "):null;if(e.linkHref?t.push(x.linkHref,"url"):(e.strong&&t.push(x.strong),e.em&&t.push(x.em),e.strikethrough&&t.push(x.strikethrough),e.emoji&&t.push(x.emoji),e.linkText&&t.push(x.linkText),e.code&&t.push(x.code),e.image&&t.push(x.image),e.imageAltText&&t.push(x.imageAltText,"link"),e.imageMarker&&t.push(x.imageMarker)),e.header&&t.push(x.header,x.header+"-"+e.header),e.quote&&(t.push(x.quote),!n.maxBlockquoteDepth||n.maxBlockquoteDepth>=e.quote?t.push(x.quote+"-"+e.quote):t.push(x.quote+"-"+n.maxBlockquoteDepth)),e.list!==!1){var i=(e.listStack.length-1)%3;i?1===i?t.push(x.list2):t.push(x.list3):t.push(x.list1)}return e.trailingSpaceNewLine?t.push("trailing-space-new-line"):e.trailingSpace&&t.push("trailing-space-"+(e.trailingSpace%2?"a":"b")),t.length?t.join(" "):null}function d(e,t){if(e.match(M,!0))return f(t)}function p(t,r){var i=r.text(t,r);if("undefined"!=typeof i)return i;if(r.list)return r.list=null,f(r);if(r.taskList){var o=" "===t.match(E,!0)[1];return o?r.taskOpen=!0:r.taskClosed=!0,n.highlightFormatting&&(r.formatting="task"),r.taskList=!1,f(r)}if(r.taskOpen=!1,r.taskClosed=!1,r.header&&t.match(/^#+$/,!0))return n.highlightFormatting&&(r.formatting="header"),f(r);var s=t.next();if(r.linkTitle){r.linkTitle=!1;var l=s;"("===s&&(l=")"),l=(l+"").replace(/([.?*+^\[\]\\(){}|-])/g,"\\$1");var c="^\\s*(?:[^"+l+"\\\\]+|\\\\\\\\|\\\\.)"+l;if(t.match(new RegExp(c),!0))return x.linkHref}if("`"===s){var d=r.formatting;n.highlightFormatting&&(r.formatting="code"),t.eatWhile("`");var v=t.current().length;if(0!=r.code||r.quote&&1!=v){if(v==r.code){var g=f(r);return r.code=0,g}return r.formatting=d,f(r)}return r.code=v,f(r)}if(r.code)return f(r);if("\\"===s&&(t.next(),n.highlightFormatting)){var y=f(r),_=x.formatting+"-escape";return y?y+" "+_:_}if("!"===s&&t.match(/\[[^\]]*\] ?(?:\(|\[)/,!1))return r.imageMarker=!0,r.image=!0,n.highlightFormatting&&(r.formatting="image"),f(r);if("["===s&&r.imageMarker&&t.match(/[^\]]*\](\(.*?\)| ?\[.*?\])/,!1))return r.imageMarker=!1,r.imageAltText=!0,n.highlightFormatting&&(r.formatting="image"),f(r);if("]"===s&&r.imageAltText){n.highlightFormatting&&(r.formatting="image");var y=f(r);return r.imageAltText=!1,r.image=!1,r.inline=r.f=m,y}if("["===s&&!r.image)return r.linkText&&t.match(/^.*?\]/)?f(r):(r.linkText=!0,n.highlightFormatting&&(r.formatting="link"),f(r));if("]"===s&&r.linkText){n.highlightFormatting&&(r.formatting="link");var y=f(r);return r.linkText=!1,r.inline=r.f=t.match(/\(.*?\)| ?\[.*?\]/,!1)?m:p,y}if("<"===s&&t.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/,!1)){r.f=r.inline=h,n.highlightFormatting&&(r.formatting="link");var y=f(r);return y?y+=" ":y="",y+x.linkInline}if("<"===s&&t.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/,!1)){r.f=r.inline=h,n.highlightFormatting&&(r.formatting="link");var y=f(r);return y?y+=" ":y="",y+x.linkEmail}if(n.xml&&"<"===s&&t.match(/^(!--|\?|!\[CDATA\[|[a-z][a-z0-9-]*(?:\s+[a-z_:.\-]+(?:\s*=\s*[^>]+)?)*\s*(?:>|$))/i,!1)){var w=t.string.indexOf(">",t.pos);if(w!=-1){var k=t.string.substring(t.start,w);/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(k)&&(r.md_inside=!0)}return t.backUp(1),r.htmlState=e.startState(b),a(t,r,u)}if(n.xml&&"<"===s&&t.match(/^\/\w*?>/))return r.md_inside=!1,"tag";if("*"===s||"_"===s){for(var S=1,C=1==t.pos?" ":t.string.charAt(t.pos-2);S<3&&t.eat(s);)S++;var A=t.peek()||" ",I=!/\s/.test(A)&&(!P.test(A)||/\s/.test(C)||P.test(C)),M=!/\s/.test(C)&&(!P.test(C)||/\s/.test(A)||P.test(A)),O=null,T=null;if(S%2&&(r.em||!I||"*"!==s&&M&&!P.test(C)?r.em!=s||!M||"*"!==s&&I&&!P.test(A)||(O=!1):O=!0),S>1&&(r.strong||!I||"*"!==s&&M&&!P.test(C)?r.strong!=s||!M||"*"!==s&&I&&!P.test(A)||(T=!1):T=!0),null!=T||null!=O){n.highlightFormatting&&(r.formatting=null==O?"strong":null==T?"em":"strong em"),O===!0&&(r.em=s),T===!0&&(r.strong=s);var g=f(r);return O===!1&&(r.em=!1),T===!1&&(r.strong=!1),g}}else if(" "===s&&(t.eat("*")||t.eat("_"))){if(" "===t.peek())return f(r);t.backUp(1)}if(n.strikethrough)if("~"===s&&t.eatWhile(s)){if(r.strikethrough){n.highlightFormatting&&(r.formatting="strikethrough");var g=f(r);return r.strikethrough=!1,g}if(t.match(/^[^\s]/,!1))return r.strikethrough=!0,n.highlightFormatting&&(r.formatting="strikethrough"),f(r)}else if(" "===s&&t.match(/^~~/,!0)){if(" "===t.peek())return f(r);t.backUp(2)}if(n.emoji&&":"===s&&t.match(/^(?:[a-z_\d+][a-z_\d+-]*|\-[a-z_\d+][a-z_\d+-]*):/)){r.emoji=!0,n.highlightFormatting&&(r.formatting="emoji");var j=f(r);return r.emoji=!1,j}return" "===s&&(t.match(/^ +$/,!1)?r.trailingSpace++:r.trailingSpace&&(r.trailingSpaceNewLine=!0)),f(r)}function h(e,t){var r=e.next();if(">"===r){t.f=t.inline=p,n.highlightFormatting&&(t.formatting="link");var i=f(t);return i?i+=" ":i="",i+x.linkInline}return e.match(/^[^>]+/,!0),x.linkInline}function m(e,t){if(e.eatSpace())return null;var r=e.next();return"("===r||"["===r?(t.f=t.inline=v("("===r?")":"]"),n.highlightFormatting&&(t.formatting="link-string"),t.linkHref=!0,f(t)):"error"}function v(e){return function(t,r){var i=t.next();if(i===e){r.f=r.inline=p,n.highlightFormatting&&(r.formatting="link-string");var a=f(r);return r.linkHref=!1,a}return t.match(D[e]),r.linkHref=!0,f(r)}}function g(e,t){return e.match(/^([^\]\\]|\\.)*\]:/,!1)?(t.f=y,e.next(),n.highlightFormatting&&(t.formatting="link"),t.linkText=!0,f(t)):i(e,t,p)}function y(e,t){if(e.match(/^\]:/,!0)){t.f=t.inline=_,n.highlightFormatting&&(t.formatting="link");var r=f(t);return t.linkText=!1,r}return e.match(/^([^\]\\]|\\.)+/,!0),x.linkText}function _(e,t){return e.eatSpace()?null:(e.match(/^[^\s]+/,!0),void 0===e.peek()?t.linkTitle=!0:e.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/,!0),t.f=t.inline=p,x.linkHref+" url")}var b=e.getMode(t,"text/html"),w="null"==b.name;void 0===n.highlightFormatting&&(n.highlightFormatting=!1),void 0===n.maxBlockquoteDepth&&(n.maxBlockquoteDepth=0),void 0===n.taskLists&&(n.taskLists=!1),void 0===n.strikethrough&&(n.strikethrough=!1),void 0===n.emoji&&(n.emoji=!1),void 0===n.fencedCodeBlockHighlighting&&(n.fencedCodeBlockHighlighting=!0),void 0===n.xml&&(n.xml=!0),void 0===n.tokenTypeOverrides&&(n.tokenTypeOverrides={});var x={header:"header",code:"comment",quote:"quote",list1:"variable-2",list2:"variable-3",list3:"keyword",hr:"hr",image:"image",imageAltText:"image-alt-text",imageMarker:"image-marker",formatting:"formatting",linkInline:"link",linkEmail:"link",linkText:"link",linkHref:"string",em:"em",strong:"strong",strikethrough:"strikethrough",emoji:"builtin"};for(var k in x)x.hasOwnProperty(k)&&n.tokenTypeOverrides[k]&&(x[k]=n.tokenTypeOverrides[k]);var S=/^([*\-_])(?:\s*\1){2,}\s*$/,C=/^(?:[*\-+]|^[0-9]+([.)]))\s+/,E=/^\[(x| )\](?=\s)/i,A=n.allowAtxHeaderWithoutSpace?/^(#+)/:/^(#+)(?: |$)/,I=/^ *(?:\={1,}|-{1,})\s*$/,M=/^[^#!\[\]*_\\<>` "'(~:]+/,O=/^(~~~+|```+)[ \t]*([\w+#-]*)[^\n`]*$/,T=/^\s*\[[^\]]+?\]:.*$/,P=/[!\"#$%&\'()*+,\-\.\/:;<=>?@\[\\\]^_`{|}~—]/,j=" ",D={")":/^(?:[^\\\(\)]|\\.|\((?:[^\\\(\)]|\\.)*\))*?(?=\))/,"]":/^(?:[^\\\[\]]|\\.|\[(?:[^\\\[\]]|\\.)*\])*?(?=\])/},z={startState:function(){return{f:l,prevLine:{stream:null},thisLine:{stream:null},block:l,htmlState:null,indentation:0,inline:p,text:d,formatting:!1,linkText:!1,linkHref:!1,linkTitle:!1,code:0,em:!1,strong:!1,header:0,setext:0,hr:!1,taskList:!1,list:!1,listStack:[],quote:0,trailingSpace:0,trailingSpaceNewLine:!1,strikethrough:!1,emoji:!1,fencedEndRE:null}},copyState:function(t){return{f:t.f,prevLine:t.prevLine,thisLine:t.thisLine,block:t.block,htmlState:t.htmlState&&e.copyState(b,t.htmlState),indentation:t.indentation,localMode:t.localMode,localState:t.localMode?e.copyState(t.localMode,t.localState):null,inline:t.inline,text:t.text,formatting:!1,linkText:t.linkText,linkTitle:t.linkTitle,linkHref:t.linkHref,code:t.code,em:t.em,strong:t.strong,strikethrough:t.strikethrough,emoji:t.emoji,header:t.header,setext:t.setext,hr:t.hr,taskList:t.taskList,list:t.list,listStack:t.listStack.slice(0),quote:t.quote,indentedCode:t.indentedCode,trailingSpace:t.trailingSpace,trailingSpaceNewLine:t.trailingSpaceNewLine,md_inside:t.md_inside,fencedEndRE:t.fencedEndRE}},token:function(e,t){if(t.formatting=!1,e!=t.thisLine.stream){if(t.header=0,t.hr=!1,e.match(/^\s*$/,!0))return s(t),null;if(t.prevLine=t.thisLine,t.thisLine={stream:e},t.taskList=!1,t.trailingSpace=0,t.trailingSpaceNewLine=!1,!t.localState&&(t.f=t.block,t.f!=u)){var n=e.match(/^\s*/,!0)[0].replace(/\t/g,j).length;if(t.indentation=n,t.indentationDiff=null,n>0)return null}}return t.f(e,t)},innerMode:function(e){return e.block==u?{state:e.htmlState,mode:b}:e.localState?{state:e.localState,mode:e.localMode}:{state:e,mode:z}},indent:function(t,n,r){return t.block==u&&b.indent?b.indent(t.htmlState,n,r):t.localState&&t.localMode.indent?t.localMode.indent(t.localState,n,r):e.Pass},blankLine:s,getType:f,blockCommentStart:"",closeBrackets:"()[]{}''\"\"``",fold:"markdown"};return z},"xml"),e.defineMIME("text/markdown","markdown"),e.defineMIME("text/x-markdown","markdown")})},function(e,t,n){!function(e){e(n(393))}(function(e){"use strict";var t={autoSelfClosers:{area:!0,base:!0,br:!0,col:!0,command:!0,embed:!0,frame:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0,menuitem:!0},implicitlyClosed:{dd:!0,li:!0,optgroup:!0,option:!0,p:!0,rp:!0,rt:!0,tbody:!0,td:!0,tfoot:!0,th:!0,tr:!0},contextGrabbers:{dd:{dd:!0,dt:!0},dt:{dd:!0,dt:!0},li:{li:!0},option:{option:!0,optgroup:!0},optgroup:{optgroup:!0},p:{address:!0,article:!0,aside:!0,blockquote:!0,dir:!0,div:!0,dl:!0,fieldset:!0,footer:!0,form:!0,h1:!0,h2:!0,h3:!0,h4:!0,h5:!0,h6:!0,header:!0,hgroup:!0,hr:!0,menu:!0,nav:!0,ol:!0,p:!0,pre:!0,section:!0,table:!0,ul:!0},rp:{rp:!0,rt:!0},rt:{rp:!0,rt:!0},tbody:{tbody:!0,tfoot:!0},td:{td:!0,th:!0},tfoot:{tbody:!0},th:{td:!0,th:!0},thead:{tbody:!0,tfoot:!0},tr:{tr:!0}},doNotIndent:{pre:!0},allowUnquoted:!0,allowMissing:!0,caseFold:!0},n={autoSelfClosers:{},implicitlyClosed:{},contextGrabbers:{},doNotIndent:{},allowUnquoted:!1,allowMissing:!1,allowMissingTagName:!1,caseFold:!1};e.defineMode("xml",function(r,i){function a(e,t){function n(n){return t.tokenize=n,n(e,t)}var r=e.next();if("<"==r)return e.eat("!")?e.eat("[")?e.match("CDATA[")?n(l("atom","]]>")):null:e.match("--")?n(l("comment","-->")):e.match("DOCTYPE",!0,!0)?(e.eatWhile(/[\w\._\-]/),n(u(1))):null:e.eat("?")?(e.eatWhile(/[\w\._\-]/),t.tokenize=l("meta","?>"),"meta"):(E=e.eat("/")?"closeTag":"openTag",t.tokenize=o,"tag bracket");if("&"==r){var i;return i=e.eat("#")?e.eat("x")?e.eatWhile(/[a-fA-F\d]/)&&e.eat(";"):e.eatWhile(/[\d]/)&&e.eat(";"):e.eatWhile(/[\w\.\-:]/)&&e.eat(";"),i?"atom":"error"}return e.eatWhile(/[^&<]/),null}function o(e,t){var n=e.next();if(">"==n||"/"==n&&e.eat(">"))return t.tokenize=a,E=">"==n?"endTag":"selfcloseTag","tag bracket";if("="==n)return E="equals",null;if("<"==n){t.tokenize=a,t.state=p,t.tagName=t.tagStart=null;var r=t.tokenize(e,t);return r?r+" tag error":"tag error"}return/[\'\"]/.test(n)?(t.tokenize=s(n),t.stringStartCol=e.column(),t.tokenize(e,t)):(e.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/),"word")}function s(e){var t=function(t,n){for(;!t.eol();)if(t.next()==e){n.tokenize=o;break}return"string"};return t.isInAttribute=!0,t}function l(e,t){return function(n,r){for(;!n.eol();){if(n.match(t)){r.tokenize=a;break}n.next()}return e}}function u(e){return function(t,n){for(var r;null!=(r=t.next());){if("<"==r)return n.tokenize=u(e+1),n.tokenize(t,n);if(">"==r){if(1==e){n.tokenize=a;break}return n.tokenize=u(e-1),n.tokenize(t,n)}}return"meta"}}function c(e,t,n){this.prev=e.context,this.tagName=t,this.indent=e.indented,this.startOfLine=n,(k.doNotIndent.hasOwnProperty(t)||e.context&&e.context.noIndent)&&(this.noIndent=!0)}function f(e){e.context&&(e.context=e.context.prev)}function d(e,t){for(var n;;){if(!e.context)return;if(n=e.context.tagName,!k.contextGrabbers.hasOwnProperty(n)||!k.contextGrabbers[n].hasOwnProperty(t))return;f(e)}}function p(e,t,n){return"openTag"==e?(n.tagStart=t.column(),h):"closeTag"==e?m:p}function h(e,t,n){return"word"==e?(n.tagName=t.current(),A="tag",y):k.allowMissingTagName&&"endTag"==e?(A="tag bracket",y(e,t,n)):(A="error",h)}function m(e,t,n){if("word"==e){var r=t.current();return n.context&&n.context.tagName!=r&&k.implicitlyClosed.hasOwnProperty(n.context.tagName)&&f(n),n.context&&n.context.tagName==r||k.matchClosing===!1?(A="tag",v):(A="tag error",g)}return k.allowMissingTagName&&"endTag"==e?(A="tag bracket",v(e,t,n)):(A="error",g)}function v(e,t,n){return"endTag"!=e?(A="error",v):(f(n),p)}function g(e,t,n){return A="error",v(e,t,n)}function y(e,t,n){if("word"==e)return A="attribute",_;if("endTag"==e||"selfcloseTag"==e){var r=n.tagName,i=n.tagStart;return n.tagName=n.tagStart=null,"selfcloseTag"==e||k.autoSelfClosers.hasOwnProperty(r)?d(n,r):(d(n,r),n.context=new c(n,r,i==n.indented)),p}return A="error",y}function _(e,t,n){return"equals"==e?b:(k.allowMissing||(A="error"),y(e,t,n))}function b(e,t,n){return"string"==e?w:"word"==e&&k.allowUnquoted?(A="string",y):(A="error",y(e,t,n))}function w(e,t,n){return"string"==e?w:y(e,t,n)}var x=r.indentUnit,k={},S=i.htmlMode?t:n;for(var C in S)k[C]=S[C];for(var C in i)k[C]=i[C];var E,A;return a.isInText=!0,{startState:function(e){var t={tokenize:a,state:p,indented:e||0,tagName:null,tagStart:null,context:null};return null!=e&&(t.baseIndent=e),t},token:function(e,t){if(!t.tagName&&e.sol()&&(t.indented=e.indentation()),e.eatSpace())return null;E=null;var n=t.tokenize(e,t);return(n||E)&&"comment"!=n&&(A=null,t.state=t.state(E||n,e,t),A&&(n="error"==A?n+" error":A)),n},indent:function(t,n,r){var i=t.context;if(t.tokenize.isInAttribute)return t.tagStart==t.indented?t.stringStartCol+1:t.indented+x;if(i&&i.noIndent)return e.Pass;if(t.tokenize!=o&&t.tokenize!=a)return r?r.match(/^(\s*)/)[0].length:0;if(t.tagName)return k.multilineTagIndentPastTag!==!1?t.tagStart+t.tagName.length+2:t.tagStart+x*(k.multilineTagIndentFactor||1);if(k.alignCDATA&&/$/,blockCommentStart:"",configuration:k.htmlMode?"html":"xml",helperType:k.htmlMode?"html":"xml",skipAttribute:function(e){e.state==b&&(e.state=y)}}}),e.defineMIME("text/xml","xml"),e.defineMIME("application/xml","xml"),e.mimeModes.hasOwnProperty("text/html")||e.defineMIME("text/html",{name:"xml",htmlMode:!0})})},function(e,t,n){!function(e){e(n(393))}(function(e){"use strict";e.modeInfo=[{name:"APL",mime:"text/apl",mode:"apl",ext:["dyalog","apl"]},{name:"PGP",mimes:["application/pgp","application/pgp-encrypted","application/pgp-keys","application/pgp-signature"],mode:"asciiarmor",ext:["asc","pgp","sig"]},{name:"ASN.1",mime:"text/x-ttcn-asn",mode:"asn.1",ext:["asn","asn1"]},{name:"Asterisk",mime:"text/x-asterisk",mode:"asterisk",file:/^extensions\.conf$/i},{name:"Brainfuck",mime:"text/x-brainfuck",mode:"brainfuck",ext:["b","bf"]},{name:"C",mime:"text/x-csrc",mode:"clike",ext:["c","h","ino"]},{name:"C++",mime:"text/x-c++src",mode:"clike",ext:["cpp","c++","cc","cxx","hpp","h++","hh","hxx"],alias:["cpp"]},{name:"Cobol",mime:"text/x-cobol",mode:"cobol",ext:["cob","cpy"]},{name:"C#",mime:"text/x-csharp",mode:"clike",ext:["cs"],alias:["csharp"]},{name:"Clojure",mime:"text/x-clojure",mode:"clojure",ext:["clj","cljc","cljx"]},{name:"ClojureScript",mime:"text/x-clojurescript",mode:"clojure",ext:["cljs"]},{name:"Closure Stylesheets (GSS)",mime:"text/x-gss",mode:"css",ext:["gss"]},{name:"CMake",mime:"text/x-cmake",mode:"cmake",ext:["cmake","cmake.in"],file:/^CMakeLists.txt$/},{name:"CoffeeScript",mimes:["application/vnd.coffeescript","text/coffeescript","text/x-coffeescript"],mode:"coffeescript",ext:["coffee"],alias:["coffee","coffee-script"]},{name:"Common Lisp",mime:"text/x-common-lisp",mode:"commonlisp",ext:["cl","lisp","el"],alias:["lisp"]},{name:"Cypher",mime:"application/x-cypher-query",mode:"cypher",ext:["cyp","cypher"]},{name:"Cython",mime:"text/x-cython",mode:"python",ext:["pyx","pxd","pxi"]},{name:"Crystal",mime:"text/x-crystal",mode:"crystal",ext:["cr"]},{name:"CSS",mime:"text/css",mode:"css",ext:["css"]},{name:"CQL",mime:"text/x-cassandra",mode:"sql",ext:["cql"]},{name:"D",mime:"text/x-d",mode:"d",ext:["d"]},{name:"Dart",mimes:["application/dart","text/x-dart"],mode:"dart",ext:["dart"]},{name:"diff",mime:"text/x-diff",mode:"diff",ext:["diff","patch"]},{name:"Django",mime:"text/x-django",mode:"django"},{name:"Dockerfile",mime:"text/x-dockerfile",mode:"dockerfile",file:/^Dockerfile$/},{name:"DTD",mime:"application/xml-dtd",mode:"dtd",ext:["dtd"]},{name:"Dylan",mime:"text/x-dylan",mode:"dylan",ext:["dylan","dyl","intr"]},{name:"EBNF",mime:"text/x-ebnf",mode:"ebnf"},{name:"ECL",mime:"text/x-ecl",mode:"ecl",ext:["ecl"]},{name:"edn",mime:"application/edn",mode:"clojure",ext:["edn"]},{name:"Eiffel",mime:"text/x-eiffel",mode:"eiffel",ext:["e"]},{name:"Elm",mime:"text/x-elm",mode:"elm",ext:["elm"]},{name:"Embedded Javascript",mime:"application/x-ejs",mode:"htmlembedded",ext:["ejs"]},{name:"Embedded Ruby",mime:"application/x-erb",mode:"htmlembedded",ext:["erb"]},{name:"Erlang",mime:"text/x-erlang",mode:"erlang",ext:["erl"]},{name:"Esper",mime:"text/x-esper",mode:"sql"},{name:"Factor",mime:"text/x-factor",mode:"factor",ext:["factor"]},{name:"FCL",mime:"text/x-fcl",mode:"fcl"},{name:"Forth",mime:"text/x-forth",mode:"forth",ext:["forth","fth","4th"]},{name:"Fortran",mime:"text/x-fortran",mode:"fortran",ext:["f","for","f77","f90"]},{name:"F#",mime:"text/x-fsharp",mode:"mllike",ext:["fs"],alias:["fsharp"]},{name:"Gas",mime:"text/x-gas",mode:"gas",ext:["s"]},{name:"Gherkin",mime:"text/x-feature",mode:"gherkin",ext:["feature"]},{name:"GitHub Flavored Markdown",mime:"text/x-gfm",mode:"gfm",file:/^(readme|contributing|history).md$/i},{name:"Go",mime:"text/x-go",mode:"go",ext:["go"]},{name:"Groovy",mime:"text/x-groovy",mode:"groovy",ext:["groovy","gradle"],file:/^Jenkinsfile$/},{name:"HAML",mime:"text/x-haml",mode:"haml",ext:["haml"]},{name:"Haskell",mime:"text/x-haskell",mode:"haskell",ext:["hs"]},{name:"Haskell (Literate)",mime:"text/x-literate-haskell",mode:"haskell-literate",ext:["lhs"]},{name:"Haxe",mime:"text/x-haxe",mode:"haxe",ext:["hx"]},{name:"HXML",mime:"text/x-hxml",mode:"haxe",ext:["hxml"]},{name:"ASP.NET",mime:"application/x-aspx",mode:"htmlembedded",ext:["aspx"],alias:["asp","aspx"]},{name:"HTML",mime:"text/html",mode:"htmlmixed",ext:["html","htm","handlebars","hbs"],alias:["xhtml"]},{name:"HTTP",mime:"message/http",mode:"http"},{name:"IDL",mime:"text/x-idl",mode:"idl",ext:["pro"]},{name:"Pug",mime:"text/x-pug",mode:"pug",ext:["jade","pug"],alias:["jade"]},{name:"Java",mime:"text/x-java",mode:"clike",ext:["java"]},{name:"Java Server Pages",mime:"application/x-jsp",mode:"htmlembedded",ext:["jsp"],alias:["jsp"]},{name:"JavaScript",mimes:["text/javascript","text/ecmascript","application/javascript","application/x-javascript","application/ecmascript"],mode:"javascript",ext:["js"],alias:["ecmascript","js","node"]},{name:"JSON",mimes:["application/json","application/x-json"],mode:"javascript",ext:["json","map"],alias:["json5"]},{name:"JSON-LD",mime:"application/ld+json",mode:"javascript",ext:["jsonld"],alias:["jsonld"]},{name:"JSX",mime:"text/jsx",mode:"jsx",ext:["jsx"]},{name:"Jinja2",mime:"null",mode:"jinja2",ext:["j2","jinja","jinja2"]},{name:"Julia",mime:"text/x-julia",mode:"julia",ext:["jl"]},{name:"Kotlin",mime:"text/x-kotlin",mode:"clike",ext:["kt"]},{name:"LESS",mime:"text/x-less",mode:"css",ext:["less"]},{name:"LiveScript",mime:"text/x-livescript",mode:"livescript",ext:["ls"],alias:["ls"]},{name:"Lua",mime:"text/x-lua",mode:"lua",ext:["lua"]},{name:"Markdown",mime:"text/x-markdown",mode:"markdown",ext:["markdown","md","mkd"]},{name:"mIRC",mime:"text/mirc",mode:"mirc"},{name:"MariaDB SQL",mime:"text/x-mariadb",mode:"sql"},{name:"Mathematica",mime:"text/x-mathematica",mode:"mathematica",ext:["m","nb"]},{name:"Modelica",mime:"text/x-modelica",mode:"modelica",ext:["mo"]},{name:"MUMPS",mime:"text/x-mumps",mode:"mumps",ext:["mps"]},{name:"MS SQL",mime:"text/x-mssql",mode:"sql"},{name:"mbox",mime:"application/mbox",mode:"mbox",ext:["mbox"]},{name:"MySQL",mime:"text/x-mysql",mode:"sql"},{name:"Nginx",mime:"text/x-nginx-conf",mode:"nginx",file:/nginx.*\.conf$/i},{name:"NSIS",mime:"text/x-nsis",mode:"nsis",ext:["nsh","nsi"]},{name:"NTriples",mimes:["application/n-triples","application/n-quads","text/n-triples"],mode:"ntriples",ext:["nt","nq"]},{name:"Objective-C",mime:"text/x-objectivec",mode:"clike",ext:["m","mm"],alias:["objective-c","objc"]},{name:"OCaml",mime:"text/x-ocaml",mode:"mllike",ext:["ml","mli","mll","mly"]},{name:"Octave",mime:"text/x-octave",mode:"octave",ext:["m"]},{name:"Oz",mime:"text/x-oz",mode:"oz",ext:["oz"]},{name:"Pascal",mime:"text/x-pascal",mode:"pascal",ext:["p","pas"]},{name:"PEG.js",mime:"null",mode:"pegjs",ext:["jsonld"]},{name:"Perl",mime:"text/x-perl",mode:"perl",ext:["pl","pm"]},{name:"PHP",mimes:["text/x-php","application/x-httpd-php","application/x-httpd-php-open"],mode:"php",ext:["php","php3","php4","php5","php7","phtml"]},{name:"Pig",mime:"text/x-pig",mode:"pig",ext:["pig"]},{name:"Plain Text",mime:"text/plain",mode:"null",ext:["txt","text","conf","def","list","log"]},{name:"PLSQL",mime:"text/x-plsql",mode:"sql",ext:["pls"]},{name:"PowerShell",mime:"application/x-powershell",mode:"powershell",ext:["ps1","psd1","psm1"]},{name:"Properties files",mime:"text/x-properties",mode:"properties",ext:["properties","ini","in"],alias:["ini","properties"]},{name:"ProtoBuf",mime:"text/x-protobuf",mode:"protobuf",ext:["proto"]},{name:"Python",mime:"text/x-python",mode:"python",ext:["BUILD","bzl","py","pyw"],file:/^(BUCK|BUILD)$/},{name:"Puppet",mime:"text/x-puppet",mode:"puppet",ext:["pp"]},{name:"Q",mime:"text/x-q",mode:"q",ext:["q"]},{name:"R",mime:"text/x-rsrc",mode:"r",ext:["r","R"],alias:["rscript"]},{name:"reStructuredText",mime:"text/x-rst",mode:"rst",ext:["rst"],alias:["rst"]},{name:"RPM Changes",mime:"text/x-rpm-changes",mode:"rpm"},{name:"RPM Spec",mime:"text/x-rpm-spec",mode:"rpm",ext:["spec"]},{name:"Ruby",mime:"text/x-ruby",mode:"ruby",ext:["rb"],alias:["jruby","macruby","rake","rb","rbx"]},{name:"Rust",mime:"text/x-rustsrc",mode:"rust",ext:["rs"]},{name:"SAS",mime:"text/x-sas",mode:"sas",ext:["sas"]},{name:"Sass",mime:"text/x-sass",mode:"sass",ext:["sass"]},{name:"Scala",mime:"text/x-scala",mode:"clike",ext:["scala"]},{name:"Scheme",mime:"text/x-scheme",mode:"scheme",ext:["scm","ss"]},{name:"SCSS",mime:"text/x-scss",mode:"css",ext:["scss"]},{name:"Shell",mimes:["text/x-sh","application/x-sh"],mode:"shell",ext:["sh","ksh","bash"],alias:["bash","sh","zsh"],file:/^PKGBUILD$/},{name:"Sieve",mime:"application/sieve",mode:"sieve",ext:["siv","sieve"]},{name:"Slim",mimes:["text/x-slim","application/x-slim"],mode:"slim",ext:["slim"]},{name:"Smalltalk",mime:"text/x-stsrc",mode:"smalltalk",ext:["st"]},{name:"Smarty",mime:"text/x-smarty",mode:"smarty",ext:["tpl"]},{name:"Solr",mime:"text/x-solr",mode:"solr"},{name:"SML",mime:"text/x-sml",mode:"mllike",ext:["sml","sig","fun","smackspec"]},{name:"Soy",mime:"text/x-soy",mode:"soy",ext:["soy"],alias:["closure template"]},{name:"SPARQL",mime:"application/sparql-query",mode:"sparql",ext:["rq","sparql"],alias:["sparul"]},{name:"Spreadsheet",mime:"text/x-spreadsheet",mode:"spreadsheet",alias:["excel","formula"]},{name:"SQL",mime:"text/x-sql",mode:"sql",ext:["sql"]},{name:"SQLite",mime:"text/x-sqlite",mode:"sql"},{name:"Squirrel",mime:"text/x-squirrel",mode:"clike",ext:["nut"]},{name:"Stylus",mime:"text/x-styl",mode:"stylus",ext:["styl"]},{name:"Swift",mime:"text/x-swift",mode:"swift",ext:["swift"]},{name:"sTeX",mime:"text/x-stex",mode:"stex"},{name:"LaTeX",mime:"text/x-latex",mode:"stex",ext:["text","ltx","tex"],alias:["tex"]},{name:"SystemVerilog",mime:"text/x-systemverilog",mode:"verilog",ext:["v","sv","svh"]},{name:"Tcl",mime:"text/x-tcl",mode:"tcl",ext:["tcl"]},{name:"Textile",mime:"text/x-textile",mode:"textile",ext:["textile"]},{name:"TiddlyWiki ",mime:"text/x-tiddlywiki",mode:"tiddlywiki"},{name:"Tiki wiki",mime:"text/tiki",mode:"tiki"},{name:"TOML",mime:"text/x-toml",mode:"toml",ext:["toml"]},{name:"Tornado",mime:"text/x-tornado",mode:"tornado"},{name:"troff",mime:"text/troff",mode:"troff",ext:["1","2","3","4","5","6","7","8","9"]},{name:"TTCN",mime:"text/x-ttcn",mode:"ttcn",ext:["ttcn","ttcn3","ttcnpp"]},{name:"TTCN_CFG",mime:"text/x-ttcn-cfg",mode:"ttcn-cfg",ext:["cfg"]},{name:"Turtle",mime:"text/turtle",mode:"turtle",ext:["ttl"]},{name:"TypeScript",mime:"application/typescript",mode:"javascript",ext:["ts"],alias:["ts"]},{name:"TypeScript-JSX",mime:"text/typescript-jsx",mode:"jsx",ext:["tsx"],alias:["tsx"]},{name:"Twig",mime:"text/x-twig",mode:"twig"},{name:"Web IDL",mime:"text/x-webidl",mode:"webidl",ext:["webidl"]},{name:"VB.NET",mime:"text/x-vb",mode:"vb",ext:["vb"]},{name:"VBScript",mime:"text/vbscript",mode:"vbscript",ext:["vbs"]},{name:"Velocity",mime:"text/velocity",mode:"velocity",ext:["vtl"]},{name:"Verilog",mime:"text/x-verilog",mode:"verilog",ext:["v"]},{name:"VHDL",mime:"text/x-vhdl",mode:"vhdl",ext:["vhd","vhdl"]},{name:"Vue.js Component",mimes:["script/x-vue","text/x-vue"],mode:"vue",ext:["vue"]},{name:"XML",mimes:["application/xml","text/xml"],mode:"xml",ext:["xml","xsl","xsd","svg"],alias:["rss","wsdl","xsd"]},{name:"XQuery",mime:"application/xquery",mode:"xquery",ext:["xy","xquery"]},{name:"Yacas",mime:"text/x-yacas",mode:"yacas",ext:["ys"]},{name:"YAML",mimes:["text/x-yaml","text/yaml"],mode:"yaml",ext:["yaml","yml"],alias:["yml"]},{name:"Z80",mime:"text/x-z80",mode:"z80",ext:["z80"]},{name:"mscgen",mime:"text/x-mscgen",mode:"mscgen",ext:["mscgen","mscin","msc"]},{name:"xu",mime:"text/x-xu",mode:"mscgen",ext:["xu"]},{name:"msgenny",mime:"text/x-msgenny",mode:"mscgen",ext:["msgenny"]}];for(var t=0;t-1&&t.substring(i+1,t.length);if(a)return e.findModeByExtension(a)},e.findModeByName=function(t){t=t.toLowerCase();for(var n=0;n-1?e.backUp(r.length-i):r.match(/<\/?$/)&&(e.backUp(r.length),e.match(t,!1)||e.match(r)),n}function n(e){var t=l[e];return t?t:l[e]=new RegExp("\\s+"+e+"\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*")}function r(e,t){var r=e.match(n(t));return r?/^\s*(.*?)\s*$/.exec(r[2])[1]:""}function i(e,t){return new RegExp((t?"^":"")+"","i")}function a(e,t){for(var n in e)for(var r=t[n]||(t[n]=[]),i=e[n],a=i.length-1;a>=0;a--)r.unshift(i[a])}function o(e,t){for(var n=0;n\s\/]/.test(r.current())&&(s=a.htmlState.tagName&&a.htmlState.tagName.toLowerCase())&&c.hasOwnProperty(s))a.inTag=s+" ";else if(a.inTag&&d&&/>$/.test(r.current())){var p=/^([\S]+) (.*)/.exec(a.inTag);a.inTag=null;var h=">"==r.current()&&o(c[p[1]],p[2]),m=e.getMode(n,h),v=i(p[1],!0),g=i(p[1],!1);a.token=function(e,n){return e.match(v,!1)?(n.token=l,n.localState=n.localMode=null,null):t(e,g,n.localMode.token(e,n.localState))},a.localMode=m,a.localState=e.startState(m,u.indent(a.htmlState,""))}else a.inTag&&(a.inTag+=r.current(),r.eol()&&(a.inTag+=" "));return f}var u=e.getMode(n,{name:"xml",htmlMode:!0,multilineTagIndentFactor:r.multilineTagIndentFactor,multilineTagIndentPastTag:r.multilineTagIndentPastTag}),c={},f=r&&r.tags,d=r&&r.scriptTypes;if(a(s,c),f&&a(f,c),d)for(var p=d.length-1;p>=0;p--)c.script.unshift(["type",d[p].matches,d[p].mode]);return{startState:function(){var t=e.startState(u);return{token:l,inTag:null,localMode:null,localState:null,htmlState:t}},copyState:function(t){var n;return t.localState&&(n=e.copyState(t.localMode,t.localState)),{token:t.token,inTag:t.inTag,localMode:t.localMode,localState:n,htmlState:e.copyState(u,t.htmlState)}},token:function(e,t){
+return t.token(e,t)},indent:function(t,n,r){return!t.localMode||/^\s*<\//.test(n)?u.indent(t.htmlState,n):t.localMode.indent?t.localMode.indent(t.localState,n,r):e.Pass},innerMode:function(e){return{state:e.localState||e.htmlState,mode:e.localMode||u}}}},"xml","javascript","css"),e.defineMIME("text/html","htmlmixed")})},function(e,t,n){!function(e){e(n(393))}(function(e){"use strict";e.defineMode("javascript",function(t,n){function r(e){for(var t,n=!1,r=!1;null!=(t=e.next());){if(!n){if("/"==t&&!r)return;"["==t?r=!0:r&&"]"==t&&(r=!1)}n=!n&&"\\"==t}}function i(e,t,n){return Re=e,qe=n,t}function a(e,t){var n=e.next();if('"'==n||"'"==n)return t.tokenize=o(n),t.tokenize(e,t);if("."==n&&e.match(/^\d+(?:[eE][+\-]?\d+)?/))return i("number","number");if("."==n&&e.match(".."))return i("spread","meta");if(/[\[\]{}\(\),;\:\.]/.test(n))return i(n);if("="==n&&e.eat(">"))return i("=>","operator");if("0"==n&&e.match(/^(?:x[\da-f]+|o[0-7]+|b[01]+)n?/i))return i("number","number");if(/\d/.test(n))return e.match(/^\d*(?:n|(?:\.\d*)?(?:[eE][+\-]?\d+)?)?/),i("number","number");if("/"==n)return e.eat("*")?(t.tokenize=s,s(e,t)):e.eat("/")?(e.skipToEnd(),i("comment","comment")):Fe(e,t,1)?(r(e),e.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/),i("regexp","string-2")):(e.eat("="),i("operator","operator",e.current()));if("`"==n)return t.tokenize=l,l(e,t);if("#"==n)return e.skipToEnd(),i("error","error");if(Ke.test(n))return">"==n&&t.lexical&&">"==t.lexical.type||(e.eat("=")?"!"!=n&&"="!=n||e.eat("="):/[<>*+\-]/.test(n)&&(e.eat(n),">"==n&&e.eat(n))),i("operator","operator",e.current());if($e.test(n)){e.eatWhile($e);var a=e.current();if("."!=t.lastType){if(We.propertyIsEnumerable(a)){var u=We[a];return i(u.type,u.style,a)}if("async"==a&&e.match(/^(\s|\/\*.*?\*\/)*[\[\(\w]/,!1))return i("async","keyword",a)}return i("variable","variable",a)}}function o(e){return function(t,n){var r,o=!1;if(Be&&"@"==t.peek()&&t.match(Ye))return n.tokenize=a,i("jsonld-keyword","meta");for(;null!=(r=t.next())&&(r!=e||o);)o=!o&&"\\"==r;return o||(n.tokenize=a),i("string","string")}}function s(e,t){for(var n,r=!1;n=e.next();){if("/"==n&&r){t.tokenize=a;break}r="*"==n}return i("comment","comment")}function l(e,t){for(var n,r=!1;null!=(n=e.next());){if(!r&&("`"==n||"$"==n&&e.eat("{"))){t.tokenize=a;break}r=!r&&"\\"==n}return i("quasi","string-2",e.current())}function u(e,t){t.fatArrowAt&&(t.fatArrowAt=null);var n=e.string.indexOf("=>",e.start);if(!(n<0)){if(He){var r=/:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(e.string.slice(e.start,n));r&&(n=r.index)}for(var i=0,a=!1,o=n-1;o>=0;--o){var s=e.string.charAt(o),l=Je.indexOf(s);if(l>=0&&l<3){if(!i){++o;break}if(0==--i){"("==s&&(a=!0);break}}else if(l>=3&&l<6)++i;else if($e.test(s))a=!0;else{if(/["'\/]/.test(s))return;if(a&&!i){++o;break}}}a&&!i&&(t.fatArrowAt=o)}}function c(e,t,n,r,i,a){this.indented=e,this.column=t,this.type=n,this.prev=i,this.info=a,null!=r&&(this.align=r)}function f(e,t){for(var n=e.localVars;n;n=n.next)if(n.name==t)return!0;for(var r=e.context;r;r=r.prev)for(var n=r.vars;n;n=n.next)if(n.name==t)return!0}function d(e,t,n,r,i){var a=e.cc;for(Qe.state=e,Qe.stream=i,Qe.marked=null,Qe.cc=a,Qe.style=t,e.lexical.hasOwnProperty("align")||(e.lexical.align=!0);;){var o=a.length?a.pop():Ge?M:A;if(o(n,r)){for(;a.length&&a[a.length-1].lex;)a.pop()();return Qe.marked?Qe.marked:"variable"==n&&f(e,r)?"variable-2":t}}}function p(){for(var e=arguments.length-1;e>=0;e--)Qe.cc.push(arguments[e])}function h(){return p.apply(null,arguments),!0}function m(e,t){for(var n=t;n;n=n.next)if(n.name==e)return!0;return!1}function v(e){var t=Qe.state;if(Qe.marked="def",t.context)if("var"==t.lexical.info&&t.context&&t.context.block){var r=g(e,t.context);if(null!=r)return void(t.context=r)}else if(!m(e,t.localVars))return void(t.localVars=new b(e,t.localVars));n.globalVars&&!m(e,t.globalVars)&&(t.globalVars=new b(e,t.globalVars))}function g(e,t){if(t){if(t.block){var n=g(e,t.prev);return n?n==t.prev?t:new _(n,t.vars,!0):null}return m(e,t.vars)?t:new _(t.prev,new b(e,t.vars),!1)}return null}function y(e){return"public"==e||"private"==e||"protected"==e||"abstract"==e||"readonly"==e}function _(e,t,n){this.prev=e,this.vars=t,this.block=n}function b(e,t){this.name=e,this.next=t}function w(){Qe.state.context=new _(Qe.state.context,Qe.state.localVars,!1),Qe.state.localVars=Ze}function x(){Qe.state.context=new _(Qe.state.context,Qe.state.localVars,!0),Qe.state.localVars=null}function k(){Qe.state.localVars=Qe.state.context.vars,Qe.state.context=Qe.state.context.prev}function S(e,t){var n=function(){var n=Qe.state,r=n.indented;if("stat"==n.lexical.type)r=n.lexical.indented;else for(var i=n.lexical;i&&")"==i.type&&i.align;i=i.prev)r=i.indented;n.lexical=new c(r,Qe.stream.column(),e,null,n.lexical,t)};return n.lex=!0,n}function C(){var e=Qe.state;e.lexical.prev&&(")"==e.lexical.type&&(e.indented=e.lexical.indented),e.lexical=e.lexical.prev)}function E(e){function t(n){return n==e?h():";"==e||"}"==n||")"==n||"]"==n?p():h(t)}return t}function A(e,t){return"var"==e?h(S("vardef",t),le,E(";"),C):"keyword a"==e?h(S("form"),T,A,C):"keyword b"==e?h(S("form"),A,C):"keyword d"==e?Qe.stream.match(/^\s*$/,!1)?h():h(S("stat"),j,E(";"),C):"debugger"==e?h(E(";")):"{"==e?h(S("}"),x,J,C,k):";"==e?h():"if"==e?("else"==Qe.state.lexical.info&&Qe.state.cc[Qe.state.cc.length-1]==C&&Qe.state.cc.pop()(),h(S("form"),T,A,C,he)):"function"==e?h(be):"for"==e?h(S("form"),me,A,C):"class"==e||He&&"interface"==t?(Qe.marked="keyword",h(S("form"),ke,C)):"variable"==e?He&&"declare"==t?(Qe.marked="keyword",h(A)):He&&("module"==t||"enum"==t||"type"==t)&&Qe.stream.match(/^\s*\w/,!1)?(Qe.marked="keyword","enum"==t?h(ze):"type"==t?h(ee,E("operator"),ee,E(";")):h(S("form"),ue,E("{"),S("}"),J,C,C)):He&&"namespace"==t?(Qe.marked="keyword",h(S("form"),M,J,C)):He&&"abstract"==t?(Qe.marked="keyword",h(A)):h(S("stat"),B):"switch"==e?h(S("form"),T,E("{"),S("}","switch"),x,J,C,C,k):"case"==e?h(M,E(":")):"default"==e?h(E(":")):"catch"==e?h(S("form"),w,I,A,C,k):"export"==e?h(S("stat"),Ae,C):"import"==e?h(S("stat"),Me,C):"async"==e?h(A):"@"==t?h(M,A):p(S("stat"),M,E(";"),C)}function I(e){if("("==e)return h(we,E(")"))}function M(e,t){return P(e,t,!1)}function O(e,t){return P(e,t,!0)}function T(e){return"("!=e?p():h(S(")"),M,E(")"),C)}function P(e,t,n){if(Qe.state.fatArrowAt==Qe.stream.start){var r=n?R:F;if("("==e)return h(w,S(")"),K(we,")"),C,E("=>"),r,k);if("variable"==e)return p(w,ue,E("=>"),r,k)}var i=n?z:D;return Xe.hasOwnProperty(e)?h(i):"function"==e?h(be,i):"class"==e||He&&"interface"==t?(Qe.marked="keyword",h(S("form"),xe,C)):"keyword c"==e||"async"==e?h(n?O:M):"("==e?h(S(")"),j,E(")"),C,i):"operator"==e||"spread"==e?h(n?O:M):"["==e?h(S("]"),De,C,i):"{"==e?Y(H,"}",null,i):"quasi"==e?p(L,i):"new"==e?h(q(n)):"import"==e?h(M):h()}function j(e){return e.match(/[;\}\)\],]/)?p():p(M)}function D(e,t){return","==e?h(M):z(e,t,!1)}function z(e,t,n){var r=0==n?D:z,i=0==n?M:O;return"=>"==e?h(w,n?R:F,k):"operator"==e?/\+\+|--/.test(t)||He&&"!"==t?h(r):He&&"<"==t&&Qe.stream.match(/^([^>]|<.*?>)*>\s*\(/,!1)?h(S(">"),K(ee,">"),C,r):"?"==t?h(M,E(":"),i):h(i):"quasi"==e?p(L,r):";"!=e?"("==e?Y(O,")","call",r):"."==e?h(G,r):"["==e?h(S("]"),j,E("]"),C,r):He&&"as"==t?(Qe.marked="keyword",h(ee,r)):"regexp"==e?(Qe.state.lastType=Qe.marked="operator",Qe.stream.backUp(Qe.stream.pos-Qe.stream.start-1),h(i)):void 0:void 0}function L(e,t){return"quasi"!=e?p():"${"!=t.slice(t.length-2)?h(L):h(M,N)}function N(e){if("}"==e)return Qe.marked="string-2",Qe.state.tokenize=l,h(L)}function F(e){return u(Qe.stream,Qe.state),p("{"==e?A:M)}function R(e){return u(Qe.stream,Qe.state),p("{"==e?A:O)}function q(e){return function(t){return"."==t?h(e?V:U):"variable"==t&&He?h(ae,e?z:D):p(e?O:M)}}function U(e,t){if("target"==t)return Qe.marked="keyword",h(D)}function V(e,t){if("target"==t)return Qe.marked="keyword",h(z)}function B(e){return":"==e?h(C,A):p(D,E(";"),C)}function G(e){if("variable"==e)return Qe.marked="property",h()}function H(e,t){if("async"==e)return Qe.marked="property",h(H);if("variable"==e||"keyword"==Qe.style){if(Qe.marked="property","get"==t||"set"==t)return h($);var n;return He&&Qe.state.fatArrowAt==Qe.stream.start&&(n=Qe.stream.match(/^\s*:\s*/,!1))&&(Qe.state.fatArrowAt=Qe.stream.pos+n[0].length),h(W)}return"number"==e||"string"==e?(Qe.marked=Be?"property":Qe.style+" property",h(W)):"jsonld-keyword"==e?h(W):He&&y(t)?(Qe.marked="keyword",h(H)):"["==e?h(M,X,E("]"),W):"spread"==e?h(O,W):"*"==t?(Qe.marked="keyword",h(H)):":"==e?p(W):void 0}function $(e){return"variable"!=e?p(W):(Qe.marked="property",h(be))}function W(e){return":"==e?h(O):"("==e?p(be):void 0}function K(e,t,n){function r(i,a){if(n?n.indexOf(i)>-1:","==i){var o=Qe.state.lexical;return"call"==o.info&&(o.pos=(o.pos||0)+1),h(function(n,r){return n==t||r==t?p():p(e)},r)}return i==t||a==t?h():h(E(t))}return function(n,i){return n==t||i==t?h():p(e,r)}}function Y(e,t,n){for(var r=3;r"),ee):void 0}function te(e){if("=>"==e)return h(ee)}function ne(e,t){return"variable"==e||"keyword"==Qe.style?(Qe.marked="property",h(ne)):"?"==t?h(ne):":"==e?h(ee):"["==e?h(M,X,E("]"),ne):void 0}function re(e,t){return"variable"==e&&Qe.stream.match(/^\s*[?:]/,!1)||"?"==t?h(re):":"==e?h(ee):p(ee)}function ie(e,t){return"<"==t?h(S(">"),K(ee,">"),C,ie):"|"==t||"."==e||"&"==t?h(ee):"["==e?h(E("]"),ie):"extends"==t||"implements"==t?(Qe.marked="keyword",h(ee)):void 0}function ae(e,t){if("<"==t)return h(S(">"),K(ee,">"),C,ie)}function oe(){return p(ee,se)}function se(e,t){if("="==t)return h(ee)}function le(e,t){return"enum"==t?(Qe.marked="keyword",h(ze)):p(ue,X,de,pe)}function ue(e,t){return He&&y(t)?(Qe.marked="keyword",h(ue)):"variable"==e?(v(t),h()):"spread"==e?h(ue):"["==e?Y(fe,"]"):"{"==e?Y(ce,"}"):void 0}function ce(e,t){return"variable"!=e||Qe.stream.match(/^\s*:/,!1)?("variable"==e&&(Qe.marked="property"),"spread"==e?h(ue):"}"==e?p():h(E(":"),ue,de)):(v(t),h(de))}function fe(){return p(ue,de)}function de(e,t){if("="==t)return h(O)}function pe(e){if(","==e)return h(le)}function he(e,t){if("keyword b"==e&&"else"==t)return h(S("form","else"),A,C)}function me(e,t){return"await"==t?h(me):"("==e?h(S(")"),ve,E(")"),C):void 0}function ve(e){return"var"==e?h(le,E(";"),ye):";"==e?h(ye):"variable"==e?h(ge):p(M,E(";"),ye)}function ge(e,t){return"in"==t||"of"==t?(Qe.marked="keyword",h(M)):h(D,ye)}function ye(e,t){return";"==e?h(_e):"in"==t||"of"==t?(Qe.marked="keyword",h(M)):p(M,E(";"),_e)}function _e(e){")"!=e&&h(M)}function be(e,t){return"*"==t?(Qe.marked="keyword",h(be)):"variable"==e?(v(t),h(be)):"("==e?h(w,S(")"),K(we,")"),C,Q,A,k):He&&"<"==t?h(S(">"),K(oe,">"),C,be):void 0}function we(e,t){return"@"==t&&h(M,we),"spread"==e?h(we):He&&y(t)?(Qe.marked="keyword",h(we)):p(ue,X,de)}function xe(e,t){return"variable"==e?ke(e,t):Se(e,t)}function ke(e,t){if("variable"==e)return v(t),h(Se)}function Se(e,t){return"<"==t?h(S(">"),K(oe,">"),C,Se):"extends"==t||"implements"==t||He&&","==e?("implements"==t&&(Qe.marked="keyword"),h(He?ee:M,Se)):"{"==e?h(S("}"),Ce,C):void 0}function Ce(e,t){return"async"==e||"variable"==e&&("static"==t||"get"==t||"set"==t||He&&y(t))&&Qe.stream.match(/^\s+[\w$\xa1-\uffff]/,!1)?(Qe.marked="keyword",h(Ce)):"variable"==e||"keyword"==Qe.style?(Qe.marked="property",h(He?Ee:be,Ce)):"["==e?h(M,X,E("]"),He?Ee:be,Ce):"*"==t?(Qe.marked="keyword",h(Ce)):";"==e?h(Ce):"}"==e?h():"@"==t?h(M,Ce):void 0}function Ee(e,t){return"?"==t?h(Ee):":"==e?h(ee,de):"="==t?h(O):p(be)}function Ae(e,t){return"*"==t?(Qe.marked="keyword",h(je,E(";"))):"default"==t?(Qe.marked="keyword",h(M,E(";"))):"{"==e?h(K(Ie,"}"),je,E(";")):p(A)}function Ie(e,t){return"as"==t?(Qe.marked="keyword",h(E("variable"))):"variable"==e?p(O,Ie):void 0}function Me(e){return"string"==e?h():"("==e?p(M):p(Oe,Te,je)}function Oe(e,t){return"{"==e?Y(Oe,"}"):("variable"==e&&v(t),"*"==t&&(Qe.marked="keyword"),h(Pe))}function Te(e){if(","==e)return h(Oe,Te)}function Pe(e,t){if("as"==t)return Qe.marked="keyword",h(Oe)}function je(e,t){if("from"==t)return Qe.marked="keyword",h(M)}function De(e){return"]"==e?h():p(K(O,"]"))}function ze(){return p(S("form"),ue,E("{"),S("}"),K(Le,"}"),C,C)}function Le(){return p(ue,de)}function Ne(e,t){return"operator"==e.lastType||","==e.lastType||Ke.test(t.charAt(0))||/[,.]/.test(t.charAt(0))}function Fe(e,t,n){return t.tokenize==a&&/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(t.lastType)||"quasi"==t.lastType&&/\{\s*$/.test(e.string.slice(0,e.pos-(n||0)))}var Re,qe,Ue=t.indentUnit,Ve=n.statementIndent,Be=n.jsonld,Ge=n.json||Be,He=n.typescript,$e=n.wordCharacters||/[\w$\xa1-\uffff]/,We=function(){function e(e){return{type:e,style:"keyword"}}var t=e("keyword a"),n=e("keyword b"),r=e("keyword c"),i=e("keyword d"),a=e("operator"),o={type:"atom",style:"atom"};return{if:e("if"),while:t,with:t,else:n,do:n,try:n,finally:n,return:i,break:i,continue:i,new:e("new"),delete:r,void:r,throw:r,debugger:e("debugger"),var:e("var"),const:e("var"),let:e("var"),function:e("function"),catch:e("catch"),for:e("for"),switch:e("switch"),case:e("case"),default:e("default"),in:a,typeof:a,instanceof:a,true:o,false:o,null:o,undefined:o,NaN:o,Infinity:o,this:e("this"),class:e("class"),super:e("atom"),yield:r,export:e("export"),import:e("import"),extends:r,await:r}}(),Ke=/[+\-*&%=<>!?|~^@]/,Ye=/^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/,Je="([{}])",Xe={atom:!0,number:!0,variable:!0,string:!0,regexp:!0,this:!0,"jsonld-keyword":!0},Qe={state:null,column:null,marked:null,cc:null},Ze=new b("this",new b("arguments",null));return k.lex=!0,C.lex=!0,{startState:function(e){var t={tokenize:a,lastType:"sof",cc:[],lexical:new c((e||0)-Ue,0,"block",!1),localVars:n.localVars,context:n.localVars&&new _(null,null,!1),indented:e||0};return n.globalVars&&"object"==typeof n.globalVars&&(t.globalVars=n.globalVars),t},token:function(e,t){if(e.sol()&&(t.lexical.hasOwnProperty("align")||(t.lexical.align=!1),t.indented=e.indentation(),u(e,t)),t.tokenize!=s&&e.eatSpace())return null;var n=t.tokenize(e,t);return"comment"==Re?n:(t.lastType="operator"!=Re||"++"!=qe&&"--"!=qe?Re:"incdec",d(t,n,Re,qe,e))},indent:function(t,r){if(t.tokenize==s)return e.Pass;if(t.tokenize!=a)return 0;var i,o=r&&r.charAt(0),l=t.lexical;if(!/^\s*else\b/.test(r))for(var u=t.cc.length-1;u>=0;--u){var c=t.cc[u];if(c==C)l=l.prev;else if(c!=he)break}for(;("stat"==l.type||"form"==l.type)&&("}"==o||(i=t.cc[t.cc.length-1])&&(i==D||i==z)&&!/^[,\.=+\-*:?[\(]/.test(r));)l=l.prev;Ve&&")"==l.type&&"stat"==l.prev.type&&(l=l.prev);var f=l.type,d=o==f;return"vardef"==f?l.indented+("operator"==t.lastType||","==t.lastType?l.info.length+1:0):"form"==f&&"{"==o?l.indented:"form"==f?l.indented+Ue:"stat"==f?l.indented+(Ne(t,r)?Ve||Ue:0):"switch"!=l.info||d||0==n.doubleIndentSwitch?l.align?l.column+(d?0:1):l.indented+(d?0:Ue):l.indented+(/^(?:case|default)\b/.test(r)?Ue:2*Ue)},electricInput:/^\s*(?:case .*?:|default:|\{|\})$/,blockCommentStart:Ge?null:"/*",blockCommentEnd:Ge?null:"*/",blockCommentContinue:Ge?null:" * ",lineComment:Ge?null:"//",fold:"brace",closeBrackets:"()[]{}''\"\"``",helperType:Ge?"json":"javascript",jsonldMode:Be,jsonMode:Ge,expressionAllowed:Fe,skipExpression:function(e){var t=e.cc[e.cc.length-1];t!=M&&t!=O||e.cc.pop()}}}),e.registerHelper("wordChars","javascript",/[\w$]/),e.defineMIME("text/javascript","javascript"),e.defineMIME("text/ecmascript","javascript"),e.defineMIME("application/javascript","javascript"),e.defineMIME("application/x-javascript","javascript"),e.defineMIME("application/ecmascript","javascript"),e.defineMIME("application/json",{name:"javascript",json:!0}),e.defineMIME("application/x-json",{name:"javascript",json:!0}),e.defineMIME("application/ld+json",{name:"javascript",jsonld:!0}),e.defineMIME("text/typescript",{name:"javascript",typescript:!0}),e.defineMIME("application/typescript",{name:"javascript",typescript:!0})})},function(e,t,n){!function(e){e(n(393),n(436),n(439))}(function(e){"use strict";function t(e){for(var t={},n=e.split(" "),r=0;r\w/,!1)&&(t.tokenize=n([[["->",null]],[[/[\w]+/,"variable"]]],r,i)),"variable-2";for(var a=!1;!e.eol()&&(a||i===!1||!e.match("{$",!1)&&!e.match(/^(\$[a-zA-Z_][a-zA-Z0-9_]*|\$\{)/,!1));){if(!a&&e.match(r)){t.tokenize=null,t.tokStack.pop(),t.tokStack.pop();break}a="\\"==e.next()&&!a}return"string"}var a="abstract and array as break case catch class clone const continue declare default do else elseif enddeclare endfor endforeach endif endswitch endwhile extends final for foreach function global goto if implements interface instanceof namespace new or private protected public static switch throw trait try use var while xor die echo empty exit eval include include_once isset list require require_once return print unset __halt_compiler self static parent yield insteadof finally",o="true false null TRUE FALSE NULL __CLASS__ __DIR__ __FILE__ __LINE__ __METHOD__ __FUNCTION__ __NAMESPACE__ __TRAIT__",s="func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex hex2bin sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents file_put_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists array_intersect_key array_combine array_column pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport http_response_code get_declared_traits getimagesizefromstring socket_import_stream stream_set_chunk_size trait_exists header_register_callback class_uses session_status session_register_shutdown echo print global static exit array empty eval isset unset die include require include_once require_once json_decode json_encode json_last_error json_last_error_msg curl_close curl_copy_handle curl_errno curl_error curl_escape curl_exec curl_file_create curl_getinfo curl_init curl_multi_add_handle curl_multi_close curl_multi_exec curl_multi_getcontent curl_multi_info_read curl_multi_init curl_multi_remove_handle curl_multi_select curl_multi_setopt curl_multi_strerror curl_pause curl_reset curl_setopt_array curl_setopt curl_share_close curl_share_init curl_share_setopt curl_strerror curl_unescape curl_version mysqli_affected_rows mysqli_autocommit mysqli_change_user mysqli_character_set_name mysqli_close mysqli_commit mysqli_connect_errno mysqli_connect_error mysqli_connect mysqli_data_seek mysqli_debug mysqli_dump_debug_info mysqli_errno mysqli_error_list mysqli_error mysqli_fetch_all mysqli_fetch_array mysqli_fetch_assoc mysqli_fetch_field_direct mysqli_fetch_field mysqli_fetch_fields mysqli_fetch_lengths mysqli_fetch_object mysqli_fetch_row mysqli_field_count mysqli_field_seek mysqli_field_tell mysqli_free_result mysqli_get_charset mysqli_get_client_info mysqli_get_client_stats mysqli_get_client_version mysqli_get_connection_stats mysqli_get_host_info mysqli_get_proto_info mysqli_get_server_info mysqli_get_server_version mysqli_info mysqli_init mysqli_insert_id mysqli_kill mysqli_more_results mysqli_multi_query mysqli_next_result mysqli_num_fields mysqli_num_rows mysqli_options mysqli_ping mysqli_prepare mysqli_query mysqli_real_connect mysqli_real_escape_string mysqli_real_query mysqli_reap_async_query mysqli_refresh mysqli_rollback mysqli_select_db mysqli_set_charset mysqli_set_local_infile_default mysqli_set_local_infile_handler mysqli_sqlstate mysqli_ssl_set mysqli_stat mysqli_stmt_init mysqli_store_result mysqli_thread_id mysqli_thread_safe mysqli_use_result mysqli_warning_count";e.registerHelper("hintWords","php",[a,o,s].join(" ").split(" ")),e.registerHelper("wordChars","php",/[\w$]/);var l={name:"clike",helperType:"php",keywords:t(a),blockKeywords:t("catch do else elseif for foreach if switch try while finally"),defKeywords:t("class function interface namespace trait"),atoms:t(o),builtin:t(s),multiLineStrings:!0,hooks:{$:function(e){return e.eatWhile(/[\w\$_]/),"variable-2"},"<":function(e,t){var n;if(n=e.match(/<<\s*/)){var i=e.eat(/['"]/);e.eatWhile(/[\w\.]/);var a=e.current().slice(n[0].length+(i?2:1));if(i&&e.eat(i),a)return(t.tokStack||(t.tokStack=[])).push(a,0),t.tokenize=r(a,"'"!=i),"string"}return!1},"#":function(e){for(;!e.eol()&&!e.match("?>",!1);)e.next();return"comment"},"/":function(e){if(e.eat("/")){for(;!e.eol()&&!e.match("?>",!1);)e.next();return"comment"}return!1},'"':function(e,t){return(t.tokStack||(t.tokStack=[])).push('"',0),t.tokenize=r('"'),"string"},"{":function(e,t){return t.tokStack&&t.tokStack.length&&t.tokStack[t.tokStack.length-1]++,!1},"}":function(e,t){return t.tokStack&&t.tokStack.length>0&&!--t.tokStack[t.tokStack.length-1]&&(t.tokenize=r(t.tokStack[t.tokStack.length-2])),!1}}};e.defineMode("php",function(t,n){function r(t,n){var r=n.curMode==a;if(t.sol()&&n.pending&&'"'!=n.pending&&"'"!=n.pending&&(n.pending=null),r)return r&&null==n.php.tokenize&&t.match("?>")?(n.curMode=i,n.curState=n.html,n.php.context.prev||(n.php=null),"meta"):a.token(t,n.curState);if(t.match(/^<\?\w*/))return n.curMode=a,n.php||(n.php=e.startState(a,i.indent(n.html,""))),n.curState=n.php,"meta";if('"'==n.pending||"'"==n.pending){for(;!t.eol()&&t.next()!=n.pending;);var o="string"}else if(n.pending&&t.pos/.test(l)?n.pending=s[0]:n.pending={end:t.pos,style:o},t.backUp(l.length-u)),o}var i=e.getMode(t,n&&n.htmlMode||"text/html"),a=e.getMode(t,l);return{startState:function(){var t=e.startState(i),r=n.startOpen?e.startState(a):null;return{html:t,php:r,curMode:n.startOpen?a:i,curState:n.startOpen?r:t,pending:null}},copyState:function(t){var n,r=t.html,o=e.copyState(i,r),s=t.php,l=s&&e.copyState(a,s);return n=t.curMode==i?o:l,{html:o,php:l,curMode:t.curMode,curState:n,pending:t.pending}},token:r,indent:function(e,t){return e.curMode!=a&&/^\s*<\//.test(t)||e.curMode==a&&/^\?>/.test(t)?i.indent(e.html,t):e.curMode.indent(e.curState,t)},blockCommentStart:"/*",blockCommentEnd:"*/",lineComment:"//",innerMode:function(e){return{state:e.curState,mode:e.curMode}}}},"htmlmixed","clike"),e.defineMIME("application/x-httpd-php","php"),e.defineMIME("application/x-httpd-php-open",{name:"php",startOpen:!0}),e.defineMIME("text/x-php",l)})},function(e,t,n){!function(e){e(n(393))}(function(e){"use strict";function t(e,t,n,r,i,a){this.indented=e,this.column=t,this.type=n,this.info=r,this.align=i,this.prev=a}function n(e,n,r,i){var a=e.indented;return e.context&&"statement"==e.context.type&&"statement"!=r&&(a=e.context.indented),e.context=new t(a,n,r,i,null,e.context)}function r(e){var t=e.context.type;return")"!=t&&"]"!=t&&"}"!=t||(e.indented=e.context.indented),e.context=e.context.prev}function i(e,t,n){return"variable"==t.prevToken||"type"==t.prevToken||(!!/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(e.string.slice(0,n))||(!(!t.typeAtEndOfLine||e.column()!=e.indentation())||void 0))}function a(e){for(;;){if(!e||"top"==e.type)return!0;if("}"==e.type&&"namespace"!=e.prev.info)return!1;e=e.prev}}function o(e){for(var t={},n=e.split(" "),r=0;r!?|\/]/,j=l.isIdentifierChar||/[\w\$_\xa1-\uffff]/;return{startState:function(e){return{tokenize:null,context:new t((e||0)-m,0,"top",null,!1),indented:0,startOfLine:!0,prevToken:null}},token:function(e,t){var o=t.context;if(e.sol()&&(null==o.align&&(o.align=!1),t.indented=e.indentation(),t.startOfLine=!0),e.eatSpace())return d(e,t),null;p=h=null;var s=(t.tokenize||u)(e,t);if("comment"==s||"meta"==s)return s;if(null==o.align&&(o.align=!0),";"==p||":"==p||","==p&&e.match(/^\s*(?:\/\/.*)?$/,!1))for(;"statement"==t.context.type;)r(t);else if("{"==p)n(t,e.column(),"}");else if("["==p)n(t,e.column(),"]");else if("("==p)n(t,e.column(),")");else if("}"==p){for(;"statement"==o.type;)o=r(t);for("}"==o.type&&(o=r(t));"statement"==o.type;)o=r(t)}else p==o.type?r(t):E&&(("}"==o.type||"top"==o.type)&&";"!=p||"statement"==o.type&&"newstatement"==p)&&n(t,e.column(),"statement",e.current());if("variable"==s&&("def"==t.prevToken||l.typeFirstDefinitions&&i(e,t,e.start)&&a(t.context)&&e.match(/^\s*\(/,!1))&&(s="def"),S.token){var c=S.token(e,t,s);void 0!==c&&(s=c)}return"def"==s&&l.styleDefs===!1&&(s="variable"),t.startOfLine=!1,t.prevToken=h?"def":s||p,d(e,t),s},indent:function(t,n){if(t.tokenize!=u&&null!=t.tokenize||t.typeAtEndOfLine)return e.Pass;var r=t.context,i=n&&n.charAt(0),a=i==r.type;if("statement"==r.type&&"}"==i&&(r=r.prev),l.dontIndentStatements)for(;"statement"==r.type&&l.dontIndentStatements.test(r.info);)r=r.prev;if(S.indent){var o=S.indent(t,r,n,m);if("number"==typeof o)return o}var s=r.prev&&"switch"==r.prev.info;if(l.allmanIndentation&&/[{(]/.test(i)){for(;"top"!=r.type&&"}"!=r.type;)r=r.prev;return r.indented}return"statement"==r.type?r.indented+("{"==i?0:v):!r.align||g&&")"==r.type?")"!=r.type||a?r.indented+(a?0:m)+(a||!s||/^(?:case|default)\b/.test(n)?0:m):r.indented+v:r.column+(a?0:1)},electricInput:A?/^\s*(?:case .*?:|default:|\{\}?|\})$/:/^\s*[{}]$/,blockCommentStart:"/*",blockCommentEnd:"*/",blockCommentContinue:" * ",lineComment:"//",fold:"brace"}});var b="auto if break case register continue return default do sizeof static else struct switch extern typedef union for goto while enum const volatile",w="int long char short double float unsigned signed void size_t ptrdiff_t";m(["text/x-csrc","text/x-c","text/x-chdr"],{name:"clike",keywords:o(b),types:o(w+" bool _Complex _Bool float_t double_t intptr_t intmax_t int8_t int16_t int32_t int64_t uintptr_t uintmax_t uint8_t uint16_t uint32_t uint64_t"),blockKeywords:o("case do else for if switch while struct"),defKeywords:o("struct"),typeFirstDefinitions:!0,atoms:o("NULL true false"),hooks:{"#":l,"*":u},modeProps:{fold:["brace","include"]}}),m(["text/x-c++src","text/x-c++hdr"],{name:"clike",keywords:o(b+" asm dynamic_cast namespace reinterpret_cast try explicit new static_cast typeid catch operator template typename class friend private this using const_cast inline public throw virtual delete mutable protected alignas alignof constexpr decltype nullptr noexcept thread_local final static_assert override"),types:o(w+" bool wchar_t"),blockKeywords:o("catch class do else finally for if struct switch try while"),defKeywords:o("class namespace struct enum union"),typeFirstDefinitions:!0,atoms:o("true false NULL"),dontIndentStatements:/^template$/,isIdentifierChar:/[\w\$_~\xa1-\uffff]/,hooks:{"#":l,"*":u,u:f,U:f,L:f,R:f,0:c,1:c,2:c,3:c,4:c,5:c,6:c,7:c,8:c,9:c,token:function(e,t,n){if("variable"==n&&"("==e.peek()&&(";"==t.prevToken||null==t.prevToken||"}"==t.prevToken)&&d(e.current()))return"def"}},namespaceSeparator:"::",modeProps:{fold:["brace","include"]}}),m("text/x-java",{name:"clike",keywords:o("abstract assert break case catch class const continue default do else enum extends final finally float for goto if implements import instanceof interface native new package private protected public return static strictfp super switch synchronized this throw throws transient try volatile while @interface"),types:o("byte short int long float double boolean char void Boolean Byte Character Double Float Integer Long Number Object Short String StringBuffer StringBuilder Void"),blockKeywords:o("catch class do else finally for if switch try while"),defKeywords:o("class interface enum @interface"),typeFirstDefinitions:!0,atoms:o("true false null"),number:/^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+\.?\d*|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,hooks:{"@":function(e){return!e.match("interface",!1)&&(e.eatWhile(/[\w\$_]/),"meta")}},modeProps:{fold:["brace","import"]}}),m("text/x-csharp",{name:"clike",keywords:o("abstract as async await base break case catch checked class const continue default delegate do else enum event explicit extern finally fixed for foreach goto if implicit in interface internal is lock namespace new operator out override params private protected public readonly ref return sealed sizeof stackalloc static struct switch this throw try typeof unchecked unsafe using virtual void volatile while add alias ascending descending dynamic from get global group into join let orderby partial remove select set value var yield"),types:o("Action Boolean Byte Char DateTime DateTimeOffset Decimal Double Func Guid Int16 Int32 Int64 Object SByte Single String Task TimeSpan UInt16 UInt32 UInt64 bool byte char decimal double short int long object sbyte float string ushort uint ulong"),blockKeywords:o("catch class do else finally for foreach if struct switch try while"),defKeywords:o("class interface namespace struct var"),typeFirstDefinitions:!0,atoms:o("true false null"),hooks:{"@":function(e,t){return e.eat('"')?(t.tokenize=p,p(e,t)):(e.eatWhile(/[\w\$_]/),"meta")}}}),m("text/x-scala",{name:"clike",keywords:o("abstract case catch class def do else extends final finally for forSome if implicit import lazy match new null object override package private protected return sealed super this throw trait try type val var while with yield _ assert assume require print println printf readLine readBoolean readByte readShort readChar readInt readLong readFloat readDouble"),types:o("AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either Enumeration Equiv Error Exception Fractional Function IndexedSeq Int Integral Iterable Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable Compiler Double Exception Float Integer Long Math Number Object Package Pair Process Runtime Runnable SecurityManager Short StackTraceElement StrictMath String StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"),multiLineStrings:!0,blockKeywords:o("catch class enum do else finally for forSome if match switch try while"),defKeywords:o("class enum def object package trait type val var"),atoms:o("true false null"),indentStatements:!1,indentSwitch:!1,isOperatorChar:/[+\-*&%=<>!?|\/#:@]/,hooks:{"@":function(e){return e.eatWhile(/[\w\$_]/),"meta"},'"':function(e,t){return!!e.match('""')&&(t.tokenize=v,t.tokenize(e,t))},"'":function(e){return e.eatWhile(/[\w\$_\xa1-\uffff]/),"atom"},"=":function(e,n){var r=n.context;return!("}"!=r.type||!r.align||!e.eat(">"))&&(n.context=new t(r.indented,r.column,r.type,r.info,null,r.prev),"operator")},"/":function(e,t){return!!e.eat("*")&&(t.tokenize=g(1),t.tokenize(e,t))}},modeProps:{closeBrackets:{pairs:'()[]{}""',triples:'"'}}}),m("text/x-kotlin",{name:"clike",keywords:o("package as typealias class interface this super val operator var fun for is in This throw return annotation break continue object if else while do try when !in !is as? file import where by get set abstract enum open inner override private public internal protected catch finally out final vararg reified dynamic companion constructor init sealed field property receiver param sparam lateinit data inline noinline tailrec external annotation crossinline const operator infix suspend actual expect setparam"),types:o("Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable Compiler Double Exception Float Integer Long Math Number Object Package Pair Process Runtime Runnable SecurityManager Short StackTraceElement StrictMath String StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void Annotation Any BooleanArray ByteArray Char CharArray DeprecationLevel DoubleArray Enum FloatArray Function Int IntArray Lazy LazyThreadSafetyMode LongArray Nothing ShortArray Unit"),intendSwitch:!1,indentStatements:!1,multiLineStrings:!0,number:/^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+(\.\d+)?|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,blockKeywords:o("catch class do else finally for if where try while enum"),defKeywords:o("class val var object interface fun"),atoms:o("true false null this"),hooks:{"@":function(e){return e.eatWhile(/[\w\$_]/),"meta"},'"':function(e,t){return t.tokenize=y(e.match('""')),t.tokenize(e,t)},indent:function(e,t,n,r){var i=n&&n.charAt(0);return"}"!=e.prevToken&&")"!=e.prevToken||""!=n?"operator"==e.prevToken&&"}"!=n||"variable"==e.prevToken&&"."==i||("}"==e.prevToken||")"==e.prevToken)&&"."==i?2*r+t.indented:t.align&&"}"==t.type?t.indented+(e.context.type==(n||"").charAt(0)?0:r):void 0:e.indented}},modeProps:{closeBrackets:{triples:'"'}}}),m(["x-shader/x-vertex","x-shader/x-fragment"],{name:"clike",keywords:o("sampler1D sampler2D sampler3D samplerCube sampler1DShadow sampler2DShadow const attribute uniform varying break continue discard return for while do if else struct in out inout"),types:o("float int bool void vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 mat2 mat3 mat4"),blockKeywords:o("for while do if else struct"),builtin:o("radians degrees sin cos tan asin acos atan pow exp log exp2 sqrt inversesqrt abs sign floor ceil fract mod min max clamp mix step smoothstep length distance dot cross normalize ftransform faceforward reflect refract matrixCompMult lessThan lessThanEqual greaterThan greaterThanEqual equal notEqual any all not texture1D texture1DProj texture1DLod texture1DProjLod texture2D texture2DProj texture2DLod texture2DProjLod texture3D texture3DProj texture3DLod texture3DProjLod textureCube textureCubeLod shadow1D shadow2D shadow1DProj shadow2DProj shadow1DLod shadow2DLod shadow1DProjLod shadow2DProjLod dFdx dFdy fwidth noise1 noise2 noise3 noise4"),atoms:o("true false gl_FragColor gl_SecondaryColor gl_Normal gl_Vertex gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 gl_FogCoord gl_PointCoord gl_Position gl_PointSize gl_ClipVertex gl_FrontColor gl_BackColor gl_FrontSecondaryColor gl_BackSecondaryColor gl_TexCoord gl_FogFragCoord gl_FragCoord gl_FrontFacing gl_FragData gl_FragDepth gl_ModelViewMatrix gl_ProjectionMatrix gl_ModelViewProjectionMatrix gl_TextureMatrix gl_NormalMatrix gl_ModelViewMatrixInverse gl_ProjectionMatrixInverse gl_ModelViewProjectionMatrixInverse gl_TexureMatrixTranspose gl_ModelViewMatrixInverseTranspose gl_ProjectionMatrixInverseTranspose gl_ModelViewProjectionMatrixInverseTranspose gl_TextureMatrixInverseTranspose gl_NormalScale gl_DepthRange gl_ClipPlane gl_Point gl_FrontMaterial gl_BackMaterial gl_LightSource gl_LightModel gl_FrontLightModelProduct gl_BackLightModelProduct gl_TextureColor gl_EyePlaneS gl_EyePlaneT gl_EyePlaneR gl_EyePlaneQ gl_FogParameters gl_MaxLights gl_MaxClipPlanes gl_MaxTextureUnits gl_MaxTextureCoords gl_MaxVertexAttribs gl_MaxVertexUniformComponents gl_MaxVaryingFloats gl_MaxVertexTextureImageUnits gl_MaxTextureImageUnits gl_MaxFragmentUniformComponents gl_MaxCombineTextureImageUnits gl_MaxDrawBuffers"),indentSwitch:!1,hooks:{"#":l},modeProps:{fold:["brace","include"]}}),m("text/x-nesc",{name:"clike",keywords:o(b+"as atomic async call command component components configuration event generic implementation includes interface module new norace nx_struct nx_union post provides signal task uses abstract extends"),types:o(w),blockKeywords:o("case do else for if switch while struct"),atoms:o("null true false"),hooks:{"#":l},modeProps:{fold:["brace","include"]}}),m("text/x-objectivec",{name:"clike",keywords:o(b+"inline restrict _Bool _Complex _Imaginary BOOL Class bycopy byref id IMP in inout nil oneway out Protocol SEL self super atomic nonatomic retain copy readwrite readonly"),types:o(w),atoms:o("YES NO NULL NILL ON OFF true false"),hooks:{"@":function(e){return e.eatWhile(/[\w\$]/),"keyword"},"#":l,indent:function(e,t,n){if("statement"==t.type&&/^@\w/.test(n))return t.indented}},modeProps:{fold:"brace"}}),m("text/x-squirrel",{name:"clike",keywords:o("base break clone continue const default delete enum extends function in class foreach local resume return this throw typeof yield constructor instanceof static"),types:o(w),blockKeywords:o("case catch class else for foreach if switch try while"),defKeywords:o("function local class"),typeFirstDefinitions:!0,atoms:o("true false null"),hooks:{"#":l},modeProps:{fold:["brace","include"]}});var x=null;m("text/x-ceylon",{name:"clike",keywords:o("abstracts alias assembly assert assign break case catch class continue dynamic else exists extends finally for function given if import in interface is let module new nonempty object of out outer package return satisfies super switch then this throw try value void while"),types:function(e){var t=e.charAt(0);return t===t.toUpperCase()&&t!==t.toLowerCase()},blockKeywords:o("case catch class dynamic else finally for function if interface module new object switch try while"),defKeywords:o("class dynamic function interface module object package value"),builtin:o("abstract actual aliased annotation by default deprecated doc final formal late license native optional sealed see serializable shared suppressWarnings tagged throws variable"),isPunctuationChar:/[\[\]{}\(\),;\:\.`]/,isOperatorChar:/[+\-*&%=<>!?|^~:\/]/,numberStart:/[\d#$]/,number:/^(?:#[\da-fA-F_]+|\$[01_]+|[\d_]+[kMGTPmunpf]?|[\d_]+\.[\d_]+(?:[eE][-+]?\d+|[kMGTPmunpf]|)|)/i,multiLineStrings:!0,typeFirstDefinitions:!0,atoms:o("true false null larger smaller equal empty finished"),indentSwitch:!1,styleDefs:!1,hooks:{"@":function(e){return e.eatWhile(/[\w\$_]/),"meta"},'"':function(e,t){return t.tokenize=_(e.match('""')?"triple":"single"),t.tokenize(e,t)},"`":function(e,t){return!(!x||!e.match("`"))&&(t.tokenize=x,x=null,t.tokenize(e,t))},"'":function(e){return e.eatWhile(/[\w\$_\xa1-\uffff]/),"atom"},token:function(e,t,n){if(("variable"==n||"type"==n)&&"."==t.prevToken)return"variable-2"}},modeProps:{fold:["brace","import"],closeBrackets:{triples:'"'}}})})},function(e,t,n){!function(e){e(n(393),n(430))}(function(e){"use strict";e.defineMode("sass",function(t){function n(e){return new RegExp("^"+e.join("|"))}function r(e){return!e.peek()||e.match(/\s+$/,!1)}function i(e,t){var n=e.peek();return")"===n?(e.next(),t.tokenizer=c,"operator"):"("===n?(e.next(),e.eatSpace(),"operator"):"'"===n||'"'===n?(t.tokenizer=o(e.next()),"string"):(t.tokenizer=o(")",!1),"string")}function a(e,t){return function(n,r){return n.sol()&&n.indentation()<=e?(r.tokenizer=c,c(n,r)):(t&&n.skipTo("*/")?(n.next(),n.next(),r.tokenizer=c):n.skipToEnd(),"comment")}}function o(e,t){function n(i,a){var o=i.next(),l=i.peek(),u=i.string.charAt(i.pos-2),f="\\"!==o&&l===e||o===e&&"\\"!==u;return f?(o!==e&&t&&i.next(),r(i)&&(a.cursorHalf=0),a.tokenizer=c,"string"):"#"===o&&"{"===l?(a.tokenizer=s(n),i.next(),"operator"):"string"}return null==t&&(t=!0),n}function s(e){return function(t,n){return"}"===t.peek()?(t.next(),n.tokenizer=e,"operator"):c(t,n)}}function l(e){if(0==e.indentCount){e.indentCount++;var n=e.scopes[0].offset,r=n+t.indentUnit;e.scopes.unshift({offset:r})}}function u(e){1!=e.scopes.length&&e.scopes.shift()}function c(e,t){var n=e.peek();if(e.match("/*"))return t.tokenizer=a(e.indentation(),!0),t.tokenizer(e,t);if(e.match("//"))return t.tokenizer=a(e.indentation(),!1),t.tokenizer(e,t);if(e.match("#{"))return t.tokenizer=s(c),"operator";if('"'===n||"'"===n)return e.next(),t.tokenizer=o(n),"string";if(t.cursorHalf){if("#"===n&&(e.next(),e.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/)))return r(e)&&(t.cursorHalf=0),"number";if(e.match(/^-?[0-9\.]+/))return r(e)&&(t.cursorHalf=0),"number";if(e.match(/^(px|em|in)\b/))return r(e)&&(t.cursorHalf=0),"unit";if(e.match(_))return r(e)&&(t.cursorHalf=0),"keyword";if(e.match(/^url/)&&"("===e.peek())return t.tokenizer=i,r(e)&&(t.cursorHalf=0),"atom";if("$"===n)return e.next(),e.eatWhile(/[\w-]/),r(e)&&(t.cursorHalf=0),"variable-2";if("!"===n)return e.next(),t.cursorHalf=0,e.match(/^[\w]+/)?"keyword":"operator";if(e.match(w))return r(e)&&(t.cursorHalf=0),"operator";if(e.eatWhile(/[\w-]/))return r(e)&&(t.cursorHalf=0),d=e.current().toLowerCase(),v.hasOwnProperty(d)?"atom":m.hasOwnProperty(d)?"keyword":h.hasOwnProperty(d)?(t.prevProp=e.current().toLowerCase(),"property"):"tag";if(r(e))return t.cursorHalf=0,null}else{if("-"===n&&e.match(/^-\w+-/))return"meta";if("."===n){if(e.next(),e.match(/^[\w-]+/))return l(t),"qualifier";if("#"===e.peek())return l(t),"tag"}if("#"===n){if(e.next(),e.match(/^[\w-]+/))return l(t),"builtin";if("#"===e.peek())return l(t),"tag"}if("$"===n)return e.next(),e.eatWhile(/[\w-]/),"variable-2";if(e.match(/^-?[0-9\.]+/))return"number";if(e.match(/^(px|em|in)\b/))return"unit";if(e.match(_))return"keyword";if(e.match(/^url/)&&"("===e.peek())return t.tokenizer=i,"atom";if("="===n&&e.match(/^=[\w-]+/))return l(t),"meta";if("+"===n&&e.match(/^\+[\w-]+/))return"variable-3";if("@"===n&&e.match(/@extend/)&&(e.match(/\s*[\w]/)||u(t)),e.match(/^@(else if|if|media|else|for|each|while|mixin|function)/))return l(t),"def";if("@"===n)return e.next(),e.eatWhile(/[\w-]/),"def";if(e.eatWhile(/[\w-]/)){if(e.match(/ *: *[\w-\+\$#!\("']/,!1)){d=e.current().toLowerCase();var f=t.prevProp+"-"+d;return h.hasOwnProperty(f)?"property":h.hasOwnProperty(d)?(t.prevProp=d,"property"):g.hasOwnProperty(d)?"property":"tag"}return e.match(/ *:/,!1)?(l(t),t.cursorHalf=1,t.prevProp=e.current().toLowerCase(),"property"):e.match(/ *,/,!1)?"tag":(l(t),"tag")}if(":"===n)return e.match(x)?"variable-3":(e.next(),t.cursorHalf=1,"operator")}return e.match(w)?"operator":(e.next(),null)}function f(e,n){e.sol()&&(n.indentCount=0);var r=n.tokenizer(e,n),i=e.current();if("@return"!==i&&"}"!==i||u(n),null!==r){for(var a=e.pos-i.length,o=a+t.indentUnit*n.indentCount,s=[],l=0;l","<","==",">=","<=","\\+","-","\\!=","/","\\*","%","and","or","not",";","\\{","\\}",":"],w=n(b),x=/^::?[a-zA-Z_][\w\-]*/;return{startState:function(){return{tokenizer:c,scopes:[{offset:0,type:"sass"}],indentCount:0,cursorHalf:0,definedVars:[],definedMixins:[]}},token:function(e,t){var n=f(e,t);return t.lastToken={style:n,content:e.current()},n},indent:function(e){return e.scopes[0].offset}}},"css"),e.defineMIME("text/x-sass","sass")})},function(e,t,n){!function(e){e(n(393),n(442))}(function(e){"use strict";e.defineMode("twig:inner",function(){function e(e,o){var s=e.peek();if(o.incomment)return e.skipTo("#}")?(e.eatWhile(/\#|}/),o.incomment=!1):e.skipToEnd(),"comment";if(o.intag){if(o.operator){if(o.operator=!1,e.match(i))return"atom";if(e.match(a))return"number"}if(o.sign){if(o.sign=!1,e.match(i))return"atom";if(e.match(a))return"number"}if(o.instring)return s==o.instring&&(o.instring=!1),e.next(),"string";if("'"==s||'"'==s)return o.instring=s,e.next(),"string";if(e.match(o.intag+"}")||e.eat("-")&&e.match(o.intag+"}"))return o.intag=!1,"tag";if(e.match(n))return o.operator=!0,"operator";if(e.match(r))o.sign=!0;else if(e.eat(" ")||e.sol()){if(e.match(t))return"keyword";if(e.match(i))return"atom";if(e.match(a))return"number";e.sol()&&e.next()}else e.next();return"variable"}if(e.eat("{")){if(e.eat("#"))return o.incomment=!0,e.skipTo("#}")?(e.eatWhile(/\#|}/),o.incomment=!1):e.skipToEnd(),"comment";if(s=e.eat(/\{|%/))return o.intag=s,"{"==s&&(o.intag="}"),e.eat("-"),"tag"}e.next()}var t=["and","as","autoescape","endautoescape","block","do","endblock","else","elseif","extends","for","endfor","embed","endembed","filter","endfilter","flush","from","if","endif","in","is","include","import","not","or","set","spaceless","endspaceless","with","endwith","trans","endtrans","blocktrans","endblocktrans","macro","endmacro","use","verbatim","endverbatim"],n=/^[+\-*&%=<>!?|~^]/,r=/^[:\[\(\{]/,i=["true","false","null","empty","defined","divisibleby","divisible by","even","odd","iterable","sameas","same as"],a=/^(\d[+\-\*\/])?\d+(\.\d+)?/;return t=new RegExp("(("+t.join(")|(")+"))\\b"),i=new RegExp("(("+i.join(")|(")+"))\\b"),{startState:function(){return{}},token:function(t,n){return e(t,n)}}}),e.defineMode("twig",function(t,n){var r=e.getMode(t,"twig:inner");return n&&n.base?e.multiplexingMode(e.getMode(t,n.base),{open:/\{[{#%]/,close:/[}#%]\}/,mode:r,parseDelimiters:!0}):r}),e.defineMIME("text/x-twig","twig")})},function(e,t,n){!function(e){e(n(393))}(function(e){"use strict";e.multiplexingMode=function(t){function n(e,t,n,r){if("string"==typeof t){var i=e.indexOf(t,n);return r&&i>-1?i+t.length:i}var a=t.exec(n?e.slice(n):e);return a?a.index+n+(r?a[0].length:0):-1}var r=Array.prototype.slice.call(arguments,1);return{startState:function(){return{outer:e.startState(t),innerActive:null,inner:null}},copyState:function(n){return{outer:e.copyState(t,n.outer),innerActive:n.innerActive,inner:n.innerActive&&e.copyState(n.innerActive.mode,n.inner)}},token:function(i,a){if(a.innerActive){var o=a.innerActive,s=i.string;if(!o.close&&i.sol())return a.innerActive=a.inner=null,this.token(i,a);var l=o.close?n(s,o.close,i.pos,o.parseDelimiters):-1;if(l==i.pos&&!o.parseDelimiters)return i.match(o.close),a.innerActive=a.inner=null,o.delimStyle&&o.delimStyle+" "+o.delimStyle+"-close";l>-1&&(i.string=s.slice(0,l));var u=o.mode.token(i,a.inner);return l>-1&&(i.string=s),l==i.pos&&o.parseDelimiters&&(a.innerActive=a.inner=null),o.innerStyle&&(u=u?u+" "+o.innerStyle:o.innerStyle),u}for(var c=1/0,s=i.string,f=0;fn.keyCol)return e.skipToEnd(),"string";if(n.literal&&(n.literal=!1),e.sol()){if(n.keyCol=0,n.pair=!1,n.pairStart=!1,e.match(/---/))return"def";if(e.match(/\.\.\./))return"def";if(e.match(/\s*-\s+/))return"meta"}if(e.match(/^(\{|\}|\[|\])/))return"{"==r?n.inlinePairs++:"}"==r?n.inlinePairs--:"["==r?n.inlineList++:n.inlineList--,"meta";if(n.inlineList>0&&!i&&","==r)return e.next(),"meta";if(n.inlinePairs>0&&!i&&","==r)return n.keyCol=0,n.pair=!1,n.pairStart=!1,e.next(),"meta";if(n.pairStart){if(e.match(/^\s*(\||\>)\s*/))return n.literal=!0,"meta";if(e.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i))return"variable-2";if(0==n.inlinePairs&&e.match(/^\s*-?[0-9\.\,]+\s?$/))return"number";if(n.inlinePairs>0&&e.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/))return"number";if(e.match(t))return"keyword"}return!n.pair&&e.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^,\[\]{}#&*!|>'"%@`])[^#]*?(?=\s*:($|\s))/)?(n.pair=!0,n.keyCol=e.indentation(),"atom"):n.pair&&e.match(/^:\s*/)?(n.pairStart=!0,"meta"):(n.pairStart=!1,n.escaped="\\"==r,e.next(),null)},startState:function(){return{pair:!1,pairStart:!1,keyCol:0,inlinePairs:0,inlineList:0,literal:!1,escaped:!1}},lineComment:"#",fold:"indent"}}),e.defineMIME("text/x-yaml","yaml"),e.defineMIME("text/yaml","yaml")})},function(e,t,n){!function(e){e(n(393))}(function(e){"use strict";function t(e,t){var r=t.line,i=0,a=0,o=n.exec(e.getLine(r)),s=o[1];do{i+=1;var l=r+i,u=e.getLine(l),c=n.exec(u);if(c){var f=c[1],d=parseInt(o[3],10)+i-a,p=parseInt(c[3],10),h=p;if(s!==f||isNaN(p)){if(s.length>f.length)return;if(s.lengthp&&(h=d+1),e.replaceRange(u.replace(n,f+h+c[4]+c[5]),{line:l,ch:0},{line:l,ch:u.length})}}while(c)}var n=/^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]))(\s*)/,r=/^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/,i=/[*+-]\s/;e.commands.newlineAndIndentContinueMarkdownList=function(a){if(a.getOption("disableInput"))return e.Pass;for(var o=a.listSelections(),s=[],l=0;l \s*$/.test(p)||a.replaceRange("",{line:u.line,ch:0},{line:u.line,ch:u.ch+1}),s[l]="\n";else{var v=h[1],g=h[5],y=!(i.test(h[2])||h[2].indexOf(">")>=0),_=y?parseInt(h[3],10)+1+h[4]:h[2].replace("x"," ");s[l]="\n"+v+_+g,y&&t(a,u)}}a.replaceSelections(s)}})},function(e,t,n){!function(e){e(n(393))}(function(e){"use strict";function t(e){for(var t=0;t1,n.options.tooltips))}}r.onUpdateLinting&&r.onUpdateLinting(t,i,e)}function m(e){var t=e.state.lint;t&&(clearTimeout(t.timeout),t.timeout=setTimeout(function(){p(e)},t.options.delay||500))}function v(e,t){for(var n=t.target||t.srcElement,r=document.createDocumentFragment(),a=0;a=0&&b.splice(t,1)}function s(e){var t=document.createElement("style");return e.attrs.type="text/css",u(t,e.attrs),a(e,t),t}function l(e){var t=document.createElement("link");return e.attrs.type="text/css",e.attrs.rel="stylesheet",u(t,e.attrs),a(e,t),t}function u(e,t){Object.keys(t).forEach(function(n){e.setAttribute(n,t[n])})}function c(e,t){var n,r,i,a;if(t.transform&&e.css){if(a=t.transform(e.css),!a)return function(){};e.css=a}if(t.singleton){var u=_++;n=y||(y=s(t)),r=f.bind(null,n,u,!1),i=f.bind(null,n,u,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=l(t),r=p.bind(null,n,t),i=function(){o(n),n.href&&URL.revokeObjectURL(n.href)}):(n=s(t),r=d.bind(null,n),i=function(){o(n)});return r(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;r(e=t)}else i()}}function f(e,t,n,r){var i=n?"":r.css;if(e.styleSheet)e.styleSheet.cssText=x(t,i);else{var a=document.createTextNode(i),o=e.childNodes;o[t]&&e.removeChild(o[t]),o.length?e.insertBefore(a,o[t]):e.appendChild(a)}}function d(e,t){var n=t.css,r=t.media;if(r&&e.setAttribute("media",r),e.styleSheet)e.styleSheet.cssText=n;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(n))}}function p(e,t,n){var r=n.css,i=n.sourceMap,a=void 0===t.convertToAbsoluteUrls&&i;(t.convertToAbsoluteUrls||a)&&(r=w(r)),i&&(r+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(i))))+" */");var o=new Blob([r],{type:"text/css"}),s=e.href;e.href=URL.createObjectURL(o),s&&URL.revokeObjectURL(s)}var h={},m=function(e){var t;return function(){return"undefined"==typeof t&&(t=e.apply(this,arguments)),t}},v=m(function(){return window&&document&&document.all&&!window.atob}),g=function(e){var t={};return function(n){return"undefined"==typeof t[n]&&(t[n]=e.call(this,n)),t[n]}}(function(e){return document.querySelector(e)}),y=null,_=0,b=[],w=n(451);e.exports=function(e,t){t=t||{},t.attrs="object"==typeof t.attrs?t.attrs:{},t.singleton||(t.singleton=v()),t.insertInto||(t.insertInto="head"),t.insertAt||(t.insertAt="bottom");var n=i(e,t);return r(n,t),function(e){for(var a=[],o=0;o-1&&(o+=s)}var l={message:i.reason,severity:i.code&&i.code.startsWith("W")?"warning":"error",from:e.Pos(i.line-1,a),to:e.Pos(i.line-1,o)};n.push(l)}}}e.registerHelper("lint","javascript",t)})},function(e,t,n){!function(e){e(n(393))}(function(e){"use strict";e.registerHelper("lint","json",function(t){var n=[];if(!window.jsonlint)return window.console&&window.console.error("Error: window.jsonlint not defined, CodeMirror JSON linting cannot run."),n;var r=window.jsonlint.parser||window.jsonlint;r.parseError=function(t,r){var i=r.loc;n.push({from:e.Pos(i.first_line-1,i.first_column),to:e.Pos(i.last_line-1,i.last_column),message:t})};try{r.parse(t)}catch(e){}return n})})},function(e,t,n){!function(e){e(n(393))}(function(e){"use strict";e.registerHelper("lint","yaml",function(t){var n=[];if(!window.jsyaml)return window.console&&window.console.error("Error: window.jsyaml not defined, CodeMirror YAML linting cannot run."),n;try{jsyaml.load(t)}catch(t){var r=t.mark,i=r?e.Pos(r.line,r.column):e.Pos(0,0),a=i;n.push({from:i,to:a,message:t.message})}return n})})},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var i=n(1),a=r(i);(0,a.default)('[name="task"][value="saveas"], [name="task"][value="switchlanguage"]').on("mousedown touchstart",function(e){var t=["lang","redirect"],n=(0,a.default)(e.currentTarget),r=(0,a.default)("#"+n.attr("form"));if(r.length)return t.forEach(function(e){var t=n.attr(e);if(t){var i=r.find('[name="data['+e+']"]');i.length||(i=(0,a.default)(' '),r.append(i)),i.val(t)}}),!0})},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(458),a=r(i),o=n(464),s=r(o),l=n(465),u=r(l);t.default={Form:{Form:s.default,Instance:o.Instance},Fields:u.default,FormState:{FormState:a.default,Instance:i.Instance}}},function(e,t,n){(function(e){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0}),t.DOMBehaviors=t.Instance=void 0;var a=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{ignore:[],form_id:"blueprints"};i(this,e),this.options=t,this.refresh(),this.form&&this.fields.length&&(d=this.collect(),this.loadState=d,p.attach())}return a(e,[{key:"refresh",value:function(){return this.form=(0,s.default)("form#"+this.options.form_id).filter(":noparents(.remodal)"),this.fields=(0,s.default)("form#"+this.options.form_id+' *, [form="'+this.options.form_id+'"]').filter(":input:not(.no-form)").filter(":noparents(.remodal)"),this}},{key:"collect",value:function(){var e=this;if(this.form&&this.fields.length){var t={};return this.refresh().fields.each(function(n,r){r=(0,s.default)(r);var i=r.prop("name"),a=r.prop("type"),o=r.prop("tagName").toLowerCase(),l=void 0;if(!i.startsWith("toggleable_")&&"data[lang]"!==i&&"data[redirect]"!==i){switch(a){case"checkbox":l=r.is(":checked");break;case"radio":if(!r.is(":checked"))return;l=r.val();break;default:l=r.val()}"select"===o&&null===l&&(l=""),Array.isArray(l)&&(l=l.join("|")),i&&!~e.options.ignore.indexOf(i)&&(t[i]=l)}}),u.default.OrderedMap(t)}}},{key:"diff",value:function(){return(0,f.default)(d,this.collect())}},{key:"equals",value:function(){return this.form&&this.fields.length?u.default.is(d,this.collect()):null}}]),e}();t.default=h;var m=t.Instance=new h;t.DOMBehaviors=p}).call(t,function(){return this}())},function(e,t,n){!function(t,n){e.exports=n()}(this,function(){"use strict";function e(e,t){t&&(e.prototype=Object.create(t.prototype)),e.prototype.constructor=e}function t(e){return a(e)?e:M(e)}function n(e){return o(e)?e:O(e)}function r(e){return s(e)?e:T(e)}function i(e){return a(e)&&!l(e)?e:P(e)}function a(e){return!(!e||!e[un])}function o(e){return!(!e||!e[cn])}function s(e){return!(!e||!e[fn])}function l(e){return o(e)||s(e)}function u(e){return!(!e||!e[dn])}function c(e){return e.value=!1,e}function f(e){e&&(e.value=!0)}function d(){}function p(e,t){t=t||0;for(var n=Math.max(0,e.length-t),r=new Array(n),i=0;i>>0;if(""+n!==t||4294967295===n)return NaN;t=n}return t<0?h(e)+t:t}function v(){return!0}function g(e,t,n){return(0===e||void 0!==n&&e<=-n)&&(void 0===t||void 0!==n&&t>=n)}function y(e,t){return b(e,t,0)}function _(e,t){return b(e,t,t)}function b(e,t,n){return void 0===e?n:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}function w(e){this.next=e}function x(e,t,n,r){var i=0===e?t:1===e?n:[t,n];return r?r.value=i:r={value:i,done:!1},r}function k(){return{value:void 0,done:!0}}function S(e){return!!A(e)}function C(e){return e&&"function"==typeof e.next}function E(e){var t=A(e);return t&&t.call(e)}function A(e){var t=e&&(kn&&e[kn]||e[Sn]);if("function"==typeof t)return t}function I(e){return e&&"number"==typeof e.length}function M(e){return null===e||void 0===e?F():a(e)?e.toSeq():U(e)}function O(e){return null===e||void 0===e?F().toKeyedSeq():a(e)?o(e)?e.toSeq():e.fromEntrySeq():R(e)}function T(e){return null===e||void 0===e?F():a(e)?o(e)?e.entrySeq():e.toIndexedSeq():q(e)}function P(e){return(null===e||void 0===e?F():a(e)?o(e)?e.entrySeq():e:q(e)).toSetSeq()}function j(e){this._array=e,this.size=e.length}function D(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function z(e){this._iterable=e,this.size=e.length||e.size}function L(e){this._iterator=e,this._iteratorCache=[]}function N(e){return!(!e||!e[En])}function F(){return An||(An=new j([]))}function R(e){var t=Array.isArray(e)?new j(e).fromEntrySeq():C(e)?new L(e).fromEntrySeq():S(e)?new z(e).fromEntrySeq():"object"==typeof e?new D(e):void 0;if(!t)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+e);return t}function q(e){var t=V(e);if(!t)throw new TypeError("Expected Array or iterable object of values: "+e);return t}function U(e){var t=V(e)||"object"==typeof e&&new D(e);if(!t)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+e);return t}function V(e){return I(e)?new j(e):C(e)?new L(e):S(e)?new z(e):void 0}function B(e,t,n,r){var i=e._cache;if(i){for(var a=i.length-1,o=0;o<=a;o++){var s=i[n?a-o:o];if(t(s[1],r?s[0]:o,e)===!1)return o+1}return o}return e.__iterateUncached(t,n)}function G(e,t,n,r){var i=e._cache;if(i){var a=i.length-1,o=0;return new w(function(){var e=i[n?a-o:o];return o++>a?k():x(t,r?e[0]:o-1,e[1])})}return e.__iteratorUncached(t,n)}function H(e,t){return t?$(t,e,"",{"":e}):W(e)}function $(e,t,n,r){return Array.isArray(t)?e.call(r,n,T(t).map(function(n,r){return $(e,n,r,t)})):K(t)?e.call(r,n,O(t).map(function(n,r){return $(e,n,r,t)})):t}function W(e){return Array.isArray(e)?T(e).map(W).toList():K(e)?O(e).map(W).toMap():e}function K(e){return e&&(e.constructor===Object||void 0===e.constructor)}function Y(e,t){if(e===t||e!==e&&t!==t)return!0;if(!e||!t)return!1;if("function"==typeof e.valueOf&&"function"==typeof t.valueOf){if(e=e.valueOf(),t=t.valueOf(),e===t||e!==e&&t!==t)return!0;if(!e||!t)return!1}return!("function"!=typeof e.equals||"function"!=typeof t.equals||!e.equals(t))}function J(e,t){if(e===t)return!0;if(!a(t)||void 0!==e.size&&void 0!==t.size&&e.size!==t.size||void 0!==e.__hash&&void 0!==t.__hash&&e.__hash!==t.__hash||o(e)!==o(t)||s(e)!==s(t)||u(e)!==u(t))return!1;if(0===e.size&&0===t.size)return!0;var n=!l(e);if(u(e)){var r=e.entries();return t.every(function(e,t){var i=r.next().value;return i&&Y(i[1],e)&&(n||Y(i[0],t))})&&r.next().done}var i=!1;if(void 0===e.size)if(void 0===t.size)"function"==typeof e.cacheResult&&e.cacheResult();else{i=!0;var c=e;e=t,t=c}var f=!0,d=t.__iterate(function(t,r){if(n?!e.has(t):i?!Y(t,e.get(r,gn)):!Y(e.get(r,gn),t))return f=!1,!1});return f&&e.size===d}function X(e,t){if(!(this instanceof X))return new X(e,t);if(this._value=e,this.size=void 0===t?1/0:Math.max(0,t),0===this.size){if(In)return In;In=this}}function Q(e,t){if(!e)throw new Error(t)}function Z(e,t,n){if(!(this instanceof Z))return new Z(e,t,n);if(Q(0!==n,"Cannot step a Range by 0"),e=e||0,void 0===t&&(t=1/0),n=void 0===n?1:Math.abs(n),t>>1&1073741824|3221225471&e}function ae(e){if(e===!1||null===e||void 0===e)return 0;if("function"==typeof e.valueOf&&(e=e.valueOf(),e===!1||null===e||void 0===e))return 0;if(e===!0)return 1;var t=typeof e;if("number"===t){if(e!==e||e===1/0)return 0;var n=0|e;for(n!==e&&(n^=4294967295*e);e>4294967295;)e/=4294967295,n^=e;return ie(n)}if("string"===t)return e.length>Nn?oe(e):se(e);if("function"==typeof e.hashCode)return e.hashCode();if("object"===t)return le(e);if("function"==typeof e.toString)return se(e.toString());throw new Error("Value type "+t+" cannot be hashed.")}function oe(e){var t=qn[e];return void 0===t&&(t=se(e),Rn===Fn&&(Rn=0,qn={}),Rn++,qn[e]=t),t}function se(e){for(var t=0,n=0;n0)switch(e.nodeType){case 1:return e.uniqueID;case 9:return e.documentElement&&e.documentElement.uniqueID}}function ce(e){Q(e!==1/0,"Cannot perform this action with an infinite size.")}function fe(e){return null===e||void 0===e?xe():de(e)&&!u(e)?e:xe().withMutations(function(t){var r=n(e);ce(r.size),r.forEach(function(e,n){return t.set(n,e)})})}function de(e){return!(!e||!e[Un])}function pe(e,t){this.ownerID=e,this.entries=t}function he(e,t,n){this.ownerID=e,this.bitmap=t,this.nodes=n}function me(e,t,n){this.ownerID=e,this.count=t,this.nodes=n}function ve(e,t,n){this.ownerID=e,this.keyHash=t,this.entries=n}function ge(e,t,n){this.ownerID=e,this.keyHash=t,this.entry=n}function ye(e,t,n){this._type=t,this._reverse=n,this._stack=e._root&&be(e._root)}function _e(e,t){return x(e,t[0],t[1])}function be(e,t){return{node:e,index:0,__prev:t}}function we(e,t,n,r){var i=Object.create(Vn);return i.size=e,i._root=t,i.__ownerID=n,i.__hash=r,i.__altered=!1,i}function xe(){return Bn||(Bn=we(0))}function ke(e,t,n){var r,i;if(e._root){var a=c(yn),o=c(_n);if(r=Se(e._root,e.__ownerID,0,void 0,t,n,a,o),!o.value)return e;i=e.size+(a.value?n===gn?-1:1:0)}else{if(n===gn)return e;i=1,r=new pe(e.__ownerID,[[t,n]])}return e.__ownerID?(e.size=i,e._root=r,e.__hash=void 0,e.__altered=!0,e):r?we(i,r):xe()}function Se(e,t,n,r,i,a,o,s){return e?e.update(t,n,r,i,a,o,s):a===gn?e:(f(s),f(o),new ge(t,r,[i,a]))}function Ce(e){return e.constructor===ge||e.constructor===ve}function Ee(e,t,n,r,i){if(e.keyHash===r)return new ve(t,r,[e.entry,i]);var a,o=(0===n?e.keyHash:e.keyHash>>>n)&vn,s=(0===n?r:r>>>n)&vn,l=o===s?[Ee(e,t,n+hn,r,i)]:(a=new ge(t,r,i),o>>=1)o[s]=1&n?t[a++]:void 0;return o[r]=i,new me(e,a+1,o)}function Oe(e,t,r){for(var i=[],o=0;o>1&1431655765,e=(858993459&e)+(e>>2&858993459),e=e+(e>>4)&252645135,e+=e>>8,e+=e>>16,127&e}function Le(e,t,n,r){var i=r?e:p(e);return i[t]=n,i}function Ne(e,t,n,r){var i=e.length+1;if(r&&t+1===i)return e[t]=n,e;for(var a=new Array(i),o=0,s=0;s0&&ia?0:a-n,u=o-n;return u>mn&&(u=mn),function(){if(i===u)return Jn;var e=t?--u:i++;return r&&r[e]}}function i(e,r,i){var s,l=e&&e.array,u=i>a?0:a-i>>r,c=(o-i>>r)+1;return c>mn&&(c=mn),function(){for(;;){if(s){var e=s();if(e!==Jn)return e;s=null}if(u===c)return Jn;var a=t?--c:u++;s=n(l&&l[a],r-hn,i+(a<=e.size||t<0)return e.withMutations(function(e){t<0?Ye(e,t).set(0,n):Ye(e,0,t+1).set(t,n)});t+=e._origin;var r=e._tail,i=e._root,a=c(_n);return t>=Xe(e._capacity)?r=$e(r,e.__ownerID,0,t,n,a):i=$e(i,e.__ownerID,e._level,t,n,a),a.value?e.__ownerID?(e._root=i,e._tail=r,e.__hash=void 0,e.__altered=!0,e):Be(e._origin,e._capacity,e._level,i,r):e}function $e(e,t,n,r,i,a){var o=r>>>n&vn,s=e&&o0){var u=e&&e.array[o],c=$e(u,t,n-hn,r,i,a);return c===u?e:(l=We(e,t),l.array[o]=c,l)}return s&&e.array[o]===i?e:(f(a),l=We(e,t),void 0===i&&o===l.array.length-1?l.array.pop():l.array[o]=i,l)}function We(e,t){return t&&e&&t===e.ownerID?e:new Ue(e?e.array.slice():[],t)}function Ke(e,t){if(t>=Xe(e._capacity))return e._tail;if(t<1<0;)n=n.array[t>>>r&vn],r-=hn;return n}}function Ye(e,t,n){void 0!==t&&(t|=0),void 0!==n&&(n|=0);var r=e.__ownerID||new d,i=e._origin,a=e._capacity,o=i+t,s=void 0===n?a:n<0?a+n:i+n;if(o===i&&s===a)return e;if(o>=s)return e.clear();for(var l=e._level,u=e._root,c=0;o+c<0;)u=new Ue(u&&u.array.length?[void 0,u]:[],r),l+=hn,c+=1<=1<f?new Ue([],r):h;if(h&&p>f&&ohn;g-=hn){var y=f>>>g&vn;v=v.array[y]=We(v.array[y],r)}v.array[f>>>hn&vn]=h}if(s =p)o-=p,s-=p,l=hn,u=null,m=m&&m.removeBefore(r,0,o);else if(o>i||p>>l&vn;if(_!==p>>>l&vn)break;_&&(c+=(1<i&&(u=u.removeBefore(r,l,o-c)),u&&po&&(o=u.size),a(l)||(u=u.map(function(e){return H(e)})),i.push(u)}return o>e.size&&(e=e.setSize(o)),je(e,t,i)}function Xe(e){return e>>hn<=mn&&o.size>=2*a.size?(i=o.filter(function(e,t){return void 0!==e&&s!==t}),r=i.toKeyedSeq().map(function(e){return e[0]}).flip().toMap(),e.__ownerID&&(r.__ownerID=i.__ownerID=e.__ownerID)):(r=a.remove(t),i=s===o.size-1?o.pop():o.set(s,void 0))}else if(l){if(n===o.get(s)[1])return e;r=a,i=o.set(s,[t,n])}else r=a.set(t,o.size),i=o.set(o.size,[t,n]);return e.__ownerID?(e.size=r.size,e._map=r,e._list=i,e.__hash=void 0,e):et(r,i)}function rt(e,t){this._iter=e,this._useKeys=t,this.size=e.size}function it(e){this._iter=e,this.size=e.size}function at(e){this._iter=e,this.size=e.size}function ot(e){this._iter=e,this.size=e.size}function st(e){var t=It(e);return t._iter=e,t.size=e.size,t.flip=function(){return e},t.reverse=function(){var t=e.reverse.apply(this);return t.flip=function(){return e.reverse()},t},t.has=function(t){return e.includes(t)},t.includes=function(t){return e.has(t)},t.cacheResult=Mt,t.__iterateUncached=function(t,n){var r=this;return e.__iterate(function(e,n){return t(n,e,r)!==!1},n)},t.__iteratorUncached=function(t,n){if(t===xn){var r=e.__iterator(t,n);return new w(function(){var e=r.next();if(!e.done){var t=e.value[0];e.value[0]=e.value[1],e.value[1]=t}return e})}return e.__iterator(t===wn?bn:wn,n)},t}function lt(e,t,n){var r=It(e);return r.size=e.size,r.has=function(t){return e.has(t)},r.get=function(r,i){var a=e.get(r,gn);return a===gn?i:t.call(n,a,r,e)},r.__iterateUncached=function(r,i){var a=this;return e.__iterate(function(e,i,o){return r(t.call(n,e,i,o),i,a)!==!1},i)},r.__iteratorUncached=function(r,i){var a=e.__iterator(xn,i);return new w(function(){var i=a.next();if(i.done)return i;var o=i.value,s=o[0];return x(r,s,t.call(n,o[1],s,e),i)})},r}function ut(e,t){var n=It(e);return n._iter=e,n.size=e.size,n.reverse=function(){return e},e.flip&&(n.flip=function(){var t=st(e);return t.reverse=function(){return e.flip()},t}),n.get=function(n,r){return e.get(t?n:-1-n,r)},n.has=function(n){return e.has(t?n:-1-n)},n.includes=function(t){return e.includes(t)},n.cacheResult=Mt,n.__iterate=function(t,n){var r=this;return e.__iterate(function(e,n){return t(e,n,r)},!n)},n.__iterator=function(t,n){return e.__iterator(t,!n)},n}function ct(e,t,n,r){
+var i=It(e);return r&&(i.has=function(r){var i=e.get(r,gn);return i!==gn&&!!t.call(n,i,r,e)},i.get=function(r,i){var a=e.get(r,gn);return a!==gn&&t.call(n,a,r,e)?a:i}),i.__iterateUncached=function(i,a){var o=this,s=0;return e.__iterate(function(e,a,l){if(t.call(n,e,a,l))return s++,i(e,r?a:s-1,o)},a),s},i.__iteratorUncached=function(i,a){var o=e.__iterator(xn,a),s=0;return new w(function(){for(;;){var a=o.next();if(a.done)return a;var l=a.value,u=l[0],c=l[1];if(t.call(n,c,u,e))return x(i,r?u:s++,c,a)}})},i}function ft(e,t,n){var r=fe().asMutable();return e.__iterate(function(i,a){r.update(t.call(n,i,a,e),0,function(e){return e+1})}),r.asImmutable()}function dt(e,t,n){var r=o(e),i=(u(e)?Qe():fe()).asMutable();e.__iterate(function(a,o){i.update(t.call(n,a,o,e),function(e){return e=e||[],e.push(r?[o,a]:a),e})});var a=At(e);return i.map(function(t){return St(e,a(t))})}function pt(e,t,n,r){var i=e.size;if(void 0!==t&&(t|=0),void 0!==n&&(n===1/0?n=i:n|=0),g(t,n,i))return e;var a=y(t,i),o=_(n,i);if(a!==a||o!==o)return pt(e.toSeq().cacheResult(),t,n,r);var s,l=o-a;l===l&&(s=l<0?0:l);var u=It(e);return u.size=0===s?s:e.size&&s||void 0,!r&&N(e)&&s>=0&&(u.get=function(t,n){return t=m(this,t),t>=0&&ts)return k();var e=i.next();return r||t===wn?e:t===bn?x(t,l-1,void 0,e):x(t,l-1,e.value[1],e)})},u}function ht(e,t,n){var r=It(e);return r.__iterateUncached=function(r,i){var a=this;if(i)return this.cacheResult().__iterate(r,i);var o=0;return e.__iterate(function(e,i,s){return t.call(n,e,i,s)&&++o&&r(e,i,a)}),o},r.__iteratorUncached=function(r,i){var a=this;if(i)return this.cacheResult().__iterator(r,i);var o=e.__iterator(xn,i),s=!0;return new w(function(){if(!s)return k();var e=o.next();if(e.done)return e;var i=e.value,l=i[0],u=i[1];return t.call(n,u,l,a)?r===xn?e:x(r,l,u,e):(s=!1,k())})},r}function mt(e,t,n,r){var i=It(e);return i.__iterateUncached=function(i,a){var o=this;if(a)return this.cacheResult().__iterate(i,a);var s=!0,l=0;return e.__iterate(function(e,a,u){if(!s||!(s=t.call(n,e,a,u)))return l++,i(e,r?a:l-1,o)}),l},i.__iteratorUncached=function(i,a){var o=this;if(a)return this.cacheResult().__iterator(i,a);var s=e.__iterator(xn,a),l=!0,u=0;return new w(function(){var e,a,c;do{if(e=s.next(),e.done)return r||i===wn?e:i===bn?x(i,u++,void 0,e):x(i,u++,e.value[1],e);var f=e.value;a=f[0],c=f[1],l&&(l=t.call(n,c,a,o))}while(l);return i===xn?e:x(i,a,c,e)})},i}function vt(e,t){var r=o(e),i=[e].concat(t).map(function(e){return a(e)?r&&(e=n(e)):e=r?R(e):q(Array.isArray(e)?e:[e]),e}).filter(function(e){return 0!==e.size});if(0===i.length)return e;if(1===i.length){var l=i[0];if(l===e||r&&o(l)||s(e)&&s(l))return l}var u=new j(i);return r?u=u.toKeyedSeq():s(e)||(u=u.toSetSeq()),u=u.flatten(!0),u.size=i.reduce(function(e,t){if(void 0!==e){var n=t.size;if(void 0!==n)return e+n}},0),u}function gt(e,t,n){var r=It(e);return r.__iterateUncached=function(r,i){function o(e,u){var c=this;e.__iterate(function(e,i){return(!t||u0}function kt(e,n,r){var i=It(e);return i.size=new j(r).map(function(e){return e.size}).min(),i.__iterate=function(e,t){for(var n,r=this.__iterator(wn,t),i=0;!(n=r.next()).done&&e(n.value,i++,this)!==!1;);return i},i.__iteratorUncached=function(e,i){var a=r.map(function(e){return e=t(e),E(i?e.reverse():e)}),o=0,s=!1;return new w(function(){var t;return s||(t=a.map(function(e){return e.next()}),s=t.some(function(e){return e.done})),s?k():x(e,o++,n.apply(null,t.map(function(e){return e.value})))})},i}function St(e,t){return N(e)?t:e.constructor(t)}function Ct(e){if(e!==Object(e))throw new TypeError("Expected [K, V] tuple: "+e)}function Et(e){return ce(e.size),h(e)}function At(e){return o(e)?n:s(e)?r:i}function It(e){return Object.create((o(e)?O:s(e)?T:P).prototype)}function Mt(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):M.prototype.cacheResult.call(this)}function Ot(e,t){return e>t?1:et?-1:0}function an(e){if(e.size===1/0)return 0;var t=u(e),n=o(e),r=t?1:0,i=e.__iterate(n?t?function(e,t){r=31*r+sn(ae(e),ae(t))|0}:function(e,t){r=r+sn(ae(e),ae(t))|0}:t?function(e){r=31*r+ae(e)|0}:function(e){r=r+ae(e)|0});return on(i,r)}function on(e,t){return t=Tn(t,3432918353),t=Tn(t<<15|t>>>-15,461845907),t=Tn(t<<13|t>>>-13,5),t=(t+3864292196|0)^e,t=Tn(t^t>>>16,2246822507),t=Tn(t^t>>>13,3266489909),t=ie(t^t>>>16)}function sn(e,t){return e^t+2654435769+(e<<6)+(e>>2)|0}var ln=Array.prototype.slice;e(n,t),e(r,t),e(i,t),t.isIterable=a,t.isKeyed=o,t.isIndexed=s,t.isAssociative=l,t.isOrdered=u,t.Keyed=n,t.Indexed=r,t.Set=i;var un="@@__IMMUTABLE_ITERABLE__@@",cn="@@__IMMUTABLE_KEYED__@@",fn="@@__IMMUTABLE_INDEXED__@@",dn="@@__IMMUTABLE_ORDERED__@@",pn="delete",hn=5,mn=1<r?k():x(e,i,n[t?r-i++:i++])})},e(D,O),D.prototype.get=function(e,t){return void 0===t||this.has(e)?this._object[e]:t},D.prototype.has=function(e){return this._object.hasOwnProperty(e)},D.prototype.__iterate=function(e,t){for(var n=this._object,r=this._keys,i=r.length-1,a=0;a<=i;a++){var o=r[t?i-a:a];if(e(n[o],o,this)===!1)return a+1}return a},D.prototype.__iterator=function(e,t){var n=this._object,r=this._keys,i=r.length-1,a=0;return new w(function(){var o=r[t?i-a:a];return a++>i?k():x(e,o,n[o])})},D.prototype[dn]=!0,e(z,T),z.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);var n=this._iterable,r=E(n),i=0;if(C(r))for(var a;!(a=r.next()).done&&e(a.value,i++,this)!==!1;);return i},z.prototype.__iteratorUncached=function(e,t){if(t)return this.cacheResult().__iterator(e,t);var n=this._iterable,r=E(n);if(!C(r))return new w(k);var i=0;return new w(function(){var t=r.next();return t.done?t:x(e,i++,t.value)})},e(L,T),L.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);for(var n=this._iterator,r=this._iteratorCache,i=0;i=r.length){var t=n.next();if(t.done)return t;r[i]=t.value}return x(e,i,r[i++])})};var An;e(X,T),X.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},X.prototype.get=function(e,t){return this.has(e)?this._value:t},X.prototype.includes=function(e){return Y(this._value,e)},X.prototype.slice=function(e,t){var n=this.size;return g(e,t,n)?this:new X(this._value,_(t,n)-y(e,n))},X.prototype.reverse=function(){return this},X.prototype.indexOf=function(e){return Y(this._value,e)?0:-1},X.prototype.lastIndexOf=function(e){return Y(this._value,e)?this.size:-1},X.prototype.__iterate=function(e,t){for(var n=0;n=0&&t=0&&nn?k():x(e,a++,o)})},Z.prototype.equals=function(e){return e instanceof Z?this._start===e._start&&this._end===e._end&&this._step===e._step:J(this,e)};var Mn;e(ee,t),e(te,ee),e(ne,ee),e(re,ee),ee.Keyed=te,ee.Indexed=ne,ee.Set=re;var On,Tn="function"==typeof Math.imul&&Math.imul(4294967295,2)===-2?Math.imul:function(e,t){e|=0,t|=0;var n=65535&e,r=65535&t;return n*r+((e>>>16)*r+n*(t>>>16)<<16>>>0)|0},Pn=Object.isExtensible,jn=function(){try{return Object.defineProperty({},"@",{}),!0}catch(e){return!1}}(),Dn="function"==typeof WeakMap;Dn&&(On=new WeakMap);var zn=0,Ln="__immutablehash__";"function"==typeof Symbol&&(Ln=Symbol(Ln));var Nn=16,Fn=255,Rn=0,qn={};e(fe,te),fe.of=function(){var e=ln.call(arguments,0);return xe().withMutations(function(t){for(var n=0;n=e.length)throw new Error("Missing value for key: "+e[n]);t.set(e[n],e[n+1])}})},fe.prototype.toString=function(){return this.__toString("Map {","}")},fe.prototype.get=function(e,t){return this._root?this._root.get(0,void 0,e,t):t},fe.prototype.set=function(e,t){return ke(this,e,t)},fe.prototype.setIn=function(e,t){return this.updateIn(e,gn,function(){return t})},fe.prototype.remove=function(e){return ke(this,e,gn)},fe.prototype.deleteIn=function(e){return this.updateIn(e,function(){return gn})},fe.prototype.update=function(e,t,n){return 1===arguments.length?e(this):this.updateIn([e],t,n)},fe.prototype.updateIn=function(e,t,n){n||(n=t,t=void 0);var r=De(this,Tt(e),t,n);return r===gn?void 0:r},fe.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):xe()},fe.prototype.merge=function(){return Oe(this,void 0,arguments)},fe.prototype.mergeWith=function(e){var t=ln.call(arguments,1);return Oe(this,e,t)},fe.prototype.mergeIn=function(e){var t=ln.call(arguments,1);return this.updateIn(e,xe(),function(e){return"function"==typeof e.merge?e.merge.apply(e,t):t[t.length-1]})},fe.prototype.mergeDeep=function(){return Oe(this,Te,arguments)},fe.prototype.mergeDeepWith=function(e){var t=ln.call(arguments,1);return Oe(this,Pe(e),t)},fe.prototype.mergeDeepIn=function(e){var t=ln.call(arguments,1);return this.updateIn(e,xe(),function(e){return"function"==typeof e.mergeDeep?e.mergeDeep.apply(e,t):t[t.length-1]})},fe.prototype.sort=function(e){return Qe(bt(this,e))},fe.prototype.sortBy=function(e,t){return Qe(bt(this,t,e))},fe.prototype.withMutations=function(e){var t=this.asMutable();return e(t),t.wasAltered()?t.__ensureOwner(this.__ownerID):this},fe.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new d)},fe.prototype.asImmutable=function(){return this.__ensureOwner()},fe.prototype.wasAltered=function(){return this.__altered},fe.prototype.__iterator=function(e,t){return new ye(this,e,t)},fe.prototype.__iterate=function(e,t){var n=this,r=0;return this._root&&this._root.iterate(function(t){return r++,e(t[1],t[0],n)},t),r},fe.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?we(this.size,this._root,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},fe.isMap=de;var Un="@@__IMMUTABLE_MAP__@@",Vn=fe.prototype;Vn[Un]=!0,Vn[pn]=Vn.remove,Vn.removeIn=Vn.deleteIn,pe.prototype.get=function(e,t,n,r){for(var i=this.entries,a=0,o=i.length;a=Gn)return Ae(e,l,r,i);var h=e&&e===this.ownerID,m=h?l:p(l);return d?s?u===c-1?m.pop():m[u]=m.pop():m[u]=[r,i]:m.push([r,i]),h?(this.entries=m,this):new pe(e,m)}},he.prototype.get=function(e,t,n,r){void 0===t&&(t=ae(n));var i=1<<((0===e?t:t>>>e)&vn),a=this.bitmap;return 0===(a&i)?r:this.nodes[ze(a&i-1)].get(e+hn,t,n,r)},he.prototype.update=function(e,t,n,r,i,a,o){void 0===n&&(n=ae(r));var s=(0===t?n:n>>>t)&vn,l=1<=Hn)return Me(e,d,u,s,h);if(c&&!h&&2===d.length&&Ce(d[1^f]))return d[1^f];if(c&&h&&1===d.length&&Ce(h))return h;var m=e&&e===this.ownerID,v=c?h?u:u^l:u|l,g=c?h?Le(d,f,h,m):Fe(d,f,m):Ne(d,f,h,m);return m?(this.bitmap=v,this.nodes=g,this):new he(e,v,g)},me.prototype.get=function(e,t,n,r){void 0===t&&(t=ae(n));var i=(0===e?t:t>>>e)&vn,a=this.nodes[i];return a?a.get(e+hn,t,n,r):r},me.prototype.update=function(e,t,n,r,i,a,o){void 0===n&&(n=ae(r));var s=(0===t?n:n>>>t)&vn,l=i===gn,u=this.nodes,c=u[s];if(l&&!c)return this;var f=Se(c,e,t+hn,n,r,i,a,o);if(f===c)return this;var d=this.count;if(c){if(!f&&(d--,d<$n))return Ie(e,u,d,s)}else d++;var p=e&&e===this.ownerID,h=Le(u,s,f,p);return p?(this.count=d,this.nodes=h,this):new me(e,d,h)},ve.prototype.get=function(e,t,n,r){for(var i=this.entries,a=0,o=i.length;a=0&&e>>t&vn;if(r>=this.array.length)return new Ue([],e);var i,a=0===r;if(t>0){var o=this.array[r];if(i=o&&o.removeBefore(e,t-hn,n),i===o&&a)return this}if(a&&!i)return this;var s=We(this,e);if(!a)for(var l=0;l>>t&vn;if(r>=this.array.length)return this;var i;if(t>0){var a=this.array[r];if(i=a&&a.removeAfter(e,t-hn,n),i===a&&r===this.array.length-1)return this}var o=We(this,e);return o.array.splice(r+1),i&&(o.array[r]=i),o};var Yn,Jn={};e(Qe,fe),Qe.of=function(){return this(arguments)},Qe.prototype.toString=function(){return this.__toString("OrderedMap {","}")},Qe.prototype.get=function(e,t){var n=this._map.get(e);return void 0!==n?this._list.get(n)[1]:t},Qe.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._map.clear(),this._list.clear(),this):tt()},Qe.prototype.set=function(e,t){return nt(this,e,t)},Qe.prototype.remove=function(e){return nt(this,e,gn)},Qe.prototype.wasAltered=function(){return this._map.wasAltered()||this._list.wasAltered()},Qe.prototype.__iterate=function(e,t){var n=this;return this._list.__iterate(function(t){return t&&e(t[1],t[0],n)},t)},Qe.prototype.__iterator=function(e,t){return this._list.fromEntrySeq().__iterator(e,t)},Qe.prototype.__ensureOwner=function(e){if(e===this.__ownerID)return this;var t=this._map.__ensureOwner(e),n=this._list.__ensureOwner(e);return e?et(t,n,e,this.__hash):(this.__ownerID=e,this._map=t,this._list=n,this)},Qe.isOrderedMap=Ze,Qe.prototype[dn]=!0,Qe.prototype[pn]=Qe.prototype.remove;var Xn;e(rt,O),rt.prototype.get=function(e,t){return this._iter.get(e,t)},rt.prototype.has=function(e){return this._iter.has(e)},rt.prototype.valueSeq=function(){return this._iter.valueSeq()},rt.prototype.reverse=function(){var e=this,t=ut(this,!0);return this._useKeys||(t.valueSeq=function(){return e._iter.toSeq().reverse()}),t},rt.prototype.map=function(e,t){var n=this,r=lt(this,e,t);return this._useKeys||(r.valueSeq=function(){return n._iter.toSeq().map(e,t)}),r},rt.prototype.__iterate=function(e,t){var n,r=this;return this._iter.__iterate(this._useKeys?function(t,n){return e(t,n,r)}:(n=t?Et(this):0,function(i){return e(i,t?--n:n++,r)}),t)},rt.prototype.__iterator=function(e,t){if(this._useKeys)return this._iter.__iterator(e,t);var n=this._iter.__iterator(wn,t),r=t?Et(this):0;return new w(function(){var i=n.next();return i.done?i:x(e,t?--r:r++,i.value,i)})},rt.prototype[dn]=!0,e(it,T),it.prototype.includes=function(e){return this._iter.includes(e)},it.prototype.__iterate=function(e,t){var n=this,r=0;return this._iter.__iterate(function(t){return e(t,r++,n)},t)},it.prototype.__iterator=function(e,t){var n=this._iter.__iterator(wn,t),r=0;return new w(function(){var t=n.next();return t.done?t:x(e,r++,t.value,t)})},e(at,P),at.prototype.has=function(e){return this._iter.includes(e)},at.prototype.__iterate=function(e,t){var n=this;return this._iter.__iterate(function(t){return e(t,t,n)},t)},at.prototype.__iterator=function(e,t){var n=this._iter.__iterator(wn,t);return new w(function(){var t=n.next();return t.done?t:x(e,t.value,t.value,t)})},e(ot,O),ot.prototype.entrySeq=function(){return this._iter.toSeq()},ot.prototype.__iterate=function(e,t){var n=this;return this._iter.__iterate(function(t){if(t){Ct(t);var r=a(t);return e(r?t.get(1):t[1],r?t.get(0):t[0],n)}},t)},ot.prototype.__iterator=function(e,t){var n=this._iter.__iterator(wn,t);return new w(function(){for(;;){var t=n.next();if(t.done)return t;var r=t.value;if(r){Ct(r);var i=a(r);return x(e,i?r.get(0):r[0],i?r.get(1):r[1],t)}}})},it.prototype.cacheResult=rt.prototype.cacheResult=at.prototype.cacheResult=ot.prototype.cacheResult=Mt,e(Pt,te),Pt.prototype.toString=function(){return this.__toString(Dt(this)+" {","}")},Pt.prototype.has=function(e){return this._defaultValues.hasOwnProperty(e)},Pt.prototype.get=function(e,t){if(!this.has(e))return t;var n=this._defaultValues[e];return this._map?this._map.get(e,n):n},Pt.prototype.clear=function(){if(this.__ownerID)return this._map&&this._map.clear(),this;var e=this.constructor;return e._empty||(e._empty=jt(this,xe()))},Pt.prototype.set=function(e,t){if(!this.has(e))throw new Error('Cannot set unknown key "'+e+'" on '+Dt(this));if(this._map&&!this._map.has(e)){var n=this._defaultValues[e];if(t===n)return this}var r=this._map&&this._map.set(e,t);return this.__ownerID||r===this._map?this:jt(this,r)},Pt.prototype.remove=function(e){if(!this.has(e))return this;var t=this._map&&this._map.remove(e);return this.__ownerID||t===this._map?this:jt(this,t)},Pt.prototype.wasAltered=function(){return this._map.wasAltered()},Pt.prototype.__iterator=function(e,t){var r=this;return n(this._defaultValues).map(function(e,t){return r.get(t)}).__iterator(e,t)},Pt.prototype.__iterate=function(e,t){var r=this;return n(this._defaultValues).map(function(e,t){return r.get(t)}).__iterate(e,t)},Pt.prototype.__ensureOwner=function(e){if(e===this.__ownerID)return this;var t=this._map&&this._map.__ensureOwner(e);return e?jt(this,t,e):(this.__ownerID=e,this._map=t,this)};var Qn=Pt.prototype;Qn[pn]=Qn.remove,Qn.deleteIn=Qn.removeIn=Vn.removeIn,Qn.merge=Vn.merge,Qn.mergeWith=Vn.mergeWith,Qn.mergeIn=Vn.mergeIn,Qn.mergeDeep=Vn.mergeDeep,Qn.mergeDeepWith=Vn.mergeDeepWith,Qn.mergeDeepIn=Vn.mergeDeepIn,Qn.setIn=Vn.setIn,Qn.update=Vn.update,Qn.updateIn=Vn.updateIn,Qn.withMutations=Vn.withMutations,Qn.asMutable=Vn.asMutable,Qn.asImmutable=Vn.asImmutable,e(Nt,re),Nt.of=function(){return this(arguments)},Nt.fromKeys=function(e){return this(n(e).keySeq())},Nt.prototype.toString=function(){return this.__toString("Set {","}")},Nt.prototype.has=function(e){return this._map.has(e)},Nt.prototype.add=function(e){return Rt(this,this._map.set(e,!0))},Nt.prototype.remove=function(e){return Rt(this,this._map.remove(e))},Nt.prototype.clear=function(){return Rt(this,this._map.clear())},Nt.prototype.union=function(){var e=ln.call(arguments,0);return e=e.filter(function(e){return 0!==e.size}),0===e.length?this:0!==this.size||this.__ownerID||1!==e.length?this.withMutations(function(t){for(var n=0;n=0;n--)t={value:arguments[n],next:t};return this.__ownerID?(this.size=e,this._head=t,this.__hash=void 0,this.__altered=!0,this):Kt(e,t)},$t.prototype.pushAll=function(e){if(e=r(e),0===e.size)return this;ce(e.size);var t=this.size,n=this._head;return e.reverse().forEach(function(e){t++,n={value:e,next:n}}),this.__ownerID?(this.size=t,this._head=n,this.__hash=void 0,this.__altered=!0,this):Kt(t,n)},$t.prototype.pop=function(){return this.slice(1)},$t.prototype.unshift=function(){return this.push.apply(this,arguments)},$t.prototype.unshiftAll=function(e){return this.pushAll(e)},$t.prototype.shift=function(){return this.pop.apply(this,arguments)},$t.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):Yt()},$t.prototype.slice=function(e,t){if(g(e,t,this.size))return this;
+var n=y(e,this.size),r=_(t,this.size);if(r!==this.size)return ne.prototype.slice.call(this,e,t);for(var i=this.size-n,a=this._head;n--;)a=a.next;return this.__ownerID?(this.size=i,this._head=a,this.__hash=void 0,this.__altered=!0,this):Kt(i,a)},$t.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?Kt(this.size,this._head,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},$t.prototype.__iterate=function(e,t){if(t)return this.reverse().__iterate(e);for(var n=0,r=this._head;r&&e(r.value,n++,this)!==!1;)r=r.next;return n},$t.prototype.__iterator=function(e,t){if(t)return this.reverse().__iterator(e);var n=0,r=this._head;return new w(function(){if(r){var t=r.value;return r=r.next,x(e,n++,t)}return k()})},$t.isStack=Wt;var ir="@@__IMMUTABLE_STACK__@@",ar=$t.prototype;ar[ir]=!0,ar.withMutations=Vn.withMutations,ar.asMutable=Vn.asMutable,ar.asImmutable=Vn.asImmutable,ar.wasAltered=Vn.wasAltered;var or;t.Iterator=w,Jt(t,{toArray:function(){ce(this.size);var e=new Array(this.size||0);return this.valueSeq().__iterate(function(t,n){e[n]=t}),e},toIndexedSeq:function(){return new it(this)},toJS:function(){return this.toSeq().map(function(e){return e&&"function"==typeof e.toJS?e.toJS():e}).__toJS()},toJSON:function(){return this.toSeq().map(function(e){return e&&"function"==typeof e.toJSON?e.toJSON():e}).__toJS()},toKeyedSeq:function(){return new rt(this,!0)},toMap:function(){return fe(this.toKeyedSeq())},toObject:function(){ce(this.size);var e={};return this.__iterate(function(t,n){e[n]=t}),e},toOrderedMap:function(){return Qe(this.toKeyedSeq())},toOrderedSet:function(){return Vt(o(this)?this.valueSeq():this)},toSet:function(){return Nt(o(this)?this.valueSeq():this)},toSetSeq:function(){return new at(this)},toSeq:function(){return s(this)?this.toIndexedSeq():o(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return $t(o(this)?this.valueSeq():this)},toList:function(){return Re(o(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(e,t){return 0===this.size?e+t:e+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+t},concat:function(){var e=ln.call(arguments,0);return St(this,vt(this,e))},includes:function(e){return this.some(function(t){return Y(t,e)})},entries:function(){return this.__iterator(xn)},every:function(e,t){ce(this.size);var n=!0;return this.__iterate(function(r,i,a){if(!e.call(t,r,i,a))return n=!1,!1}),n},filter:function(e,t){return St(this,ct(this,e,t,!0))},find:function(e,t,n){var r=this.findEntry(e,t);return r?r[1]:n},forEach:function(e,t){return ce(this.size),this.__iterate(t?e.bind(t):e)},join:function(e){ce(this.size),e=void 0!==e?""+e:",";var t="",n=!0;return this.__iterate(function(r){n?n=!1:t+=e,t+=null!==r&&void 0!==r?r.toString():""}),t},keys:function(){return this.__iterator(bn)},map:function(e,t){return St(this,lt(this,e,t))},reduce:function(e,t,n){ce(this.size);var r,i;return arguments.length<2?i=!0:r=t,this.__iterate(function(t,a,o){i?(i=!1,r=t):r=e.call(n,r,t,a,o)}),r},reduceRight:function(e,t,n){var r=this.toKeyedSeq().reverse();return r.reduce.apply(r,arguments)},reverse:function(){return St(this,ut(this,!0))},slice:function(e,t){return St(this,pt(this,e,t,!0))},some:function(e,t){return!this.every(Zt(e),t)},sort:function(e){return St(this,bt(this,e))},values:function(){return this.__iterator(wn)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some(function(){return!0})},count:function(e,t){return h(e?this.toSeq().filter(e,t):this)},countBy:function(e,t){return ft(this,e,t)},equals:function(e){return J(this,e)},entrySeq:function(){var e=this;if(e._cache)return new j(e._cache);var t=e.toSeq().map(Qt).toIndexedSeq();return t.fromEntrySeq=function(){return e.toSeq()},t},filterNot:function(e,t){return this.filter(Zt(e),t)},findEntry:function(e,t,n){var r=n;return this.__iterate(function(n,i,a){if(e.call(t,n,i,a))return r=[i,n],!1}),r},findKey:function(e,t){var n=this.findEntry(e,t);return n&&n[0]},findLast:function(e,t,n){return this.toKeyedSeq().reverse().find(e,t,n)},findLastEntry:function(e,t,n){return this.toKeyedSeq().reverse().findEntry(e,t,n)},findLastKey:function(e,t){return this.toKeyedSeq().reverse().findKey(e,t)},first:function(){return this.find(v)},flatMap:function(e,t){return St(this,yt(this,e,t))},flatten:function(e){return St(this,gt(this,e,!0))},fromEntrySeq:function(){return new ot(this)},get:function(e,t){return this.find(function(t,n){return Y(n,e)},void 0,t)},getIn:function(e,t){for(var n,r=this,i=Tt(e);!(n=i.next()).done;){var a=n.value;if(r=r&&r.get?r.get(a,gn):gn,r===gn)return t}return r},groupBy:function(e,t){return dt(this,e,t)},has:function(e){return this.get(e,gn)!==gn},hasIn:function(e){return this.getIn(e,gn)!==gn},isSubset:function(e){return e="function"==typeof e.includes?e:t(e),this.every(function(t){return e.includes(t)})},isSuperset:function(e){return e="function"==typeof e.isSubset?e:t(e),e.isSubset(this)},keyOf:function(e){return this.findKey(function(t){return Y(t,e)})},keySeq:function(){return this.toSeq().map(Xt).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(e){return this.toKeyedSeq().reverse().keyOf(e)},max:function(e){return wt(this,e)},maxBy:function(e,t){return wt(this,t,e)},min:function(e){return wt(this,e?en(e):rn)},minBy:function(e,t){return wt(this,t?en(t):rn,e)},rest:function(){return this.slice(1)},skip:function(e){return this.slice(Math.max(0,e))},skipLast:function(e){return St(this,this.toSeq().reverse().skip(e).reverse())},skipWhile:function(e,t){return St(this,mt(this,e,t,!0))},skipUntil:function(e,t){return this.skipWhile(Zt(e),t)},sortBy:function(e,t){return St(this,bt(this,t,e))},take:function(e){return this.slice(0,Math.max(0,e))},takeLast:function(e){return St(this,this.toSeq().reverse().take(e).reverse())},takeWhile:function(e,t){return St(this,ht(this,e,t))},takeUntil:function(e,t){return this.takeWhile(Zt(e),t)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=an(this))}});var sr=t.prototype;sr[un]=!0,sr[Cn]=sr.values,sr.__toJS=sr.toArray,sr.__toStringMapper=tn,sr.inspect=sr.toSource=function(){return this.toString()},sr.chain=sr.flatMap,sr.contains=sr.includes,Jt(n,{flip:function(){return St(this,st(this))},mapEntries:function(e,t){var n=this,r=0;return St(this,this.toSeq().map(function(i,a){return e.call(t,[a,i],r++,n)}).fromEntrySeq())},mapKeys:function(e,t){var n=this;return St(this,this.toSeq().flip().map(function(r,i){return e.call(t,r,i,n)}).flip())}});var lr=n.prototype;lr[cn]=!0,lr[Cn]=sr.entries,lr.__toJS=sr.toObject,lr.__toStringMapper=function(e,t){return JSON.stringify(t)+": "+tn(e)},Jt(r,{toKeyedSeq:function(){return new rt(this,!1)},filter:function(e,t){return St(this,ct(this,e,t,!1))},findIndex:function(e,t){var n=this.findEntry(e,t);return n?n[0]:-1},indexOf:function(e){var t=this.keyOf(e);return void 0===t?-1:t},lastIndexOf:function(e){var t=this.lastKeyOf(e);return void 0===t?-1:t},reverse:function(){return St(this,ut(this,!1))},slice:function(e,t){return St(this,pt(this,e,t,!1))},splice:function(e,t){var n=arguments.length;if(t=Math.max(0|t,0),0===n||2===n&&!t)return this;e=y(e,e<0?this.count():this.size);var r=this.slice(0,e);return St(this,1===n?r:r.concat(p(arguments,2),this.slice(e+t)))},findLastIndex:function(e,t){var n=this.findLastEntry(e,t);return n?n[0]:-1},first:function(){return this.get(0)},flatten:function(e){return St(this,gt(this,e,!1))},get:function(e,t){return e=m(this,e),e<0||this.size===1/0||void 0!==this.size&&e>this.size?t:this.find(function(t,n){return n===e},void 0,t)},has:function(e){return e=m(this,e),e>=0&&(void 0!==this.size?this.size===1/0||e=1e4)return d(e,t,n);var l=a.diff(e,t),f=0;return l.forEach(function(e){if("="===e.op)f++;else if("!="===e.op){if(c(e.val)&&c(e.newVal)){var t=d(e.val,e.newVal,s(o,f));i=i.concat(t)}else i.push(u("replace",s(o,f),e.newVal));f++}else"+"===e.op?(i.push(u("add",s(o,f),e.val)),f++):"-"===e.op&&i.push(u("remove",s(o,f)))}),i},h=function(e,t,n){var r=n||"";return e===t?[]:[u("replace",s(r,""),t)]},m=function(e,t,n){return r.is(e,t)?r.List():e==t||null!=e&&null!=t?f(e)&&f(t)?r.fromJS(p(e,t)):c(e)&&c(t)?r.fromJS(d(e,t)):r.fromJS(h(e,t,n)):r.fromJS([u("replace","/",t)])};e.exports=m},function(e,t,n){"use strict";var r=n(459),i=function(e){return r.Iterable.isKeyed(e)},a=function(e){return r.Iterable.isIndexed(e)},o=function(e,t,n){return"remove"===e?{op:e,path:t}:{op:e,path:t,value:n}};e.exports={isMap:i,isIndexed:a,op:o}},function(e,t,n){"use strict";function r(e,t){for(var n=e.size||0,r=t.size||0,o=a(n+1,r+1,0),s=0;s=0||c>=0;)u>=0&&c>=0&&i.is(t.get(u),n.get(c))?(o.push(new s({op:"=",val:t.get(u)})),u-=1,c-=1):u>=0&&c>=0&&u===c&&!i.is(t.get(u),n.get(c))?(o.push(new l({val:t.get(u),newVal:n.get(u)})),u-=1,c-=1):c>=0&&(u===-1||e[u+1][c]>=e[u][c+1])?(o.push(new s({op:"+",val:n.get(c)})),c-=1):u>=0&&(c===-1||e[u+1][c] ');n.append(i)}),e=!0,!0)})})}},{key:"addedNodes",value:function(e){var t=this;e.forEach(function(e){"childList"===e.type&&(e.addedNodes&&(0,s.default)("body").trigger("mutation._grav",e.target,e,t),e.removedNodes&&(0,s.default)("body").trigger("mutation_removed._grav",{target:e.target,mutation:e},t))})}}]),t}();t.default=l;t.Instance=new l("form#blueprints")}).call(t,function(){return this}())},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(466),a=r(i),o=n(467),s=r(o),l=n(469),u=r(l),c=n(470),f=r(c),d=n(471),p=r(d),h=n(391),m=r(h),v=n(598),g=r(v),y=n(387),_=r(y),b=n(603),w=r(b),x=n(606),k=r(x),S=n(607),C=r(S),E=n(608),A=r(E),I=n(609),M=r(I),O=n(611),T=r(O);t.default={FilepickerField:{FilepickerField:a.default,Instance:i.Instance},SelectizeField:{SelectizeField:s.default,Instance:o.Instance},ArrayField:{ArrayField:u.default,Instance:l.Instance},CollectionsField:{CollectionsField:f.default,Instance:c.Instance},DateTimeField:{DateTimeField:p.default,Instance:d.Instance},EditorField:{EditorField:m.default,Instance:h.Instance},ColorpickerField:{ColorpickerField:g.default,Instance:v.Instance},FilesField:{FilesField:_.default,Instance:y.Instance},SelectUniqueField:{SelectUniqueField:C.default,Instance:S.Instance},MediapickerField:{MediapickerField:w.default,Instance:b.Instance},MultilevelField:{MultilevelField:k.default,Instance:x.Instance},IconpickerField:{IconpickerField:A.default,Instance:E.Instance},CronField:{CronField:M.default,Insance:I.Instance},TextField:{TextField:T.default,Insance:O.Instance}}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0}),t.Instance=void 0;var a=function(){function e(e,t){for(var n=0;n2&&void 0!==arguments[2]?arguments[2]:"all",r=l.config.current_url+(".json/task"+l.config.param_sep+"getFilesInFolder"),o=e.closest("[data-grav-filepicker]"),s=o.data("name"),u=o.data("value"),f=JSON.stringify(l.uri_params||"{}");(0,c.default)(r,{method:"post",body:{name:s,params:f}},function(e){if("undefined"!=typeof e.files){var r=[];a=e.thumbs||{};for(var o=0;o0,f=!1,d=function(e,t){var n="";if(u&&i&&(!e.status||"available"===e.status)&&e.name.match(/\.(jpg|jpeg|png|gif)$/i)){var r=a[e.name]||l.config.base_url_relative+"/../"+i+"/"+e.name;n=' '}return'\n \n '+n+' '+t(e.name)+" \n \n
"};r.selectize({plugins:["required-fix"],valueField:"name",labelField:"name",searchField:"name",optgroups:[{$order:1,value:"pending",label:"Pending"},{$order:2,value:"available",label:"Available"}],optgroupField:"status",create:!1,preload:!1,render:{option:function(e,t){return d(e,t)},item:function(e,t){return d(e,t)}},onInitialize:function(){this.load(function(e){return o(r,function(t){return e(t)},"selected")})},onLoad:function(){if(!f){var e=this.getValue();this.updateOption(e,{name:e}),f=!0}},onFocus:function(){this.load(function(e){return o(r,function(t){return e(t)})})}})}}}]),e}();t.default=f;t.Instance=new f},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0}),t.Instance=void 0;var a=function(){function e(e,t){for(var n=0;n\n '+r+' \n \n '+i.replace("(","/").replace(")","")+" \n "+n.join(" ")+" \n \n "}},u=function(){function e(){var t=this,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};i(this,e),this.options=Object.assign({},n),this.elements=[],(0,s.default)("[data-grav-selectize]").each(function(e,n){return t.add(n)}),(0,s.default)("body").on("mutation._grav",this._onAddedNodes.bind(this))}return a(e,[{key:"add",value:function(e){e=(0,s.default)(e);var t=e.prop("tagName").toLowerCase(),n="input"===t||"select"===t,r=(n?e.closest("[data-grav-selectize]"):e).data("grav-selectize")||{},i=n?e:e.find("input, select");if("data[route]"===i.attr("name")&&(r=s.default.extend({},r,{render:l})),i.length&&!i.get(0).selectize){var a=s.default.merge(r.plugins?r.plugins:[],["required-fix"]);i.selectize(s.default.extend({},r,{plugins:a})),this.elements.push(i.data("selectize"))}}},{key:"_onAddedNodes",value:function(e,t){var n=this,r=(0,s.default)(t).find("select.fancy, input.fancy, [data-grav-selectize]");r.length&&r.each(function(e,t){return n.add(t)})}}]),e}();t.default=u;t.Instance=new u},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var i=n(1),a=r(i),o=n(374),s=r(o);s.default.define("option_click",function(e){var t=this,n=t.setup;this.setup=function(){n.apply(t,arguments);var e=!1;t.$dropdown_content.on("mousedown click",function(n){var r=(0,a.default)(n.target);r.hasClass("clickable")||r.closest(".clickable").length?"mousedown"===n.type?(e=!0,t.isFocused=!1):(t.isFocused=!0,setTimeout(function(){e=!1})):(e=!1,t.isFocused=!0)}),t.$dropdown.off("mousedown click","[data-selectable]").on("mousedown click","[data-selectable]",function(){if(!e)return t.onOptionSelect.apply(t,arguments)}),t.$control_input.off("blur").on("blur",function(){if(!e)return t.onBlur.apply(t,arguments)})}})},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0}),t.Instance=void 0;var a=function(){function e(e,t){for(var n=0;n':" ';return e+=this.isValueOnly()?'\n \n
\n '+t+"\n ":'\n
\n \n \n '+t+"\n ",e+='\n \n \n
'}}]),e}(),d=function(){function e(){var t=this;i(this,e),c.on("input",'[data-grav-array-type="key"], [data-grav-array-type="value"]',function(e){return t.actionInput(e)}),c.on("click touch",'[data-grav-array-action]:not([data-grav-array-action="sort"])',function(e){return t.actionEvent(e)}),this.arrays=(0,s.default)(),(0,s.default)('[data-grav-field="array"]').each(function(e,n){return t.addArray(n)}),(0,s.default)("body").on("mutation._grav",this._onAddedNodes.bind(this))}return a(e,[{key:"addArray",value:function(e){e=(0,s.default)(e),e.find('[data-grav-array-type="container"]').each(function(e,t){t=(0,s.default)(t),t.data("array-sort")||t[0].hasAttribute("data-array-nosort")||t.data("array-sort",new u.default(t.get(0),{handle:".fa-bars",animation:150}))})}},{key:"actionInput",value:function(e){var t=(0,s.default)(e.target),n=t.data("grav-array-type");this._setTemplate(t);var r=t.data("array-template"),i="key"===n?t:t.siblings('[data-grav-array-type="key"]:first'),a="value"===n?t:t.siblings('[data-grav-array-type="value"]:first'),o=r.isValueOnly()?this.getIndexFor(t):i.val();o=o.toString().replace(/\[/g,"%5B").replace(/]/g,"%5D");var l=r.getName()+"["+o+"]";a.attr("name",a.val()?l:r.getName()),this.refreshNames(r)}},{key:"actionEvent",value:function(e){e&&e.preventDefault();var t=(0,s.default)(e.target),n=t.data("grav-array-action"),r=t.parents('[data-grav-array-type="container"]');this._setTemplate(t),this[n+"Action"](t);var i=r.find("> div");r[i.length>1?"removeClass":"addClass"]("one-child")}},{key:"addAction",value:function(e){var t=e.data("array-template"),n=e.closest('[data-grav-array-type="row"]');n.after(t.getNewRow())}},{key:"remAction",value:function(e){var t=e.data("array-template"),n=e.closest('[data-grav-array-type="row"]'),r=!n.siblings().length;if(r){var i=(0,s.default)(t.getNewRow());n.after(i),i.find('[data-grav-array-type="value"]:last').attr("name",t.getName())}n.remove(),this.refreshNames(t)}},{key:"refreshNames",value:function(e){if(e.isValueOnly()){var t=e.container.find('> div > [data-grav-array-type="row"]'),n=t.find('[name]:not([name=""])');n.each(function(e,t){t=(0,s.default)(t);var n=t.attr("name");n=n.replace(/\[\d+\]$/,"["+e+"]"),t.attr("name",n)}),n.length||t.find('[data-grav-array-type="value"]').attr("name",e.getName())}}},{key:"getIndexFor",value:function(e){var t=e.data("array-template"),n=e.closest('[data-grav-array-type="row"]');return t.container.find((t.isValueOnly()?"> div ":"")+' > [data-grav-array-type="row"]').index(n)}},{key:"_setTemplate",value:function(e){e.data("array-template")||e.data("array-template",new f(e.closest("[data-grav-array-name]")))}},{key:"_onAddedNodes",value:function(e,t){var n=this,r=(0,s.default)(t).find('[data-grav-field="array"]');r.length&&r.each(function(e,t){t=(0,s.default)(t),~n.arrays.index(t)||n.addArray(t)})}}]),e}();t.default=d;t.Instance=new d},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0}),t.Instance=void 0;var a=function(){function e(e,t){for(var n=0;n
.collection-actions [data-action="add"]',function(e){return t.addItem(e)}),e.on("click",'> ul > li > .item-actions [data-action="delete"]',function(e){return t.removeItem(e)}),e.on("click",'> ul > li > .item-actions [data-action="collapse"]',function(e){return t.collapseItem(e)}),e.on("click",'> ul > li > .item-actions [data-action="expand"]',function(e){return t.expandItem(e)}),e.on("click",'> .collection-actions [data-action-sort="date"]',function(e){return t.sortItems(e)}),e.on("click",'> .collection-actions [data-action="collapse_all"]',function(e){return t.collapseItems(e)}),e.on("click",'> .collection-actions [data-action="expand_all"]',function(e){return t.expandItems(e)}),e.on("input change","[data-key-observe]",function(e){return t.observeKey(e)}),e.find("[data-collection-holder]").each(function(e,n){n=(0,s.default)(n),n.data("collection-sort")||n[0].hasAttribute("data-collection-nosort")||n.data("collection-sort",new u.default(n.get(0),{forceFallback:!1,handle:".collection-sort",animation:150,onUpdate:function(){return t.reindex(n)}}))}),this._updateActionsStateBasedOnMinMax(e)}},{key:"addItem",value:function(e){var t=(0,s.default)(e.currentTarget),n=t.data("action-add")||"bottom",r=(0,s.default)(t.closest('[data-type="collection"]')),i=(0,s.default)(r.find('> [data-collection-template="new"]').data("collection-template-html"));this._updateActionsStateBasedOnMinMax(r);var a=r.closest('[data-type="collection"]').find("> ul > [data-collection-item]"),o=r.data("max");if(!("undefined"!=typeof o&&a.length>=o)){r.find("> [data-collection-holder]")["top"===n?"prepend":"append"](i),this.reindex(r),a=r.closest('[data-type="collection"]').find("> ul > [data-collection-item]");var l=r.closest('[data-type="collection"]').find('[data-action-add="top"]'),u=r.closest('[data-type="collection"]').find('[data-action="sort"]');a.length&&(l.length&&l.parent().removeClass("hidden"),u.length&&a.length>1&&u.removeClass("hidden")),(0,s.default)('[data-grav-field="toggleable"] input[type="checkbox"]').trigger("change")}}},{key:"removeItem",value:function(e){var t=(0,s.default)(e.currentTarget),n=t.closest("[data-collection-item]"),r=(0,s.default)(t.closest('[data-type="collection"]')),i=r.closest('[data-type="collection"]').find("> ul > [data-collection-item]"),a=r.data("min");if(!("undefined"!=typeof a&&i.length<=a)){n.remove(),this.reindex(r),i=r.closest('[data-type="collection"]').find("> ul > [data-collection-item]");var o=r.closest('[data-type="collection"]').find('[data-action-add="top"]'),l=r.closest('[data-type="collection"]').find('[data-action="sort"]');i.length||o.length&&o.parent().addClass("hidden"),l.length&&i.length<=1&&l.addClass("hidden"),this._updateActionsStateBasedOnMinMax(r)}}},{key:"collapseItems",value:function(e){var t=(0,s.default)(e.currentTarget),n=(0,s.default)(t.closest('[data-type="collection"]')).find('> ul > [data-collection-item] > .item-actions [data-action="collapse"]');n.click()}},{key:"collapseItem",value:function(e){var t=(0,s.default)(e.currentTarget),n=t.closest("[data-collection-item]");t.attr("data-action","expand").removeClass("fa-chevron-circle-down").addClass("fa-chevron-circle-right"),n.addClass("collection-collapsed")}},{key:"expandItems",value:function(e){var t=(0,s.default)(e.currentTarget),n=(0,s.default)(t.closest('[data-type="collection"]')).find('> ul > [data-collection-item] > .item-actions [data-action="expand"]');n.click()}},{key:"expandItem",value:function(e){var t=(0,s.default)(e.currentTarget),n=t.closest("[data-collection-item]");t.attr("data-action","collapse").removeClass("fa-chevron-circle-right").addClass("fa-chevron-circle-down"),n.removeClass("collection-collapsed");
+}},{key:"sortItems",value:function(e){var t=(0,s.default)(e.currentTarget),n=t.data("action-sort"),r=t.data("action-sort-dir")||"asc",i=(0,s.default)(t.closest('[data-type="collection"]')),a=i.closest('[data-type="collection"]').find("> ul > [data-collection-item]");a.sort(function(e,t){var i=(0,s.default)(e).find('[name$="['+n+']"]'),a=(0,s.default)(t).find('[name$="['+n+']"]'),o=void 0;return o="asc"===r?i.val()a.val()?1:0:i.val()>a.val()?-1:i.val() ul > [data-collection-item]"),t.each(function(e,t){t=(0,s.default)(t);var n=t.find("[data-key-observe]"),r=n.val(),i=n.length,a=t.data("collection-key-backup");t.attr("data-collection-key",i?r:e),["name","data-grav-field-name","for","id","data-grav-file-settings","data-grav-array-name"].forEach(function(e){t.find("["+e+"], [_"+e+"]").each(function(){var t=(0,s.default)(this),n=[],o=null,l=[new RegExp("\\[(\\d+|\\*|"+a+")\\]","g"),new RegExp("\\.(\\d+|\\*|"+a+")\\.","g")];if("name"===e&&t.data("gravArrayType")){var u=t.attr(e).match(/\[[0-9]{1,}\]$/),c=t.closest("[data-grav-array-name]").data("gravArrayName");if(u&&c)return o=u[0],void t.attr(e,""+c+u[0])}if(i&&!r)return t.attr("_"+e,t.attr(e)),void t.attr(e,null);t.attr("_"+e)&&(t.attr(e,t.attr("_"+e)),t.attr("_"+e,null)),t.parents("[data-collection-key]").map(function(e,t){return n.push((0,s.default)(t).attr("data-collection-key"))}),n.reverse();var f=a,d=t.attr(e).replace(l[0],function(){var e="";return o&&(e=o,console.log(n,e)),f=n.shift()||f,"["+f+"]"+e});d=d.replace(l[1],function(){return f=n.shift()||f,"."+f+"."}),t.attr(e,d)})})})}},{key:"_onAddedNodes",value:function(e,t){var n=this,r=(0,s.default)(t).find('[data-type="collection"]');r.length&&r.each(function(e,t){t=(0,s.default)(t),~n.lists.index(t)||n.addList(t)})}},{key:"_updateActionsStateBasedOnMinMax",value:function(e){var t=e.closest('[data-type="collection"]').find("> ul > [data-collection-item]"),n=e.data("min"),r=e.data("max");e.find('> .collection-actions [data-action="add"]').attr("disabled",!1),e.find('> ul > li > .item-actions [data-action="delete"]').attr("disabled",!1),"undefined"!=typeof n&&t.length<=n&&e.find('> ul > li > .item-actions [data-action="delete"]').attr("disabled",!0),"undefined"!=typeof r&&t.length>=r&&e.find('> .collection-actions [data-action="add"]').attr("disabled",!0)}}]),e}();t.default=c;t.Instance=new c},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0}),t.Instance=void 0;var a=function(){function e(e,t){for(var n=0;n-1,h=(0,s.default)("body"),m="mousedown touchstart MSPointerDown pointerdown",v="mousemove touchmove MSPointerMove pointermove",g="mouseup touchend MSPointerUp pointerup",y=p?"focus":"focusin",_=function(){function e(t){var n=this;i(this,e),this.selector=t,this.field=(0,s.default)(this.selector),this.options=Object.assign({},this.field.data("grav-colorpicker")),this.built=!1,this.attach(),this.options.update&&this.field.on("change._grav_colorpicker",function(e,t,r,i){var a=r,o=(0,d.hex2rgb)(r);i<1&&(a="rgba("+o.r+", "+o.g+", "+o.b+", "+i+")");var s=t.closest(n.options.update);s.length||(s=t.siblings(n.options.update)),s.length||(s=t.parent(".g-colorpicker").find(n.options.update)),s.css({backgroundColor:a})})}return a(e,[{key:"attach",value:function(){var e=this;h.on(y,this.selector,function(t){return e.show(t,t.currentTarget)}),h.on(m,this.selector+" .g-colorpicker, "+this.selector+" .g-colorpicker i",this.bound("iconClick")),h.on("keydown",this.selector,function(t){switch(t.keyCode){case 9:e.hide();break;case 13:case 27:e.hide(),t.currentTarget.blur()}return!0}),h.on("keyup",this.selector,function(t){return e.updateFromInput(!0,t.currentTarget),!0}),h.on("paste",this.selector,function(t){setTimeout(function(){return e.updateFromInput(!0,t.currentTarget)},1)})}},{key:"show",value:function(e,t){t=(0,s.default)(t),this.built||this.build(),this.element=t,this.reposition(),this.wrapper.addClass("cp-visible"),this.updateFromInput();var n=(0,s.default)("#admin-main .content-wrapper").data("scrollbar").getViewElement();this.wrapper.on(m,".cp-grid, .cp-slider, .cp-opacity-slider",this.bound("bodyDown")),h.on(v,this.bound("bodyMove")),h.on(m,this.bound("bodyClick")),h.on(g,this.bound("targetReset")),(0,s.default)(n).on("scroll",this.bound("reposition"))}},{key:"hide",value:function(){if(this.built){this.wrapper.removeClass("cp-visible");var e=(0,s.default)("#admin-main .content-wrapper").data("scrollbar").getViewElement();this.wrapper.undelegate(m,".cp-grid, .cp-slider, .cp-opacity-slider",this.bound("bodyDown")),h.off(v,this.bound("bodyMove")),h.off(m,this.bound("bodyClick")),h.off(g,this.bound("targetReset")),(0,s.default)(e).off("scroll",this.bound("reposition"))}}},{key:"build",value:function(){var e=this;this.wrapper=(0,s.default)('
'),this.slider=(0,s.default)('
').appendTo(this.wrapper).append((0,s.default)('
')),this.opacitySlider=(0,s.default)('
').appendTo(this.wrapper).append((0,s.default)('
')),this.grid=(0,s.default)('
').appendTo(this.wrapper).append((0,s.default)('
')).append((0,s.default)('
')),(0,s.default)("
").appendTo(this.grid.find(".cp-picker"));var t=(0,s.default)('
').appendTo(this.wrapper);this.tabs={hue:(0,s.default)('
').text("HUE").appendTo(t),brightness:(0,s.default)('
').text("BRI").appendTo(t),saturation:(0,s.default)('
').text("SAT").appendTo(t),wheel:(0,s.default)('
').text("WHEEL").appendTo(t),transparent:(0,s.default)('
').text("TRANSPARENT").appendTo(t)},t.on(m,"> div",function(n){var r=(0,s.default)(n.currentTarget);if(r.is(e.tabs.transparent)){var i=e.opacitySlider.height();return e.opacity=0,e.opacitySlider.find(".cp-picker").css({top:(0,u.default)(i-i*e.opacity,0,i)}),void e.move(e.opacitySlider,{manualOpacity:!0})}var a=t.find(".active"),o=a.attr("class").replace(/\s|active|cp-tab-/g,""),l=r.attr("class").replace(/\s|active|cp-tab-/g,"");e.wrapper.removeClass("cp-mode-"+o).addClass("cp-mode-"+l),a.removeClass("active"),r.addClass("active"),e.mode=l,e.updateFromInput()}),this.wrapper.appendTo(".content-wrapper"),this.built=!0,this.mode="hue"}},{key:"reposition",value:function(){var e=(0,s.default)(".content-wrapper")[0],t=this.element[0].getBoundingClientRect(),n=e.getBoundingClientRect(),r={x:0,y:0};this.options.offset&&(r.x=this.options.offset.x||0,r.y=this.options.offset.y||0),this.wrapper.css({top:t.top+t.height+e.scrollTop-n.top+r.y,left:t.left+e.scrollLeft-n.left+r.x})}},{key:"iconClick",value:function(e){if(this.wrapper&&this.wrapper.hasClass("cp-visible"))return!0;e&&e.preventDefault();var t=(0,s.default)(e.currentTarget).find("input");setTimeout(function(){return t.focus()},50)}},{key:"bodyMove",value:function(e){e&&e.preventDefault(),this.target&&this.move(this.target,e)}},{key:"bodyClick",value:function(e){var t=(0,s.default)(e.target);t.closest(".cp-wrapper").length||t.is(this.selector)||this.hide()}},{key:"bodyDown",value:function(e){e&&e.preventDefault(),this.target=(0,s.default)(e.currentTarget),this.move(this.target,e,!0)}},{key:"targetReset",value:function(e){e&&e.preventDefault(),this.target=null}},{key:"move",value:function(e,t){var n=this.element,r=e.find(".cp-picker"),i=e[0].getBoundingClientRect(),a=i.left+window.scrollX,o=i.top+window.scrollY,s=Math.round((t?t.pageX:0)-a),l=Math.round((t?t.pageY:0)-o),u=void 0,c=void 0,f=void 0,d=void 0,p=t.changedTouches||t.originalEvent&&t.originalEvent.changedTouches;t&&p&&(s=(p?p[0].pageX:0)-a,l=(p?p[0].pageY:0)-o),t&&t.manualOpacity&&(l=i.height),s<0&&(s=0),l<0&&(l=0),s>i.width&&(s=i.width),l>i.height&&(l=i.height),e.parent(".cp-mode-wheel").length&&r.parent(".cp-grid").length&&(u=75-s,c=75-l,f=Math.sqrt(u*u+c*c),d=Math.atan2(c,u),d<0&&(d+=2*Math.PI),f>75&&(s=75-75*Math.cos(d),l=75-75*Math.sin(d)),s=Math.round(s),l=Math.round(l)),e.hasClass("cp-grid")?(r.css({top:l,left:s}),this.updateFromPicker(n,e)):(r.css({top:l}),this.updateFromPicker(n,e))}},{key:"updateFromInput",value:function(e,t){t=t?(0,s.default)(t):this.element;var n=t.val(),r=n.replace(/\s/g,"").match(/^rgba?\([0-9]{1,3},[0-9]{1,3},[0-9]{1,3},(.+)\)/),i=void 0,a=void 0;if(n=(0,d.rgbstr2hex)(n)||n,r=r?(0,u.default)(r[1],0,1):1,(i=(0,d.parseHex)(n))||(i="#ffffff"),a=(0,d.hex2hsb)(i),this.built){this.opacity=r;var o=this.opacitySlider.height();this.opacitySlider.find(".cp-picker").css({top:(0,u.default)(o-o*this.opacity,0,o)});var l=this.grid.height(),c=this.grid.width(),f=void 0,p=void 0,h=void 0,m=void 0;switch(o=this.slider.height(),this.mode){case"wheel":f=(0,u.default)(Math.ceil(.75*a.s),0,l/2),p=a.h*Math.PI/180,h=(0,u.default)(75-Math.cos(p)*f,0,c),m=(0,u.default)(75-Math.sin(p)*f,0,l),this.grid.css({backgroundColor:"transparent"}).find(".cp-picker").css({top:m,left:h}),m=150-a.b/(100/l),""===i&&(m=0),this.slider.find(".cp-picker").css({top:m}),this.slider.css({backgroundColor:(0,d.hsb2hex)({h:a.h,s:a.s,b:100})});break;case"saturation":h=(0,u.default)(5*a.h/12,0,150),m=(0,u.default)(l-Math.ceil(a.b/(100/l)),0,l),this.grid.find(".cp-picker").css({top:m,left:h}),m=(0,u.default)(o-a.s*(o/100),0,o),this.slider.find(".cp-picker").css({top:m}),this.slider.css({backgroundColor:(0,d.hsb2hex)({h:a.h,s:100,b:a.b})}),this.grid.find(".cp-grid-inner").css({opacity:a.s/100});break;case"brightness":h=(0,u.default)(5*a.h/12,0,150),m=(0,u.default)(l-Math.ceil(a.s/(100/l)),0,l),this.grid.find(".cp-picker").css({top:m,left:h}),m=(0,u.default)(o-a.b*(o/100),0,o),this.slider.find(".cp-picker").css({top:m}),this.slider.css({backgroundColor:(0,d.hsb2hex)({h:a.h,s:a.s,b:100})}),this.grid.find(".cp-grid-inner").css({opacity:1-a.b/100});break;case"hue":default:h=(0,u.default)(Math.ceil(a.s/(100/c)),0,c),m=(0,u.default)(l-Math.ceil(a.b/(100/l)),0,l),this.grid.find(".cp-picker").css({top:m,left:h}),m=(0,u.default)(o-a.h/(360/o),0,o),this.slider.find(".cp-picker").css({top:m}),this.grid.css({backgroundColor:(0,d.hsb2hex)({h:a.h,s:100,b:100})})}}e||t.val(this.getValue(i)),(this.element||t).trigger("change._grav_colorpicker",[t,i,r])}},{key:"updateFromPicker",value:function(e,t){var n=function(e,t){var n,r;return e.length&&t?(n=e[0].getBoundingClientRect().left,r=e[0].getBoundingClientRect().top,{x:n-t[0].getBoundingClientRect().left+e[0].offsetWidth/2,y:r-t[0].getBoundingClientRect().top+e[0].offsetHeight/2}):null},r=void 0,i=void 0,a=void 0,o=void 0,s=void 0,l=void 0,c=void 0,f=void 0,p=this.wrapper.find(".cp-grid"),h=this.wrapper.find(".cp-slider"),m=this.wrapper.find(".cp-opacity-slider"),v=p.find(".cp-picker"),g=h.find(".cp-picker"),y=m.find(".cp-picker"),_=n(v,p),b=n(g,h),w=n(y,m),x=p[0].getBoundingClientRect().width,k=p[0].getBoundingClientRect().height,S=h[0].getBoundingClientRect().height,C=m[0].getBoundingClientRect().height,E=this.element.val();if(E=(0,d.rgbstr2hex)(E)||E,(r=(0,d.parseHex)(E))||(r="#ffffff"),t.hasClass("cp-grid")||t.hasClass("cp-slider"))switch(this.mode){case"wheel":s=x/2-_.x,l=k/2-_.y,c=Math.sqrt(s*s+l*l),f=Math.atan2(l,s),f<0&&(f+=2*Math.PI),c>75&&(c=75,_.x=69-75*Math.cos(f),_.y=69-75*Math.sin(f)),a=(0,u.default)(c/.75,0,100),i=(0,u.default)(180*f/Math.PI,0,360),o=(0,u.default)(100-Math.floor(b.y*(100/S)),0,100),r=(0,d.hsb2hex)({h:i,s:a,b:o}),h.css({backgroundColor:(0,d.hsb2hex)({h:i,s:a,b:100})});break;case"saturation":i=(0,u.default)(parseInt(_.x*(360/x),10),0,360),a=(0,u.default)(100-Math.floor(b.y*(100/S)),0,100),o=(0,u.default)(100-Math.floor(_.y*(100/k)),0,100),r=(0,d.hsb2hex)({h:i,s:a,b:o}),h.css({backgroundColor:(0,d.hsb2hex)({h:i,s:100,b:o})}),p.find(".cp-grid-inner").css({opacity:a/100});break;case"brightness":i=(0,u.default)(parseInt(_.x*(360/x),10),0,360),a=(0,u.default)(100-Math.floor(_.y*(100/k)),0,100),o=(0,u.default)(100-Math.floor(b.y*(100/S)),0,100),r=(0,d.hsb2hex)({h:i,s:a,b:o}),h.css({backgroundColor:(0,d.hsb2hex)({h:i,s:a,b:100})}),p.find(".cp-grid-inner").css({opacity:1-o/100});break;default:i=(0,u.default)(360-parseInt(b.y*(360/S),10),0,360),a=(0,u.default)(Math.floor(_.x*(100/x)),0,100),o=(0,u.default)(100-Math.floor(_.y*(100/k)),0,100),r=(0,d.hsb2hex)({h:i,s:a,b:o}),p.css({backgroundColor:(0,d.hsb2hex)({h:i,s:100,b:100})})}t.hasClass("cp-opacity-slider")&&(this.opacity=parseFloat(1-w.y/C).toFixed(2)),e.val(this.getValue(r)),this.element.trigger("change._grav_colorpicker",[this.element,r,this.opacity])}},{key:"getValue",value:function(e){if(1===this.opacity)return e;var t=(0,d.hex2rgb)(e);return"rgba("+t.r+", "+t.g+", "+t.b+", "+this.opacity+")"}},{key:"bound",value:function e(t){var e=this._bound||(this._bound={});return e[t]||(e[t]=(0,f.default)(this[t],this))}}]),e}();t.default=_;t.Instance=new _("[data-grav-colorpicker]")},function(e,t){function n(e,t,n){return en?n:e}e.exports=n},function(e,t,n){function r(e,t,n){var r=i(arguments,2);return function(){return e.apply(t,r.concat(i(arguments)))}}var i=n(601);e.exports=r},function(e,t){function n(e,t,n){var r=e.length;t=null==t?0:t<0?Math.max(r+t,0):Math.min(t,r),n=null==n?r:n<0?Math.max(r+n,0):Math.min(n,r);for(var i=[];t-1?e.substring(1):e,16),{r:e>>16,g:(65280&e)>>8,b:255&e}}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var i=n(1),a=r(i),o=n(604),s=r(o),l=n(372),u={tree:".pages-list-container .mediapicker-scroll",thumb:".thumbs-list-container .mediapicker-scroll"};(0,a.default)(function(){var e="",t=(0,a.default)("body"),n=[],r=[];(0,a.default)(u.tree).each(function(e,t){n.push(new s.default(t,{autoshow:!0}))}),(0,a.default)(u.thumb).each(function(e,t){r.push(new s.default(t,{autoshow:!0}))}),(0,a.default)(document).on("input change",".media-container .media-range",function(e){var t=(0,a.default)(e.currentTarget),i=t.closest(".remodal"),o=i.find(".media-container div.card-item"),s=t.val()+"px";o.each(function(){(0,a.default)(this).css("width",s)}),n.forEach(function(e){return e.update()}),r.forEach(function(e){return e.update()})}),(0,a.default)(document).on("opened",".remodal",function(){setTimeout(function(){n.forEach(function(e){return e.update()}),r.forEach(function(e){return e.update()})},10)}),t.on("click","[data-mediapicker-modal-trigger]",function(i){var o=(0,a.default)(i.currentTarget),c=(0,a.default)(this).data("grav-mediapicker-unique-identifier"),f=t.find('[data-remodal-unique-identifier="'+c+'"]');e=a.default.remodal.lookup[f.data("remodal")],e||(f.remodal(),e=a.default.remodal.lookup[f.data("remodal")]),(0,a.default)(u.tree).filter(function(e,t){return!(0,a.default)(t).data("scrollbar")}).each(function(e,t){n.push(new s.default(t,{autoshow:!0}))}),(0,a.default)(u.thumb).filter(function(e,t){return!(0,a.default)(t).data("scrollbar")}).each(function(e,t){r.push(new s.default(t,{autoshow:!0}))}),e.open(),e.dataField=o.find("input"),n.forEach(function(e){return e.update()}),r.forEach(function(e){return e.update()}),f.find(".js__files").trigger("fillView"),setTimeout(function(){return l.Instance.reload()},100)}),t.on("click","[data-remodal-mediapicker] .media-container.in-modal .admin-media-details a",function(t){t.preventDefault(),t.stopPropagation();var n=(0,a.default)(t.target).parents(".js__media-element").data("file-url"),r=n.replace(/ /g,"%20");e.dataField.val(r),e.close()})})},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0}),t.Instance=void 0;var a=function(){function e(e,t){for(var n=0;n0;)this._viewElement.appendChild(this.element.childNodes[0]);this.element.appendChild(this._viewElement)}else this._viewElement=this.element.querySelector("."+l.view);n(this.element,[l.element]),n(this._viewElement,[l.view]),this._createResizeTrigger()}return this}if(this._created===!0)return console.warn("calling on a already-created object"),this;if(this.autoshow&&n(this.element,[l.autoshow]),this._document=document,this.createElements===!0){for(this._viewElement=document.createElement("div"),this._scrollbarVerticalElement=document.createElement("div"),this._thumbVerticalElement=document.createElement("div"),this._scrollbarHorizontalElement=document.createElement("div"),this._thumbHorizontalElement=document.createElement("div");this.element.childNodes.length>0;)this._viewElement.appendChild(this.element.childNodes[0]);this._scrollbarVerticalElement.appendChild(this._thumbVerticalElement),this._scrollbarHorizontalElement.appendChild(this._thumbHorizontalElement),this.element.appendChild(this._scrollbarVerticalElement),this.element.appendChild(this._scrollbarHorizontalElement),this.element.appendChild(this._viewElement)}else this._viewElement=this.element.querySelector("."+l.view),this._scrollbarVerticalElement=this.element.querySelector("."+l.verticalScrollbar.split(" ").join(".")),this._thumbVerticalElement=this._scrollbarVerticalElement.querySelector("."+l.thumb),this._scrollbarHorizontalElement=this.element.querySelector("."+l.horizontalScrollbar.split(" ").join(".")),this._thumbHorizontalElement=this._scrollbarHorizontalElement.querySelector("."+l.thumb);return n(this.element,[l.element]),n(this._viewElement,[l.view]),n(this._scrollbarVerticalElement,l.verticalScrollbar.split(/\s/)),n(this._scrollbarHorizontalElement,l.horizontalScrollbar.split(/\s/)),n(this._thumbVerticalElement,[l.thumb]),n(this._thumbHorizontalElement,[l.thumb]),this._scrollbarVerticalElement.style.display="",this._scrollbarHorizontalElement.style.display="",this._createResizeTrigger(),this._created=!0,this._bindEvents().update()},a.prototype._createResizeTrigger=function(){var e=document.createElement("object");n(e,[l.resizeTrigger]),e.type="text/html",e.setAttribute("tabindex","-1");var t=this._resizeHandler.bind(this);e.onload=function(){var n=e.contentDocument.defaultView;n.addEventListener("resize",t)},i()||(e.data="about:blank"),this.element.appendChild(e),i()&&(e.data="about:blank"),this._resizeTriggerElement=e},a.prototype.update=function(){return s?this:this._created===!1?(console.warn("calling on a not-yet-created object"),this):(this._viewElement.style.width=(this.element.offsetWidth+o).toString()+"px",this._viewElement.style.height=(this.element.offsetHeight+o).toString()+"px",this._naturalThumbSizeX=this._scrollbarHorizontalElement.clientWidth/this._viewElement.scrollWidth*this._scrollbarHorizontalElement.clientWidth,this._naturalThumbSizeY=this._scrollbarVerticalElement.clientHeight/this._viewElement.scrollHeight*this._scrollbarVerticalElement.clientHeight,this._scrollTopMax=this._viewElement.scrollHeight-this._viewElement.clientHeight,this._scrollLeftMax=this._viewElement.scrollWidth-this._viewElement.clientWidth,this._naturalThumbSizeY0;)this.element.appendChild(this._viewElement.childNodes[0]);this.element.removeChild(this._viewElement)}else this._viewElement.style.width="",this._viewElement.style.height="",this._scrollbarVerticalElement.style.display="none",this._scrollbarHorizontalElement.style.display="none";return this._created=!1,this._document=null,null},a.prototype.getViewElement=function(){return this._viewElement},a.prototype._bindEvents=function(){return this._cache.events.scrollHandler=this._scrollHandler.bind(this),this._cache.events.clickVerticalTrackHandler=this._clickVerticalTrackHandler.bind(this),this._cache.events.clickHorizontalTrackHandler=this._clickHorizontalTrackHandler.bind(this),this._cache.events.clickVerticalThumbHandler=this._clickVerticalThumbHandler.bind(this),this._cache.events.clickHorizontalThumbHandler=this._clickHorizontalThumbHandler.bind(this),this._cache.events.mouseUpDocumentHandler=this._mouseUpDocumentHandler.bind(this),this._cache.events.mouseMoveDocumentHandler=this._mouseMoveDocumentHandler.bind(this),this._viewElement.addEventListener("scroll",this._cache.events.scrollHandler),this._scrollbarVerticalElement.addEventListener("mousedown",this._cache.events.clickVerticalTrackHandler),this._scrollbarHorizontalElement.addEventListener("mousedown",this._cache.events.clickHorizontalTrackHandler),this._thumbVerticalElement.addEventListener("mousedown",this._cache.events.clickVerticalThumbHandler),this._thumbHorizontalElement.addEventListener("mousedown",this._cache.events.clickHorizontalThumbHandler),this._document.addEventListener("mouseup",this._cache.events.mouseUpDocumentHandler),this},a.prototype._unbinEvents=function(){return this._viewElement.removeEventListener("scroll",this._cache.events.scrollHandler),this._scrollbarVerticalElement.removeEventListener("mousedown",this._cache.events.clickVerticalTrackHandler),this._scrollbarHorizontalElement.removeEventListener("mousedown",this._cache.events.clickHorizontalTrackHandler),this._thumbVerticalElement.removeEventListener("mousedown",this._cache.events.clickVerticalThumbHandler),this._thumbHorizontalElement.removeEventListener("mousedown",this._cache.events.clickHorizontalThumbHandler),this._document.removeEventListener("mouseup",this._cache.events.mouseUpDocumentHandler),this._document.removeEventListener("mousemove",this._cache.events.mouseMoveDocumentHandler),this},a.prototype._scrollHandler=function(){var e=this._viewElement.scrollLeft*this._trackLeftMax/this._scrollLeftMax||0,t=this._viewElement.scrollTop*this._trackTopMax/this._scrollTopMax||0;this._thumbHorizontalElement.style.msTransform="translateX("+e+"px)",this._thumbHorizontalElement.style.webkitTransform="translate3d("+e+"px, 0, 0)",this._thumbHorizontalElement.style.transform="translate3d("+e+"px, 0, 0)",this._thumbVerticalElement.style.msTransform="translateY("+t+"px)",this._thumbVerticalElement.style.webkitTransform="translate3d(0, "+t+"px, 0)",this._thumbVerticalElement.style.transform="translate3d(0, "+t+"px, 0)"},a.prototype._resizeHandler=function(){this.update(),this.onResize&&this.onResize()},a.prototype._clickVerticalTrackHandler=function(e){if(e.target===e.currentTarget){var t=e.offsetY-.5*this._naturalThumbSizeY,n=100*t/this._scrollbarVerticalElement.clientHeight;this._viewElement.scrollTop=n*this._viewElement.scrollHeight/100}},a.prototype._clickHorizontalTrackHandler=function(e){if(e.target===e.currentTarget){var t=e.offsetX-.5*this._naturalThumbSizeX,n=100*t/this._scrollbarHorizontalElement.clientWidth;this._viewElement.scrollLeft=n*this._viewElement.scrollWidth/100}},a.prototype._clickVerticalThumbHandler=function(e){this._startDrag(e),this._prevPageY=this._thumbSizeY-e.offsetY},a.prototype._clickHorizontalThumbHandler=function(e){this._startDrag(e),this._prevPageX=this._thumbSizeX-e.offsetX},a.prototype._startDrag=function(e){this._cursorDown=!0,n(document.body,[l.disable]),this._document.addEventListener("mousemove",this._cache.events.mouseMoveDocumentHandler),this._document.onselectstart=function(){return!1}},a.prototype._mouseUpDocumentHandler=function(){this._cursorDown=!1,this._prevPageX=this._prevPageY=0,r(document.body,[l.disable]),this._document.removeEventListener("mousemove",this._cache.events.mouseMoveDocumentHandler),this._document.onselectstart=null},a.prototype._mouseMoveDocumentHandler=function(e){if(this._cursorDown!==!1){var t,n;return this._prevPageY?(t=e.clientY-this._scrollbarVerticalElement.getBoundingClientRect().top,n=this._thumbSizeY-this._prevPageY,void(this._viewElement.scrollTop=this._scrollTopMax*(t-n)/this._trackTopMax)):void(this._prevPageX&&(t=e.clientX-this._scrollbarHorizontalElement.getBoundingClientRect().left,n=this._thumbSizeX-this._prevPageX,this._viewElement.scrollLeft=this._scrollLeftMax*(t-n)/this._trackLeftMax))}},e.exports=a}()},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var i=n(1),a=r(i);(0,a.default)(function(){var e=function(e,t){var n=50*e,r=0===e?"top":"",i='name="'+t+'"';0===e&&(i='data-attr-name="'+t+'"');var a='\n \n ';return a},t=function(e){return!e.attr("name")},n=function(e){return(0,a.default)(e+" .js__multilevel-field.top")},r=function(e){var t="[data-grav-multilevel-field]";e&&(t='[data-grav-multilevel-field][data-id="'+e+'"]');var r=function(){(0,a.default)(t+" .js__add-sibling").addClass("hidden"),(0,a.default)(t+" .js__add-children").addClass("hidden")},i=function(){(0,a.default)(t+" .children-wrapper").each(function(){var e=(0,a.default)(this).children();e.last().each(function(){var e=(0,a.default)(this);(0,a.default)(this).hasClass("js__multilevel-field")||(e=(0,a.default)(this).find(".js__multilevel-field").first()),e.find(".js__add-sibling").removeClass("hidden")})}),(0,a.default)(t+" .js__multilevel-field.top").last().find(".js__add-sibling").removeClass("hidden")},o=function(){(0,a.default)(t+" .js__multilevel-field").each(function(){0!==(0,a.default)(this).siblings(".children-wrapper").length&&0!==(0,a.default)(this).siblings(".children-wrapper").find(".js__multilevel-field").length||(0,a.default)(this).find(".js__add-children").removeClass("hidden")})},s=function(){var e=n(t);1===e.length&&e.first().find(".js__remove-item").addClass("hidden")};r(),i(),o(),s()},i=function(e,t,n){e.parents("[data-grav-multilevel-field]").find("input").each(function(){var e=(0,a.default)(this);e.attr("name")&&e.attr("name",e.attr("name").replace(t,n)),e.attr("data-attr-name")&&e.attr("data-attr-name",e.attr("data-attr-name").replace(t,n))})};(0,a.default)(document).ready(function(){r()}),(0,a.default)(document).on("mouseleave","[data-grav-multilevel-field]",function(e){var t=n('[data-id="'+(0,a.default)(this).data("id")+'"]'),r=!1,i="";t.each(function(){var e=(0,a.default)(this);0===(0,a.default)(e).siblings(".children-wrapper").find("input").length&&(r=!0,i=e.find("input").val())}),r&&(i?alert("Warning: if you save now, the element "+i+", without children, will be removed, because it's invalid YAML"):alert("Warning: if you save now, the top elements without children will be removed, because it's invalid YAML"))}),(0,a.default)(document).on("click","[data-grav-multilevel-field] .js__add-children",function(t){var n=(0,a.default)(this),i=n.closest(".js__multilevel-field").data("id"),o=n.data("level")+1,s=function(e){var t=e.closest(".js__multilevel-field").parent().first();return 0===t.find(".children-wrapper").length&&(0,a.default)(t).append('
'),t=t.find(".children-wrapper").first()},l=function(e,t){return e.hasClass("children-wrapper")&&(e=e.siblings(".js__multilevel-field").first().find("input")),e.attr(t)+"["+e.val()+"]"},u=function(e){return e.siblings(".js__multilevel-field").first().find("input")},c=s(n),f=u(c),d="name";f.closest(".js__multilevel-field").hasClass("top")&&(d="data-attr-name"),f.attr(d,f.attr(d).replace("[]",""));var p=l(f,d),h=e(o,p);(0,a.default)(c).append(h),r(i)}),(0,a.default)(document).on("click","[data-grav-multilevel-field] .js__add-sibling",function(n){var o=(0,a.default)(this),s=o.closest(".js__multilevel-field").data("id"),l=o.data("level");o.closest(".children-wrapper").find(".js__add-sibling").addClass("hidden");var u=null,c=!1;o.closest(".js__multilevel-field").hasClass("top")&&(c=!0),c?u=o.closest(".js__multilevel-field").first().find("input").last():(u=o.siblings("input").first(),u||(u=o.closest(".children-wrapper").first().find("input").last()));var f=function(e){var t=e.closest(".js__multilevel-field").parent().first();return t.hasClass("element-wrapper")||(0===t.find(".element-wrapper").length&&(0,a.default)(t).append('
'),t=t.find(".element-wrapper").first()),t},d=function(e,n){var r=arguments.length>2&&void 0!==arguments[2]&&arguments[2],o=n.attr("name");if(t(n)){var s=n.data("attr-name")+"[]";return n.removeAttr("name"),s}var l=o.lastIndexOf("["),u=o.substr(l+1),c=u.substr(0,u.length-1);return a.default.isNumeric(c)?o=o.replace("["+c+"]","["+(parseInt(c,10)+1)+"]"):r?o=o.replace("["+c+"]",""):(o+="[1]","[0]"!==n.attr("name").slice("-2")&&i(n,n.attr("name"),n.attr("name")+"[0]")),o},p=f(o),h=d(p,u,c),m=e(l,h);(0,a.default)(m).insertAfter(p),r(s)}),(0,a.default)(document).on("click","[data-grav-multilevel-field] .js__remove-item",function(e){(0,a.default)(this).parents(".element-wrapper").first().remove();var t=(0,a.default)(this).closest(".js__multilevel-field").data("id");r(t)}),(0,a.default)(document).on("focusin","[data-grav-multilevel-field] input",function(e){(0,a.default)(this).data("current-value",(0,a.default)(this).val())}),(0,a.default)(document).on("change","[data-grav-multilevel-field] input",function(e){var t=(0,a.default)(this),n=t.data("current-value"),r=t.val(),o=t.attr("name")||t.attr("data-attr-name"),s=o+"["+n+"]",l=o+"["+r+"]";i(t,s,l)})})},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0}),t.Instance=void 0;var a=function(){function e(e,t){for(var n=0;n"+r+"");if(a){if(e.get(0).selectize){var s=e.data("selectize");s.setValue(i)}delete c[n].state[t]}})}},{key:"refreshOptions",value:function(e,t){var n=e.val(),r=e.closest("[data-collection-holder]").data("collectionHolder");delete c[r].state[n],t&&c[r].original[t]&&(c[r].state[t]=c[r].original[t]),this.items.each(function(t,n){if(n=(0,s.default)(n),n[0]!==e[0]){var i=n.val();if(n.data("dummyChange",!0),n.get(0).selectize){var a=n.data("selectize");a&&(a.clearOptions(),i&&a.addOption({value:i,text:c[r].original[i]||i}),(0,u.default)(c[r].state,function(e,t){a.addOption({value:t,text:e})}),a.setValue(i,!0))}else n.empty(),(0,u.default)(c[r].state,function(e,t){var r=t===i?'selected="selected"':"";n.append('"+e+" ")});n.data("dummyChange",!1)}})}}]),e}();t.default=f;t.Instance=new f},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0}),t.Instance=void 0;var a=function(){function e(e,t){for(var n=0;n ');this.$field=e.find("input"),this.makePreview(e,t,n),this.makeClear(t,n),this.makeLaunchers(e,t),this.makeDisplay(e)}},{key:"makePreview",value:function(e,t,n){var r=(0,s.default)(" "),i=this.$field.val();n.prependTo(e),r.prependTo(n),""!==i&&(n.addClass("icon-preview-on"),r.addClass(i))}},{key:"makeClear",value:function(e,t){var n=this,r=(0,s.default)(' ');r.hide().prependTo(t),""!==n.$field.val()&&r.show(),t.on("click",".remove-icon",function(e){e.preventDefault(),n.$field.val(""),t.removeClass("icon-preview-on").find("i").removeClass(),(0,s.default)(this).hide()})}},{key:"makeDisplay",value:function(e){var t=this,n=t.settings.classes.close,r=(0,s.default)("body"),i=(0,s.default)(' ');"inline"===t.settings.mode?e.find(".icon-set").append(i).removeClass("dialog").addClass("ip-inline "+t.settings.size).parent().addClass("icon-set-wrap"):"dialog"===t.settings.mode&&((0,s.default)(".icon-set").addClass("dialog "+t.settings.size),(0,s.default)(".icon-picker-overlay").length<=0&&r.append('
').append(i)),r.on("click",".icon-picker-close, .icon-picker-overlay",function(n){n.preventDefault(),t.closePicker(e,(0,s.default)(t.iconSet),t.settings.mode)}).on("mouseenter mouseleave",".icon-picker-close",function(e){"mouseenter"===e.type?(0,s.default)(this).addClass(n):(0,s.default)(this).removeClass(n)})}},{key:"makeLaunchers",value:function(e){var t,n=this,r=e.data("iconsets");"undefined"==typeof r&&(r=n.settings.iconSets);for(t in r)r.hasOwnProperty(t)&&e.append(''+r[t]+" ");e.find(".launch-icons").on("click",function(t){t.preventDefault();var r=(0,s.default)(this),i=r.data("icons");n.iconSetName=i,n.iconSet="."+i+"-set",n.iconPick(e),n.showPicker(e,(0,s.default)(n.iconSet),n.settings.mode)})}},{key:"iconPick",value:function(e){var t=this,n="icon-highlight "+t.settings.classes.highlight;(0,s.default)(t.iconSet).on("click","li",function(n){n.preventDefault();var r=(0,s.default)(this),i=r.data(t.settings.save);(0,s.default)(".icon-selected").removeClass("icon-selected"),r.addClass("icon-selected"),"short"===t.$field.data("format")&&(i=i.slice(6)),t.$field.val(i),t.settings.closeOnPick&&t.closePicker(e,r.closest(t.iconSet),t.settings.mode),t.setPreview(r.data("class")),(0,s.default)("body").trigger("iconselected.queryloop",i)}),(0,s.default)(t.iconSet).on("mouseenter mouseleave","li",function(e){"mouseenter"===e.type?(0,s.default)(this).addClass(n):(0,s.default)(this).removeClass(n)})}},{key:"showPicker",value:function(e,t,n){"inline"===n?((0,s.default)(".icon-set").removeClass("ip-inline-open"),e.find(t).toggleClass("ip-inline-open")):"dialog"===n&&(e.find(".icon-picker-close").addClass("make-visible"),e.find(".icon-picker-overlay").addClass("make-visible"),t.addClass("dialog-open")),t.find(".icon-selected").removeClass("icon-selected");var r=this.$field.val().replace(" ",".");""!==r&&("class"===this.settings.save?t.find("."+r).addClass("icon-selected"):t.find('[data-code="'+r+'"]').addClass("icon-selected")),(0,s.default)("body").trigger("iconpickershow.queryloop",n)}},{key:"closePicker",value:function(e,t,n){(0,s.default)(this.iconSet).off("click","li"),"inline"===n?e.find(t).removeClass("ip-inline-open"):"dialog"===n&&(0,s.default)(".icon-picker-close, .icon-picker-overlay").removeClass("make-visible"),(0,s.default)("body").trigger("iconpickerclose.queryloop",n),(0,s.default)(".icon-set").removeClass("dialog-open")}},{key:"setPreview",value:function(e){var t=(0,s.default)(this.element).find(".icon-preview");t.addClass("icon-preview-on").find("i").removeClass().addClass(this.iconSetName).addClass(e),t.find("a").show()}}]),e}();s.default.fn.qlIconPicker=function(e){return this.each(function(){s.default.data(this,"plugin_qlIconPicker")||s.default.data(this,"plugin_qlIconPicker",new u(this,e))}),this};var c=function(){function e(t){var n=this;i(this,e),this.items=(0,s.default)(),this.options=Object.assign({},this.defaults,t),(0,s.default)("[data-grav-iconpicker]").each(function(e,t){return n.addItem(t)}),(0,s.default)("body").on("mutation._grav",this._onAddedNodes.bind(this))}return a(e,[{key:"_onAddedNodes",value:function(e,t){var n=this,r=(0,s.default)(t).find("[data-grav-iconpicker]");r.length&&r.each(function(e,t){t=(0,s.default)(t),~n.items.index(t)||n.addItem(t)})}},{key:"addItem",value:function(e){e=(0,s.default)(e),this.items=this.items.add(e),e.find(".icon-picker").qlIconPicker({save:"class"}),(0,s.default)(".icon-set:not(:first)").remove()}}]),e}();t.default=c;t.Instance=new c;(0,s.default)(document).on("click",function(e){var t=(0,s.default)(e.target),n=".icon-set.dialog-open, .launch-icons[data-icons]";if(!t.is(n)&&!t.closest(n).length){var r=(0,s.default)(".icon-set.dialog-open");r.length&&r.each(function(e,t){var n=(0,s.default)(t).siblings(".icon-picker"),r=n.data("plugin_qlIconPicker");r.closePicker(n,(0,s.default)(r.iconSet),r.settings.mode)})}})},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0}),t.Instance=void 0;var a=function(){function e(e,t){for(var n=0;n').uniqueId("jqCron").insertAfter(a)}a.is(":input")&&(o.bind_to=o.bind_to||a),o.bind_to?(o.bind_to.is(":input")&&o.bind_to.blur(function(){var e=o.bind_method.get(o.bind_to);a.jqCronGetInstance().setCron(e)}),r=o.bind_method.get(o.bind_to),t=new jqCron(o),t.setCron(r)):t=new jqCron(o),e(this).data("jqCron",t)})}}.call(window,o.default),function(e){function t(t){function r(e,r,i){var o=new jqCronSelector(d,e,r,i);return o.$.bind("selector:open",function(){for(var e=n.length;e--;)if(n[e]!=d)n[e].closeSelectors();else for(var t=k.length;t--;)k[t]!=o&&k[t].close()}),o.$.bind("selector:change",function(){var e=!1;f&&(t.multiple_mins==t.multiple_time_minutes&&(o==a?e=s.setValue(a.getValue()):o==s&&(e=a.setValue(s.getValue()))),
+e||h.trigger("cron:change",d.getCron()))}),k.push(o),o}var i,a,o,s,l,u,c,f=!1,d=this,p=this,h=e(' '),m=e(' '),v=e(' '),g=e(' '),y=e(' '),_=e(' '),b=e(' '),w=e(' '),x=e('✘ '),k=[];this.disable=function(){h.addClass("disable"),t.disable=!0,d.closeSelectors()},this.isDisabled=function(){return 1==t.disable},this.enable=function(){h.removeClass("disable"),t.disable=!1},this.getCron=function(){var e=i.getValue(),t=["*","*","*","*","*"];return"hour"==e&&(t[0]=a.getCronValue()),"day"!=e&&"week"!=e&&"month"!=e&&"year"!=e||(t[0]=s.getCronValue(),t[1]=o.getCronValue()),"month"!=e&&"year"!=e||(t[2]=u.getCronValue()),"year"==e&&(t[3]=c.getCronValue()),"week"==e&&(t[4]=l.getCronValue()),t.join(" ")},this.setCron=function(e){if(e)try{e=e.replace(/\s+/g," ").replace(/^ +/,"").replace(/ +$/,"");var t=e.replace(/[^\* ]/g,"-").replace(/-+/g,"-").replace(/ +/g,""),n=e.split(" ");5!=n.length&&d.error(d.getText("error2")),"*****"==t?i.setValue("minute"):"-****"==t?(i.setValue("hour"),a.setCronValue(n[0]),s.setCronValue(n[0])):"***"==t.substring(2,t.length)?(i.setValue("day"),a.setCronValue(n[0]),s.setCronValue(n[0]),o.setCronValue(n[1])):"-**"==t.substring(2,t.length)?(i.setValue("month"),a.setCronValue(n[0]),s.setCronValue(n[0]),o.setCronValue(n[1]),u.setCronValue(n[2])):"**-"==t.substring(2,t.length)?(i.setValue("week"),a.setCronValue(n[0]),s.setCronValue(n[0]),o.setCronValue(n[1]),l.setCronValue(n[4])):"-*"==t.substring(3,t.length)?(i.setValue("year"),a.setCronValue(n[0]),s.setCronValue(n[0]),o.setCronValue(n[1]),u.setCronValue(n[2]),c.setCronValue(n[3])):d.error(d.getText("error4")),d.clearError()}catch(e){}},this.closeSelectors=function(){for(var e=k.length;e--;)k[e].close()},this.getId=function(){return p.attr("id")},this.getText=function(e){var n=t.texts[t.lang][e]||null;return"string"==typeof n&&n.match(" )/gi,''),n=''+n+" "),n},this.getHumanText=function(){var t=[];return h.find("> span > span:visible").find(".jqCron-text, .jqCron-selector > span").each(function(){var n=e(this).text().replace(/\s+$/g,"").replace(/^\s+/g,"");n&&t.push(n)}),t.join(" ").replace(/\s:\s/g,":")},this.getSettings=function(){return t},this.error=function(e){throw console&&console.error("[jqCron Error] "+e),h.addClass("jqCron-error").attr("title",e),e},this.clearError=function(){h.attr("title","").removeClass("jqCron-error")},this.clear=function(){u.setValue([]),l.setValue([]),a.setValue([]),c.setValue([]),o.setValue([]),s.setValue([]),d.triggerChange()},this.init=function(){var n,S,C;if(!f){for(t=jqCronMergeSettings(t),t.jquery_element||d.error(d.getText("error3")),p=t.jquery_element,p.append(h),h.data("id",t.id),h.data("jqCron",d),h.append(m),t.no_reset_button||h.append(x),!t.disable||h.addClass("disable"),m.append(v),/^(ko)$/i.test(t.lang)?m.append(y,g):m.append(g,y),m.append(_),m.append(b),m.append(w),x.click(function(){d.isDisabled()||d.clear()}),h.bind("cron:change",function(e,n){t.bind_to&&(t.bind_method.set&&t.bind_method.set(t.bind_to,n),d.clearError())}),v.append(d.getText("text_period")),i=r(v,!1,"period"),t.enabled_minute&&i.add("minute",d.getText("name_minute")),t.enabled_hour&&i.add("hour",d.getText("name_hour")),t.enabled_day&&i.add("day",d.getText("name_day")),t.enabled_week&&i.add("week",d.getText("name_week")),t.enabled_month&&i.add("month",d.getText("name_month")),t.enabled_year&&i.add("year",d.getText("name_year")),i.$.bind("selector:change",function(e,t){g.hide(),y.hide(),_.hide(),b.hide(),w.hide(),"hour"==t?_.show():"day"==t?w.show():"week"==t?(b.show(),w.show()):"month"==t?(g.show(),w.show()):"year"==t&&(g.show(),y.show(),w.show())}),i.setValue(t.default_period),_.append(d.getText("text_mins")),a=r(_,t.multiple_mins,"minutes"),n=0,C=t.minutes;n'),u=e(' '),c=e(' '),f={},d=[],p=!0,h=t.getSettings().numeric_zero_pad;this.getValue=function(){return r?d:d[0]},this.getCronValue=function(){if(0==d.length)return"*";var e,t=[d[0]],n=d[0],r=d[0],i=d.length;for(e=1;e"+n+"");l.append(i),f[t]=i,i.click(function(){r&&e(this).hasClass("selected")?s.removeValue(t):(s.addValue(t),r||s.close())})},this.$=c,n.find("b:eq(0)").after(c).remove(),c.addClass("jqCron-selector-"+n.find(".jqCron-selector").length).append(u).append(l).bind("selector:open",function(){if(p){var e=1,t=l.find("li").length;t>5&&t<=16?e=2:t>16&&t<=23?e=3:t>23&&t<=40?e=4:t>40&&(e=5),l.addClass("cols"+e)}l.show()}).bind("selector:close",function(){l.hide()}).bind("selector:change",function(){u.html(s.getTitleText())}).click(function(e){e.stopPropagation()}).trigger("selector:change"),e.fn.disableSelection&&c.disableSelection(),u.click(function(e){s.isOpened()||t.isDisabled()?s.close():s.open()}),s.close(),s.clear()}this.jqCronSelector=t}.call(window,o.default),function(e){var t=0,n=function(e){for(var n;;)if(t++,n=(e||"JQUID")+""+t,!document.getElementById(n))return n};e.fn.uniqueId=function(t){return this.each(function(){if(!e(this).attr("id")){var r=n(t);e(this).attr("id",r)}})}}.call(window,o.default),function(e){e.extend(e.expr[":"],{container:function(e){return(e.tagName+"").toLowerCase()in{a:1,abbr:1,acronym:1,address:1,b:1,big:1,blockquote:1,button:1,cite:1,code:1,dd:1,del:1,dfn:1,div:1,dt:1,em:1,fieldset:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,i:1,ins:1,kbd:1,label:1,li:1,p:1,pre:1,q:1,samp:1,small:1,span:1,strong:1,sub:1,sup:1,td:1,tt:1}},autoclose:function(e){return(e.tagName+"").toLowerCase()in{area:1,base:1,basefont:1,br:1,col:1,frame:1,hr:1,img:1,input:1,link:1,meta:1,param:1}}})}.call(window,o.default)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var i=n(1),a=r(i);(0,a.default)(document).ready(function(){(0,a.default)(".copy-to-clipboard").click(function(e){var t=(0,a.default)(" ");(0,a.default)("body").append(t),t.val((0,a.default)(this).prev("input").val()).select(),document.execCommand("Copy"),t.remove(),(0,a.default)(this).attr("data-hint","Copied to clipboard!").addClass("hint--left")})})},,,function(e,t,n){function r(e){return e=i(e),e=a(e),e=o(e).replace(/[\-_]/g," "),/[a-z]/.test(e)&&/^|\s[A-Z]+\s|$/.test(e)?e=e.replace(/\s(\w+)/g,function(e,t){return/^[A-Z]+$/.test(t)?e:l(e)}):/\s/.test(e)&&(e=l(e)),e.replace(/\s[a-z]/g,s).replace(/^\s*[A-Z]+/g,l).replace(/\s+/g,"")}var i=n(337),a=n(615),o=n(616),s=n(617),l=n(618);e.exports=r},function(e,t,n){function r(e){return e=i(e),e.search(/[\xC0-\xFF]/g)>-1&&(e=e.replace(/[\xC0-\xC5]/g,"A").replace(/[\xC6]/g,"AE").replace(/[\xC7]/g,"C").replace(/[\xC8-\xCB]/g,"E").replace(/[\xCC-\xCF]/g,"I").replace(/[\xD0]/g,"D").replace(/[\xD1]/g,"N").replace(/[\xD2-\xD6\xD8]/g,"O").replace(/[\xD9-\xDC]/g,"U").replace(/[\xDD]/g,"Y").replace(/[\xDE]/g,"P").replace(/[\xE0-\xE5]/g,"a").replace(/[\xE6]/g,"ae").replace(/[\xE7]/g,"c").replace(/[\xE8-\xEB]/g,"e").replace(/[\xEC-\xEF]/g,"i").replace(/[\xF1]/g,"n").replace(/[\xF2-\xF6\xF8]/g,"o").replace(/[\xF9-\xFC]/g,"u").replace(/[\xFE]/g,"p").replace(/[\xFD\xFF]/g,"y")),e}var i=n(337);e.exports=r},function(e,t,n){function r(e){return e=i(e),e.replace(a,"")}var i=n(337),a=/[^\x20\x2D0-9A-Z\x5Fa-z\xC0-\xD6\xD8-\xF6\xF8-\xFF]/g;e.exports=r},function(e,t,n){function r(e){return e=i(e),e.toUpperCase()}var i=n(337);e.exports=r},function(e,t,n){function r(e){return e=i(e),e.toLowerCase()}var i=n(337);e.exports=r},function(e,t,n){function r(e,t,n){return e=i(e),t=i(t),e.indexOf(t,n)!==-1}var i=n(337);e.exports=r},,function(e,t,n){(function(e){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0}),t.Instance=void 0;var a=function(){function e(e,t){for(var n=0;nNo media found')},w=function(){y(),h=0,m=!1,(0,s.default)(".empty-space").remove(),g(p,function(e){e.trim().length?p.page||p.date&&"*"!==p.date||p.type&&"*"!==p.type||(0,s.default)(".js__files").trigger("fillView"):b()})};(0,s.default)("body").on("click",".pages-list-container .js__page-link",function(e){var t=(0,s.default)(e.target).data("page");p.page=t,(0,s.default)(".media-list-title .page-indicator").html(t),(0,s.default)(".js__reset-pages-filter").removeClass("hidden"),_(),(0,s.default)(e.target).parents(".row").addClass("active"),(0,s.default)(".js__file-uploader").removeClass("hidden"),(0,s.default)(".dropzone")[0]&&(0,s.default)(".dropzone")[0].dropzone.on("processing",function(e){this.options.url=l.config.base_url_relative+"/media-manager"+t+".json/task"+l.config.param_sep+"addmedia"}),(0,s.default)(".js__button-clear-media-cache").addClass("hidden"),w(),C()}),(0,s.default)("body").on("click",".js__reset-pages-filter",function(e){(0,s.default)(".media-list-title .page-indicator").html("All Pages"),y(),_(),(0,s.default)(".js__reset-pages-filter").addClass("hidden"),(0,s.default)(".js__file-uploader").addClass("hidden"),(0,s.default)(".js__button-clear-media-cache").removeClass("hidden"),delete p.page,w()});var x=function(){(0,s.default)(".spinning-wheel").hide();var e=(0,s.default)(".mediapicker-scroll").last(),t=e.data("scrollbar");t&&(t=t.getViewElement()),t&&(t.length||e.length)&&(0,s.default)(t||e).on("scroll",function(){(0,s.default)(this).scrollTop()+(0,s.default)(this).innerHeight()+100>=(0,s.default)(this)[0].scrollHeight&&S()})},k=function(e){m||g({},function(t){!(0,s.default)(t).length||t.split("card-item").length-1 .fa").addClass("fa-spin"),(0,l.default)(n,{method:"post"},function(e){(0,a.default)("[data-2fa-image]").attr("src",e.image),(0,a.default)("[data-2fa-secret]").text(e.secret),(0,a.default)("[data-2fa-value]").val(e.secret.replace(" ","")),t.removeAttr("disabled").find("> .fa").removeClass("fa-spin")})});var c=function(){var e=(0,a.default)("#toggle_twofa_enabled1"),t=(0,a.default)(".twofa-secret");t[e.is(":checked")?"addClass":"removeClass"]("show")};u.on("click",".twofa-toggle input",c),c()},function(e,t,n){"use strict";n(624)},function(e,t,n){(function(e){"use strict";function t(e){return e&&e.__esModule?e:{default:e}}var r=n(1),i=t(r),a=n(625),o=function(t,n){return(0,a.setParam)(e.location.href,t,n)};(0,i.default)(document).on("change",".logs-content .block-select select[name]",function(t){var n=(0,i.default)(t.currentTarget),r=n.attr("name"),a=n.val();e.location.href=o(r,a)})}).call(t,function(){return this}())},function(e,t,n){e.exports={contains:n(626),decode:n(628),encode:n(630),getParam:n(632),getQuery:n(627),parse:n(633),setParam:n(634)}},function(e,t,n){function r(e,t){var n=new RegExp("(\\?|&)"+t+"=","g");return n.test(i(e))}var i=n(627);e.exports=r},function(e,t){function n(e){var t=/\?[a-zA-Z0-9\=\&\%\$\-\_\.\+\!\*\'\(\)\,]+/.exec(e);return t?decodeURIComponent(t[0].replace(/\+/g," ")):""}e.exports=n},function(e,t,n){function r(e,t){for(var n,r,s,l,u=(e||"").replace("?","").split("&"),c=-1,f={};r=u[++c];)n=r.indexOf("="),l=r.substring(0,n),s=decodeURIComponent(r.substring(n+1)),t!==!1&&(s=i(s)),o(f,l)?a(f[l])?f[l].push(s):f[l]=[f[l],s]:f[l]=s;return f}var i=n(629),a=n(353),o=n(351);e.exports=r},function(e,t){function n(e){var t;return t=null===e||"null"===e?null:"true"===e||"false"!==e&&(e===r||"undefined"===e?r:""===e||isNaN(e)?e:parseFloat(e))}var r;e.exports=n},function(e,t,n){function r(e){var t,n,r=[];return i(e,function(e,i){a(e)?(t=i+"=",n=new RegExp("&"+i+"+=$"),o(e,function(e){t+=encodeURIComponent(e)+"&"+i+"="}),r.push(t.replace(n,""))):r.push(i+"="+encodeURIComponent(e))}),r.length?"?"+r.join("&"):""}var i=n(350),a=n(353),o=n(631);e.exports=r},function(e,t){function n(e,t,n){if(null!=e)for(var r=-1,i=e.length;++r1&&void 0!==arguments[1]&&arguments[1];e&&e.preventDefault();var r=(0,s.default)("#overlay"),i=(0,s.default)("#admin-sidebar"),a=(0,s.default)("#admin-menu").data("scrollbar");this.body.addClass("sidebar-mobile-open"),r.css("display","block"),n?(i.css({display:"block",opacity:1}),this.isOpen=!0):i.css("display","block").animate({opacity:1},200,function(){t.isOpen=!0}),a&&a.update()}},{key:"close",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]&&arguments[1];e&&e.preventDefault();var r=(0,s.default)("#overlay"),i=(0,s.default)("#admin-sidebar"),a=(0,s.default)("#admin-menu").data("scrollbar");this.body.removeClass("sidebar-mobile-open"),r.css("display","none"),n?(i.css({opacity:0,display:"none"}),this.isOpen=!1):i.animate({opacity:0},200,function(){i.css("display","none"),t.isOpen=!1}),a&&a.update()}},{key:"toggle",value:function(e){return e&&e.preventDefault(),this[this.isOpen?"close":"open"](e)}},{key:"toggleSidebarState",value:function(t){t&&t.preventDefault(),clearTimeout(this.timeout);var n=e.matchMedia(g).matches;n&&this.body.removeClass("sidebar-open"),n||(this.body.removeClass("sidebar-closed"),this.body.removeClass("sidebar-mobile-open")),this.body.toggleClass("sidebar-"+(n?"closed":"open")),(0,s.default)(e).trigger("sidebar_state._grav",n)}},{key:"checkMatch",value:function(e){var t=(0,s.default)("#admin-sidebar"),n=(0,s.default)("#overlay");this.isOpen=!1,n.css("display","none"),t.css({display:e.matches?"none":"inherit",opacity:e.matches?0:1}),e.matches&&this.body.removeClass("sidebar-open sidebar-closed"),this[e.matches?"attach":"detach"]()}},{key:"_resetMap",value:function(){return y.clear()}},{key:"_getBound",value:function(e){return y.has(e)?y.get(e):y.set(e,this[e].bind(this)).get(e)}}]),t}();t.default=_;t.Instance=new _}).call(t,function(){return this}())},function(e,t,n){"use strict";e.exports=n(641)()?Map:n(642)},function(e,t){"use strict";e.exports=function(){var e,t,n;if("function"!=typeof Map)return!1;try{e=new Map([["raz","one"],["dwa","two"],["trzy","three"]])}catch(e){return!1}return"[object Map]"===String(e)&&(3===e.size&&("function"==typeof e.clear&&("function"==typeof e.delete&&("function"==typeof e.entries&&("function"==typeof e.forEach&&("function"==typeof e.get&&("function"==typeof e.has&&("function"==typeof e.keys&&("function"==typeof e.set&&("function"==typeof e.values&&(t=e.entries(),n=t.next(),n.done===!1&&(!!n.value&&("raz"===n.value[0]&&"one"===n.value[1])))))))))))))}},function(e,t,n){"use strict";var r,i=n(643),a=n(647),o=n(656),s=n(661),l=n(644),u=n(662),c=n(674),f=n(675),d=n(680),p=n(684),h=n(698),m=n(701),v=Function.prototype.call,g=Object.defineProperties,y=Object.getPrototypeOf;e.exports=r=function(){var e,t,n,i=arguments[0];if(!(this instanceof r))throw new TypeError("Constructor requires 'new'");return n=m&&o&&Map!==r?o(new Map,y(this)):this,null!=i&&d(i),g(n,{__mapKeysData__:u("c",e=[]),__mapValuesData__:u("c",t=[])}),i?(p(i,function(n){var r=l(n)[0];n=n[1],a.call(e,r)===-1&&(e.push(r),
+t.push(n))},n),n):n},m&&(o&&o(r,Map),r.prototype=Object.create(Map.prototype,{constructor:u(r)})),c(g(r.prototype,{clear:u(function(){this.__mapKeysData__.length&&(i.call(this.__mapKeysData__),i.call(this.__mapValuesData__),this.emit("_clear"))}),delete:u(function(e){var t=a.call(this.__mapKeysData__,e);return t!==-1&&(this.__mapKeysData__.splice(t,1),this.__mapValuesData__.splice(t,1),this.emit("_delete",t,e),!0)}),entries:u(function(){return new h(this,"key+value")}),forEach:u(function(e){var t,n,r=arguments[1];for(s(e),t=this.entries(),n=t._next();void 0!==n;)v.call(e,r,this.__mapValuesData__[n],this.__mapKeysData__[n],this),n=t._next()}),get:u(function(e){var t=a.call(this.__mapKeysData__,e);if(t!==-1)return this.__mapValuesData__[t]}),has:u(function(e){return a.call(this.__mapKeysData__,e)!==-1}),keys:u(function(){return new h(this,"key")}),set:u(function(e,t){var n,r=a.call(this.__mapKeysData__,e);return r===-1&&(r=this.__mapKeysData__.push(e)-1,n=!0),this.__mapValuesData__[r]=t,n&&this.emit("_add",r,e),this}),size:u.gs(function(){return this.__mapKeysData__.length}),values:u(function(){return new h(this,"value")}),toString:u(function(){return"[object Map]"})})),Object.defineProperty(r.prototype,f.iterator,u(function(){return this.entries()})),Object.defineProperty(r.prototype,f.toStringTag,u("c","Map"))},function(e,t,n){"use strict";var r=n(644);e.exports=function(){return r(this).length=0,this}},function(e,t,n){"use strict";var r=n(645);e.exports=function(e){if(!r(e))throw new TypeError("Cannot use null or undefined");return e}},function(e,t,n){"use strict";var r=n(646)();e.exports=function(e){return e!==r&&null!==e}},function(e,t){"use strict";e.exports=function(){}},function(e,t,n){"use strict";var r=n(648),i=n(651),a=n(644),o=Array.prototype.indexOf,s=Object.prototype.hasOwnProperty,l=Math.abs,u=Math.floor;e.exports=function(e){var t,n,c,f;if(!r(e))return o.apply(this,arguments);for(n=i(a(this).length),c=arguments[1],c=isNaN(c)?0:c>=0?u(c):i(this.length)-u(l(c)),t=c;t0?1:-1}},function(e,t,n){"use strict";e.exports=n(657)()?Object.setPrototypeOf:n(658)},function(e,t){"use strict";var n=Object.create,r=Object.getPrototypeOf,i={};e.exports=function(){var e=Object.setPrototypeOf,t=arguments[0]||n;return"function"==typeof e&&r(e(t(null),i))===i}},function(e,t,n){"use strict";var r,i=n(659),a=n(644),o=Object.prototype.isPrototypeOf,s=Object.defineProperty,l={configurable:!0,enumerable:!1,writable:!0,value:void 0};r=function(e,t){if(a(e),null===t||i(t))return e;throw new TypeError("Prototype must be null or an object")},e.exports=function(e){var t,n;return e?(2===e.level?e.set?(n=e.set,t=function(e,t){return n.call(r(e,t),t),e}):t=function(e,t){return r(e,t).__proto__=t,e}:t=function e(t,n){var i;return r(t,n),i=o.call(e.nullPolyfill,t),i&&delete e.nullPolyfill.__proto__,null===n&&(n=e.nullPolyfill),t.__proto__=n,i&&s(e.nullPolyfill,"__proto__",l),t},Object.defineProperty(t,"level",{configurable:!1,enumerable:!1,writable:!1,value:e.level})):null}(function(){var e,t=Object.create(null),n={},r=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__");if(r){try{e=r.set,e.call(t,n)}catch(e){}if(Object.getPrototypeOf(t)===n)return{set:e,level:2}}return t.__proto__=n,Object.getPrototypeOf(t)===n?{level:2}:(t={},t.__proto__=n,Object.getPrototypeOf(t)===n&&{level:1})}()),n(660)},function(e,t,n){"use strict";var r=n(645),i={function:!0,object:!0};e.exports=function(e){return r(e)&&i[typeof e]||!1}},function(e,t,n){"use strict";var r,i=Object.create;n(657)()||(r=n(658)),e.exports=function(){var e,t,n;return r?1!==r.level?i:(e={},t={},n={configurable:!1,enumerable:!1,writable:!0,value:void 0},Object.getOwnPropertyNames(Object.prototype).forEach(function(e){return"__proto__"===e?void(t[e]={configurable:!0,enumerable:!1,writable:!0,value:void 0}):void(t[e]=n)}),Object.defineProperties(e,t),Object.defineProperty(r,"nullPolyfill",{configurable:!1,enumerable:!1,writable:!1,value:e}),function(t,n){return i(null===t?e:t,n)}):i}()},function(e,t){"use strict";e.exports=function(e){if("function"!=typeof e)throw new TypeError(e+" is not a function");return e}},function(e,t,n){"use strict";var r,i=n(663),a=n(669),o=n(670),s=n(671);r=e.exports=function(e,t){var n,r,o,l,u;return arguments.length<2||"string"!=typeof e?(l=t,t=e,e=null):l=arguments[2],null==e?(n=o=!0,r=!1):(n=s.call(e,"c"),r=s.call(e,"e"),o=s.call(e,"w")),u={value:t,configurable:n,enumerable:r,writable:o},l?i(a(l),u):u},r.gs=function(e,t,n){var r,l,u,c;return"string"!=typeof e?(u=n,n=t,t=e,e=null):u=arguments[3],null==t?t=void 0:o(t)?null==n?n=void 0:o(n)||(u=n,n=void 0):(u=t,t=n=void 0),null==e?(r=!0,l=!1):(r=s.call(e,"c"),l=s.call(e,"e")),c={get:t,set:n,configurable:r,enumerable:l},u?i(a(u),c):c}},function(e,t,n){"use strict";e.exports=n(664)()?Object.assign:n(665)},function(e,t){"use strict";e.exports=function(){var e,t=Object.assign;return"function"==typeof t&&(e={foo:"raz"},t(e,{bar:"dwa"},{trzy:"trzy"}),e.foo+e.bar+e.trzy==="razdwatrzy")}},function(e,t,n){"use strict";var r=n(666),i=n(644),a=Math.max;e.exports=function(e,t){var n,o,s,l=a(arguments.length,2);for(e=Object(i(e)),s=function(r){try{e[r]=t[r]}catch(e){n||(n=e)}},o=1;o-1}},function(e,t,n){"use strict";var r,i,a,o,s,l,u,c=n(662),f=n(661),d=Function.prototype.apply,p=Function.prototype.call,h=Object.create,m=Object.defineProperty,v=Object.defineProperties,g=Object.prototype.hasOwnProperty,y={configurable:!0,enumerable:!1,writable:!0};r=function(e,t){var n;return f(t),g.call(this,"__ee__")?n=this.__ee__:(n=y.value=h(null),m(this,"__ee__",y),y.value=null),n[e]?"object"==typeof n[e]?n[e].push(t):n[e]=[n[e],t]:n[e]=t,this},i=function(e,t){var n,i;return f(t),i=this,r.call(this,e,n=function(){a.call(i,e,n),d.call(t,this,arguments)}),n.__eeOnceListener__=t,this},a=function(e,t){var n,r,i,a;if(f(t),!g.call(this,"__ee__"))return this;if(n=this.__ee__,!n[e])return this;if(r=n[e],"object"==typeof r)for(a=0;i=r[a];++a)i!==t&&i.__eeOnceListener__!==t||(2===r.length?n[e]=r[a?0:1]:r.splice(a,1));else r!==t&&r.__eeOnceListener__!==t||delete n[e];return this},o=function(e){var t,n,r,i,a;if(g.call(this,"__ee__")&&(i=this.__ee__[e]))if("object"==typeof i){for(n=arguments.length,a=new Array(n-1),t=1;t=55296&&v<=56319&&(m+=e[++p])),l.call(t,g,m,f),!d);++p);}},function(e,t,n){"use strict";var r=n(682),i=n(683),a=n(686),o=n(697),s=n(680),l=n(675).iterator;e.exports=function(e){return"function"==typeof s(e)[l]?e[l]():r(e)?new a(e):i(e)?new o(e):new a(e)}},function(e,t,n){"use strict";var r,i=n(656),a=n(671),o=n(662),s=n(675),l=n(687),u=Object.defineProperty;r=e.exports=function(e,t){if(!(this instanceof r))throw new TypeError("Constructor requires 'new'");l.call(this,e),t=t?a.call(t,"key+value")?"key+value":a.call(t,"key")?"key":"value":"value",u(this,"__kind__",o("",t))},i&&i(r,l),delete r.prototype.constructor,r.prototype=Object.create(l.prototype,{_resolve:o(function(e){return"value"===this.__kind__?this.__list__[e]:"key+value"===this.__kind__?[e,this.__list__[e]]:e})}),u(r.prototype,s.toStringTag,o("c","Array Iterator"))},function(e,t,n){"use strict";var r,i=n(643),a=n(663),o=n(661),s=n(644),l=n(662),u=n(688),c=n(675),f=Object.defineProperty,d=Object.defineProperties;e.exports=r=function(e,t){if(!(this instanceof r))throw new TypeError("Constructor requires 'new'");d(this,{__list__:l("w",s(e)),__context__:l("w",t),__nextIndex__:l("w",0)}),t&&(o(t.on),t.on("_add",this._onAdd),t.on("_delete",this._onDelete),t.on("_clear",this._onClear))},delete r.prototype.constructor,d(r.prototype,a({_next:l(function(){var e;if(this.__list__)return this.__redo__&&(e=this.__redo__.shift(),void 0!==e)?e:this.__nextIndex__=this.__nextIndex__)){if(++this.__nextIndex__,!this.__redo__)return void f(this,"__redo__",l("c",[e]));this.__redo__.forEach(function(t,n){t>=e&&(this.__redo__[n]=++t)},this),this.__redo__.push(e)}}),_onDelete:l(function(e){var t;e>=this.__nextIndex__||(--this.__nextIndex__,this.__redo__&&(t=this.__redo__.indexOf(e),t!==-1&&this.__redo__.splice(t,1),this.__redo__.forEach(function(t,n){t>e&&(this.__redo__[n]=--t)},this)))}),_onClear:l(function(){this.__redo__&&i.call(this.__redo__),this.__nextIndex__=0})}))),f(r.prototype,c.iterator,l(function(){return this}))},function(e,t,n){"use strict";var r,i=n(689),a=n(669),o=n(661),s=n(694),l=n(661),u=n(644),c=Function.prototype.bind,f=Object.defineProperty,d=Object.prototype.hasOwnProperty;r=function(e,t,n){var r,a=u(t)&&l(t.value);return r=i(t),delete r.writable,delete r.value,r.get=function(){return!n.overwriteDefinition&&d.call(this,e)?a:(t.value=c.call(a,n.resolveContext?n.resolveContext(this):this),f(this,e,t),this[e])},r},e.exports=function(e){var t=a(arguments[1]);return null!=t.resolveContext&&o(t.resolveContext),s(e,function(e,n){return r(n,e,t)})}},function(e,t,n){"use strict";var r=n(690),i=n(663),a=n(644);e.exports=function(e){var t=Object(a(e)),n=arguments[1],o=Object(arguments[2]);if(t!==e&&!n)return t;var s={};return n?r(n,function(t){(o.ensure||t in e)&&(s[t]=e[t])}):i(s,e),s}},function(e,t,n){"use strict";e.exports=n(691)()?Array.from:n(692)},function(e,t){"use strict";e.exports=function(){var e,t,n=Array.from;return"function"==typeof n&&(e=["raz","dwa"],t=n(e),Boolean(t&&t!==e&&"dwa"===t[1]))}},function(e,t,n){"use strict";var r=n(675).iterator,i=n(682),a=n(693),o=n(651),s=n(661),l=n(644),u=n(645),c=n(683),f=Array.isArray,d=Function.prototype.call,p={configurable:!0,enumerable:!0,writable:!0,value:null},h=Object.defineProperty;e.exports=function(e){var t,n,m,v,g,y,_,b,w,x,k=arguments[1],S=arguments[2];if(e=Object(l(e)),u(k)&&s(k),this&&this!==Array&&a(this))t=this;else{if(!k){if(i(e))return g=e.length,1!==g?Array.apply(null,e):(v=new Array(1),v[0]=e[0],v);if(f(e)){for(v=new Array(g=e.length),n=0;n=55296&&y<=56319&&(x+=e[++n])),x=k?d.call(k,S,x,m):x,t?(p.value=x,h(v,m,p)):v[m]=x,++m;g=m}if(void 0===g)for(g=o(e.length),t&&(v=new t(g)),n=0;n=55296&&t<=56319?n+this.__list__[this.__nextIndex__++]:n)})}),l(r.prototype,o.toStringTag,a("c","String Iterator"))},function(e,t,n){"use strict";var r,i=n(656),a=n(662),o=n(687),s=n(675).toStringTag,l=n(699),u=Object.defineProperties,c=o.prototype._unBind;r=e.exports=function(e,t){return this instanceof r?(o.call(this,e.__mapKeysData__,e),t&&l[t]||(t="key+value"),void u(this,{__kind__:a("",t),__values__:a("w",e.__mapValuesData__)})):new r(e,t)},i&&i(r,o),r.prototype=Object.create(o.prototype,{constructor:a(r),_resolve:a(function(e){return"value"===this.__kind__?this.__values__[e]:"key"===this.__kind__?this.__list__[e]:[this.__list__[e],this.__values__[e]]}),_unBind:a(function(){this.__values__=null,c.call(this)}),toString:a(function(){return"[object Map Iterator]"})}),Object.defineProperty(r.prototype,s,a("c","Map Iterator"))},function(e,t,n){"use strict";e.exports=n(700)("key","value","key+value")},function(e,t){"use strict";var n=Array.prototype.forEach,r=Object.create;e.exports=function(e){var t=r(null);return n.call(arguments,function(e){t[e]=!0}),t}},function(e,t){"use strict";e.exports=function(){return"undefined"!=typeof Map&&"[object Map]"===Object.prototype.toString.call(new Map)}()},,,,,,,,,,,,,,,,function(e,t,n){(function(e){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var a=function(){function e(e,t){for(var n=0;n table > tbody, .gpm-themes > .themes.card-row"),this.elements}},{key:"byCommon",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"asc",n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",r=this.getElements().sort(function(r,i){var a=(0,s.default)(r).data(n).toString().toLowerCase(),o=(0,s.default)(i).data(n).toString().toLowerCase();return e.sort(a,o,t)});return r.appendTo(this.container)}},{key:"byName",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"asc",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"gpm-name";return this.byCommon(e,t)}},{key:"byAuthor",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"asc",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"gpm-author";return this.byCommon(e,t)}},{key:"byOfficial",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"asc",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"gpm-official";return this.byCommon(e,t)}},{key:"byReleaseDate",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"asc",n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"gpm-release-date",r=this.getElements().sort(function(r,i){var a=new Date((0,s.default)(r).data(n)).getTime(),o=new Date((0,s.default)(i).data(n)).getTime();return e.sort(a,o,"asc"===t?"desc":"asc")});r.appendTo(this.container)}},{key:"byUpdatable",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"asc",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"gpm-updatable";return this.byCommon(e,t)}},{key:"byEnabled",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"asc",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"gpm-enabled";return this.byCommon(e,t)}},{key:"byTesting",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"asc",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"gpm-testing";return this.byCommon(e,t)}}],[{key:"sort",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"asc";return e>t?"asc"===n?1:-1:e1&&void 0!==arguments[1]?arguments[1]:"";e.forEach(function(e){t.addDependencyToList("plugin",e.name||e,n)})}},{key:"removePackage",value:function(e,n){var r=this,i=t.getRemovePackageUrl(e);(0,c.default)(i,{method:"post",body:{package:n}},function(n){"success"===n.status?((0,s.default)(".remove-package-confirm").addClass("hidden"),n.dependencies&&n.dependencies.length>0?(r.addDependenciesToList(n.dependencies),(0,s.default)(".remove-package-dependencies").removeClass("hidden")):(0,s.default)(".remove-package-done").removeClass("hidden"),(0,s.default)(document).on("closing",'[data-remodal-id="remove-package"]',function(){t.getBackToList(e)})):((0,s.default)(".remove-package-confirm").addClass("hidden"),(0,s.default)(".remove-package-error").removeClass("hidden"))})}},{key:"reinstallPackage",value:function(e,n,r,i){(0,s.default)(".button-bar button").addClass("hidden"),(0,s.default)(".button-bar .spinning-wheel").removeClass("hidden");var a=t.getReinstallPackageUrl(e);(0,c.default)(a,{method:"post",body:{slug:n,type:e,package_name:r,current_version:i}},function(e){"success"===e.status?((0,s.default)(".reinstall-package-confirm").addClass("hidden"),(0,s.default)(".reinstall-package-done").removeClass("hidden")):((0,s.default)(".reinstall-package-confirm").addClass("hidden"),(0,s.default)(".reinstall-package-error").removeClass("hidden")),window.location.reload()})}},{key:"removeDependency",value:function(e,n,r){var i=this,a=t.getRemovePackageUrl(e);(0,c.default)(a,{method:"post",body:{package:n}},function(e){"success"===e.status&&(r.removeClass("button"),r.replaceWith((0,s.default)("Removed successfully ")),e.dependencies&&e.dependencies.length>0&&i.addDependenciesToList(e.dependencies,n))})}},{key:"getPackagesDependencies",value:function(e,n,r){var i=t.getGetPackagesDependenciesUrl(e);(0,c.default)(i,{method:"post",body:{packages:n}},function(e){if(r(),"success"===e.status)if(e.dependencies){var n=!1;for(var i in e.dependencies)if(e.dependencies.hasOwnProperty(i)){if("grav"===i)continue;n=!0;var a=i,o=e.dependencies[i];t.addNeededDependencyToList(o,a)}n?(0,s.default)("[data-packages-modal] .install-dependencies-package-container").removeClass("hidden"):(0,s.default)("[data-packages-modal] .install-package-container").removeClass("hidden")}else(0,s.default)("[data-packages-modal] .install-package-container").removeClass("hidden");else(0,s.default)("[data-packages-modal] .install-package-error").removeClass("hidden")})}},{key:"installDependenciesOfPackages",value:function(e,n,r,i){var a=t.getInstallDependenciesOfPackagesUrl(e);(0,c.default)(a,{method:"post",body:{packages:n}},r)}},{key:"installPackages",value:function(e,n,r){var i=t.getInstallPackageUrl(e);d.Promise.all(n.map(function(t){return new d.Promise(function(n,r){(0,c.default)(i,{method:"post",body:{package:t,type:e}},function(e){n(e)})})})).then(r)}},{key:"handleGettingPackageDependencies",value:function(e,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"update",i=t.getSlugsFromEvent(n);if(!i)return void alert("No slug set");(0,s.default)(".packages-names-list").html(""),(0,s.default)(".install-dependencies-package-container li").remove(),i.forEach(function(e){if("update"===r){var t="",n="",i="",a=f.Instance.payload.payload.resources;a.plugins[e]?(n=a.plugins[e].available,t=a.plugins[e].version,i=a.plugins[e].name):a.themes[e]&&(n=a.themes[e].available,t=a.themes[e].version,i=a.themes[e].name),(0,s.default)(".packages-names-list").append(""+(i?i:e)+", "+l.translations.PLUGIN_ADMIN.FROM+" v"+t+" "+l.translations.PLUGIN_ADMIN.TO+" v"+n+" ")}else(0,s.default)(".packages-names-list").append(""+(name?name:e)+" ")}),n.preventDefault(),n.stopPropagation();var a="[data-packages-modal] [data-theme-action], [data-packages-modal] [data-plugin-action]",o=(0,s.default)(a).data("themeAction")||(0,s.default)(a).data("pluginAction");(0,s.default)(a).removeAttr("data-theme-action").removeAttr("data-plugin-action").attr("data-"+e+"-action",o),(0,s.default)("[data-packages-modal] .loading").removeClass("hidden"),(0,s.default)("[data-packages-modal] .install-dependencies-package-container").addClass("hidden"),(0,s.default)("[data-packages-modal] .install-package-container").addClass("hidden"),(0,s.default)("[data-packages-modal] .installing-dependencies").addClass("hidden"),(0,s.default)("[data-packages-modal] .installing-package").addClass("hidden"),(0,s.default)("[data-packages-modal] .installation-complete").addClass("hidden"),(0,s.default)("[data-packages-modal] .install-package-error").addClass("hidden"),this.getPackagesDependencies(e,i,function(){var t=i.join();(0,s.default)("[data-packages-modal] [data-"+e+'-action="install-dependencies-and-package"]').attr("data-packages-slugs",t),(0,s.default)("[data-packages-modal] [data-"+e+'-action="install-package"]').attr("data-packages-slugs",t),(0,s.default)("[data-packages-modal] .loading").addClass("hidden")})}},{key:"handleInstallingDependenciesAndPackage",value:function(n,r){var i=this,a=t.getSlugsFromEvent(r);r.preventDefault(),r.stopPropagation(),(0,s.default)("[data-packages-modal] .install-dependencies-package-container").addClass("hidden"),(0,s.default)("[data-packages-modal] .install-package-container").addClass("hidden"),(0,s.default)("[data-packages-modal] .installing-dependencies").removeClass("hidden"),this.installDependenciesOfPackages(n,a,function(t){(0,s.default)("[data-packages-modal] .installing-dependencies").addClass("hidden"),(0,s.default)("[data-packages-modal] .installing-package").removeClass("hidden"),i.installPackages(n,a,function(){if((0,s.default)("[data-packages-modal] .installing-package").addClass("hidden"),(0,s.default)("[data-packages-modal] .installation-complete").removeClass("hidden"),"error"===t.status){var r=s.default.remodal.lookup[(0,s.default)("[data-packages-modal]").data("remodal")];return void r.close()}setTimeout(function(){1===a.length?e.location.href=l.config.base_url_relative+"/"+n+"s/"+a[0]:e.location.href=l.config.base_url_relative+"/"+n+"s"},1e3)})})}},{key:"handleInstallingPackage",value:function(n,r){var i=t.getSlugsFromEvent(r);r.preventDefault(),r.stopPropagation(),(0,s.default)("[data-packages-modal] .install-package-container").addClass("hidden"),(0,s.default)("[data-packages-modal] .installing-package").removeClass("hidden"),this.installPackages(n,i,function(t){(0,s.default)("[data-packages-modal] .installing-package").addClass("hidden"),(0,s.default)("[data-packages-modal] .installation-complete").removeClass("hidden");var r=Array.from(t).filter(function(e){return"error"===e.status});if(r&&r.length){var a=s.default.remodal.lookup[(0,s.default)("[data-packages-modal].remodal-is-opened").data("remodal")];return void a.close()}1===i.length?e.location.href=l.config.base_url_relative+"/"+n+"s/"+i[0]:e.location.href=l.config.base_url_relative+"/"+n+"s"})}},{key:"handleRemovingPackage",value:function(e,t){var n=(0,s.default)(t.target).attr("data-packages-slugs");t.preventDefault(),t.stopPropagation(),this.removePackage(e,n)}},{key:"handleReinstallPackage",value:function(e,t){var n=(0,s.default)(t.target),r=n.attr("data-package-slug"),i=n.attr("data-package-name"),a=n.attr("data-package-current-version");t.preventDefault(),t.stopPropagation(),this.reinstallPackage(e,r,i,a)}},{key:"handleRemovingDependency",value:function(e,t){var n=(0,s.default)(t.target).attr("data-dependency-slug"),r=(0,s.default)(t.target);t.preventDefault(),t.stopPropagation(),this.removeDependency(e,n,r)}}],[{key:"getBackToList",value:function(t){e.location.href=l.config.base_url_relative+"/"+t+"s"}},{key:"addDependencyToList",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";if(["admin","form","login","email","grav"].indexOf(t)===-1){var r=(0,s.default)(".package-dependencies-container"),i=t+' Remove ';n&&(i+=" (was needed by "+n+")"),r.append(""+i+" ")}}},{key:"getTaskUrl",value:function(e,t){var n=""+l.config.base_url_relative;return n+="/"+e+"s.json",n+="/task"+l.config.param_sep+t}},{key:"getRemovePackageUrl",value:function(e){return""+t.getTaskUrl(e,"removePackage")}},{key:"getReinstallPackageUrl",value:function(e){return""+t.getTaskUrl(e,"reinstallPackage")}},{key:"getGetPackagesDependenciesUrl",value:function(e){return""+t.getTaskUrl(e,"getPackagesDependencies")}},{key:"getInstallDependenciesOfPackagesUrl",value:function(e){return""+t.getTaskUrl(e,"installDependenciesOfPackages")}},{key:"getInstallPackageUrl",value:function(e){return""+t.getTaskUrl(e,"installPackage")}},{key:"addNeededDependencyToList",value:function(e,t){(0,s.default)(".install-dependencies-package-container .type-"+e).removeClass("hidden");var n=(0,s.default)(".install-dependencies-package-container .type-"+e+" ul");if("install"!==e){var r="",i="",a="",o=f.Instance.payload.payload.resources;o.plugins[t]?(i=o.plugins[t].available,r=o.plugins[t].version,a=o.plugins[t].name):o.themes[t]&&(i=o.themes[t].available,r=o.themes[t].version,a=o.themes[t].name),n.append(""+(a?a:t)+", "+l.translations.PLUGIN_ADMIN.FROM+" v"+r+" "+l.translations.PLUGIN_ADMIN.TO+" v"+i+" ")}else n.append(""+(name?name:t)+" ")}},{key:"getSlugsFromEvent",value:function(e){var t="";return t=(0,s.default)(e.target).is("[data-packages-slugs]")?(0,s.default)(e.target).attr("data-packages-slugs"):(0,
+s.default)(e.target).parent("[data-packages-slugs]").attr("data-packages-slugs"),"undefined"==typeof t?null:(t=t.split(","),"string"==typeof t?[t]:t)}}]),t}();t.default=new h}).call(t,function(){return this}())},function(e,t,n){(function(t,n){/*!
+ * @overview es6-promise - a tiny implementation of Promises/A+.
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
+ * @license Licensed under MIT license
+ * See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE
+ * @version v4.2.5+7f2b526d
+ */
+!function(t,n){e.exports=n()}(this,function(){"use strict";function e(e){var t=typeof e;return null!==e&&("object"===t||"function"===t)}function r(e){return"function"==typeof e}function i(e){H=e}function a(e){$=e}function o(){return function(){return t.nextTick(f)}}function s(){return"undefined"!=typeof G?function(){G(f)}:c()}function l(){var e=0,t=new Y(f),n=document.createTextNode("");return t.observe(n,{characterData:!0}),function(){n.data=e=++e%2}}function u(){var e=new MessageChannel;return e.port1.onmessage=f,function(){return e.port2.postMessage(0)}}function c(){var e=setTimeout;return function(){return e(f,1)}}function f(){for(var e=0;e1)for(var n=1;n ')
+ .attr('type', 'hidden')
+ .attr('name', item.name)
+ .val(item.value).appendTo($form);
+ });
+
+ return $form;
+ };
+
+ /**
+ * Find all input fields with form attribute point to jQuery object
+ *
+ */
+ $('form[id]').submit(function(e) {
+ // serialize data
+ var data = $('[form=' + this.id + ']').serializeArray();
+ // append data to form
+ $(this).appendField(data);
+ }).each(function() {
+ var form = this,
+ $fields = $('[form=' + this.id + ']');
+
+ $fields.filter('button, input')
+ .filter('[type=reset],[type=submit],[type=image],button')
+ .click(function() {
+ var type = this.type.toLowerCase();
+ if (type === 'reset') {
+ // reset form
+ form.reset();
+ // for elements outside form
+ $fields.each(function() {
+ this.value = this.defaultValue;
+ this.checked = this.defaultChecked;
+ }).filter('select').each(function() {
+ $(this).find('option').each(function() {
+ this.selected = this.defaultSelected;
+ });
+ });
+ } else {
+ $(form).appendField({
+ name: this.name,
+ value: this.value
+ }).submit();
+ }
+ });
+ });
+ });
+
+})(jQuery);
diff --git a/user/plugins/admin/themes/grav/js/vendor.min.js b/user/plugins/admin/themes/grav/js/vendor.min.js
new file mode 100644
index 0000000..d5f3827
--- /dev/null
+++ b/user/plugins/admin/themes/grav/js/vendor.min.js
@@ -0,0 +1,52 @@
+var Grav=function(e){function t(n){if(i[n])return i[n].exports;var r=i[n]={exports:{},id:n,loaded:!1};return e[n].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n=window.webpackJsonpGrav;window.webpackJsonpGrav=function(s,a){for(var o,l,u=0,d=[];u=0;i--)d(e(n[i]),t)}function d(t,n,i){var r=!(!i||!i.force)&&i.force;return!(!t||!r&&0!==e(":focus",t).length)&&(t[n.hideMethod]({duration:n.hideDuration,easing:n.hideEasing,complete:function(){g(t)}}),!0)}function c(t){return _=e("
").attr("id",t.containerId).addClass(t.positionClass),_.appendTo(e(t.target)),_}function h(){return{tapToDismiss:!0,toastClass:"toast",containerId:"toast-container",debug:!1,showMethod:"fadeIn",showDuration:300,showEasing:"swing",onShown:void 0,hideMethod:"fadeOut",hideDuration:1e3,hideEasing:"swing",onHidden:void 0,closeMethod:!1,closeDuration:!1,closeEasing:!1,closeOnHover:!0,extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},iconClass:"toast-info",positionClass:"toast-top-right",timeOut:5e3,titleClass:"toast-title",messageClass:"toast-message",escapeHtml:!1,target:"body",closeHtml:'× ',closeClass:"toast-close-button",newestOnTop:!0,preventDuplicates:!1,progressBar:!1,progressClass:"toast-progress",rtl:!1}}function p(e){v&&v(e)}function f(t){function i(e){return null==e&&(e=""),e.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(/ /g,">")}function r(){l(),d(),c(),h(),f(),v(),u(),s()}function s(){var e="";switch(t.iconClass){case"toast-success":case"toast-info":e="polite";break;default:e="assertive"}C.attr("aria-live",e)}function a(){L.closeOnHover&&C.hover(b,x),!L.onclick&&L.tapToDismiss&&C.click(w),L.closeButton&&F&&F.click(function(e){e.stopPropagation?e.stopPropagation():void 0!==e.cancelBubble&&e.cancelBubble!==!0&&(e.cancelBubble=!0),L.onCloseClick&&L.onCloseClick(e),w(!0)}),L.onclick&&C.click(function(e){L.onclick(e),w()})}function o(){C.hide(),C[L.showMethod]({duration:L.showDuration,easing:L.showEasing,complete:L.onShown}),L.timeOut>0&&(T=setTimeout(w,L.timeOut),O.maxHideTime=parseFloat(L.timeOut),O.hideEta=(new Date).getTime()+O.maxHideTime,L.progressBar&&(O.intervalId=setInterval(k,10)))}function l(){t.iconClass&&C.addClass(L.toastClass).addClass(S)}function u(){L.newestOnTop?_.prepend(C):_.append(C)}function d(){if(t.title){var e=t.title;L.escapeHtml&&(e=i(t.title)),Y.append(e).addClass(L.titleClass),C.append(Y)}}function c(){if(t.message){var e=t.message;L.escapeHtml&&(e=i(t.message)),E.append(e).addClass(L.messageClass),C.append(E)}}function h(){L.closeButton&&(F.addClass(L.closeClass).attr("role","button"),C.prepend(F))}function f(){L.progressBar&&(A.addClass(L.progressClass),C.prepend(A))}function v(){L.rtl&&C.addClass("rtl")}function D(e,t){if(e.preventDuplicates){if(t.message===y)return!0;y=t.message}return!1}function w(t){var n=t&&L.closeMethod!==!1?L.closeMethod:L.hideMethod,i=t&&L.closeDuration!==!1?L.closeDuration:L.hideDuration,r=t&&L.closeEasing!==!1?L.closeEasing:L.hideEasing;if(!e(":focus",C).length||t)return clearTimeout(O.intervalId),C[n]({duration:i,easing:r,complete:function(){g(C),clearTimeout(T),L.onHidden&&"hidden"!==H.state&&L.onHidden(),H.state="hidden",H.endTime=new Date,p(H)}})}function x(){(L.timeOut>0||L.extendedTimeOut>0)&&(T=setTimeout(w,L.extendedTimeOut),O.maxHideTime=parseFloat(L.extendedTimeOut),O.hideEta=(new Date).getTime()+O.maxHideTime)}function b(){clearTimeout(T),O.hideEta=0,C.stop(!0,!0)[L.showMethod]({duration:L.showDuration,easing:L.showEasing})}function k(){var e=(O.hideEta-(new Date).getTime())/O.maxHideTime*100;A.width(e+"%")}var L=m(),S=t.iconClass||L.iconClass;if("undefined"!=typeof t.optionsOverride&&(L=e.extend(L,t.optionsOverride),S=t.optionsOverride.iconClass||S),!D(L,t)){M++,_=n(L,!0);var T=null,C=e("
"),Y=e("
"),E=e("
"),A=e("
"),F=e(L.closeHtml),O={intervalId:null,hideEta:null,maxHideTime:null},H={toastId:M,state:"visible",startTime:new Date,options:L,map:t};return r(),o(),a(),p(H),L.debug&&console&&console.log(H),C}}function m(){return e.extend({},h(),w.options)}function g(e){_||(_=n()),e.is(":visible")||(e.remove(),e=null,0===_.children().length&&(_.remove(),y=void 0))}var _,v,y,M=0,D={error:"error",info:"info",success:"success",warning:"warning"},w={clear:o,remove:l,error:t,getContainer:n,info:i,options:{},subscribe:r,success:s,version:"2.1.4",warning:a};return w}()}.apply(t,i),!(void 0!==r&&(e.exports=r))}(n(333))},function(e,t){e.exports=function(){throw new Error("define cannot be used indirect")}},,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,function(e,t,n){var i,r;!function(n,s){i=[],r=function(){return n.Chartist=s()}.apply(t,i),!(void 0!==r&&(e.exports=r))}(this,function(){var e={version:"0.11.0"};return function(e,t,n){"use strict";n.namespaces={svg:"http://www.w3.org/2000/svg",xmlns:"http://www.w3.org/2000/xmlns/",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",ct:"http://gionkunz.github.com/chartist-js/ct"},n.noop=function(e){return e},n.alphaNumerate=function(e){return String.fromCharCode(97+e%26)},n.extend=function(e){var t,i,r;for(e=e||{},t=1;t":">",'"':""","'":"'"},n.serialize=function(e){return null===e||void 0===e?e:("number"==typeof e?e=""+e:"object"==typeof e&&(e=JSON.stringify({data:e})),Object.keys(n.escapingMap).reduce(function(e,t){return n.replaceAll(e,t,n.escapingMap[t])},e))},n.deserialize=function(e){if("string"!=typeof e)return e;e=Object.keys(n.escapingMap).reduce(function(e,t){return n.replaceAll(e,n.escapingMap[t],t)},e);try{e=JSON.parse(e),e=void 0!==e.data?e.data:e}catch(e){}return e},n.createSvg=function(e,t,i,r){var s;return t=t||"100%",i=i||"100%",Array.prototype.slice.call(e.querySelectorAll("svg")).filter(function(e){return e.getAttributeNS(n.namespaces.xmlns,"ct")}).forEach(function(t){e.removeChild(t)}),s=new n.Svg("svg").attr({width:t,height:i}).addClass(r),s._node.style.width=t,s._node.style.height=i,e.appendChild(s._node),s},n.normalizeData=function(e,t,i){var r,s={raw:e,normalized:{}};return s.normalized.series=n.getDataArray({series:e.series||[]},t,i),r=s.normalized.series.every(function(e){return e instanceof Array})?Math.max.apply(null,s.normalized.series.map(function(e){return e.length})):s.normalized.series.length,s.normalized.labels=(e.labels||[]).slice(),Array.prototype.push.apply(s.normalized.labels,n.times(Math.max(0,r-s.normalized.labels.length)).map(function(){return""})),t&&n.reverseData(s.normalized),s},n.safeHasProperty=function(e,t){return null!==e&&"object"==typeof e&&e.hasOwnProperty(t)},n.isDataHoleValue=function(e){return null===e||void 0===e||"number"==typeof e&&isNaN(e)},n.reverseData=function(e){e.labels.reverse(),e.series.reverse();for(var t=0;ts.high&&(s.high=n),o&&n0?s.low=0:(s.high=1,s.low=0)),s},n.isNumeric=function(e){return null!==e&&isFinite(e)},n.isFalseyButZero=function(e){return!e&&0!==e},n.getNumberOrUndefined=function(e){return n.isNumeric(e)?+e:void 0},n.isMultiValue=function(e){return"object"==typeof e&&("x"in e||"y"in e)},n.getMultiValue=function(e,t){return n.isMultiValue(e)?n.getNumberOrUndefined(e[t||"y"]):n.getNumberOrUndefined(e)},n.rho=function(e){function t(e,n){return e%n===0?n:t(n,e%n)}function n(e){return e*e+1}if(1===e)return e;var i,r=2,s=2;if(e%2===0)return 2;do r=n(r)%e,s=n(n(s))%e,i=t(Math.abs(r-s),e);while(1===i);return i},n.getBounds=function(e,t,i,r){function s(e,t){return e===(e+=t)&&(e*=1+(t>0?f:-f)),e}var a,o,l,u=0,d={high:t.high,low:t.low};d.valueRange=d.high-d.low,d.oom=n.orderOfMagnitude(d.valueRange),d.step=Math.pow(10,d.oom),d.min=Math.floor(d.low/d.step)*d.step,d.max=Math.ceil(d.high/d.step)*d.step,d.range=d.max-d.min,d.numberOfSteps=Math.round(d.range/d.step);var c=n.projectLength(e,d.step,d),h=c=i)d.step=1;else if(r&&p=i)d.step=p;else for(;;){if(h&&n.projectLength(e,d.step,d)<=i)d.step*=2;else{if(h||!(n.projectLength(e,d.step/2,d)>=i))break;if(d.step/=2,r&&d.step%1!==0){d.step*=2;break}}if(u++>1e3)throw new Error("Exceeded maximum number of iterations while optimizing scale step!")}var f=2.221e-16;for(d.step=Math.max(d.step,f),o=d.min,l=d.max;o+d.step<=d.low;)o=s(o,d.step);for(;l-d.step>=d.high;)l=s(l,-d.step);d.min=o,d.max=l,d.range=d.max-d.min;var m=[];for(a=d.min;a<=d.max;a=s(a,d.step)){var g=n.roundWithPrecision(a);g!==m[m.length-1]&&m.push(g)}return d.values=m,d},n.polarToCartesian=function(e,t,n,i){var r=(i-90)*Math.PI/180;return{x:e+n*Math.cos(r),y:t+n*Math.sin(r)}},n.createChartRect=function(e,t,i){var r=!(!t.axisX&&!t.axisY),s=r?t.axisY.offset:0,a=r?t.axisX.offset:0,o=e.width()||n.quantity(t.width).value||0,l=e.height()||n.quantity(t.height).value||0,u=n.normalizePadding(t.chartPadding,i);o=Math.max(o,s+u.left+u.right),l=Math.max(l,a+u.top+u.bottom);var d={padding:u,width:function(){return this.x2-this.x1},height:function(){return this.y1-this.y2}};return r?("start"===t.axisX.position?(d.y2=u.top+a,d.y1=Math.max(l-u.bottom,d.y2+1)):(d.y2=u.top,d.y1=Math.max(l-u.bottom-a,d.y2+1)),"start"===t.axisY.position?(d.x1=u.left+s,d.x2=Math.max(o-u.right,d.x1+1)):(d.x1=u.left,d.x2=Math.max(o-u.right-s,d.x1+1))):(d.x1=u.left,d.x2=Math.max(o-u.right,d.x1+1),d.y2=u.top,d.y1=Math.max(l-u.bottom,d.y2+1)),d},n.createGrid=function(e,t,i,r,s,a,o,l){var u={};u[i.units.pos+"1"]=e,u[i.units.pos+"2"]=e,u[i.counterUnits.pos+"1"]=r,u[i.counterUnits.pos+"2"]=r+s;var d=a.elem("line",u,o.join(" "));l.emit("draw",n.extend({type:"grid",axis:i,index:t,group:a,element:d},u))},n.createGridBackground=function(e,t,n,i){var r=e.elem("rect",{x:t.x1,y:t.y2,width:t.width(),height:t.height()},n,!0);i.emit("draw",{type:"gridBackground",group:e,element:r})},n.createLabel=function(e,i,r,s,a,o,l,u,d,c,h){var p,f={};if(f[a.units.pos]=e+l[a.units.pos],f[a.counterUnits.pos]=l[a.counterUnits.pos],f[a.units.len]=i,f[a.counterUnits.len]=Math.max(0,o-10),c){var m=t.createElement("span");m.className=d.join(" "),m.setAttribute("xmlns",n.namespaces.xhtml),m.innerText=s[r],m.style[a.units.len]=Math.round(f[a.units.len])+"px",m.style[a.counterUnits.len]=Math.round(f[a.counterUnits.len])+"px",p=u.foreignObject(m,n.extend({style:"overflow: visible;"},f))}else p=u.elem("text",f,d.join(" ")).text(s[r]);h.emit("draw",n.extend({type:"label",axis:a,index:r,group:u,element:p,text:s[r]},f))},n.getSeriesOption=function(e,t,n){if(e.name&&t.series&&t.series[e.name]){var i=t.series[e.name];return i.hasOwnProperty(n)?i[n]:t[n]}return t[n]},n.optionsProvider=function(t,i,r){function s(t){var s=o;if(o=n.extend({},u),i)for(l=0;l=2&&e[o]<=e[o-2]&&(a=!0),a&&(s.push({pathCoordinates:[],valueData:[]}),a=!1),s[s.length-1].pathCoordinates.push(e[o],e[o+1]),s[s.length-1].valueData.push(t[o/2]));return s}}(window,document,e),function(e,t,n){"use strict";n.Interpolation={},n.Interpolation.none=function(e){var t={fillHoles:!1};return e=n.extend({},t,e),function(t,i){for(var r=new n.Svg.Path,s=!0,a=0;a1){var l=[];return o.forEach(function(e){l.push(t(e.pathCoordinates,e.valueData))}),n.Svg.Path.join(l)}if(s=o[0].pathCoordinates,a=o[0].valueData,s.length<=4)return n.Interpolation.none()(s,a);for(var u,d=(new n.Svg.Path).move(s[0],s[1],!1,a[0]),c=0,h=s.length;h-2*!u>c;c+=2){var p=[{x:+s[c-2],y:+s[c-1]},{x:+s[c],y:+s[c+1]},{x:+s[c+2],y:+s[c+3]},{x:+s[c+4],y:+s[c+5]}];u?c?h-4===c?p[3]={x:+s[0],y:+s[1]}:h-2===c&&(p[2]={x:+s[0],y:+s[1]},p[3]={x:+s[2],y:+s[3]}):p[0]={x:+s[h-2],y:+s[h-1]}:h-4===c?p[3]=p[2]:c||(p[0]={x:+s[c],y:+s[c+1]}),d.curve(i*(-p[0].x+6*p[1].x+p[2].x)/6+r*p[2].x,i*(-p[0].y+6*p[1].y+p[2].y)/6+r*p[2].y,i*(p[1].x+6*p[2].x-p[3].x)/6+r*p[2].x,i*(p[1].y+6*p[2].y-p[3].y)/6+r*p[2].y,p[2].x,p[2].y,!1,a[(c+2)/2])}return d}return n.Interpolation.none()([])}},n.Interpolation.monotoneCubic=function(e){var t={fillHoles:!1};return e=n.extend({},t,e),function t(i,r){var s=n.splitIntoSegments(i,r,{fillHoles:e.fillHoles,increasingX:!0});if(s.length){if(s.length>1){var a=[];return s.forEach(function(e){a.push(t(e.pathCoordinates,e.valueData))}),n.Svg.Path.join(a)}if(i=s[0].pathCoordinates,r=s[0].valueData,i.length<=4)return n.Interpolation.none()(i,r);var o,l,u=[],d=[],c=i.length/2,h=[],p=[],f=[],m=[];for(o=0;o0!=p[o]>0?h[o]=0:(h[o]=3*(m[o-1]+m[o])/((2*m[o]+m[o-1])/p[o-1]+(m[o]+2*m[o-1])/p[o]),isFinite(h[o])||(h[o]=0));for(l=(new n.Svg.Path).move(u[0],d[0],!1,r[0]),o=0;o1}).map(function(e){var t=e.pathElements[0],n=e.pathElements[e.pathElements.length-1];return e.clone(!0).position(0).remove(1).move(t.x,_).line(t.x,t.y).position(e.pathElements.length+1).line(n.x,_)}).forEach(function(n){var o=l.elem("path",{d:n.stringify()},e.classNames.area,!0);this.eventEmitter.emit("draw",{type:"area",values:t.normalized.series[a],path:n.clone(),series:s,seriesIndex:a,axisX:i,axisY:r,chartRect:u,index:a,group:l,element:o})}.bind(this))}}.bind(this)),this.eventEmitter.emit("created",{bounds:r.bounds,chartRect:u,axisX:i,axisY:r,svg:this.svg,options:e})}function r(e,t,i,r){n.Line.super.constructor.call(this,e,t,s,n.extend({},s,i),r)}var s={axisX:{offset:30,position:"end",labelOffset:{x:0,y:0},showLabel:!0,showGrid:!0,labelInterpolationFnc:n.noop,type:void 0},axisY:{offset:40,position:"start",labelOffset:{x:0,y:0},showLabel:!0,showGrid:!0,labelInterpolationFnc:n.noop,type:void 0,scaleMinSpace:20,onlyInteger:!1},width:void 0,height:void 0,showLine:!0,showPoint:!0,showArea:!1,areaBase:0,lineSmooth:!0,showGridBackground:!1,low:void 0,high:void 0,chartPadding:{top:15,right:15,bottom:5,left:10},fullWidth:!1,reverseData:!1,classNames:{chart:"ct-chart-line",label:"ct-label",labelGroup:"ct-labels",series:"ct-series",line:"ct-line",point:"ct-point",area:"ct-area",grid:"ct-grid",gridGroup:"ct-grids",gridBackground:"ct-grid-background",vertical:"ct-vertical",horizontal:"ct-horizontal",start:"ct-start",end:"ct-end"}};n.Line=n.Base.extend({constructor:r,createChart:i})}(window,document,e),function(e,t,n){"use strict";function i(e){var t,i;e.distributeSeries?(t=n.normalizeData(this.data,e.reverseData,e.horizontalBars?"x":"y"),t.normalized.series=t.normalized.series.map(function(e){return[e]})):t=n.normalizeData(this.data,e.reverseData,e.horizontalBars?"x":"y"),this.svg=n.createSvg(this.container,e.width,e.height,e.classNames.chart+(e.horizontalBars?" "+e.classNames.horizontalBars:""));var r=this.svg.elem("g").addClass(e.classNames.gridGroup),a=this.svg.elem("g"),o=this.svg.elem("g").addClass(e.classNames.labelGroup);if(e.stackBars&&0!==t.normalized.series.length){var l=n.serialMap(t.normalized.series,function(){return Array.prototype.slice.call(arguments).map(function(e){return e}).reduce(function(e,t){return{x:e.x+(t&&t.x)||0,y:e.y+(t&&t.y)||0}},{x:0,y:0})});i=n.getHighLow([l],e,e.horizontalBars?"x":"y")}else i=n.getHighLow(t.normalized.series,e,e.horizontalBars?"x":"y");i.high=+e.high||(0===e.high?0:i.high),i.low=+e.low||(0===e.low?0:i.low);var u,d,c,h,p,f=n.createChartRect(this.svg,e,s.padding);d=e.distributeSeries&&e.stackBars?t.normalized.labels.slice(0,1):t.normalized.labels,e.horizontalBars?(u=h=void 0===e.axisX.type?new n.AutoScaleAxis(n.Axis.units.x,t.normalized.series,f,n.extend({},e.axisX,{highLow:i,referenceValue:0})):e.axisX.type.call(n,n.Axis.units.x,t.normalized.series,f,n.extend({},e.axisX,{highLow:i,referenceValue:0})),c=p=void 0===e.axisY.type?new n.StepAxis(n.Axis.units.y,t.normalized.series,f,{ticks:d}):e.axisY.type.call(n,n.Axis.units.y,t.normalized.series,f,e.axisY)):(c=h=void 0===e.axisX.type?new n.StepAxis(n.Axis.units.x,t.normalized.series,f,{ticks:d}):e.axisX.type.call(n,n.Axis.units.x,t.normalized.series,f,e.axisX),u=p=void 0===e.axisY.type?new n.AutoScaleAxis(n.Axis.units.y,t.normalized.series,f,n.extend({},e.axisY,{highLow:i,referenceValue:0})):e.axisY.type.call(n,n.Axis.units.y,t.normalized.series,f,n.extend({},e.axisY,{highLow:i,referenceValue:0})));var m=e.horizontalBars?f.x1+u.projectValue(0):f.y1-u.projectValue(0),g=[];c.createGridAndLabels(r,o,this.supportsForeignObject,e,this.eventEmitter),u.createGridAndLabels(r,o,this.supportsForeignObject,e,this.eventEmitter),e.showGridBackground&&n.createGridBackground(r,f,e.classNames.gridBackground,this.eventEmitter),t.raw.series.forEach(function(i,r){var s,o,l=r-(t.raw.series.length-1)/2;s=e.distributeSeries&&!e.stackBars?c.axisLength/t.normalized.series.length/2:e.distributeSeries&&e.stackBars?c.axisLength/2:c.axisLength/t.normalized.series[r].length/2,o=a.elem("g"),o.attr({"ct:series-name":i.name,"ct:meta":n.serialize(i.meta)}),o.addClass([e.classNames.series,i.className||e.classNames.series+"-"+n.alphaNumerate(r)].join(" ")),t.normalized.series[r].forEach(function(a,d){var _,v,y,M;if(M=e.distributeSeries&&!e.stackBars?r:e.distributeSeries&&e.stackBars?0:d,_=e.horizontalBars?{x:f.x1+u.projectValue(a&&a.x?a.x:0,d,t.normalized.series[r]),y:f.y1-c.projectValue(a&&a.y?a.y:0,M,t.normalized.series[r])}:{x:f.x1+c.projectValue(a&&a.x?a.x:0,M,t.normalized.series[r]),y:f.y1-u.projectValue(a&&a.y?a.y:0,d,t.normalized.series[r])},c instanceof n.StepAxis&&(c.options.stretch||(_[c.units.pos]+=s*(e.horizontalBars?-1:1)),_[c.units.pos]+=e.stackBars||e.distributeSeries?0:l*e.seriesBarDistance*(e.horizontalBars?-1:1)),y=g[d]||m,g[d]=y-(m-_[c.counterUnits.pos]),void 0!==a){var D={};D[c.units.pos+"1"]=_[c.units.pos],D[c.units.pos+"2"]=_[c.units.pos],!e.stackBars||"accumulate"!==e.stackMode&&e.stackMode?(D[c.counterUnits.pos+"1"]=m,D[c.counterUnits.pos+"2"]=_[c.counterUnits.pos]):(D[c.counterUnits.pos+"1"]=y,D[c.counterUnits.pos+"2"]=g[d]),D.x1=Math.min(Math.max(D.x1,f.x1),f.x2),D.x2=Math.min(Math.max(D.x2,f.x1),f.x2),D.y1=Math.min(Math.max(D.y1,f.y2),f.y1),D.y2=Math.min(Math.max(D.y2,f.y2),f.y1);var w=n.getMetaData(i,d);v=o.elem("line",D,e.classNames.bar).attr({"ct:value":[a.x,a.y].filter(n.isNumeric).join(","),"ct:meta":n.serialize(w)}),this.eventEmitter.emit("draw",n.extend({type:"bar",value:a,index:d,meta:w,series:i,seriesIndex:r,axisX:h,axisY:p,chartRect:f,group:o,element:v},D))}}.bind(this))}.bind(this)),this.eventEmitter.emit("created",{bounds:u.bounds,chartRect:f,axisX:h,axisY:p,svg:this.svg,options:e})}function r(e,t,i,r){n.Bar.super.constructor.call(this,e,t,s,n.extend({},s,i),r)}var s={axisX:{offset:30,position:"end",labelOffset:{x:0,y:0},showLabel:!0,showGrid:!0,labelInterpolationFnc:n.noop,scaleMinSpace:30,onlyInteger:!1},axisY:{offset:40,position:"start",labelOffset:{x:0,y:0},showLabel:!0,showGrid:!0,labelInterpolationFnc:n.noop,scaleMinSpace:20,onlyInteger:!1},width:void 0,height:void 0,high:void 0,low:void 0,referenceValue:0,chartPadding:{top:15,right:15,bottom:5,left:10},seriesBarDistance:15,stackBars:!1,stackMode:"accumulate",horizontalBars:!1,distributeSeries:!1,reverseData:!1,showGridBackground:!1,classNames:{chart:"ct-chart-bar",horizontalBars:"ct-horizontal-bars",label:"ct-label",labelGroup:"ct-labels",series:"ct-series",bar:"ct-bar",grid:"ct-grid",gridGroup:"ct-grids",gridBackground:"ct-grid-background",vertical:"ct-vertical",horizontal:"ct-horizontal",start:"ct-start",end:"ct-end"}};n.Bar=n.Base.extend({constructor:r,createChart:i})}(window,document,e),function(e,t,n){"use strict";function i(e,t,n){var i=t.x>e.x;return i&&"explode"===n||!i&&"implode"===n?"start":i&&"implode"===n||!i&&"explode"===n?"end":"middle"}function r(e){var t,r,s,o,l,u=n.normalizeData(this.data),d=[],c=e.startAngle;this.svg=n.createSvg(this.container,e.width,e.height,e.donut?e.classNames.chartDonut:e.classNames.chartPie),r=n.createChartRect(this.svg,e,a.padding),s=Math.min(r.width()/2,r.height()/2),l=e.total||u.normalized.series.reduce(function(e,t){return e+t},0);var h=n.quantity(e.donutWidth);"%"===h.unit&&(h.value*=s/100),s-=e.donut&&!e.donutSolid?h.value/2:0,o="outside"===e.labelPosition||e.donut&&!e.donutSolid?s:"center"===e.labelPosition?0:e.donutSolid?s-h.value/2:s/2,o+=e.labelOffset;var p={x:r.x1+r.width()/2,y:r.y2+r.height()/2},f=1===u.raw.series.filter(function(e){return e.hasOwnProperty("value")?0!==e.value:0!==e}).length;u.raw.series.forEach(function(e,t){d[t]=this.svg.elem("g",null,null)}.bind(this)),e.showLabel&&(t=this.svg.elem("g",null,null)),u.raw.series.forEach(function(r,a){if(0!==u.normalized.series[a]||!e.ignoreEmptyValues){d[a].attr({"ct:series-name":r.name}),d[a].addClass([e.classNames.series,r.className||e.classNames.series+"-"+n.alphaNumerate(a)].join(" "));var m=l>0?c+u.normalized.series[a]/l*360:0,g=Math.max(0,c-(0===a||f?0:.2));m-g>=359.99&&(m=g+359.99);var _,v,y,M=n.polarToCartesian(p.x,p.y,s,g),D=n.polarToCartesian(p.x,p.y,s,m),w=new n.Svg.Path(!e.donut||e.donutSolid).move(D.x,D.y).arc(s,s,0,m-c>180,0,M.x,M.y);e.donut?e.donutSolid&&(y=s-h.value,_=n.polarToCartesian(p.x,p.y,y,c-(0===a||f?0:.2)),v=n.polarToCartesian(p.x,p.y,y,m),w.line(_.x,_.y),w.arc(y,y,0,m-c>180,1,v.x,v.y)):w.line(p.x,p.y);var x=e.classNames.slicePie;e.donut&&(x=e.classNames.sliceDonut,e.donutSolid&&(x=e.classNames.sliceDonutSolid));var b=d[a].elem("path",{d:w.stringify()},x);if(b.attr({"ct:value":u.normalized.series[a],"ct:meta":n.serialize(r.meta)}),e.donut&&!e.donutSolid&&(b._node.style.strokeWidth=h.value+"px"),this.eventEmitter.emit("draw",{type:"slice",value:u.normalized.series[a],totalDataSum:l,index:a,meta:r.meta,series:r,group:d[a],element:b,path:w.clone(),center:p,radius:s,startAngle:c,endAngle:m}),e.showLabel){var k;k=1===u.raw.series.length?{x:p.x,y:p.y}:n.polarToCartesian(p.x,p.y,o,c+(m-c)/2);var L;L=u.normalized.labels&&!n.isFalseyButZero(u.normalized.labels[a])?u.normalized.labels[a]:u.normalized.series[a];var S=e.labelInterpolationFnc(L,a);if(S||0===S){var T=t.elem("text",{dx:k.x,dy:k.y,"text-anchor":i(p,k,e.labelDirection)},e.classNames.label).text(""+S);this.eventEmitter.emit("draw",{type:"label",index:a,group:t,element:T,text:""+S,x:k.x,y:k.y})}}c=m}}.bind(this)),this.eventEmitter.emit("created",{chartRect:r,svg:this.svg,options:e})}function s(e,t,i,r){n.Pie.super.constructor.call(this,e,t,a,n.extend({},a,i),r)}var a={width:void 0,height:void 0,chartPadding:5,classNames:{chartPie:"ct-chart-pie",chartDonut:"ct-chart-donut",series:"ct-series",slicePie:"ct-slice-pie",sliceDonut:"ct-slice-donut",sliceDonutSolid:"ct-slice-donut-solid",label:"ct-label"},startAngle:0,total:void 0,donut:!1,donutSolid:!1,donutWidth:60,showLabel:!0,labelOffset:0,labelPosition:"inside",labelInterpolationFnc:n.noop,labelDirection:"neutral",reverseData:!1,ignoreEmptyValues:!1};n.Pie=n.Base.extend({constructor:s,createChart:r,determineAnchorPosition:i})}(window,document,e),e})},,,,function(e,t,n){var i,r;/**!
+ * Sortable
+ * @author RubaXa
+ * @license MIT
+ */
+!function(s){"use strict";i=s,r="function"==typeof i?i.call(t,n,t,e):i,!(void 0!==r&&(e.exports=r))}(function(){"use strict";function e(t,n){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be HTMLElement, and not "+{}.toString.call(t);this.el=t,this.options=n=y({},n),t[G]=this;var i={group:Math.random(),sort:!0,disabled:!1,store:null,handle:null,scroll:!0,scrollSensitivity:30,scrollSpeed:10,draggable:/[uo]l/i.test(t.nodeName)?"li":">*",ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,setData:function(e,t){e.setData("Text",t.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:e.supportPointer!==!1};for(var r in i)!(r in n)&&(n[r]=i[r]);de(n);for(var a in this)"_"===a.charAt(0)&&"function"==typeof this[a]&&(this[a]=this[a].bind(this));this.nativeDraggable=!n.forceFallback&&ne,s(t,"mousedown",this._onTapStart),s(t,"touchstart",this._onTapStart),n.supportPointer&&s(t,"pointerdown",this._onTapStart),this.nativeDraggable&&(s(t,"dragover",this),s(t,"dragenter",this)),le.push(this._onDragOver),n.store&&this.sort(n.store.get(this))}function t(e,t){"clone"!==e.lastPullMode&&(t=!0),S&&S.state!==t&&(l(S,"display",t?"none":""),t||S.state&&(e.options.group.revertClone?(T.insertBefore(S,C),e._animate(b,S)):T.insertBefore(S,b)),S.state=t)}function n(e,t,n){if(e){n=n||V;do if(">*"===t&&e.parentNode===n||_(e,t))return e;while(e=i(e))}return null}function i(e){var t=e.host;return t&&t.nodeType?t:e.parentNode}function r(e){e.dataTransfer&&(e.dataTransfer.dropEffect="move"),e.preventDefault()}function s(e,t,n){e.addEventListener(t,n,ee)}function a(e,t,n){e.removeEventListener(t,n,ee)}function o(e,t,n){if(e)if(e.classList)e.classList[n?"add":"remove"](t);else{var i=(" "+e.className+" ").replace($," ").replace(" "+t+" "," ");e.className=(i+(n?" "+t:"")).replace($," ")}}function l(e,t,n){var i=e&&e.style;if(i){if(void 0===n)return V.defaultView&&V.defaultView.getComputedStyle?n=V.defaultView.getComputedStyle(e,""):e.currentStyle&&(n=e.currentStyle),void 0===t?n:n[t];t in i||(t="-webkit-"+t),i[t]=n+("string"==typeof n?"":"px")}}function u(e,t,n){if(e){var i=e.getElementsByTagName(t),r=0,s=i.length;if(n)for(;r5||t.clientX-(i.left+i.width)>5}function m(e){for(var t=e.tagName+e.className+e.src+e.href+e.textContent,n=t.length,i=0;n--;)i+=t.charCodeAt(n);return i.toString(36)}function g(e,t){var n=0;if(!e||!e.parentNode)return-1;for(;e&&(e=e.previousElementSibling);)"TEMPLATE"===e.nodeName.toUpperCase()||">*"!==t&&!_(e,t)||n++;return n}function _(e,t){if(e){t=t.split(".");var n=t.shift().toUpperCase(),i=new RegExp("\\s("+t.join("|")+")(?=\\s)","g");return!(""!==n&&e.nodeName.toUpperCase()!=n||t.length&&((" "+e.className+" ").match(i)||[]).length!=t.length)}return!1}function v(e,t){var n,i;return function(){void 0===n&&(n=arguments,i=this,q(function(){1===n.length?e.call(i,n[0]):e.apply(i,n),n=void 0},t))}}function y(e,t){if(e&&t)for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}function M(e){return Q&&Q.dom?Q.dom(e).cloneNode(!0):Z?Z(e).clone(!0)[0]:e.cloneNode(!0)}function D(e){for(var t=e.getElementsByTagName("input"),n=t.length;n--;){var i=t[n];i.checked&&oe.push(i)}}function w(e){return q(e,0)}function x(e){return clearTimeout(e)}if("undefined"==typeof window||!window.document)return function(){throw new Error("Sortable.js requires a window with a document")};var b,k,L,S,T,C,Y,E,A,F,O,H,P,j,N,I,B,z,W,R,U={},$=/\s+/g,J=/left|right|inline/,G="Sortable"+(new Date).getTime(),X=window,V=X.document,K=X.parseInt,q=X.setTimeout,Z=X.jQuery||X.Zepto,Q=X.Polymer,ee=!1,te=!1,ne="draggable"in V.createElement("div"),ie=function(e){return!navigator.userAgent.match(/(?:Trident.*rv[ :]?11\.|msie)/i)&&(e=V.createElement("x"),e.style.cssText="pointer-events:auto","auto"===e.style.pointerEvents)}(),re=!1,se=Math.abs,ae=Math.min,oe=[],le=[],ue=v(function(e,t,n){if(n&&t.scroll){var i,r,s,a,o,l,u=n[G],d=t.scrollSensitivity,c=t.scrollSpeed,h=e.clientX,p=e.clientY,f=window.innerWidth,m=window.innerHeight;if(A!==n&&(E=t.scroll,A=n,F=t.scrollFn,E===!0)){E=n;do if(E.offsetWidth-1:r==e)}}var n={},i=e.group;i&&"object"==typeof i||(i={name:i}),n.name=i.name,n.checkPull=t(i.pull,!0),n.checkPut=t(i.put),n.revertClone=i.revertClone,e.group=n};try{window.addEventListener("test",null,Object.defineProperty({},"passive",{get:function(){te=!1,ee={capture:!1,passive:te}}}))}catch(e){}return e.prototype={constructor:e,_onTapStart:function(e){var t,i=this,r=this.el,s=this.options,a=s.preventOnFilter,o=e.type,l=e.touches&&e.touches[0],u=(l||e).target,c=e.target.shadowRoot&&e.path&&e.path[0]||u,h=s.filter;if(D(r),!b&&!(/mousedown|pointerdown/.test(o)&&0!==e.button||s.disabled)&&!c.isContentEditable&&(u=n(u,s.draggable,r),u&&Y!==u)){if(t=g(u,s.draggable),"function"==typeof h){if(h.call(this,e,u,this))return d(i,c,"filter",u,r,r,t),void(a&&e.preventDefault())}else if(h&&(h=h.split(",").some(function(e){if(e=n(c,e.trim(),r))return d(i,e,"filter",u,r,r,t),!0})))return void(a&&e.preventDefault());s.handle&&!n(c,s.handle,r)||this._prepareDragStart(e,l,u,t)}},_prepareDragStart:function(e,t,n,i){var r,a=this,l=a.el,c=a.options,p=l.ownerDocument;n&&!b&&n.parentNode===l&&(z=e,T=l,b=n,k=b.parentNode,C=b.nextSibling,Y=n,I=c.group,j=i,this._lastX=(t||e).clientX,this._lastY=(t||e).clientY,b.style["will-change"]="all",r=function(){a._disableDelayedDrag(),b.draggable=a.nativeDraggable,o(b,c.chosenClass,!0),a._triggerDragStart(e,t),d(a,T,"choose",b,T,T,j)},c.ignore.split(",").forEach(function(e){u(b,e.trim(),h)}),s(p,"mouseup",a._onDrop),s(p,"touchend",a._onDrop),s(p,"touchcancel",a._onDrop),s(p,"selectstart",a),c.supportPointer&&s(p,"pointercancel",a._onDrop),c.delay?(s(p,"mouseup",a._disableDelayedDrag),s(p,"touchend",a._disableDelayedDrag),s(p,"touchcancel",a._disableDelayedDrag),s(p,"mousemove",a._disableDelayedDrag),s(p,"touchmove",a._disableDelayedDrag),c.supportPointer&&s(p,"pointermove",a._disableDelayedDrag),a._dragStartTimer=q(r,c.delay)):r())},_disableDelayedDrag:function(){var e=this.el.ownerDocument;clearTimeout(this._dragStartTimer),a(e,"mouseup",this._disableDelayedDrag),a(e,"touchend",this._disableDelayedDrag),a(e,"touchcancel",this._disableDelayedDrag),a(e,"mousemove",this._disableDelayedDrag),a(e,"touchmove",this._disableDelayedDrag),a(e,"pointermove",this._disableDelayedDrag)},_triggerDragStart:function(e,t){t=t||("touch"==e.pointerType?e:null),t?(z={target:b,clientX:t.clientX,clientY:t.clientY},this._onDragStart(z,"touch")):this.nativeDraggable?(s(b,"dragend",this),s(T,"dragstart",this._onDragStart)):this._onDragStart(z,!0);try{V.selection?w(function(){V.selection.empty()}):window.getSelection().removeAllRanges()}catch(e){}},_dragStarted:function(){if(T&&b){var t=this.options;o(b,t.ghostClass,!0),o(b,t.dragClass,!1),e.active=this,d(this,T,"start",b,T,T,j)}else this._nulling()},_emulateDragOver:function(){if(W){if(this._lastX===W.clientX&&this._lastY===W.clientY)return;this._lastX=W.clientX,this._lastY=W.clientY,ie||l(L,"display","none");var e=V.elementFromPoint(W.clientX,W.clientY),t=e,n=le.length;if(e&&e.shadowRoot&&(e=e.shadowRoot.elementFromPoint(W.clientX,W.clientY),t=e),t)do{if(t[G]){for(;n--;)le[n]({clientX:W.clientX,clientY:W.clientY,target:e,rootEl:t});break}e=t}while(t=t.parentNode);ie||l(L,"display","")}},_onTouchMove:function(t){if(z){var n=this.options,i=n.fallbackTolerance,r=n.fallbackOffset,s=t.touches?t.touches[0]:t,a=s.clientX-z.clientX+r.x,o=s.clientY-z.clientY+r.y,u=t.touches?"translate3d("+a+"px,"+o+"px,0)":"translate("+a+"px,"+o+"px)";if(!e.active){if(i&&ae(se(s.clientX-this._lastX),se(s.clientY-this._lastY))b.offsetWidth,x=r.offsetHeight>b.offsetHeight,Y=(D?(i.clientX-a.left)/y:(i.clientY-a.top)/M)>.5,E=r.nextElementSibling,A=!1;if(D){var F=b.offsetTop,j=r.offsetTop;A=F===j?r.previousElementSibling===b&&!w||Y&&w:r.previousElementSibling===b||b.previousElementSibling===r?(i.clientY-a.top)/M>.5:j>F}else _||(A=E!==b&&!x||Y&&x);var N=c(T,u,b,s,r,a,i,A);N!==!1&&(1!==N&&N!==-1||(A=1===N),re=!0,q(p,30),t(m,g),b.contains(u)||(A&&!E?u.appendChild(b):r.parentNode.insertBefore(b,A?E:r)),k=b.parentNode,this._animate(s,b),this._animate(a,r))}}},_animate:function(e,t){var n=this.options.animation;if(n){var i=t.getBoundingClientRect();1===e.nodeType&&(e=e.getBoundingClientRect()),l(t,"transition","none"),l(t,"transform","translate3d("+(e.left-i.left)+"px,"+(e.top-i.top)+"px,0)"),t.offsetWidth,l(t,"transition","all "+n+"ms"),l(t,"transform","translate3d(0,0,0)"),clearTimeout(t.animated),t.animated=q(function(){l(t,"transition",""),l(t,"transform",""),t.animated=!1},n)}},_offUpEvents:function(){var e=this.el.ownerDocument;a(V,"touchmove",this._onTouchMove),a(V,"pointermove",this._onTouchMove),a(e,"mouseup",this._onDrop),a(e,"touchend",this._onDrop),a(e,"pointerup",this._onDrop),a(e,"touchcancel",this._onDrop),a(e,"pointercancel",this._onDrop),a(e,"selectstart",this)},_onDrop:function(t){var n=this.el,i=this.options;clearInterval(this._loopId),clearInterval(U.pid),clearTimeout(this._dragStartTimer),x(this._cloneId),x(this._dragStartId),a(V,"mouseover",this),a(V,"mousemove",this._onTouchMove),this.nativeDraggable&&(a(V,"drop",this),a(n,"dragstart",this._onDragStart)),this._offUpEvents(),t&&(R&&(t.preventDefault(),!i.dropBubble&&t.stopPropagation()),L&&L.parentNode&&L.parentNode.removeChild(L),T!==k&&"clone"===e.active.lastPullMode||S&&S.parentNode&&S.parentNode.removeChild(S),b&&(this.nativeDraggable&&a(b,"dragend",this),h(b),b.style["will-change"]="",o(b,this.options.ghostClass,!1),o(b,this.options.chosenClass,!1),d(this,T,"unchoose",b,k,T,j),T!==k?(N=g(b,i.draggable),N>=0&&(d(null,k,"add",b,k,T,j,N),d(this,T,"remove",b,k,T,j,N),d(null,k,"sort",b,k,T,j,N),d(this,T,"sort",b,k,T,j,N))):b.nextSibling!==C&&(N=g(b,i.draggable),N>=0&&(d(this,T,"update",b,k,T,j,N),d(this,T,"sort",b,k,T,j,N))),e.active&&(null!=N&&N!==-1||(N=j),d(this,T,"end",b,k,T,j,N),this.save()))),this._nulling()},_nulling:function(){T=b=k=L=C=S=Y=E=A=z=W=R=N=O=H=B=I=e.active=null,oe.forEach(function(e){e.checked=!0}),oe.length=0},handleEvent:function(e){switch(e.type){case"drop":case"dragend":this._onDrop(e);break;case"dragover":case"dragenter":b&&(this._onDragOver(e),r(e));break;case"mouseover":this._onDrop(e);break;case"selectstart":e.preventDefault()}},toArray:function(){for(var e,t=[],i=this.el.children,r=0,s=i.length,a=this.options;r=0&&e.data.length>0){var s=e.data.match(n),a=document.createElement("span");a.className="highlight";var o=e.splitText(r),l=(o.splitText(s[0].length),o.cloneNode(!0));a.appendChild(l),o.parentNode.replaceChild(a,o),t=1}}else if(1===e.nodeType&&e.childNodes&&!/(script|style)/i.test(e.tagName)&&("highlight"!==e.className||"SPAN"!==e.tagName))for(var u=0;u /g,">").replace(/"/g,""")},S={};S.before=function(e,t,n){var i=e[t];e[t]=function(){return n.apply(e,arguments),i.apply(e,arguments)}},S.after=function(e,t,n){var i=e[t];e[t]=function(){var t=i.apply(e,arguments);return n.apply(e,arguments),t}};var T=function(e){var t=!1;return function(){t||(t=!0,e.apply(this,arguments))}},C=function(e,t){var n;return function(){var i=this,r=arguments;window.clearTimeout(n),n=window.setTimeout(function(){e.apply(i,r)},t)}},Y=function(e,t,n){var i,r=e.trigger,s={};e.trigger=function(){var n=arguments[0];return t.indexOf(n)===-1?r.apply(e,arguments):void(s[n]=arguments)},n.apply(e,[]),e.trigger=r;for(i in s)s.hasOwnProperty(i)&&r.apply(e,s[i])},E=function(e,t,n,i){e.on(t,n,function(t){for(var n=t.target;n&&n.parentNode!==e[0];)n=n.parentNode;return t.currentTarget=n,i.apply(this,[t])})},A=function(e){var t={};if("selectionStart"in e)t.start=e.selectionStart,t.length=e.selectionEnd-t.start;else if(document.selection){e.focus();var n=document.selection.createRange(),i=document.selection.createRange().text.length;n.moveStart("character",-e.value.length),t.start=n.text.length-i,t.length=i}return t},F=function(e,t,n){var i,r,s={};if(n)for(i=0,r=n.length;i ").css({position:"absolute",top:-99999,left:-99999,width:"auto",padding:0,whiteSpace:"pre"}).appendTo("body")),N.$testInput.text(t),F(n,N.$testInput,["letterSpacing","fontSize","fontFamily","fontWeight","textTransform"]),N.$testInput.width()):0},H=function(e){var t=null,n=function(n,i){var r,s,a,o,l,u,d,c;n=n||window.event||{},i=i||{},n.metaKey||n.altKey||(i.force||e.data("grow")!==!1)&&(r=e.val(),n.type&&"keydown"===n.type.toLowerCase()&&(s=n.keyCode,a=s>=48&&s<=57||s>=65&&s<=90||s>=96&&s<=111||s>=186&&s<=222||32===s,s===g||s===m?(c=A(e[0]),c.length?r=r.substring(0,c.start)+r.substring(c.start+c.length):s===m&&c.start?r=r.substring(0,c.start-1)+r.substring(c.start+1):s===g&&"undefined"!=typeof c.start&&(r=r.substring(0,c.start)+r.substring(c.start+1))):a&&(u=n.shiftKey,d=String.fromCharCode(n.keyCode),d=u?d.toUpperCase():d.toLowerCase(),r+=d)),o=e.attr("placeholder"),!r&&o&&(r=o),l=O(r,e)+4,l!==t&&(t=l,e.width(l),e.triggerHandler("resize")))};e.on("keydown keyup update blur",n),n()},P=function(e){var t=document.createElement("div");return t.appendChild(e.cloneNode(!0)),t.innerHTML},j=function(e,t){t||(t={});var n="Selectize";console.error(n+": "+e),t.explanation&&(console.group&&console.group(),console.error(t.explanation),console.group&&console.groupEnd())},N=function(n,i){var r,s,a,o,l=this;o=n[0],o.selectize=l;var u=window.getComputedStyle&&window.getComputedStyle(o,null);if(a=u?u.getPropertyValue("direction"):o.currentStyle&&o.currentStyle.direction,a=a||n.parents("[dir]:first").attr("dir")||"",e.extend(l,{order:0,settings:i,$input:n,tabIndex:n.attr("tabindex")||"",tagType:"select"===o.tagName.toLowerCase()?D:w,rtl:/rtl/i.test(a),eventNS:".selectize"+ ++N.count,highlightedValue:null,isBlurring:!1,isOpen:!1,isDisabled:!1,isRequired:n.is("[required]"),isInvalid:!1,isLocked:!1,isFocused:!1,isInputHidden:!1,isSetup:!1,isShiftDown:!1,isCmdDown:!1,isCtrlDown:!1,ignoreFocus:!1,ignoreBlur:!1,ignoreHover:!1,hasOptions:!1,currentResults:null,lastValue:"",caretPos:0,loading:0,loadedSearches:{},$activeOption:null,$activeItems:[],optgroups:{},options:{},userOptions:{},items:[],renderCache:{},onSearchChange:null===i.loadThrottle?l.onSearchChange:C(l.onSearchChange,i.loadThrottle)}),l.sifter=new t(this.options,{diacritics:i.diacritics}),l.settings.options){for(r=0,s=l.settings.options.length;r").addClass(p.wrapperClass).addClass(u).addClass(l),n=e("").addClass(p.inputClass).addClass("items").appendTo(t),i=e('
').appendTo(n).attr("tabindex",M.is(":disabled")?"-1":h.tabIndex),o=e(p.dropdownParent||t),r=e("
").addClass(p.dropdownClass).addClass(l).hide().appendTo(o),a=e("
").addClass(p.dropdownContentClass).appendTo(r),(c=M.attr("id"))&&(i.attr("id",c+"-selectized"),e("label[for='"+c+"']").attr("for",c+"-selectized")),h.settings.copyClassesToDropdown&&r.addClass(u),t.css({width:M[0].style.width}),h.plugins.names.length&&(d="plugin-"+h.plugins.names.join(" plugin-"),t.addClass(d),r.addClass(d)),(null===p.maxItems||p.maxItems>1)&&h.tagType===D&&M.attr("multiple","multiple"),h.settings.placeholder&&i.attr("placeholder",p.placeholder),!h.settings.splitOn&&h.settings.delimiter){var w=h.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&");h.settings.splitOn=new RegExp("\\s*"+w+"+\\s*")}M.attr("autocorrect")&&i.attr("autocorrect",M.attr("autocorrect")),M.attr("autocapitalize")&&i.attr("autocapitalize",M.attr("autocapitalize")),i[0].type=M[0].type,h.$wrapper=t,h.$control=n,h.$control_input=i,h.$dropdown=r,h.$dropdown_content=a,r.on("mouseenter mousedown click","[data-disabled]>[data-selectable]",function(e){e.stopImmediatePropagation()}),r.on("mouseenter","[data-selectable]",function(){return h.onOptionHover.apply(h,arguments)}),r.on("mousedown click","[data-selectable]",function(){return h.onOptionSelect.apply(h,arguments)}),E(n,"mousedown","*:not(input)",function(){return h.onItemSelect.apply(h,arguments)}),H(i),n.on({mousedown:function(){return h.onMouseDown.apply(h,arguments)},click:function(){return h.onClick.apply(h,arguments)}}),i.on({mousedown:function(e){e.stopPropagation()},keydown:function(){return h.onKeyDown.apply(h,arguments)},keyup:function(){return h.onKeyUp.apply(h,arguments)},keypress:function(){return h.onKeyPress.apply(h,arguments)},resize:function(){h.positionDropdown.apply(h,[])},blur:function(){return h.onBlur.apply(h,arguments)},focus:function(){return h.ignoreBlur=!1,h.onFocus.apply(h,arguments)},paste:function(){return h.onPaste.apply(h,arguments)}}),g.on("keydown"+f,function(e){h.isCmdDown=e[s?"metaKey":"ctrlKey"],h.isCtrlDown=e[s?"altKey":"ctrlKey"],h.isShiftDown=e.shiftKey}),g.on("keyup"+f,function(e){e.keyCode===y&&(h.isCtrlDown=!1),e.keyCode===_&&(h.isShiftDown=!1),e.keyCode===v&&(h.isCmdDown=!1)}),g.on("mousedown"+f,function(e){if(h.isFocused){if(e.target===h.$dropdown[0]||e.target.parentNode===h.$dropdown[0])return!1;h.$control.has(e.target).length||e.target===h.$control[0]||h.blur(e.target)}}),m.on(["scroll"+f,"resize"+f].join(" "),function(){h.isOpen&&h.positionDropdown.apply(h,arguments)}),m.on("mousemove"+f,function(){h.ignoreHover=!1}),this.revertSettings={$children:M.children().detach(),tabindex:M.attr("tabindex")},M.attr("tabindex",-1).hide().after(h.$wrapper),e.isArray(p.items)&&(h.setValue(p.items),delete p.items),x&&M.on("invalid"+f,function(e){e.preventDefault(),h.isInvalid=!0,h.refreshState()}),h.updateOriginalInput(),h.refreshItems(),h.refreshState(),h.updatePlaceholder(),h.isSetup=!0,M.is(":disabled")&&h.disable(),h.on("change",this.onChange),M.data("selectize",h),M.addClass("selectized"),h.trigger("initialize"),p.preload===!0&&h.onSearchChange("")},setupTemplates:function(){var t=this,n=t.settings.labelField,i=t.settings.optgroupLabelField,r={optgroup:function(e){return'
'+e.html+"
"},optgroup_header:function(e,t){return'"},option:function(e,t){return'
'+t(e[n])+"
"},item:function(e,t){return'
'+t(e[n])+"
"},option_create:function(e,t){return'
Add '+t(e.input)+" …
"}};t.settings.render=e.extend({},r,t.settings.render)},setupCallbacks:function(){var e,t,n={initialize:"onInitialize",change:"onChange",item_add:"onItemAdd",item_remove:"onItemRemove",clear:"onClear",option_add:"onOptionAdd",option_remove:"onOptionRemove",option_clear:"onOptionClear",optgroup_add:"onOptionGroupAdd",optgroup_remove:"onOptionGroupRemove",optgroup_clear:"onOptionGroupClear",dropdown_open:"onDropdownOpen",dropdown_close:"onDropdownClose",type:"onType",load:"onLoad",focus:"onFocus",blur:"onBlur"};for(e in n)n.hasOwnProperty(e)&&(t=this.settings[n[e]],t&&this.on(e,t))},onClick:function(e){var t=this;t.isFocused&&t.isOpen||(t.focus(),e.preventDefault())},onMouseDown:function(t){var n=this,i=t.isDefaultPrevented();e(t.target);if(n.isFocused){if(t.target!==n.$control_input[0])return"single"===n.settings.mode?n.isOpen?n.close():n.open():i||n.setActiveItem(null),!1}else i||window.setTimeout(function(){n.focus()},0)},onChange:function(){this.$input.trigger("change")},onPaste:function(t){var n=this;return n.isFull()||n.isInputHidden||n.isLocked?void t.preventDefault():void(n.settings.splitOn&&setTimeout(function(){var t=n.$control_input.val();if(t.match(n.settings.splitOn))for(var i=e.trim(t).split(n.settings.splitOn),r=0,s=i.length;r
o&&(u=a,a=o,o=u),r=a;r<=o;r++)l=c.$control[0].childNodes[r],c.$activeItems.indexOf(l)===-1&&(e(l).addClass("active"),c.$activeItems.push(l));
+n.preventDefault()}else"mousedown"===i&&c.isCtrlDown||"keydown"===i&&this.isShiftDown?t.hasClass("active")?(s=c.$activeItems.indexOf(t[0]),c.$activeItems.splice(s,1),t.removeClass("active")):c.$activeItems.push(t.addClass("active")[0]):(e(c.$activeItems).removeClass("active"),c.$activeItems=[t.addClass("active")[0]]);c.hideInput(),this.isFocused||c.focus()}},setActiveOption:function(t,n,i){var r,s,a,o,l,u=this;u.$activeOption&&u.$activeOption.removeClass("active"),u.$activeOption=null,t=e(t),t.length&&(u.$activeOption=t.addClass("active"),!n&&b(n)||(r=u.$dropdown_content.height(),s=u.$activeOption.outerHeight(!0),n=u.$dropdown_content.scrollTop()||0,a=u.$activeOption.offset().top-u.$dropdown_content.offset().top+n,o=a,l=a-r+s,a+s>r+n?u.$dropdown_content.stop().animate({scrollTop:l},i?u.settings.scrollDuration:0):a=0;n--)s.items.indexOf(k(i.items[n].id))!==-1&&i.items.splice(n,1);return i},refreshOptions:function(t){var n,r,s,a,o,l,u,d,c,h,p,f,m,g,_,v;"undefined"==typeof t&&(t=!0);var y=this,M=e.trim(y.$control_input.val()),D=y.search(M),w=y.$dropdown_content,x=y.$activeOption&&k(y.$activeOption.attr("data-value"));for(a=D.items.length,"number"==typeof y.settings.maxOptions&&(a=Math.min(a,y.settings.maxOptions)),o={},l=[],n=0;n0||m,y.hasOptions?(D.items.length>0?(_=x&&y.getOption(x),_&&_.length?g=_:"single"===y.settings.mode&&y.items.length&&(g=y.getOption(y.items[0])),g&&g.length||(g=v&&!y.settings.addPrecedence?y.getAdjacentOption(v,1):w.find("[data-selectable]:first"))):g=v,y.setActiveOption(g),t&&!y.isOpen&&y.open()):(y.setActiveOption(null),t&&y.isOpen&&y.close())},addOption:function(t){var n,i,r,s=this;if(e.isArray(t))for(n=0,i=t.length;n=0&&r0),t.$control_input.data("grow",!n&&!i)},isFull:function(){return null!==this.settings.maxItems&&this.items.length>=this.settings.maxItems},updateOriginalInput:function(e){var t,n,i,r,s=this;if(e=e||{},s.tagType===D){for(i=[],t=0,n=s.items.length;t'+L(r)+"");i.length||this.$input.attr("multiple")||i.push(' '),s.$input.html(i.join(""))}else s.$input.val(s.getValue()),s.$input.attr("value",s.$input.val());s.isSetup&&(e.silent||s.trigger("change",s.$input.val()))},updatePlaceholder:function(){if(this.settings.placeholder){var e=this.$control_input;this.items.length?e.removeAttr("placeholder"):e.attr("placeholder",this.settings.placeholder),e.triggerHandler("update",{force:!0})}},open:function(){var e=this;e.isLocked||e.isOpen||"multi"===e.settings.mode&&e.isFull()||(e.focus(),e.isOpen=!0,e.refreshState(),e.$dropdown.css({visibility:"hidden",display:"block"}),e.positionDropdown(),e.$dropdown.css({visibility:"visible"}),e.trigger("dropdown_open",e.$dropdown))},close:function(){var e=this,t=e.isOpen;"single"===e.settings.mode&&e.items.length&&(e.hideInput(),e.isBlurring||e.$control_input.blur()),e.isOpen=!1,e.$dropdown.hide(),e.setActiveOption(null),e.refreshState(),t&&e.trigger("dropdown_close",e.$dropdown)},positionDropdown:function(){var e=this.$control,t="body"===this.settings.dropdownParent?e.offset():e.position();t.top+=e.outerHeight(!0),this.$dropdown.css({width:e[0].getBoundingClientRect().width,top:t.top,left:t.left})},clear:function(e){var t=this;t.items.length&&(t.$control.children(":not(input)").remove(),t.items=[],t.lastQuery=null,t.setCaret(0),t.setActiveItem(null),t.updatePlaceholder(),t.updateOriginalInput({silent:e}),t.refreshState(),t.showInput(),t.trigger("clear"))},insertAtCaret:function(e){var t=Math.min(this.caretPos,this.items.length),n=e[0],i=this.buffer||this.$control[0];0===t?i.insertBefore(n,i.firstChild):i.insertBefore(n,i.childNodes[t]),this.setCaret(t+1)},deleteSelection:function(t){var n,i,r,s,a,o,l,u,d,c=this;if(r=t&&t.keyCode===m?-1:1,s=A(c.$control_input[0]),c.$activeOption&&!c.settings.hideSelected&&(l=c.getAdjacentOption(c.$activeOption,-1).attr("data-value")),a=[],c.$activeItems.length){for(d=c.$control.children(".active:"+(r>0?"last":"first")),o=c.$control.children(":not(input)").index(d),r>0&&o++,n=0,i=c.$activeItems.length;n0&&s.start===c.$control_input.val().length&&a.push(c.items[c.caretPos]));if(!a.length||"function"==typeof c.settings.onDelete&&c.settings.onDelete.apply(c,[a])===!1)return!1;for("undefined"!=typeof o&&c.setCaret(o);a.length;)c.removeItem(a.pop());return c.showInput(),c.positionDropdown(),c.refreshOptions(!0),l&&(u=c.getOption(l),u.length&&c.setActiveOption(u)),!0},advanceSelection:function(e,t){var n,i,r,s,a,o,l=this;0!==e&&(l.rtl&&(e*=-1),n=e>0?"last":"first",i=A(l.$control_input[0]),l.isFocused&&!l.isInputHidden?(s=l.$control_input.val().length,a=e<0?0===i.start&&0===i.length:i.start===s,a&&!s&&l.advanceCaret(e,t)):(o=l.$control.children(".active:"+n),o.length&&(r=l.$control.children(":not(input)").index(o),l.setActiveItem(null),l.setCaret(e>0?r+1:r))))},advanceCaret:function(e,t){var n,i,r=this;0!==e&&(n=e>0?"next":"prev",r.isShiftDown?(i=r.$control_input[n](),i.length&&(r.hideInput(),r.setActiveItem(i),t&&t.preventDefault())):r.setCaret(r.caretPos+e))},setCaret:function(t){var n=this;if(t="single"===n.settings.mode?n.items.length:Math.max(0,Math.min(n.items.length,t)),!n.isPending){var i,r,s,a;for(s=n.$control.children(":not(input)"),i=0,r=s.length;i '}},t),n.setup=function(){var i=n.setup;return function(){i.apply(n,arguments),n.$dropdown_header=e(t.html(t)),n.$dropdown.prepend(n.$dropdown_header)}}()}),N.define("optgroup_columns",function(t){var n=this;t=e.extend({equalizeWidth:!0,equalizeHeight:!0},t),this.getAdjacentOption=function(t,n){var i=t.closest("[data-group]").find("[data-selectable]"),r=i.index(t)+n;return r>=0&&r
',e=e.firstChild,n.body.appendChild(e),t=i.width=e.offsetWidth-e.clientWidth,n.body.removeChild(e)),t},r=function(){var r,s,a,o,l,u,d;if(d=e("[data-group]",n.$dropdown_content),s=d.length,s&&n.$dropdown_content.width()){if(t.equalizeHeight){for(a=0,r=0;r
1&&(l=u-o*(s-1),d.eq(s-1).css({width:l})))}};(t.equalizeHeight||t.equalizeWidth)&&(S.after(this,"positionDropdown",r),S.after(this,"refreshOptions",r))}),N.define("remove_button",function(t){t=e.extend({label:"×",title:"Remove",className:"remove",append:!0},t);var n=function(t,n){n.className="remove-single";var i=t,r=''+n.label+" ",s=function(t,n){return e("").append(t).append(n)};t.setup=function(){var a=i.setup;return function(){if(n.append){var o=e(i.$input.context).attr("id"),l=(e("#"+o),i.settings.render.item);i.settings.render.item=function(e){return s(l.apply(t,arguments),r)}}a.apply(t,arguments),t.$control.on("click","."+n.className,function(e){e.preventDefault(),i.isLocked||i.clear()})}}()},i=function(t,n){var i=t,r=''+n.label+" ",s=function(e,t){var n=e.search(/(<\/[^>]+>\s*)$/);return e.substring(0,n)+t+e.substring(n)};t.setup=function(){var a=i.setup;return function(){if(n.append){var o=i.settings.render.item;i.settings.render.item=function(e){return s(o.apply(t,arguments),r)}}a.apply(t,arguments),t.$control.on("click","."+n.className,function(t){if(t.preventDefault(),!i.isLocked){var n=e(t.currentTarget).parent();i.setActiveItem(n),i.deleteSelection()&&i.setCaret(i.items.length)}})}}()};return"single"===this.settings.mode?void n(this,t):void i(this,t)}),N.define("restore_on_backspace",function(e){var t=this;e.text=e.text||function(e){return e[this.settings.labelField]},this.onKeyDown=function(){var n=t.onKeyDown;return function(t){var i,r;return t.keyCode===m&&""===this.$control_input.val()&&!this.$activeItems.length&&(i=this.caretPos-1,i>=0&&i0)&&i.items.push({score:n,id:r})}):a.iterator(a.items,function(e,t){i.items.push({score:1,id:t})}),r=a.getSortFunction(i,t),r&&i.items.sort(r),i.total=i.items.length,"number"==typeof t.limit&&(i.items=i.items.slice(0,t.limit)),i};var t=function(e,t){return"number"==typeof e&&"number"==typeof t?e>t?1:et?1:t>e?-1:0)},n=function(e,t){var n,i,r,s;for(n=1,i=arguments.length;n":"akbar-men","∑":"majmou","¤":"omla"},az:{},ca:{"∆":"delta","∞":"infinit","♥":"amor","&":"i","|":"o","<":"menys que",">":"mes que","∑":"suma dels","¤":"moneda"},cs:{"∆":"delta","∞":"nekonecno","♥":"laska","&":"a","|":"nebo","<":"mensi nez",">":"vetsi nez","∑":"soucet","¤":"mena"},de:{"∆":"delta","∞":"unendlich","♥":"Liebe","&":"und","|":"oder","<":"kleiner als",">":"groesser als","∑":"Summe von","¤":"Waehrung"},dv:{"∆":"delta","∞":"kolunulaa","♥":"loabi","&":"aai","|":"noonee","<":"ah vure kuda",">":"ah vure bodu","∑":"jumula","¤":"faisaa"},en:{"∆":"delta","∞":"infinity","♥":"love","&":"and","|":"or","<":"less than",">":"greater than","∑":"sum","¤":"currency"},es:{"∆":"delta","∞":"infinito","♥":"amor","&":"y","|":"u","<":"menos que",">":"mas que","∑":"suma de los","¤":"moneda"},fa:{"∆":"delta","∞":"bi-nahayat","♥":"eshgh","&":"va","|":"ya","<":"kamtar-az",">":"bishtar-az","∑":"majmooe","¤":"vahed"},fi:{"∆":"delta","∞":"aarettomyys","♥":"rakkaus","&":"ja","|":"tai","<":"pienempi kuin",">":"suurempi kuin","∑":"summa","¤":"valuutta"},fr:{"∆":"delta","∞":"infiniment","♥":"Amour","&":"et","|":"ou","<":"moins que",">":"superieure a","∑":"somme des","¤":"monnaie"},ge:{"∆":"delta","∞":"usasruloba","♥":"siqvaruli","&":"da","|":"an","<":"naklebi",">":"meti","∑":"jami","¤":"valuta"},gr:{},hu:{"∆":"delta","∞":"vegtelen","♥":"szerelem","&":"es","|":"vagy","<":"kisebb mint",">":"nagyobb mint","∑":"szumma","¤":"penznem"},it:{"∆":"delta","∞":"infinito","♥":"amore","&":"e","|":"o","<":"minore di",">":"maggiore di","∑":"somma","¤":"moneta"},lt:{"∆":"delta","∞":"begalybe","♥":"meile","&":"ir","|":"ar","<":"maziau nei",">":"daugiau nei","∑":"suma","¤":"valiuta"},lv:{"∆":"delta","∞":"bezgaliba","♥":"milestiba","&":"un","|":"vai","<":"mazak neka",">":"lielaks neka","∑":"summa","¤":"valuta"},my:{"∆":"kwahkhyaet","∞":"asaonasme","♥":"akhyait","&":"nhin","|":"tho","<":"ngethaw",">":"kyithaw","∑":"paungld","¤":"ngwekye"},mk:{},nl:{"∆":"delta","∞":"oneindig","♥":"liefde","&":"en","|":"of","<":"kleiner dan",">":"groter dan","∑":"som","¤":"valuta"},pl:{"∆":"delta","∞":"nieskonczonosc","♥":"milosc","&":"i","|":"lub","<":"mniejsze niz",">":"wieksze niz","∑":"suma","¤":"waluta"},pt:{"∆":"delta","∞":"infinito","♥":"amor","&":"e","|":"ou","<":"menor que",">":"maior que","∑":"soma","¤":"moeda"},ro:{"∆":"delta","∞":"infinit","♥":"dragoste","&":"si","|":"sau","<":"mai mic ca",">":"mai mare ca","∑":"suma","¤":"valuta"},ru:{"∆":"delta","∞":"beskonechno","♥":"lubov","&":"i","|":"ili","<":"menshe",">":"bolshe","∑":"summa","¤":"valjuta"},sk:{"∆":"delta","∞":"nekonecno","♥":"laska","&":"a","|":"alebo","<":"menej ako",">":"viac ako","∑":"sucet","¤":"mena"},sr:{},tr:{"∆":"delta","∞":"sonsuzluk","♥":"ask","&":"ve","|":"veya","<":"kucuktur",">":"buyuktur","∑":"toplam","¤":"para birimi"},uk:{"∆":"delta","∞":"bezkinechnist","♥":"lubov","&":"i","|":"abo","<":"menshe",">":"bilshe","∑":"suma","¤":"valjuta"},vn:{"∆":"delta","∞":"vo cuc","♥":"yeu","&":"va","|":"hoac","<":"nho hon",">":"lon hon","∑":"tong","¤":"tien te"}},d=[";","?",":","@","&","=","+","$",",","/"].join(""),c=[";","?",":","@","&","=","+","$",","].join(""),h=[".","!","~","*","'","(",")"].join(""),p=function(e,t){var n,i,r,p,f,_,v,y,M,D,w,x,b,k,L="-",S="",T="",C=!0,Y={},E="";if("string"!=typeof e)return"";if("string"==typeof t&&(L=t),v=u.en,y=l.en,"object"==typeof t){n=t.maintainCase||!1,Y=t.custom&&"object"==typeof t.custom?t.custom:Y,r=+t.truncate>1&&t.truncate||!1,p=t.uric||!1,f=t.uricNoSlash||!1,_=t.mark||!1,C=!1!==t.symbols&&!1!==t.lang,L=t.separator||L,p&&(E+=d),f&&(E+=c),_&&(E+=h),v=t.lang&&u[t.lang]&&C?u[t.lang]:C?u.en:{},y=t.lang&&l[t.lang]?l[t.lang]:!1===t.lang||!0===t.lang?{}:l.en,t.titleCase&&"number"==typeof t.titleCase.length&&Array.prototype.toString.call(t.titleCase)?(t.titleCase.forEach(function(e){Y[e+""]=e+""}),i=!0):i=!!t.titleCase,t.custom&&"number"==typeof t.custom.length&&Array.prototype.toString.call(t.custom)&&t.custom.forEach(function(e){Y[e+""]=e+""}),Object.keys(Y).forEach(function(t){var n;n=t.length>1?new RegExp("\\b"+m(t)+"\\b","gi"):new RegExp(m(t),"gi"),e=e.replace(n,Y[t])});for(w in Y)E+=w}for(E=m(E+=L),b=!1,k=!1,D=0,x=(e=e.replace(/(^\s+|\s+$)/g,"")).length;D=0?(T+=w,w=""):!0===k?(w=o[T]+s[w],T=""):w=b&&s[w].match(/[A-Za-z0-9]/)?" "+s[w]:s[w],b=!1,k=!1):w in o?(T+=w,w="",D===x-1&&(w=o[T]),k=!0):!v[w]||p&&-1!==d.indexOf(w)||f&&-1!==c.indexOf(w)?(!0===k?(w=o[T]+w,T="",k=!1):b&&(/[A-Za-z0-9]/.test(w)||S.substr(-1).match(/A-Za-z0-9]/))&&(w=" "+w),b=!1):(w=b||S.substr(-1).match(/[A-Za-z0-9]/)?L+v[w]:v[w],w+=void 0!==e[D+1]&&e[D+1].match(/[A-Za-z0-9]/)?L:"",b=!0),S+=w.replace(new RegExp("[^\\w\\s"+E+"_-]","g"),L);return i&&(S=S.replace(/(\w)(\S*)/g,function(e,t,n){var i=t.toUpperCase()+(null!==n?n:"");return Object.keys(Y).indexOf(i.toLowerCase())<0?i:i.toLowerCase()})),S=S.replace(/\s+/g,L).replace(new RegExp("\\"+L+"+","g"),L).replace(new RegExp("(^\\"+L+"+|\\"+L+"+$)","g"),""),r&&S.length>r&&(M=S.charAt(r)===L,S=S.slice(0,r),M||(S=S.slice(0,S.lastIndexOf(L)))),n||i||(S=S.toLowerCase()),S},f=function(e){return function(t){return p(t,e)}},m=function(e){return e.replace(/[-\\^$*+?.()|[\]{}\/]/g,"\\$&")},g=function(e,t){for(var n in t)if(t[n]===e)return!0};"undefined"!=typeof e&&e.exports?(e.exports=p,e.exports.createSlug=f):(i=[],r=function(){return p}.apply(t,i),!(void 0!==r&&(e.exports=r)))}(this)},,,,,,,function(e,t,n){(function(e){"use strict";function t(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function n(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){return"undefined"!=typeof e&&null!==e?t(e):void 0}function s(e,t,n){return"undefined"!=typeof e&&null!==e&&"function"==typeof e[t]?n(e,t):void 0}var a=function(){function e(e,t){for(var n=0;n1?n-1:0),r=1;r=s.length)break;l=s[o++]}else{if(o=s.next(),o.done)break;l=o.value}var u=l;u.apply(this,i)}}return this}},{key:"off",value:function(e,t){if(!this._callbacks||0===arguments.length)return this._callbacks={},this;var n=this._callbacks[e];if(!n)return this;if(1===arguments.length)return delete this._callbacks[e],this;for(var i=0;i=t.length)break;r=t[i++]}else{if(i=t.next(),i.done)break;r=i.value}var s=r;if(/(^| )dz-message($| )/.test(s.className)){e=s,s.className="dz-message";break}}e||(e=l.createElement('
'),this.element.appendChild(e));var a=e.getElementsByTagName("span")[0];return a&&(null!=a.textContent?a.textContent=this.options.dictFallbackMessage:null!=a.innerText&&(a.innerText=this.options.dictFallbackMessage)),this.element.appendChild(this.getFallbackForm())},resize:function(e,t,n,i){var r={srcX:0,srcY:0,srcWidth:e.width,srcHeight:e.height},s=e.width/e.height;null==t&&null==n?(t=r.srcWidth,n=r.srcHeight):null==t?t=n*s:null==n&&(n=t/s),t=Math.min(t,r.srcWidth),n=Math.min(n,r.srcHeight);var a=t/n;if(r.srcWidth>t||r.srcHeight>n)if("crop"===i)s>a?(r.srcHeight=e.height,r.srcWidth=r.srcHeight*a):(r.srcWidth=e.width,r.srcHeight=r.srcWidth/a);else{if("contain"!==i)throw new Error("Unknown resizeMethod '"+i+"'");s>a?n=t/s:t=n*s}return r.srcX=(e.width-r.srcWidth)/2,r.srcY=(e.height-r.srcHeight)/2,r.trgWidth=t,r.trgHeight=n,r},transformFile:function(e,t){return(this.options.resizeWidth||this.options.resizeHeight)&&e.type.match(/image.*/)?this.resizeImage(e,this.options.resizeWidth,this.options.resizeHeight,this.options.resizeMethod,t):t(e)},previewTemplate:'\n
\n
\n
\n
\n
\n
\n Check \n \n \n \n \n \n
\n
\n
\n Error \n \n \n \n \n \n \n \n
\n
',drop:function(e){return this.element.classList.remove("dz-drag-hover")},dragstart:function(e){},dragend:function(e){return this.element.classList.remove("dz-drag-hover")},dragenter:function(e){return this.element.classList.add("dz-drag-hover")},dragover:function(e){return this.element.classList.add("dz-drag-hover")},dragleave:function(e){return this.element.classList.remove("dz-drag-hover")},paste:function(e){},reset:function(){return this.element.classList.remove("dz-started")},addedfile:function(e){var t=this;if(this.element===this.previewsContainer&&this.element.classList.add("dz-started"),this.previewsContainer){e.previewElement=l.createElement(this.options.previewTemplate.trim()),e.previewTemplate=e.previewElement,this.previewsContainer.appendChild(e.previewElement);for(var n=e.previewElement.querySelectorAll("[data-dz-name]"),i=!0,r=0,n=i?n:n[Symbol.iterator]();;){var s;if(i){if(r>=n.length)break;s=n[r++]}else{if(r=n.next(),r.done)break;s=r.value}var a=s;a.textContent=e.name}for(var o=e.previewElement.querySelectorAll("[data-dz-size]"),u=!0,d=0,o=u?o:o[Symbol.iterator]();;){if(u){if(d>=o.length)break;a=o[d++]}else{if(d=o.next(),d.done)break;a=d.value}a.innerHTML=this.filesize(e.size)}this.options.addRemoveLinks&&(e._removeLink=l.createElement(''+this.options.dictRemoveFile+" "),e.previewElement.appendChild(e._removeLink));for(var c=function(n){return n.preventDefault(),n.stopPropagation(),e.status===l.UPLOADING?l.confirm(t.options.dictCancelUploadConfirmation,function(){return t.removeFile(e)}):t.options.dictRemoveFileConfirmation?l.confirm(t.options.dictRemoveFileConfirmation,function(){return t.removeFile(e)}):t.removeFile(e)},h=e.previewElement.querySelectorAll("[data-dz-remove]"),p=!0,f=0,h=p?h:h[Symbol.iterator]();;){var m;if(p){if(f>=h.length)break;m=h[f++]}else{if(f=h.next(),f.done)break;m=f.value}var g=m;g.addEventListener("click",c)}}},removedfile:function(e){return null!=e.previewElement&&null!=e.previewElement.parentNode&&e.previewElement.parentNode.removeChild(e.previewElement),this._updateMaxFilesReachedClass()},thumbnail:function(e,t){if(e.previewElement){e.previewElement.classList.remove("dz-file-preview");for(var n=e.previewElement.querySelectorAll("[data-dz-thumbnail]"),i=!0,r=0,n=i?n:n[Symbol.iterator]();;){var s;if(i){if(r>=n.length)break;s=n[r++]}else{if(r=n.next(),r.done)break;s=r.value}var a=s;a.alt=e.name,a.src=t}return setTimeout(function(){return e.previewElement.classList.add("dz-image-preview")},1)}},error:function(e,t){if(e.previewElement){e.previewElement.classList.add("dz-error"),"String"!=typeof t&&t.error&&(t=t.error);for(var n=e.previewElement.querySelectorAll("[data-dz-errormessage]"),i=!0,r=0,n=i?n:n[Symbol.iterator]();;){var s;if(i){if(r>=n.length)break;s=n[r++]}else{if(r=n.next(),r.done)break;s=r.value}var a=s;a.textContent=t}}},errormultiple:function(){},processing:function(e){if(e.previewElement&&(e.previewElement.classList.add("dz-processing"),e._removeLink))return e._removeLink.innerHTML=this.options.dictCancelUpload},processingmultiple:function(){},uploadprogress:function(e,t,n){if(e.previewElement)for(var i=e.previewElement.querySelectorAll("[data-dz-uploadprogress]"),r=!0,s=0,i=r?i:i[Symbol.iterator]();;){var a;if(r){if(s>=i.length)break;a=i[s++]}else{if(s=i.next(),s.done)break;a=s.value}var o=a;"PROGRESS"===o.nodeName?o.value=t:o.style.width=t+"%"}},totaluploadprogress:function(){},sending:function(){},sendingmultiple:function(){},success:function(e){if(e.previewElement)return e.previewElement.classList.add("dz-success")},successmultiple:function(){},canceled:function(e){return this.emit("error",e,this.options.dictUploadCanceled)},canceledmultiple:function(){},complete:function(e){if(e._removeLink&&(e._removeLink.innerHTML=this.options.dictRemoveFile),e.previewElement)return e.previewElement.classList.add("dz-complete")},completemultiple:function(){},maxfilesexceeded:function(){},maxfilesreached:function(){},queuecomplete:function(){},addedfiles:function(){}},this.prototype._thumbnailQueue=[],this.prototype._processingThumbnail=!1}},{key:"extend",value:function(e){for(var t=arguments.length,n=Array(t>1?t-1:0),i=1;i=r.length)break;o=r[a++]}else{if(a=r.next(),a.done)break;o=a.value}var l=o;for(var u in l){var d=l[u];e[u]=d}}return e}}]),a(l,[{key:"getAcceptedFiles",value:function(){return this.files.filter(function(e){return e.accepted}).map(function(e){return e})}},{key:"getRejectedFiles",value:function(){return this.files.filter(function(e){return!e.accepted}).map(function(e){return e})}},{key:"getFilesWithStatus",value:function(e){return this.files.filter(function(t){return t.status===e}).map(function(e){return e})}},{key:"getQueuedFiles",value:function(){return this.getFilesWithStatus(l.QUEUED)}},{key:"getUploadingFiles",value:function(){return this.getFilesWithStatus(l.UPLOADING)}},{key:"getAddedFiles",value:function(){return this.getFilesWithStatus(l.ADDED)}},{key:"getActiveFiles",value:function(){return this.files.filter(function(e){return e.status===l.UPLOADING||e.status===l.QUEUED}).map(function(e){return e})}},{key:"init",value:function(){var e=this;if("form"===this.element.tagName&&this.element.setAttribute("enctype","multipart/form-data"),this.element.classList.contains("dropzone")&&!this.element.querySelector(".dz-message")&&this.element.appendChild(l.createElement(''+this.options.dictDefaultMessage+"
")),this.clickableElements.length){var t=function t(){return e.hiddenFileInput&&e.hiddenFileInput.parentNode.removeChild(e.hiddenFileInput),e.hiddenFileInput=document.createElement("input"),e.hiddenFileInput.setAttribute("type","file"),(null===e.options.maxFiles||e.options.maxFiles>1)&&e.hiddenFileInput.setAttribute("multiple","multiple"),e.hiddenFileInput.className="dz-hidden-input",null!==e.options.acceptedFiles&&e.hiddenFileInput.setAttribute("accept",e.options.acceptedFiles),null!==e.options.capture&&e.hiddenFileInput.setAttribute("capture",e.options.capture),e.hiddenFileInput.style.visibility="hidden",e.hiddenFileInput.style.position="absolute",e.hiddenFileInput.style.top="0",e.hiddenFileInput.style.left="0",e.hiddenFileInput.style.height="0",e.hiddenFileInput.style.width="0",l.getElement(e.options.hiddenInputContainer,"hiddenInputContainer").appendChild(e.hiddenFileInput),e.hiddenFileInput.addEventListener("change",function(){var n=e.hiddenFileInput.files;if(n.length)for(var i=n,r=!0,s=0,i=r?i:i[Symbol.iterator]();;){var a;if(r){if(s>=i.length)break;a=i[s++]}else{if(s=i.next(),s.done)break;a=s.value}var o=a;e.addFile(o)}return e.emit("addedfiles",n),t()})};t()}this.URL=null!==window.URL?window.URL:window.webkitURL;for(var n=this.events,i=!0,r=0,n=i?n:n[Symbol.iterator]();;){var s;if(i){if(r>=n.length)break;s=n[r++]}else{if(r=n.next(),r.done)break;s=r.value}var a=s;this.on(a,this.options[a])}this.on("uploadprogress",function(){return e.updateTotalUploadProgress()}),this.on("removedfile",function(){return e.updateTotalUploadProgress()}),this.on("canceled",function(t){return e.emit("complete",t)}),this.on("complete",function(t){if(0===e.getAddedFiles().length&&0===e.getUploadingFiles().length&&0===e.getQueuedFiles().length)return setTimeout(function(){return e.emit("queuecomplete")},0)});var o=function(e){return e.stopPropagation(),e.preventDefault?e.preventDefault():e.returnValue=!1};return this.listeners=[{element:this.element,events:{dragstart:function(t){return e.emit("dragstart",t)},dragenter:function(t){return o(t),e.emit("dragenter",t)},dragover:function(t){var n=void 0;try{n=t.dataTransfer.effectAllowed}catch(e){}
+return t.dataTransfer.dropEffect="move"===n||"linkMove"===n?"move":"copy",o(t),e.emit("dragover",t)},dragleave:function(t){return e.emit("dragleave",t)},drop:function(t){return o(t),e.drop(t)},dragend:function(t){return e.emit("dragend",t)}}}],this.clickableElements.forEach(function(t){return e.listeners.push({element:t,events:{click:function(n){return(t!==e.element||n.target===e.element||l.elementInside(n.target,e.element.querySelector(".dz-message")))&&e.hiddenFileInput.click(),!0}}})}),this.enable(),this.options.init.call(this)}},{key:"destroy",value:function(){return this.disable(),this.removeAllFiles(!0),(null!=this.hiddenFileInput?this.hiddenFileInput.parentNode:void 0)&&(this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput),this.hiddenFileInput=null),delete this.element.dropzone,l.instances.splice(l.instances.indexOf(this),1)}},{key:"updateTotalUploadProgress",value:function(){var e=void 0,t=0,n=0,i=this.getActiveFiles();if(i.length){for(var r=this.getActiveFiles(),s=!0,a=0,r=s?r:r[Symbol.iterator]();;){var o;if(s){if(a>=r.length)break;o=r[a++]}else{if(a=r.next(),a.done)break;o=a.value}var l=o;t+=l.upload.bytesSent,n+=l.upload.total}e=100*t/n}else e=100;return this.emit("totaluploadprogress",e,n,t)}},{key:"_getParamName",value:function(e){return"function"==typeof this.options.paramName?this.options.paramName(e):""+this.options.paramName+(this.options.uploadMultiple?"["+e+"]":"")}},{key:"_renameFile",value:function(e){return"function"!=typeof this.options.renameFile?e.name:this.options.renameFile(e)}},{key:"getFallbackForm",value:function(){var e=void 0,t=void 0;if(e=this.getExistingFallback())return e;var n='';var i=l.createElement(n);return"FORM"!==this.element.tagName?(t=l.createElement(''),t.appendChild(i)):(this.element.setAttribute("enctype","multipart/form-data"),this.element.setAttribute("method",this.options.method)),null!=t?t:i}},{key:"getExistingFallback",value:function(){for(var e=function(e){for(var t=e,n=!0,i=0,t=n?t:t[Symbol.iterator]();;){var r;if(n){if(i>=t.length)break;r=t[i++]}else{if(i=t.next(),i.done)break;r=i.value}var s=r;if(/(^| )fallback($| )/.test(s.className))return s}},t=["div","form"],n=0;n0){for(var i=["tb","gb","mb","kb","b"],r=0;r=a){t=e/Math.pow(this.options.filesizeBase,4-r),n=s;break}}t=Math.round(10*t)/10}return""+t+" "+this.options.dictFileSizeUnits[n]}},{key:"_updateMaxFilesReachedClass",value:function(){return null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(this.getAcceptedFiles().length===this.options.maxFiles&&this.emit("maxfilesreached",this.files),this.element.classList.add("dz-max-files-reached")):this.element.classList.remove("dz-max-files-reached")}},{key:"drop",value:function(e){if(e.dataTransfer){this.emit("drop",e);for(var t=[],n=0;n=t.length)break;r=t[i++]}else{if(i=t.next(),i.done)break;r=i.value}var s=r;this.addFile(s)}}},{key:"_addFilesFromItems",value:function(e){var t=this;return function(){for(var n=[],i=e,r=!0,s=0,i=r?i:i[Symbol.iterator]();;){var a;if(r){if(s>=i.length)break;a=i[s++]}else{if(s=i.next(),s.done)break;a=s.value}var o,l=a;null!=l.webkitGetAsEntry&&(o=l.webkitGetAsEntry())?o.isFile?n.push(t.addFile(l.getAsFile())):o.isDirectory?n.push(t._addFilesFromDirectory(o,o.name)):n.push(void 0):null!=l.getAsFile&&(null==l.kind||"file"===l.kind)?n.push(t.addFile(l.getAsFile())):n.push(void 0)}return n}()}},{key:"_addFilesFromDirectory",value:function(e,t){var n=this,i=e.createReader(),r=function(e){return s(console,"log",function(t){return t.log(e)})},a=function e(){return i.readEntries(function(i){if(i.length>0){for(var r=i,s=!0,a=0,r=s?r:r[Symbol.iterator]();;){var o;if(s){if(a>=r.length)break;o=r[a++]}else{if(a=r.next(),a.done)break;o=a.value}var l=o;l.isFile?l.file(function(e){if(!n.options.ignoreHiddenFiles||"."!==e.name.substring(0,1))return e.fullPath=t+"/"+e.name,n.addFile(e)}):l.isDirectory&&n._addFilesFromDirectory(l,t+"/"+l.name)}e()}return null},r)};return a()}},{key:"accept",value:function(e,t){return this.options.maxFilesize&&e.size>1024*this.options.maxFilesize*1024?t(this.options.dictFileTooBig.replace("{{filesize}}",Math.round(e.size/1024/10.24)/100).replace("{{maxFilesize}}",this.options.maxFilesize)):l.isValidFile(e,this.options.acceptedFiles)?null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(t(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}",this.options.maxFiles)),this.emit("maxfilesexceeded",e)):this.options.accept.call(this,e,t):t(this.options.dictInvalidFileType)}},{key:"addFile",value:function(e){var t=this;return e.upload={uuid:l.uuidv4(),progress:0,total:e.size,bytesSent:0,filename:this._renameFile(e),chunked:this.options.chunking&&(this.options.forceChunking||e.size>this.options.chunkSize),totalChunkCount:Math.ceil(e.size/this.options.chunkSize)},this.files.push(e),e.status=l.ADDED,this.emit("addedfile",e),this._enqueueThumbnail(e),this.accept(e,function(n){return n?(e.accepted=!1,t._errorProcessing([e],n)):(e.accepted=!0,t.options.autoQueue&&t.enqueueFile(e)),t._updateMaxFilesReachedClass()})}},{key:"enqueueFiles",value:function(e){for(var t=e,n=!0,i=0,t=n?t:t[Symbol.iterator]();;){var r;if(n){if(i>=t.length)break;r=t[i++]}else{if(i=t.next(),i.done)break;r=i.value}var s=r;this.enqueueFile(s)}return null}},{key:"enqueueFile",value:function(e){var t=this;if(e.status!==l.ADDED||e.accepted!==!0)throw new Error("This file can't be queued because it has already been processed or was rejected.");if(e.status=l.QUEUED,this.options.autoProcessQueue)return setTimeout(function(){return t.processQueue()},0)}},{key:"_enqueueThumbnail",value:function(e){var t=this;if(this.options.createImageThumbnails&&e.type.match(/image.*/)&&e.size<=1024*this.options.maxThumbnailFilesize*1024)return this._thumbnailQueue.push(e),setTimeout(function(){return t._processThumbnailQueue()},0)}},{key:"_processThumbnailQueue",value:function(){var e=this;if(!this._processingThumbnail&&0!==this._thumbnailQueue.length){this._processingThumbnail=!0;var t=this._thumbnailQueue.shift();return this.createThumbnail(t,this.options.thumbnailWidth,this.options.thumbnailHeight,this.options.thumbnailMethod,!0,function(n){return e.emit("thumbnail",t,n),e._processingThumbnail=!1,e._processThumbnailQueue()})}}},{key:"removeFile",value:function(e){if(e.status===l.UPLOADING&&this.cancelUpload(e),this.files=u(this.files,e),this.emit("removedfile",e),0===this.files.length)return this.emit("reset")}},{key:"removeAllFiles",value:function(e){null==e&&(e=!1);for(var t=this.files.slice(),n=!0,i=0,t=n?t:t[Symbol.iterator]();;){var r;if(n){if(i>=t.length)break;r=t[i++]}else{if(i=t.next(),i.done)break;r=i.value}var s=r;(s.status!==l.UPLOADING||e)&&this.removeFile(s)}return null}},{key:"resizeImage",value:function(e,t,n,i,r){var s=this;return this.createThumbnail(e,t,n,i,!0,function(t,n){if(null==n)return r(e);var i=s.options.resizeMimeType;null==i&&(i=e.type);var a=n.toDataURL(i,s.options.resizeQuality);return"image/jpeg"!==i&&"image/jpg"!==i||(a=p.restore(e.dataURL,a)),r(l.dataURItoBlob(a))})}},{key:"createThumbnail",value:function(e,t,n,i,r,s){var a=this,o=new FileReader;return o.onload=function(){return e.dataURL=o.result,"image/svg+xml"===e.type?void(null!=s&&s(o.result)):a.createThumbnailFromUrl(e,t,n,i,r,s)},o.readAsDataURL(e)}},{key:"createThumbnailFromUrl",value:function(e,t,n,i,r,s,a){var o=this,l=document.createElement("img");return a&&(l.crossOrigin=a),l.onload=function(){var a=function(e){return e(1)};return"undefined"!=typeof EXIF&&null!==EXIF&&r&&(a=function(e){return EXIF.getData(l,function(){return e(EXIF.getTag(this,"Orientation"))})}),a(function(r){e.width=l.width,e.height=l.height;var a=o.options.resize.call(o,e,t,n,i),u=document.createElement("canvas"),d=u.getContext("2d");switch(u.width=a.trgWidth,u.height=a.trgHeight,r>4&&(u.width=a.trgHeight,u.height=a.trgWidth),r){case 2:d.translate(u.width,0),d.scale(-1,1);break;case 3:d.translate(u.width,u.height),d.rotate(Math.PI);break;case 4:d.translate(0,u.height),d.scale(1,-1);break;case 5:d.rotate(.5*Math.PI),d.scale(1,-1);break;case 6:d.rotate(.5*Math.PI),d.translate(0,-u.width);break;case 7:d.rotate(.5*Math.PI),d.translate(u.height,-u.width),d.scale(-1,1);break;case 8:d.rotate(-.5*Math.PI),d.translate(-u.height,0)}h(d,l,null!=a.srcX?a.srcX:0,null!=a.srcY?a.srcY:0,a.srcWidth,a.srcHeight,null!=a.trgX?a.trgX:0,null!=a.trgY?a.trgY:0,a.trgWidth,a.trgHeight);var c=u.toDataURL("image/png");if(null!=s)return s(c,u)})},null!=s&&(l.onerror=s),l.src=e.dataURL}},{key:"processQueue",value:function(){var e=this.options.parallelUploads,t=this.getUploadingFiles().length,n=t;if(!(t>=e)){var i=this.getQueuedFiles();if(i.length>0){if(this.options.uploadMultiple)return this.processFiles(i.slice(0,e-t));for(;n=t.length)break;r=t[i++]}else{if(i=t.next(),i.done)break;r=i.value}var s=r;s.processing=!0,s.status=l.UPLOADING,this.emit("processing",s)}return this.options.uploadMultiple&&this.emit("processingmultiple",e),this.uploadFiles(e)}},{key:"_getFilesWithXhr",value:function(e){var t=void 0;return t=this.files.filter(function(t){return t.xhr===e}).map(function(e){return e})}},{key:"cancelUpload",value:function(e){if(e.status===l.UPLOADING){for(var t=this._getFilesWithXhr(e.xhr),n=t,i=!0,r=0,n=i?n:n[Symbol.iterator]();;){var s;if(i){if(r>=n.length)break;s=n[r++]}else{if(r=n.next(),r.done)break;s=r.value}var a=s;a.status=l.CANCELED}"undefined"!=typeof e.xhr&&e.xhr.abort();for(var o=t,u=!0,d=0,o=u?o:o[Symbol.iterator]();;){var c;if(u){if(d>=o.length)break;c=o[d++]}else{if(d=o.next(),d.done)break;c=d.value}var h=c;this.emit("canceled",h)}this.options.uploadMultiple&&this.emit("canceledmultiple",t)}else e.status!==l.ADDED&&e.status!==l.QUEUED||(e.status=l.CANCELED,this.emit("canceled",e),this.options.uploadMultiple&&this.emit("canceledmultiple",[e]));if(this.options.autoProcessQueue)return this.processQueue()}},{key:"resolveOption",value:function(e){if("function"==typeof e){for(var t=arguments.length,n=Array(t>1?t-1:0),i=1;i=i.upload.totalChunkCount)){s++;var a=n*t.options.chunkSize,o=Math.min(a+t.options.chunkSize,i.size),u={name:t._getParamName(0),data:r.webkitSlice?r.webkitSlice(a,o):r.slice(a,o),filename:i.upload.filename,chunkIndex:n};i.upload.chunks[n]={file:i,index:n,dataBlock:u,status:l.UPLOADING,progress:0,retries:0},t._uploadData(e,[u])}};if(i.upload.finishedChunkUpload=function(n){var r=!0;n.status=l.SUCCESS,n.dataBlock=null,n.xhr=null;for(var s=0;s=r.length)break;o=r[a++]}else{if(a=r.next(),a.done)break;o=a.value}var u=o;u.xhr=i}e[0].upload.chunked&&(e[0].upload.chunks[t[0].chunkIndex].xhr=i);var d=this.resolveOption(this.options.method,e),c=this.resolveOption(this.options.url,e);i.open(d,c,!0),i.timeout=this.resolveOption(this.options.timeout,e),i.withCredentials=!!this.options.withCredentials,i.onload=function(t){n._finishedUploading(e,i,t)},i.onerror=function(){n._handleUploadError(e,i)};var h=null!=i.upload?i.upload:i;h.onprogress=function(t){return n._updateFilesUploadProgress(e,i,t)};var p={Accept:"application/json","Cache-Control":"no-cache","X-Requested-With":"XMLHttpRequest"};this.options.headers&&l.extend(p,this.options.headers);for(var f in p){var m=p[f];m&&i.setRequestHeader(f,m)}var g=new FormData;if(this.options.params){var _=this.options.params;"function"==typeof _&&(_=_.call(this,e,i,e[0].upload.chunked?this._getChunk(e[0],i):null));for(var v in _){var y=_[v];g.append(v,y)}}for(var M=e,D=!0,w=0,M=D?M:M[Symbol.iterator]();;){var x;if(D){if(w>=M.length)break;x=M[w++]}else{if(w=M.next(),w.done)break;x=w.value}var b=x;this.emit("sending",b,i,g)}this.options.uploadMultiple&&this.emit("sendingmultiple",e,i,g),this._addFormElementData(g);for(var k=0;k=t.length)break;r=t[i++]}else{if(i=t.next(),i.done)break;r=i.value}var s=r,a=s.getAttribute("name"),o=s.getAttribute("type");if(o&&(o=o.toLowerCase()),"undefined"!=typeof a&&null!==a)if("SELECT"===s.tagName&&s.hasAttribute("multiple"))for(var l=s.options,u=!0,d=0,l=u?l:l[Symbol.iterator]();;){var c;if(u){if(d>=l.length)break;c=l[d++]}else{if(d=l.next(),d.done)break;c=d.value}var h=c;h.selected&&e.append(a,h.value)}else(!o||"checkbox"!==o&&"radio"!==o||s.checked)&&e.append(a,s.value)}}},{key:"_updateFilesUploadProgress",value:function(e,t,n){var i=void 0;if("undefined"!=typeof n){if(i=100*n.loaded/n.total,e[0].upload.chunked){var r=e[0],s=this._getChunk(r,t);s.progress=i,s.total=n.total,s.bytesSent=n.loaded;r.upload.progress=0,r.upload.total=0,r.upload.bytesSent=0;for(var a=0;a=o.length)break;d=o[u++]}else{if(u=o.next(),u.done)break;d=u.value}var c=d;c.upload.progress=i,c.upload.total=n.total,c.upload.bytesSent=n.loaded}for(var h=e,p=!0,f=0,h=p?h:h[Symbol.iterator]();;){var m;if(p){if(f>=h.length)break;m=h[f++]}else{if(f=h.next(),f.done)break;m=f.value}var g=m;this.emit("uploadprogress",g,g.upload.progress,g.upload.bytesSent)}}else{var _=!0;i=100;for(var v=e,y=!0,M=0,v=y?v:v[Symbol.iterator]();;){var D;if(y){if(M>=v.length)break;D=v[M++]}else{if(M=v.next(),M.done)break;D=M.value}var w=D;100===w.upload.progress&&w.upload.bytesSent===w.upload.total||(_=!1),w.upload.progress=i,w.upload.bytesSent=w.upload.total}if(_)return;for(var x=e,b=!0,k=0,x=b?x:x[Symbol.iterator]();;){var L;if(b){if(k>=x.length)break;L=x[k++]}else{if(k=x.next(),k.done)break;L=k.value}var S=L;this.emit("uploadprogress",S,i,S.upload.bytesSent)}}}},{key:"_finishedUploading",value:function(e,t,n){var i=void 0;if(e[0].status!==l.CANCELED&&4===t.readyState){if("arraybuffer"!==t.responseType&&"blob"!==t.responseType&&(i=t.responseText,t.getResponseHeader("content-type")&&~t.getResponseHeader("content-type").indexOf("application/json")))try{i=JSON.parse(i)}catch(e){n=e,i="Invalid JSON response from server."}this._updateFilesUploadProgress(e),200<=t.status&&t.status<300?e[0].upload.chunked?e[0].upload.finishedChunkUpload(this._getChunk(e[0],t)):this._finished(e,i,n):this._handleUploadError(e,t,i)}}},{key:"_handleUploadError",value:function(e,t,n){if(e[0].status!==l.CANCELED){if(e[0].upload.chunked&&this.options.retryChunks){var i=this._getChunk(e[0],t);if(i.retries++=r.length)break;o=r[a++]}else{if(a=r.next(),a.done)break;o=a.value}this._errorProcessing(e,n||this.options.dictResponseError.replace("{{statusCode}}",t.status),t)}}}},{key:"submitRequest",value:function(e,t,n){e.send(t)}},{key:"_finished",value:function(e,t,n){for(var i=e,r=!0,s=0,i=r?i:i[Symbol.iterator]();;){var a;if(r){if(s>=i.length)break;a=i[s++]}else{if(s=i.next(),s.done)break;a=s.value}var o=a;o.status=l.SUCCESS,this.emit("success",o,t,n),this.emit("complete",o)}if(this.options.uploadMultiple&&(this.emit("successmultiple",e,t,n),this.emit("completemultiple",e)),this.options.autoProcessQueue)return this.processQueue()}},{key:"_errorProcessing",value:function(e,t,n){for(var i=e,r=!0,s=0,i=r?i:i[Symbol.iterator]();;){var a;if(r){if(s>=i.length)break;a=i[s++]}else{if(s=i.next(),s.done)break;a=s.value}var o=a;o.status=l.ERROR,this.emit("error",o,t,n),this.emit("complete",o)}if(this.options.uploadMultiple&&(this.emit("errormultiple",e,t,n),this.emit("completemultiple",e)),this.options.autoProcessQueue)return this.processQueue()}}],[{key:"uuidv4",value:function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){var t=16*Math.random()|0,n="x"===e?t:3&t|8;return n.toString(16)})}}]),l}(o);l.initClass(),l.version="5.5.1",l.options={},l.optionsForElement=function(e){return e.getAttribute("id")?l.options[d(e.getAttribute("id"))]:void 0},l.instances=[],l.forElement=function(e){if("string"==typeof e&&(e=document.querySelector(e)),null==(null!=e?e.dropzone:void 0))throw new Error("No Dropzone found for given element. This is probably because you're trying to access it before Dropzone had the time to initialize. Use the `init` option to setup any additional observers on your Dropzone.");return e.dropzone},l.autoDiscover=!0,l.discover=function(){var e=void 0;if(document.querySelectorAll)e=document.querySelectorAll(".dropzone");else{e=[];var t=function(t){return function(){for(var n=[],i=t,r=!0,s=0,i=r?i:i[Symbol.iterator]();;){var a;if(r){if(s>=i.length)break;a=i[s++]}else{if(s=i.next(),s.done)break;a=s.value}var o=a;/(^| )dropzone($| )/.test(o.className)?n.push(e.push(o)):n.push(void 0)}return n}()};t(document.getElementsByTagName("div")),t(document.getElementsByTagName("form"))}return function(){for(var t=[],n=e,i=!0,r=0,n=i?n:n[Symbol.iterator]();;){var s;if(i){if(r>=n.length)break;s=n[r++]}else{if(r=n.next(),r.done)break;s=r.value}var a=s;l.optionsForElement(a)!==!1?t.push(new l(a)):t.push(void 0)}return t}()},l.blacklistedBrowsers=[/opera.*(Macintosh|Windows Phone).*version\/12/i],l.isBrowserSupported=function(){var e=!0;if(window.File&&window.FileReader&&window.FileList&&window.Blob&&window.FormData&&document.querySelector)if("classList"in document.createElement("a"))for(var t=l.blacklistedBrowsers,n=!0,i=0,t=n?t:t[Symbol.iterator]();;){var r;if(n){if(i>=t.length)break;r=t[i++]}else{if(i=t.next(),i.done)break;r=i.value}var s=r;s.test(navigator.userAgent)&&(e=!1)}else e=!1;else e=!1;return e},l.dataURItoBlob=function(e){for(var t=atob(e.split(",")[1]),n=e.split(",")[0].split(":")[1].split(";")[0],i=new ArrayBuffer(t.length),r=new Uint8Array(i),s=0,a=t.length,o=0<=a;o?s<=a:s>=a;o?s++:s--)r[s]=t.charCodeAt(s);return new Blob([i],{type:n})};var u=function(e,t){return e.filter(function(e){return e!==t}).map(function(e){return e})},d=function(e){return e.replace(/[\-_](\w)/g,function(e){return e.charAt(1).toUpperCase()})};l.createElement=function(e){var t=document.createElement("div");return t.innerHTML=e,t.childNodes[0]},l.elementInside=function(e,t){if(e===t)return!0;for(;e=e.parentNode;)if(e===t)return!0;return!1},l.getElement=function(e,t){var n=void 0;if("string"==typeof e?n=document.querySelector(e):null!=e.nodeType&&(n=e),null==n)throw new Error("Invalid `"+t+"` option provided. Please provide a CSS selector or a plain HTML element.");return n},l.getElements=function(e,t){var n=void 0,i=void 0;if(e instanceof Array){i=[];try{for(var r=e,s=!0,a=0,r=s?r:r[Symbol.iterator]();;){if(s){if(a>=r.length)break;n=r[a++]}else{if(a=r.next(),a.done)break;n=a.value}i.push(this.getElement(n,t))}}catch(e){i=null}}else if("string"==typeof e){i=[];for(var o=document.querySelectorAll(e),l=!0,u=0,o=l?o:o[Symbol.iterator]();;){if(l){if(u>=o.length)break;n=o[u++]}else{if(u=o.next(),u.done)break;n=u.value}i.push(n)}}else null!=e.nodeType&&(i=[e]);if(null==i||!i.length)throw new Error("Invalid `"+t+"` option provided. Please provide a CSS selector, a plain HTML element or a list of those.");return i},l.confirm=function(e,t,n){return window.confirm(e)?t():null!=n?n():void 0},l.isValidFile=function(e,t){if(!t)return!0;t=t.split(",");for(var n=e.type,i=n.replace(/\/.*$/,""),r=t,s=!0,a=0,r=s?r:r[Symbol.iterator]();;){var o;if(s){if(a>=r.length)break;o=r[a++]}else{if(a=r.next(),a.done)break;o=a.value}var l=o;if(l=l.trim(),"."===l.charAt(0)){if(e.name.toLowerCase().indexOf(l.toLowerCase(),e.name.length-l.length)!==-1)return!0}else if(/\/\*$/.test(l)){if(i===l.replace(/\/.*$/,""))return!0}else if(n===l)return!0}return!1},"undefined"!=typeof jQuery&&null!==jQuery&&(jQuery.fn.dropzone=function(e){return this.each(function(){return new l(this,e)})}),"undefined"!=typeof e&&null!==e?e.exports=l:window.Dropzone=l,l.ADDED="added",l.QUEUED="queued",l.ACCEPTED=l.QUEUED,l.UPLOADING="uploading",l.PROCESSING=l.UPLOADING,l.CANCELED="canceled",l.ERROR="error",l.SUCCESS="success";var c=function(e){var t=(e.naturalWidth,e.naturalHeight),n=document.createElement("canvas");n.width=1,n.height=t;var i=n.getContext("2d");i.drawImage(e,0,0);for(var r=i.getImageData(1,0,1,t),s=r.data,a=0,o=t,l=t;l>a;){var u=s[4*(l-1)+3];0===u?o=l:a=l,l=o+a>>1}var d=l/t;return 0===d?1:d},h=function(e,t,n,i,r,s,a,o,l,u){var d=c(t);return e.drawImage(t,n,i,r,s,a,o,l,u/d)},p=function(){function e(){i(this,e)}return a(e,null,[{key:"initClass",value:function(){this.KEY_STR="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}},{key:"encode64",value:function(e){for(var t="",n=void 0,i=void 0,r="",s=void 0,a=void 0,o=void 0,l="",u=0;;)if(n=e[u++],i=e[u++],r=e[u++],s=n>>2,a=(3&n)<<4|i>>4,o=(15&i)<<2|r>>6,l=63&r,isNaN(i)?o=l=64:isNaN(r)&&(l=64),t=t+this.KEY_STR.charAt(s)+this.KEY_STR.charAt(a)+this.KEY_STR.charAt(o)+this.KEY_STR.charAt(l),n=i=r="",s=a=o=l="",!(ue.length)break}return n}},{key:"decode64",value:function(e){var t=void 0,n=void 0,i="",r=void 0,s=void 0,a=void 0,o="",l=0,u=[],d=/[^A-Za-z0-9\+\/\=]/g;for(d.exec(e)&&console.warn("There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\nExpect errors in decoding."),e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");;)if(r=this.KEY_STR.indexOf(e.charAt(l++)),s=this.KEY_STR.indexOf(e.charAt(l++)),a=this.KEY_STR.indexOf(e.charAt(l++)),o=this.KEY_STR.indexOf(e.charAt(l++)),t=r<<2|s>>4,n=(15&s)<<4|a>>2,i=(3&a)<<6|o,u.push(t),64!==a&&u.push(n),64!==o&&u.push(i),t=n=i="",r=s=a=o="",!(l0;--t)e.removeChild(e.firstChild);return e}function n(e,n){return t(e).appendChild(n)}function i(e,t,n,i){var r=document.createElement(e);if(n&&(r.className=n),i&&(r.style.cssText=i),"string"==typeof t)r.appendChild(document.createTextNode(t));else if(t)for(var s=0;s=t)return a+(t-s);a+=o-s,a+=n-a%n,s=o+1}}function h(e,t){for(var n=0;n=t)return i+Math.min(a,t-r);if(r+=s-i,r+=n-r%n,i=s+1,r>=t)return i}}function f(e){for(;Va.length<=e;)Va.push(m(Va)+" ");return Va[e]}function m(e){return e[e.length-1]}function g(e,t){for(var n=[],i=0;i""&&(e.toUpperCase()!=e.toLowerCase()||Ka.test(e))}function D(e,t){return t?!!(t.source.indexOf("\\w")>-1&&M(e))||t.test(e):M(e)}function w(e){for(var t in e)if(e.hasOwnProperty(t)&&e[t])return!1;return!0}function x(e){return e.charCodeAt(0)>=768&&qa.test(e)}function b(e,t,n){for(;(n<0?t>0:tn?-1:1;;){if(t==n)return t;var r=(t+n)/2,s=i<0?Math.ceil(r):Math.floor(r);if(s==t)return e(s)?t:n;e(s)?n=s:t=s+i}}function L(e,t,n){var s=this;this.input=n,s.scrollbarFiller=i("div",null,"CodeMirror-scrollbar-filler"),s.scrollbarFiller.setAttribute("cm-not-content","true"),s.gutterFiller=i("div",null,"CodeMirror-gutter-filler"),s.gutterFiller.setAttribute("cm-not-content","true"),s.lineDiv=r("div",null,"CodeMirror-code"),s.selectionDiv=i("div",null,null,"position: relative; z-index: 1"),s.cursorDiv=i("div",null,"CodeMirror-cursors"),s.measure=i("div",null,"CodeMirror-measure"),s.lineMeasure=i("div",null,"CodeMirror-measure"),s.lineSpace=r("div",[s.measure,s.lineMeasure,s.selectionDiv,s.cursorDiv,s.lineDiv],null,"position: relative; outline: none");var a=r("div",[s.lineSpace],"CodeMirror-lines");s.mover=i("div",[a],null,"position: relative"),s.sizer=i("div",[s.mover],"CodeMirror-sizer"),s.sizerWidth=null,s.heightForcer=i("div",null,null,"position: absolute; height: "+Ua+"px; width: 1px;"),s.gutters=i("div",null,"CodeMirror-gutters"),s.lineGutter=null,s.scroller=i("div",[s.sizer,s.heightForcer,s.gutters],"CodeMirror-scroll"),s.scroller.setAttribute("tabIndex","-1"),s.wrapper=i("div",[s.scrollbarFiller,s.gutterFiller,s.scroller],"CodeMirror"),Ma&&Da<8&&(s.gutters.style.zIndex=-1,s.scroller.style.paddingRight=0),wa||ga&&Ea||(s.scroller.draggable=!0),e&&(e.appendChild?e.appendChild(s.wrapper):e(s.wrapper)),s.viewFrom=s.viewTo=t.first,s.reportedViewFrom=s.reportedViewTo=t.first,s.view=[],s.renderedView=null,s.externalMeasured=null,s.viewOffset=0,s.lastWrapHeight=s.lastWrapWidth=0,s.updateLineNumbers=null,s.nativeBarWidth=s.barHeight=s.barWidth=0,s.scrollbarsClipped=!1,s.lineNumWidth=s.lineNumInnerWidth=s.lineNumChars=null,s.alignWidgets=!1,s.cachedCharWidth=s.cachedTextHeight=s.cachedPaddingH=null,s.maxLine=null,s.maxLineLength=0,s.maxLineChanged=!1,s.wheelDX=s.wheelDY=s.wheelStartX=s.wheelStartY=null,s.shift=!1,s.selForContextMenu=null,s.activeTouch=null,n.init(s)}function S(e,t){if(t-=e.first,t<0||t>=e.size)throw new Error("There is no line "+(t+e.first)+" in the document.");for(var n=e;!n.lines;)for(var i=0;;++i){var r=n.children[i],s=r.chunkSize();if(t=e.first&&tn?H(n,S(e,n).text.length):R(t,S(e,t.line).text.length)}function R(e,t){var n=e.ch;return null==n||n>t?H(e.line,t):n<0?H(e.line,0):e}function U(e,t){for(var n=[],i=0;i=t:s.to>t);(i||(i=[])).push(new G(a,s.from,l?null:s.to))}}return i}function Z(e,t,n){var i;if(e)for(var r=0;r=t:s.to>t);if(o||s.from==t&&"bookmark"==a.type&&(!n||s.marker.insertLeft)){var l=null==s.from||(a.inclusiveLeft?s.from<=t:s.from0&&o)for(var D=0;D0)){var d=[l,1],c=P(u.from,o.from),p=P(u.to,o.to);(c<0||!a.inclusiveLeft&&!c)&&d.push({from:u.from,to:o.from}),(p>0||!a.inclusiveRight&&!p)&&d.push({from:o.to,to:u.to}),r.splice.apply(r,d),l+=d.length-3}}return r}function ne(e){var t=e.markedSpans;if(t){for(var n=0;nt)&&(!n||ae(n,s.marker)<0)&&(n=s.marker)}return n}function ce(e,t,n,i,r){var s=S(e,t),a=Qa&&s.markedSpans;if(a)for(var o=0;o=0&&c<=0||d<=0&&c>=0)&&(d<=0&&(l.marker.inclusiveRight&&r.inclusiveLeft?P(u.to,n)>=0:P(u.to,n)>0)||d>=0&&(l.marker.inclusiveRight&&r.inclusiveLeft?P(u.from,i)<=0:P(u.from,i)<0)))return!0}}}function he(e){for(var t;t=le(e);)e=t.find(-1,!0).line;return e}function pe(e){for(var t;t=ue(e);)e=t.find(1,!0).line;return e}function fe(e){for(var t,n;t=ue(e);)e=t.find(1,!0).line,(n||(n=[])).push(e);return n}function me(e,t){var n=S(e,t),i=he(n);return n==i?t:E(i)}function ge(e,t){if(t>e.lastLine())return t;var n,i=S(e,t);if(!_e(e,i))return t;for(;n=ue(i);)i=n.find(1,!0).line;return E(i)+1}function _e(e,t){var n=Qa&&t.markedSpans;if(n)for(var i=void 0,r=0;rt.maxLineLength&&(t.maxLineLength=n,t.maxLine=e)})}function we(e,t,n,i){if(!e)return i(t,n,"ltr",0);for(var r=!1,s=0;st||t==n&&a.to==t)&&(i(Math.max(a.from,t),Math.min(a.to,n),1==a.level?"rtl":"ltr",s),r=!0)}r||i(t,n,"ltr")}function xe(e,t,n){var i;eo=null;for(var r=0;rt)return r;s.to==t&&(s.from!=s.to&&"before"==n?i=r:eo=r),s.from==t&&(s.from!=s.to&&"before"!=n?i=r:eo=r)}return null!=i?i:eo}function be(e,t){var n=e.order;return null==n&&(n=e.order=to(e.text,t)),n}function ke(e,t){return e._handlers&&e._handlers[t]||no}function Le(e,t,n){if(e.removeEventListener)e.removeEventListener(t,n,!1);else if(e.detachEvent)e.detachEvent("on"+t,n);else{var i=e._handlers,r=i&&i[t];if(r){var s=h(r,n);s>-1&&(i[t]=r.slice(0,s).concat(r.slice(s+1)))}}}function Se(e,t){var n=ke(e,t);if(n.length)for(var i=Array.prototype.slice.call(arguments,2),r=0;r0}function Ee(e){e.prototype.on=function(e,t){io(this,e,t)},e.prototype.off=function(e,t){Le(this,e,t)}}function Ae(e){e.preventDefault?e.preventDefault():e.returnValue=!1}function Fe(e){e.stopPropagation?e.stopPropagation():e.cancelBubble=!0}function Oe(e){return null!=e.defaultPrevented?e.defaultPrevented:0==e.returnValue}function He(e){Ae(e),Fe(e)}function Pe(e){return e.target||e.srcElement}function je(e){var t=e.which;return null==t&&(1&e.button?t=1:2&e.button?t=3:4&e.button&&(t=2)),Aa&&e.ctrlKey&&1==t&&(t=3),t}function Ne(e){if(null==Wa){var t=i("span","");n(e,i("span",[t,document.createTextNode("x")])),0!=e.firstChild.offsetHeight&&(Wa=t.offsetWidth<=1&&t.offsetHeight>2&&!(Ma&&Da<8))}var r=Wa?i("span",""):i("span"," ",null,"display: inline-block; width: 1px; margin-right: -1px");return r.setAttribute("cm-text",""),r}function Ie(e){if(null!=Ra)return Ra;var i=n(e,document.createTextNode("AخA")),r=Pa(i,0,1).getBoundingClientRect(),s=Pa(i,1,2).getBoundingClientRect();return t(e),!(!r||r.left==r.right)&&(Ra=s.right-r.right<3)}function Be(e){if(null!=lo)return lo;var t=n(e,i("span","x")),r=t.getBoundingClientRect(),s=Pa(t,0,1).getBoundingClientRect();return lo=Math.abs(r.left-s.left)>1}function ze(e,t){arguments.length>2&&(t.dependencies=Array.prototype.slice.call(arguments,2)),uo[e]=t}function We(e,t){co[e]=t}function Re(e){if("string"==typeof e&&co.hasOwnProperty(e))e=co[e];else if(e&&"string"==typeof e.name&&co.hasOwnProperty(e.name)){var t=co[e.name];"string"==typeof t&&(t={name:t}),e=y(t,e),e.name=t.name}else{if("string"==typeof e&&/^[\w\-]+\/[\w\-]+\+xml$/.test(e))return Re("application/xml");if("string"==typeof e&&/^[\w\-]+\/[\w\-]+\+json$/.test(e))return Re("application/json")}return"string"==typeof e?{name:e}:e||{name:"null"}}function Ue(e,t){t=Re(t);var n=uo[t.name];if(!n)return Ue(e,"text/plain");var i=n(e,t);if(ho.hasOwnProperty(t.name)){var r=ho[t.name];for(var s in r)r.hasOwnProperty(s)&&(i.hasOwnProperty(s)&&(i["_"+s]=i[s]),i[s]=r[s])}if(i.name=t.name,t.helperType&&(i.helperType=t.helperType),t.modeProps)for(var a in t.modeProps)i[a]=t.modeProps[a];return i}function $e(e,t){var n=ho.hasOwnProperty(e)?ho[e]:ho[e]={};d(t,n)}function Je(e,t){if(t===!0)return t;if(e.copyState)return e.copyState(t);var n={};for(var i in t){var r=t[i];r instanceof Array&&(r=r.concat([])),n[i]=r}return n}function Ge(e,t){for(var n;e.innerMode&&(n=e.innerMode(t),n&&n.mode!=e);)t=n.state,e=n.mode;return n||{mode:e,state:t}}function Xe(e,t,n){return!e.startState||e.startState(t,n)}function Ve(e,t,n,i){var r=[e.state.modeGen],s={};it(e,t.text,e.doc.mode,n,function(e,t){return r.push(e,t)},s,i);for(var a=n.state,o=function(i){n.baseTokens=r;var o=e.state.overlays[i],l=1,u=0;n.state=!0,it(e,t.text,o.mode,n,function(e,t){for(var n=l;ue&&r.splice(l,1,e,r[l+1],i),l+=2,u=Math.min(e,i)}if(t)if(o.opaque)r.splice(n,l-n,e,"overlay "+t),l=n+2;else for(;ne.options.maxHighlightLength&&Je(e.doc.mode,i.state),s=Ve(e,t,i);r&&(i.state=r),t.stateAfter=i.save(!r),t.styles=s.styles,s.classes?t.styleClasses=s.classes:t.styleClasses&&(t.styleClasses=null),n===e.doc.highlightFrontier&&(e.doc.modeFrontier=Math.max(e.doc.modeFrontier,++e.doc.highlightFrontier))}return t.styles}function qe(e,t,n){var i=e.doc,r=e.display;if(!i.mode.startState)return new mo(i,!0,t);var s=rt(e,t,n),a=s>i.first&&S(i,s-1).stateAfter,o=a?mo.fromSaved(i,a,s):new mo(i,Xe(i.mode),s);return i.iter(s,t,function(n){Ze(e,n.text,o);var i=o.line;n.stateAfter=i==t-1||i%5==0||i>=r.viewFrom&&it.start)return s}throw new Error("Mode "+e.name+" failed to advance stream.")}function tt(e,t,n,i){var r,s=e.doc,a=s.mode;t=W(s,t);var o,l=S(s,t.line),u=qe(e,t.line,n),d=new po(l.text,e.options.tabSize,u);for(i&&(o=[]);(i||d.pose.options.maxHighlightLength?(o=!1,a&&Ze(e,t,i,c.pos),c.pos=t.length,l=null):l=nt(et(n,c,i.state,h),s),h){var p=h[0].name;p&&(l="m-"+(l?p+" "+l:p))}if(!o||d!=l){for(;ua;--o){if(o<=s.first)return s.first;var l=S(s,o-1),u=l.stateAfter;if(u&&(!n||o+(u instanceof fo?u.lookAhead:0)<=s.modeFrontier))return o;var d=c(l.text,null,e.options.tabSize);(null==r||i>d)&&(r=o-1,i=d)}return r}function st(e,t){if(e.modeFrontier=Math.min(e.modeFrontier,t),!(e.highlightFrontiern;i--){var r=S(e,i).stateAfter;if(r&&(!(r instanceof fo)||i+r.lookAhead1&&!/ /.test(e))return e;for(var n=t,i="",r=0;ru&&c.from<=u));h++);if(c.to>=d)return e(n,i,r,s,a,o,l);e(n,i.slice(0,c.to-u),r,s,null,o,l),s=null,i=i.slice(c.to-u),u=c.to}}}function ft(e,t,n,i){var r=!i&&n.widgetNode;r&&e.map.push(e.pos,e.pos+t,r),!i&&e.cm.display.input.needsContentAttribute&&(r||(r=e.content.appendChild(document.createElement("span"))),r.setAttribute("cm-marker",n.id)),r&&(e.cm.display.input.setUneditable(r),e.content.appendChild(r)),e.pos+=t,e.trailingSpace=!1}function mt(e,t,n){var i=e.markedSpans,r=e.text,s=0;if(i)for(var a,o,l,u,d,c,h,p=r.length,f=0,m=1,g="",_=0;;){if(_==f){l=u=d=c=o="",h=null,_=1/0;for(var v=[],y=void 0,M=0;Mf||w.collapsed&&D.to==f&&D.from==f)?(null!=D.to&&D.to!=f&&_>D.to&&(_=D.to,u=""),w.className&&(l+=" "+w.className),w.css&&(o=(o?o+";":"")+w.css),w.startStyle&&D.from==f&&(d+=" "+w.startStyle),w.endStyle&&D.to==_&&(y||(y=[])).push(w.endStyle,D.to),w.title&&!c&&(c=w.title),w.collapsed&&(!h||ae(h.marker,w)<0)&&(h=D)):D.from>f&&_>D.from&&(_=D.from)}if(y)for(var x=0;x=p)break;for(var k=Math.min(p,_);;){if(g){var L=f+g.length;if(!h){var S=L>k?g.slice(0,k-f):g;t.addToken(t,S,a?a+l:l,d,f+S.length==_?u:"",c,o)}if(L>=k){g=g.slice(k-f),f=k;break}f=L,d=""}g=r.slice(s,s=n[m++]),a=lt(n[m++],t.cm.options)}}else for(var T=1;T2&&s.push((l.bottom+u.top)/2-n.top)}}s.push(n.bottom-n.top)}}function Ut(e,t,n){if(e.line==t)return{map:e.measure.map,cache:e.measure.cache};for(var i=0;in)return{map:e.measure.maps[r],cache:e.measure.caches[r],before:!0}}function $t(e,t){t=he(t);var i=E(t),r=e.display.externalMeasured=new gt(e.doc,t,i);r.lineN=i;var s=r.built=ut(e,r);return r.text=s.pre,n(e.display.lineMeasure,s.pre),r}function Jt(e,t,n,i){return Vt(e,Xt(e,t),n,i)}function Gt(e,t){if(t>=e.display.viewFrom&&t=n.lineN&&tt)&&(s=l-o,r=s-1,t>=l&&(a="right")),null!=r){if(i=e[u+2],o==l&&n==(i.insertLeft?"left":"right")&&(a=n),"left"==n&&0==r)for(;u&&e[u-2]==e[u-3]&&e[u-1].insertLeft;)i=e[(u-=3)+2],a="left";if("right"==n&&r==l-o)for(;u=0&&(n=e[r]).left==n.right;r--);return n}function Zt(e,t,n,i){var r,s=Kt(t.map,n,i),a=s.node,o=s.start,l=s.end,u=s.collapse;if(3==a.nodeType){for(var d=0;d<4;d++){for(;o&&x(t.line.text.charAt(s.coverStart+o));)--o;for(;s.coverStart+l0&&(u=i="right");var c;r=e.options.lineWrapping&&(c=a.getClientRects()).length>1?c["right"==i?c.length-1:0]:a.getBoundingClientRect()}if(Ma&&Da<9&&!o&&(!r||!r.left&&!r.right)){var h=a.parentNode.getClientRects()[0];r=h?{left:h.left,right:h.left+Dn(e.display),top:h.top,bottom:h.bottom}:xo}for(var p=r.top-t.rect.top,f=r.bottom-t.rect.top,m=(p+f)/2,g=t.view.measure.heights,_=0;_=i.text.length?(u=i.text.length,d="before"):u<=0&&(u=0,d="after"),!l)return a("before"==d?u-1:u,"before"==d);var c=xe(l,u,d),h=eo,p=o(u,c,"before"==d);return null!=h&&(p.other=o(u,h,"before"!=d)),p}function cn(e,t){var n=0;t=W(e.doc,t),e.options.lineWrapping||(n=Dn(e.display)*t.ch);var i=S(e.doc,t.line),r=ye(i)+jt(e.display);return{left:n,right:n,top:r,bottom:r+i.height}}function hn(e,t,n,i,r){var s=H(e,t,n);return s.xRel=r,i&&(s.outside=!0),s}function pn(e,t,n){var i=e.doc;if(n+=e.display.viewOffset,n<0)return hn(i.first,0,null,!0,-1);var r=A(i,n),s=i.first+i.size-1;if(r>s)return hn(i.first+i.size-1,S(i,s).text.length,null,!0,1);t<0&&(t=0);for(var a=S(i,r);;){var o=_n(e,a,r,t,n),l=de(a,o.ch+(o.xRel>0?1:0));if(!l)return o;var u=l.find(1);if(u.line==r)return u;a=S(i,r=u.line)}}function fn(e,t,n,i){i-=an(t);var r=t.text.length,s=k(function(t){return Vt(e,n,t-1).bottom<=i},r,0);return r=k(function(t){return Vt(e,n,t).top>i},s,r),{begin:s,end:r}}function mn(e,t,n,i){n||(n=Xt(e,t));var r=on(e,t,Vt(e,n,i),"line").top;return fn(e,t,n,r)}function gn(e,t,n,i){return!(e.bottom<=n)&&(e.top>n||(i?e.left:e.right)>t)}function _n(e,t,n,i,r){r-=ye(t);var s=Xt(e,t),a=an(t),o=0,l=t.text.length,u=!0,d=be(t,e.doc.direction);if(d){var c=(e.options.lineWrapping?yn:vn)(e,t,n,s,d,i,r);u=1!=c.level,o=u?c.from:c.to-1,l=u?c.to:c.from-1}var h,p,f=null,m=null,g=k(function(t){var n=Vt(e,s,t);return n.top+=a,n.bottom+=a,!!gn(n,i,r,!1)&&(n.top<=r&&n.left<=i&&(f=t,m=n),!0)},o,l),_=!1;if(m){var v=i-m.left=M.bottom}return g=b(t.text,g,1),hn(n,g,p,_,i-h)}function vn(e,t,n,i,r,s,a){var o=k(function(o){var l=r[o],u=1!=l.level;return gn(dn(e,H(n,u?l.to:l.from,u?"before":"after"),"line",t,i),s,a,!0)},0,r.length-1),l=r[o];if(o>0){var u=1!=l.level,d=dn(e,H(n,u?l.from:l.to,u?"after":"before"),"line",t,i);gn(d,s,a,!0)&&d.top>a&&(l=r[o-1])}return l}function yn(e,t,n,i,r,s,a){var o=fn(e,t,i,a),l=o.begin,u=o.end;/\s/.test(t.text.charAt(u-1))&&u--;for(var d=null,c=null,h=0;h=u||p.to<=l)){var f=1!=p.level,m=Vt(e,i,f?Math.min(u,p.to)-1:Math.max(l,p.from)).right,g=mg)&&(d=p,c=g)}}return d||(d=r[r.length-1]),d.fromu&&(d={from:d.from,to:u,level:d.level}),d}function Mn(e){if(null!=e.cachedTextHeight)return e.cachedTextHeight;if(null==vo){vo=i("pre");for(var r=0;r<49;++r)vo.appendChild(document.createTextNode("x")),vo.appendChild(i("br"));vo.appendChild(document.createTextNode("x"))}n(e.measure,vo);var s=vo.offsetHeight/50;return s>3&&(e.cachedTextHeight=s),t(e.measure),s||1}function Dn(e){if(null!=e.cachedCharWidth)return e.cachedCharWidth;var t=i("span","xxxxxxxxxx"),r=i("pre",[t]);n(e.measure,r);var s=t.getBoundingClientRect(),a=(s.right-s.left)/10;return a>2&&(e.cachedCharWidth=a),a||10}function wn(e){for(var t=e.display,n={},i={},r=t.gutters.clientLeft,s=t.gutters.firstChild,a=0;s;s=s.nextSibling,++a)n[e.options.gutters[a]]=s.offsetLeft+s.clientLeft+r,i[e.options.gutters[a]]=s.clientWidth;return{fixedPos:xn(t),gutterTotalWidth:t.gutters.offsetWidth,gutterLeft:n,gutterWidth:i,wrapperWidth:t.wrapper.clientWidth}}function xn(e){return e.scroller.getBoundingClientRect().left-e.sizer.getBoundingClientRect().left}function bn(e){var t=Mn(e.display),n=e.options.lineWrapping,i=n&&Math.max(5,e.display.scroller.clientWidth/Dn(e.display)-3);return function(r){if(_e(e.doc,r))return 0;var s=0;if(r.widgets)for(var a=0;a=e.display.viewTo)return null;if(t-=e.display.viewFrom,t<0)return null;for(var n=e.display.view,i=0;i=e.display.viewTo||o.to().line0?t.blinker=setInterval(function(){return t.cursorDiv.style.visibility=(n=!n)?"":"hidden"},e.options.cursorBlinkRate):e.options.cursorBlinkRate<0&&(t.cursorDiv.style.visibility="hidden")}}function On(e){e.state.focused||(e.display.input.focus(),Pn(e))}function Hn(e){e.state.delayingBlurEvent=!0,setTimeout(function(){e.state.delayingBlurEvent&&(e.state.delayingBlurEvent=!1,jn(e))},100)}function Pn(e,t){e.state.delayingBlurEvent&&(e.state.delayingBlurEvent=!1),"nocursor"!=e.options.readOnly&&(e.state.focused||(Se(e,"focus",e,t),e.state.focused=!0,o(e.display.wrapper,"CodeMirror-focused"),e.curOp||e.display.selForContextMenu==e.doc.sel||(e.display.input.reset(),wa&&setTimeout(function(){return e.display.input.reset(!0)},20)),e.display.input.receivedFocus()),Fn(e))}function jn(e,t){e.state.delayingBlurEvent||(e.state.focused&&(Se(e,"blur",e,t),e.state.focused=!1,Ia(e.display.wrapper,"CodeMirror-focused")),clearInterval(e.display.blinker),setTimeout(function(){e.state.focused||(e.display.shift=!1)},150))}function Nn(e){for(var t=e.display,n=t.lineDiv.offsetTop,i=0;i.005||l<-.005)&&(Y(r.line,s),In(r.line),r.rest))for(var u=0;u=a&&(s=A(t,ye(S(t,l))-e.wrapper.clientHeight),a=l)}return{from:s,to:Math.max(a,s+1)}}function zn(e){var t=e.display,n=t.view;if(t.alignWidgets||t.gutters.firstChild&&e.options.fixedGutter){for(var i=xn(t)-t.scroller.scrollLeft+e.doc.scrollLeft,r=t.gutters.offsetWidth,s=i+"px",a=0;a(window.innerHeight||document.documentElement.clientHeight)&&(s=!1),null!=s&&!Ta){var a=i("div","",null,"position: absolute;\n top: "+(t.top-n.viewOffset-jt(e.display))+"px;\n height: "+(t.bottom-t.top+Bt(e)+n.barHeight)+"px;\n left: "+t.left+"px; width: "+Math.max(2,t.right-t.left)+"px;");e.display.lineSpace.appendChild(a),a.scrollIntoView(s),e.display.lineSpace.removeChild(a)}}}function Un(e,t,n,i){null==i&&(i=0);var r;e.options.lineWrapping||t!=n||(t=t.ch?H(t.line,"before"==t.sticky?t.ch-1:t.ch,"after"):t,n="before"==t.sticky?H(t.line,t.ch+1,"before"):t);for(var s=0;s<5;s++){var a=!1,o=dn(e,t),l=n&&n!=t?dn(e,n):o;r={left:Math.min(o.left,l.left),top:Math.min(o.top,l.top)-i,right:Math.max(o.left,l.left),bottom:Math.max(o.bottom,l.bottom)+i};var u=Jn(e,r),d=e.doc.scrollTop,c=e.doc.scrollLeft;if(null!=u.scrollTop&&(Qn(e,u.scrollTop),Math.abs(e.doc.scrollTop-d)>1&&(a=!0)),null!=u.scrollLeft&&(ti(e,u.scrollLeft),Math.abs(e.doc.scrollLeft-c)>1&&(a=!0)),!a)break}return r}function $n(e,t){var n=Jn(e,t);null!=n.scrollTop&&Qn(e,n.scrollTop),null!=n.scrollLeft&&ti(e,n.scrollLeft)}function Jn(e,t){var n=e.display,i=Mn(e.display);t.top<0&&(t.top=0);var r=e.curOp&&null!=e.curOp.scrollTop?e.curOp.scrollTop:n.scroller.scrollTop,s=Wt(e),a={};t.bottom-t.top>s&&(t.bottom=t.top+s);var o=e.doc.height+Nt(n),l=t.topo-i;if(t.topr+s){var d=Math.min(t.top,(u?o:t.bottom)-s);d!=r&&(a.scrollTop=d)}var c=e.curOp&&null!=e.curOp.scrollLeft?e.curOp.scrollLeft:n.scroller.scrollLeft,h=zt(e)-(e.options.fixedGutter?n.gutters.offsetWidth:0),p=t.right-t.left>h;return p&&(t.right=t.left+h),t.left<10?a.scrollLeft=0:t.lefth+c-3&&(a.scrollLeft=t.right+(p?0:10)-h),a}function Gn(e,t){null!=t&&(qn(e),e.curOp.scrollTop=(null==e.curOp.scrollTop?e.doc.scrollTop:e.curOp.scrollTop)+t)}function Xn(e){qn(e);var t=e.getCursor();e.curOp.scrollToPos={from:t,to:t,margin:e.options.cursorScrollMargin}}function Vn(e,t,n){null==t&&null==n||qn(e),null!=t&&(e.curOp.scrollLeft=t),null!=n&&(e.curOp.scrollTop=n)}function Kn(e,t){qn(e),e.curOp.scrollToPos=t}function qn(e){var t=e.curOp.scrollToPos;if(t){e.curOp.scrollToPos=null;var n=cn(e,t.from),i=cn(e,t.to);Zn(e,n,i,t.margin)}}function Zn(e,t,n,i){var r=Jn(e,{left:Math.min(t.left,n.left),top:Math.min(t.top,n.top)-i,right:Math.max(t.right,n.right),bottom:Math.max(t.bottom,n.bottom)+i});Vn(e,r.scrollLeft,r.scrollTop)}function Qn(e,t){Math.abs(e.doc.scrollTop-t)<2||(ga||Ei(e,{top:t}),ei(e,t,!0),ga&&Ei(e),bi(e,100))}function ei(e,t,n){t=Math.min(e.display.scroller.scrollHeight-e.display.scroller.clientHeight,t),(e.display.scroller.scrollTop!=t||n)&&(e.doc.scrollTop=t,e.display.scrollbars.setScrollTop(t),e.display.scroller.scrollTop!=t&&(e.display.scroller.scrollTop=t))}function ti(e,t,n,i){t=Math.min(t,e.display.scroller.scrollWidth-e.display.scroller.clientWidth),(n?t==e.doc.scrollLeft:Math.abs(e.doc.scrollLeft-t)<2)&&!i||(e.doc.scrollLeft=t,zn(e),e.display.scroller.scrollLeft!=t&&(e.display.scroller.scrollLeft=t),e.display.scrollbars.setScrollLeft(t))}function ni(e){var t=e.display,n=t.gutters.offsetWidth,i=Math.round(e.doc.height+Nt(e.display));return{clientHeight:t.scroller.clientHeight,viewHeight:t.wrapper.clientHeight,scrollWidth:t.scroller.scrollWidth,clientWidth:t.scroller.clientWidth,viewWidth:t.wrapper.clientWidth,barLeft:e.options.fixedGutter?n:0,docHeight:i,scrollHeight:i+Bt(e)+t.barHeight,nativeBarWidth:t.nativeBarWidth,gutterWidth:n}}function ii(e,t){t||(t=ni(e));var n=e.display.barWidth,i=e.display.barHeight;ri(e,t);for(var r=0;r<4&&n!=e.display.barWidth||i!=e.display.barHeight;r++)n!=e.display.barWidth&&e.options.lineWrapping&&Nn(e),ri(e,ni(e)),n=e.display.barWidth,i=e.display.barHeight}function ri(e,t){var n=e.display,i=n.scrollbars.update(t);n.sizer.style.paddingRight=(n.barWidth=i.right)+"px",n.sizer.style.paddingBottom=(n.barHeight=i.bottom)+"px",n.heightForcer.style.borderBottom=i.bottom+"px solid transparent",i.right&&i.bottom?(n.scrollbarFiller.style.display="block",n.scrollbarFiller.style.height=i.bottom+"px",n.scrollbarFiller.style.width=i.right+"px"):n.scrollbarFiller.style.display="",i.bottom&&e.options.coverGutterNextToScrollbar&&e.options.fixedGutter?(n.gutterFiller.style.display="block",n.gutterFiller.style.height=i.bottom+"px",n.gutterFiller.style.width=t.gutterWidth+"px"):n.gutterFiller.style.display=""}function si(e){e.display.scrollbars&&(e.display.scrollbars.clear(),e.display.scrollbars.addClass&&Ia(e.display.wrapper,e.display.scrollbars.addClass)),e.display.scrollbars=new Lo[e.options.scrollbarStyle](function(t){e.display.wrapper.insertBefore(t,e.display.scrollbarFiller),io(t,"mousedown",function(){e.state.focused&&setTimeout(function(){return e.display.input.focus()},0)}),t.setAttribute("cm-not-content","true")},function(t,n){"horizontal"==n?ti(e,t):Qn(e,t)},e),e.display.scrollbars.addClass&&o(e.display.wrapper,e.display.scrollbars.addClass)}function ai(e){e.curOp={cm:e,viewChanged:!1,startHeight:e.doc.height,forceUpdate:!1,updateInput:null,typing:!1,changeObjs:null,cursorActivityHandlers:null,cursorActivityCalled:0,selectionChanged:!1,updateMaxLine:!1,scrollLeft:null,scrollTop:null,scrollToPos:null,focus:!1,id:++So},vt(e.curOp)}function oi(e){var t=e.curOp;Mt(t,function(e){for(var t=0;t=n.viewTo)||n.maxLineChanged&&t.options.lineWrapping,e.update=e.mustUpdate&&new To(t,e.mustUpdate&&{top:e.scrollTop,ensure:e.scrollToPos},e.forceUpdate)}function di(e){e.updatedDisplay=e.mustUpdate&&Ci(e.cm,e.update)}function ci(e){var t=e.cm,n=t.display;e.updatedDisplay&&Nn(t),e.barMeasure=ni(t),n.maxLineChanged&&!t.options.lineWrapping&&(e.adjustWidthTo=Jt(t,n.maxLine,n.maxLine.text.length).left+3,t.display.sizerWidth=e.adjustWidthTo,e.barMeasure.scrollWidth=Math.max(n.scroller.clientWidth,n.sizer.offsetLeft+e.adjustWidthTo+Bt(t)+t.display.barWidth),e.maxScrollLeft=Math.max(0,n.sizer.offsetLeft+e.adjustWidthTo-zt(t))),(e.updatedDisplay||e.selectionChanged)&&(e.preparedSelection=n.input.prepareSelection())}function hi(e){var t=e.cm;null!=e.adjustWidthTo&&(t.display.sizer.style.minWidth=e.adjustWidthTo+"px",e.maxScrollLeftt)&&(r.updateLineNumbers=t),e.curOp.viewChanged=!0,t>=r.viewTo)Qa&&me(e.doc,t)r.viewFrom?Mi(e):(r.viewFrom+=i,r.viewTo+=i);else if(t<=r.viewFrom&&n>=r.viewTo)Mi(e);else if(t<=r.viewFrom){var s=Di(e,n,n+i,1);s?(r.view=r.view.slice(s.index),r.viewFrom=s.lineN,r.viewTo+=i):Mi(e)}else if(n>=r.viewTo){var a=Di(e,t,t,-1);a?(r.view=r.view.slice(0,a.index),r.viewTo=a.lineN):Mi(e)}else{var o=Di(e,t,t,-1),l=Di(e,n,n+i,1);o&&l?(r.view=r.view.slice(0,o.index).concat(_t(e,o.lineN,l.lineN)).concat(r.view.slice(l.index)),r.viewTo+=i):Mi(e)}var u=r.externalMeasured;u&&(n=r.lineN&&t=i.viewTo)){var s=i.view[Sn(e,t)];if(null!=s.node){var a=s.changes||(s.changes=[]);h(a,n)==-1&&a.push(n)}}}function Mi(e){e.display.viewFrom=e.display.viewTo=e.doc.first,e.display.view=[],e.display.viewOffset=0}function Di(e,t,n,i){var r,s=Sn(e,t),a=e.display.view;if(!Qa||n==e.doc.first+e.doc.size)return{index:s,lineN:n};for(var o=e.display.viewFrom,l=0;l0){if(s==a.length-1)return null;r=o+a[s].size-t,s++}else r=o-t;t+=r,n+=r}for(;me(e.doc,n)!=n;){if(s==(i<0?0:a.length-1))return null;n+=i*a[s-(i<0?1:0)].size,s+=i}return{index:s,lineN:n}}function wi(e,t,n){var i=e.display,r=i.view;0==r.length||t>=i.viewTo||n<=i.viewFrom?(i.view=_t(e,t,n),i.viewFrom=t):(i.viewFrom>t?i.view=_t(e,t,i.viewFrom).concat(i.view):i.viewFromn&&(i.view=i.view.slice(0,Sn(e,n)))),i.viewTo=n}function xi(e){for(var t=e.display.view,n=0,i=0;i=e.display.viewTo)){var n=+new Date+e.options.workTime,i=qe(e,t.highlightFrontier),r=[];t.iter(i.line,Math.min(t.first+t.size,e.display.viewTo+500),function(s){if(i.line>=e.display.viewFrom){var a=s.styles,o=s.text.length>e.options.maxHighlightLength?Je(t.mode,i.state):null,l=Ve(e,s,i,!0);o&&(i.state=o),s.styles=l.styles;var u=s.styleClasses,d=l.classes;d?s.styleClasses=d:u&&(s.styleClasses=null);for(var c=!a||a.length!=s.styles.length||u!=d&&(!u||!d||u.bgClass!=d.bgClass||u.textClass!=d.textClass),h=0;!c&&hn)return bi(e,e.options.workDelay),!0}),t.highlightFrontier=i.line,t.modeFrontier=Math.max(t.modeFrontier,i.line),r.length&&fi(e,function(){for(var t=0;t=i.viewFrom&&n.visible.to<=i.viewTo&&(null==i.updateLineNumbers||i.updateLineNumbers>=i.viewTo)&&i.renderedView==i.view&&0==xi(e))return!1;Wn(e)&&(Mi(e),n.dims=wn(e));var s=r.first+r.size,a=Math.max(n.visible.from-e.options.viewportMargin,r.first),o=Math.min(s,n.visible.to+e.options.viewportMargin);i.viewFromo&&i.viewTo-o<20&&(o=Math.min(s,i.viewTo)),Qa&&(a=me(e.doc,a),o=ge(e.doc,o));var l=a!=i.viewFrom||o!=i.viewTo||i.lastWrapHeight!=n.wrapperHeight||i.lastWrapWidth!=n.wrapperWidth;wi(e,a,o),i.viewOffset=ye(S(e.doc,i.viewFrom)),e.display.mover.style.top=i.viewOffset+"px";var u=xi(e);if(!l&&0==u&&!n.force&&i.renderedView==i.view&&(null==i.updateLineNumbers||i.updateLineNumbers>=i.viewTo))return!1;var d=Si(e);return u>4&&(i.lineDiv.style.display="none"),Ai(e,i.updateLineNumbers,n.dims),u>4&&(i.lineDiv.style.display=""),i.renderedView=i.view,Ti(d),t(i.cursorDiv),t(i.selectionDiv),i.gutters.style.height=i.sizer.style.minHeight=0,l&&(i.lastWrapHeight=n.wrapperHeight,i.lastWrapWidth=n.wrapperWidth,bi(e,400)),i.updateLineNumbers=null,!0}function Yi(e,t){for(var n=t.viewport,i=!0;(i&&e.options.lineWrapping&&t.oldDisplayWidth!=zt(e)||(n&&null!=n.top&&(n={top:Math.min(e.doc.height+Nt(e.display)-Wt(e),n.top)}),t.visible=Bn(e.display,e.doc,n),!(t.visible.from>=e.display.viewFrom&&t.visible.to<=e.display.viewTo)))&&Ci(e,t);i=!1){Nn(e);var r=ni(e);Tn(e),ii(e,r),Oi(e,r),t.force=!1}t.signal(e,"update",e),e.display.viewFrom==e.display.reportedViewFrom&&e.display.viewTo==e.display.reportedViewTo||(t.signal(e,"viewportChange",e,e.display.viewFrom,e.display.viewTo),e.display.reportedViewFrom=e.display.viewFrom,e.display.reportedViewTo=e.display.viewTo)}function Ei(e,t){var n=new To(e,t);if(Ci(e,n)){Nn(e),Yi(e,n);var i=ni(e);Tn(e),ii(e,i),Oi(e,i),n.finish()}}function Ai(e,n,i){function r(t){var n=t.nextSibling;return wa&&Aa&&e.display.currentWheelTarget==t?t.style.display="none":t.parentNode.removeChild(t),n}for(var s=e.display,a=e.options.lineNumbers,o=s.lineDiv,l=o.firstChild,u=s.view,d=s.viewFrom,c=0;c-1&&(f=!1),xt(e,p,d,i)),f&&(t(p.lineNumber),p.lineNumber.appendChild(document.createTextNode(O(e.options,d)))),l=p.node.nextSibling}else{var m=Et(e,p,d,i);o.insertBefore(m,l)}d+=p.size}for(;l;)l=r(l)}function Fi(e){var t=e.display.gutters.offsetWidth;e.display.sizer.style.marginLeft=t+"px"}function Oi(e,t){e.display.sizer.style.minHeight=t.docHeight+"px",e.display.heightForcer.style.top=t.docHeight+"px",e.display.gutters.style.height=t.docHeight+e.display.barHeight+Bt(e)+"px"}function Hi(e){var n=e.display.gutters,r=e.options.gutters;t(n);for(var s=0;s-1&&!e.lineNumbers&&(e.gutters=e.gutters.slice(0),e.gutters.splice(t,1))}function ji(e){var t=e.wheelDeltaX,n=e.wheelDeltaY;return null==t&&e.detail&&e.axis==e.HORIZONTAL_AXIS&&(t=e.detail),null==n&&e.detail&&e.axis==e.VERTICAL_AXIS?n=e.detail:null==n&&(n=e.wheelDelta),{x:t,y:n}}function Ni(e){var t=ji(e);return t.x*=Yo,t.y*=Yo,t}function Ii(e,t){var n=ji(t),i=n.x,r=n.y,s=e.display,a=s.scroller,o=a.scrollWidth>a.clientWidth,l=a.scrollHeight>a.clientHeight;if(i&&o||r&&l){if(r&&Aa&&wa)e:for(var u=t.target,d=s.view;u!=a;u=u.parentNode)for(var c=0;c=0){var a=B(s.from(),r.from()),o=I(s.to(),r.to()),l=s.empty()?r.from()==r.head:s.from()==s.head;i<=t&&--t,e.splice(--i,2,new Ao(l?o:a,l?a:o))}}return new Eo(e,t)}function zi(e,t){return new Eo([new Ao(e,t||e)],0)}function Wi(e){return e.text?H(e.from.line+e.text.length-1,m(e.text).length+(1==e.text.length?e.from.ch:0)):e.to}function Ri(e,t){if(P(e,t.from)<0)return e;if(P(e,t.to)<=0)return Wi(t);var n=e.line+t.text.length-(t.to.line-t.from.line)-1,i=e.ch;return e.line==t.to.line&&(i+=Wi(t).ch-t.to.ch),H(n,i)}function Ui(e,t){for(var n=[],i=0;i1&&e.remove(o.line+1,f-1),e.insert(o.line+1,v)}Dt(e,"change",e,t)}function qi(e,t,n){function i(e,r,s){if(e.linked)for(var a=0;a1&&!e.done[e.done.length-2].ranges?(e.done.pop(),m(e.done)):void 0}function sr(e,t,n,i){var r=e.history;r.undone.length=0;var s,a,o=+new Date;if((r.lastOp==i||r.lastOrigin==t.origin&&t.origin&&("+"==t.origin.charAt(0)&&r.lastModTime>o-(e.cm?e.cm.options.historyEventDelay:500)||"*"==t.origin.charAt(0)))&&(s=rr(r,r.lastOp==i)))a=m(s.changes),0==P(t.from,t.to)&&0==P(t.from,a.to)?a.to=Wi(t):s.changes.push(nr(e,t));else{var l=m(r.done);for(l&&l.ranges||lr(e.sel,r.done),s={changes:[nr(e,t)],generation:r.generation},r.done.push(s);r.done.length>r.undoDepth;)r.done.shift(),r.done[0].ranges||r.done.shift()}r.done.push(n),r.generation=++r.maxGeneration,r.lastModTime=r.lastSelTime=o,r.lastOp=r.lastSelOp=i,r.lastOrigin=r.lastSelOrigin=t.origin,a||Se(e,"historyAdded")}function ar(e,t,n,i){var r=t.charAt(0);return"*"==r||"+"==r&&n.ranges.length==i.ranges.length&&n.somethingSelected()==i.somethingSelected()&&new Date-e.history.lastSelTime<=(e.cm?e.cm.options.historyEventDelay:500)}function or(e,t,n,i){var r=e.history,s=i&&i.origin;n==r.lastSelOp||s&&r.lastSelOrigin==s&&(r.lastModTime==r.lastSelTime&&r.lastOrigin==s||ar(e,s,m(r.done),t))?r.done[r.done.length-1]=t:lr(t,r.done),r.lastSelTime=+new Date,r.lastSelOrigin=s,r.lastSelOp=n,i&&i.clearRedo!==!1&&ir(r.undone)}function lr(e,t){var n=m(t);n&&n.ranges&&n.equals(e)||t.push(e)}function ur(e,t,n,i){var r=t["spans_"+e.id],s=0;e.iter(Math.max(e.first,n),Math.min(e.first+e.size,i),function(n){n.markedSpans&&((r||(r=t["spans_"+e.id]={}))[s]=n.markedSpans),++s})}function dr(e){if(!e)return null;for(var t,n=0;n-1&&(m(o)[c]=u[c],delete u[c])}}}return i}function fr(e,t,n,i){if(i){var r=e.anchor;if(n){var s=P(t,r)<0;s!=P(n,r)<0?(r=t,t=n):s!=P(t,n)<0&&(t=n)}return new Ao(r,t)}return new Ao(n||t,t)}function mr(e,t,n,i,r){null==r&&(r=e.cm&&(e.cm.display.shift||e.extend)),Dr(e,new Eo([fr(e.sel.primary(),t,n,r)],0),i)}function gr(e,t,n){
+for(var i=[],r=e.cm&&(e.cm.display.shift||e.extend),s=0;s=t.ch:o.to>t.ch))){if(r&&(Se(l,"beforeCursorEnter"),l.explicitlyCleared)){if(s.markedSpans){--a;continue}break}if(!l.atomic)continue;if(n){var u=l.find(i<0?1:-1),d=void 0;if((i<0?l.inclusiveRight:l.inclusiveLeft)&&(u=Tr(e,u,-i,u&&u.line==t.line?s:null)),u&&u.line==t.line&&(d=P(u,n))&&(i<0?d<0:d>0))return Lr(e,u,t,i,r)}var c=l.find(i<0?-1:1);return(i<0?l.inclusiveLeft:l.inclusiveRight)&&(c=Tr(e,c,i,c.line==t.line?s:null)),c?Lr(e,c,t,i,r):null}}return t}function Sr(e,t,n,i,r){var s=i||1,a=Lr(e,t,n,s,r)||!r&&Lr(e,t,n,s,!0)||Lr(e,t,n,-s,r)||!r&&Lr(e,t,n,-s,!0);return a?a:(e.cantEdit=!0,H(e.first,0))}function Tr(e,t,n,i){return n<0&&0==t.ch?t.line>e.first?W(e,H(t.line-1)):null:n>0&&t.ch==(i||S(e,t.line)).text.length?t.line=0;--r)Ar(e,{from:i[r].from,to:i[r].to,text:r?[""]:t.text,origin:t.origin});else Ar(e,t)}}function Ar(e,t){if(1!=t.text.length||""!=t.text[0]||0!=P(t.from,t.to)){var n=Ui(e,t);sr(e,t,n,e.cm?e.cm.curOp.id:NaN),Hr(e,t,n,Q(e,t));var i=[];qi(e,function(e,n){n||h(i,e.history)!=-1||(Br(e.history,t),i.push(e.history)),Hr(e,t,null,Q(e,t))})}}function Fr(e,t,n){var i=e.cm&&e.cm.state.suppressEdits;if(!i||n){for(var r,s=e.history,a=e.sel,o="undo"==t?s.done:s.undone,l="undo"==t?s.undone:s.done,u=0;u