diff --git a/CHANGELOG.md b/CHANGELOG.md
index 617449b80..813d9e6af 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@
1. [](#bugfix)
* Fixed a fatal error if you have a collection with missing or invalid `@page: /route`
+ * Fixed gzip compression making it to work correctly with all servers and browsers
# v1.0.0-rc.3
## 10/29/2015
diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml
index e79da93e3..ce1169ea4 100644
--- a/system/blueprints/config/system.yaml
+++ b/system/blueprints/config/system.yaml
@@ -73,7 +73,7 @@ form:
options:
"F jS \\a\\t g:ia": Date1
"l jS \\of F g:i A": Date2
- "D, m M Y G:i:s": Date3
+ "D, d M Y G:i:s": Date3
"d-m-y G:i": Date4
"jS M Y": Date5
@@ -86,7 +86,7 @@ form:
options:
"F jS \\a\\t g:ia": Date1
"l jS \\of F g:i A": Date2
- "D, m M Y G:i:s": Date3
+ "D, d M Y G:i:s": Date3
"d-m-y G:i": Date4
"jS M Y": Date5
diff --git a/system/config/system.yaml b/system/config/system.yaml
index 43b1dbba5..a53951e7c 100644
--- a/system/config/system.yaml
+++ b/system/config/system.yaml
@@ -1,113 +1,114 @@
-absolute_urls: false # Absolute or relative URLs for `base_url`
-timezone: '' # Valid values: http://php.net/manual/en/timezones.php
-default_locale: # Default locale (defaults to system)
-param_sep: ':' # Parameter separator, use ';' for Apache on windows
-wrapped_site: false # For themes/plugins to know if Grav is wrapped by another platform
+absolute_urls: false # Absolute or relative URLs for `base_url`
+timezone: '' # Valid values: http://php.net/manual/en/timezones.php
+default_locale: # Default locale (defaults to system)
+param_sep: ':' # Parameter separator, use ';' for Apache on windows
+wrapped_site: false # For themes/plugins to know if Grav is wrapped by another platform
languages:
- supported: [] # List of languages supported. eg: [en, fr, de]
- include_default_lang: true # Include the default lang prefix in all URLs
- translations: true # Enable translations by default
- translations_fallback: true # Fallback through supported translations if active lang doesn't exist
- session_store_active: false # Store active language in session
- http_accept_language: false # Attempt to set the language based on http_accept_language header in the browser
- override_locale: false # Override the default or system locale with language specific one
+ supported: [] # List of languages supported. eg: [en, fr, de]
+ include_default_lang: true # Include the default lang prefix in all URLs
+ translations: true # Enable translations by default
+ translations_fallback: true # Fallback through supported translations if active lang doesn't exist
+ session_store_active: false # Store active language in session
+ http_accept_language: false # Attempt to set the language based on http_accept_language header in the browser
+ override_locale: false # Override the default or system locale with language specific one
home:
- alias: '/home' # Default path for home, ie /
+ alias: '/home' # Default path for home, ie /
pages:
- theme: antimatter # Default theme (defaults to "antimatter" theme)
+ theme: antimatter # Default theme (defaults to "antimatter" theme)
order:
- by: default # Order pages by "default", "alpha" or "date"
- dir: asc # Default ordering direction, "asc" or "desc"
+ by: default # Order pages by "default", "alpha" or "date"
+ dir: asc # Default ordering direction, "asc" or "desc"
list:
- count: 20 # Default item count per page
+ count: 20 # Default item count per page
dateformat:
- default: # The default date format Grav expects in the `date: ` field
- short: 'jS M Y' # Short date format
- long: 'F jS \a\t g:ia' # Long date format
- publish_dates: true # automatically publish/unpublish based on dates
+ default: # The default date format Grav expects in the `date: ` field
+ short: 'jS M Y' # Short date format
+ long: 'F jS \a\t g:ia' # Long date format
+ publish_dates: true # automatically publish/unpublish based on dates
process:
- markdown: true # Process Markdown
- twig: false # Process Twig
+ markdown: true # Process Markdown
+ twig: false # Process Twig
events:
- page: true # Enable page level events
- twig: true # Enable twig level events
+ page: true # Enable page level events
+ twig: true # Enable twig level events
markdown:
- extra: false # Enable support for Markdown Extra support (GFM by default)
- auto_line_breaks: false # Enable automatic line breaks
- auto_url_links: false # Enable automatic HTML links
- escape_markup: false # Escape markup tags into entities
- special_chars: # List of special characters to automatically convert to entities
+ extra: false # Enable support for Markdown Extra support (GFM by default)
+ auto_line_breaks: false # Enable automatic line breaks
+ auto_url_links: false # Enable automatic HTML links
+ escape_markup: false # Escape markup tags into entities
+ special_chars: # List of special characters to automatically convert to entities
'>': 'gt'
'<': 'lt'
- types: [txt,xml,html,json,rss,atom] # list of valid page types
- expires: 604800 # Page expires time in seconds (604800 seconds = 7 days)
- last_modified: false # Set the last modified date header based on file modifcation timestamp
- etag: false # Set the etag header tag
- vary_accept_encoding: false # Add `Vary: Accept-Encoding` header
- redirect_default_route: false # Automatically redirect to a page's default route
- redirect_default_code: 301 # Default code to use for redirects
- redirect_trailing_slash: true # Handle automatically or 301 redirect a trailing / URL
- ignore_files: [.DS_Store] # Files to ignore in Pages
- ignore_folders: [.git, .idea] # Folders to ignore in Pages
- ignore_hidden: true # Ignore all Hidden files and folders
- url_taxonomy_filters: true # Enable auto-magic URL-based taxonomy filters for page collections
- fallback_types: [png,jpg,jpeg,gif] # Allowed types of files found if accessed via Page route
+ types: [txt,xml,html,htm,json,rss,atom] # list of valid page types
+ append_url_extension: '' # Append page's extension in Page urls (e.g. '.html' results in /path/page.html)
+ expires: 604800 # Page expires time in seconds (604800 seconds = 7 days)
+ last_modified: false # Set the last modified date header based on file modifcation timestamp
+ etag: false # Set the etag header tag
+ vary_accept_encoding: false # Add `Vary: Accept-Encoding` header
+ redirect_default_route: false # Automatically redirect to a page's default route
+ redirect_default_code: 301 # Default code to use for redirects
+ redirect_trailing_slash: true # Handle automatically or 301 redirect a trailing / URL
+ ignore_files: [.DS_Store] # Files to ignore in Pages
+ ignore_folders: [.git, .idea] # Folders to ignore in Pages
+ ignore_hidden: true # Ignore all Hidden files and folders
+ url_taxonomy_filters: true # Enable auto-magic URL-based taxonomy filters for page collections
+ fallback_types: [png,jpg,jpeg,gif] # Allowed types of files found if accessed via Page route
cache:
- enabled: true # Set to true to enable caching
+ enabled: true # Set to true to enable caching
check:
- method: file # Method to check for updates in pages: file|folder|none
- driver: auto # One of: auto|file|apc|xcache|memcache|wincache
- prefix: 'g' # Cache prefix string (prevents cache conflicts)
- lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite)
- gzip: false # GZip compress the page output
+ method: file # Method to check for updates in pages: file|folder|none
+ driver: auto # One of: auto|file|apc|xcache|memcache|wincache
+ prefix: 'g' # Cache prefix string (prevents cache conflicts)
+ lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite)
+ gzip: false # GZip compress the page output
twig:
- cache: true # Set to true to enable twig caching
- debug: false # Enable Twig debug
- auto_reload: true # Refresh cache on changes
- autoescape: false # Autoescape Twig vars
- undefined_functions: true # Allow undefined functions
- undefined_filters: true # Allow undefined filters
- umask_fix: false # By default Twig creates cached files as 755, fix switches this to 775
+ cache: true # Set to true to enable twig caching
+ debug: false # Enable Twig debug
+ auto_reload: true # Refresh cache on changes
+ autoescape: false # Autoescape Twig vars
+ undefined_functions: true # Allow undefined functions
+ undefined_filters: true # Allow undefined filters
+ umask_fix: false # By default Twig creates cached files as 755, fix switches this to 775
-assets: # Configuration for Assets Manager (JS, CSS)
- css_pipeline: false # The CSS pipeline is the unification of multiple CSS resources into one file
- css_minify: true # Minify the CSS during pipelining
- css_minify_windows: false # Minify Override for Windows platforms. False by default due to ThreadStackSize
- css_rewrite: true # Rewrite any CSS relative URLs during pipelining
- js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file
- js_minify: true # Minify the JS during pipelining
- enable_asset_timestamp: false # Enable asset timestamps
+assets: # Configuration for Assets Manager (JS, CSS)
+ css_pipeline: false # The CSS pipeline is the unification of multiple CSS resources into one file
+ css_minify: true # Minify the CSS during pipelining
+ css_minify_windows: false # Minify Override for Windows platforms. False by default due to ThreadStackSize
+ css_rewrite: true # Rewrite any CSS relative URLs during pipelining
+ js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file
+ js_minify: true # Minify the JS during pipelining
+ enable_asset_timestamp: false # Enable asset timestamps
collections:
jquery: system://assets/jquery/jquery-2.1.4.min.js
errors:
- display: false # Display full backtrace-style error page
- log: true # Log errors to /logs folder
+ display: false # Display full backtrace-style error page
+ log: true # Log errors to /logs folder
debugger:
- enabled: false # Enable Grav debugger and following settings
+ enabled: false # Enable Grav debugger and following settings
shutdown:
- close_connection: true # Close the connection before calling onShutdown(). false for debugging
+ close_connection: true # Close the connection before calling onShutdown(). false for debugging
images:
- default_image_quality: 85 # Default image quality to use when resampling images (85%)
- cache_all: false # Cache all image by default
- debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example
+ default_image_quality: 85 # Default image quality to use when resampling images (85%)
+ cache_all: false # Cache all image by default
+ debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example
media:
- enable_media_timestamp: false # Enable media timetsamps
- upload_limit: 0 # Set maximum upload size in bytes (0 is unlimited)
- unsupported_inline_types: [] # Array of unsupported media file types to try to display inline
+ enable_media_timestamp: false # Enable media timetsamps
+ upload_limit: 0 # Set maximum upload size in bytes (0 is unlimited)
+ unsupported_inline_types: [] # Array of unsupported media file types to try to display inline
session:
- enabled: true # Enable Session support
- timeout: 1800 # Timeout in seconds
- name: grav-site # Name prefix of the session cookie
+ enabled: true # Enable Session support
+ timeout: 1800 # Timeout in seconds
+ name: grav-site # Name prefix of the session cookie
security:
default_hash: $2y$10$kwsyMVwM8/7j0K/6LHT.g.Fs49xOCTp2b8hh/S5.dPJuJcJB6T.UK
diff --git a/system/languages/en.yaml b/system/languages/en.yaml
index 5ae01dbde..a1eab6f19 100644
--- a/system/languages/en.yaml
+++ b/system/languages/en.yaml
@@ -92,3 +92,6 @@ NICETIME:
MO_PLURAL: mos
YR_PLURAL: yrs
DEC_PLURAL: decs
+FORM:
+ VALIDATION_FAIL: Validation failed:
+ INVALID_INPUT: Invalid input in
diff --git a/system/src/Grav/Common/Data/Blueprint.php b/system/src/Grav/Common/Data/Blueprint.php
index 6ebe8ae19..6f0c71e50 100644
--- a/system/src/Grav/Common/Data/Blueprint.php
+++ b/system/src/Grav/Common/Data/Blueprint.php
@@ -79,7 +79,8 @@ class Blueprint implements \ArrayAccess, ExportInterface
$this->validateArray($data, $this->nested);
} catch (\RuntimeException $e) {
$language = self::getGrav()['language'];
- throw new \RuntimeException(sprintf('Validation failed: %s', $language->translate($e->getMessage())));
+ $message = sprintf($language->translate('FORM.VALIDATION_FAIL', null, true) . ' %s', $e->getMessage());
+ throw new \RuntimeException($message);
}
}
@@ -452,7 +453,8 @@ class Blueprint implements \ArrayAccess, ExportInterface
if (isset($field['validate']['required'])
&& $field['validate']['required'] === true
&& empty($data[$name])) {
- throw new \RuntimeException("Missing required field: {$field['label']}");
+ $value = isset($field['label']) ? $field['label'] : $field['name'];
+ throw new \RuntimeException("Missing required field: {$value}");
}
}
}
diff --git a/system/src/Grav/Common/Data/Validation.php b/system/src/Grav/Common/Data/Validation.php
index 653386fe9..c232467ba 100644
--- a/system/src/Grav/Common/Data/Validation.php
+++ b/system/src/Grav/Common/Data/Validation.php
@@ -38,7 +38,7 @@ class Validation
$type = (string) isset($field['validate']['type']) ? $field['validate']['type'] : $field['type'];
$method = 'type'.strtr($type, '-', '_');
$name = ucfirst(isset($field['label']) ? $field['label'] : $field['name']);
- $message = (string) isset($field['validate']['message']) ? $field['validate']['message'] : 'Invalid input in "' . $language->translate($name) . '""';
+ $message = (string) isset($field['validate']['message']) ? $field['validate']['message'] : $language->translate('FORM.INVALID_INPUT', null, true) . ' "' . $language->translate($name) . '"';
if (method_exists(__CLASS__, $method)) {
$success = self::$method($value, $validate, $field);
diff --git a/system/src/Grav/Common/Filesystem/Folder.php b/system/src/Grav/Common/Filesystem/Folder.php
index 6026d8f04..af1b93352 100644
--- a/system/src/Grav/Common/Filesystem/Folder.php
+++ b/system/src/Grav/Common/Filesystem/Folder.php
@@ -64,8 +64,8 @@ abstract class Folder
/**
* Get relative path between target and base path. If path isn't relative, return full path.
*
- * @param string $path
- * @param string $base
+ * @param string $path
+ * @param mixed|string $base
* @return string
*/
public static function getRelativePath($path, $base = GRAV_ROOT)
diff --git a/system/src/Grav/Common/GPM/PackageInterface.php b/system/src/Grav/Common/GPM/PackageInterface.php
deleted file mode 100644
index ad856c08f..000000000
--- a/system/src/Grav/Common/GPM/PackageInterface.php
+++ /dev/null
@@ -1,58 +0,0 @@
-get('system.cache.gzip')) {
- ob_start('ob_gzhandler');
+ // Enable zip/deflate with a fallback in case of if browser does not support compressing.
+ if(!ob_start("ob_gzhandler")) {
+ ob_start();
+ }
}
// Initialize the timezone
@@ -416,22 +419,25 @@ class Grav extends Container
public function shutdown()
{
if ($this['config']->get('system.debugger.shutdown.close_connection')) {
- //stop user abort
+ // Prevent user abort.
if (function_exists('ignore_user_abort')) {
@ignore_user_abort(true);
}
- // close the session
+ // Close the session.
if (isset($this['session'])) {
$this['session']->close();
}
- // flush buffer if gzip buffer was started
if ($this['config']->get('system.cache.gzip')) {
- ob_end_flush(); // gzhandler buffer
+ // Flush gzhandler buffer if gzip was enabled.
+ ob_end_flush();
+ } else {
+ // Otherwise prevent server from compressing the output.
+ header('Content-Encoding: none');
}
- // get lengh and close the connection
+ // Get length and close the connection.
header('Content-Length: ' . ob_get_length());
header("Connection: close");
@@ -440,7 +446,7 @@ class Grav extends Container
@ob_flush();
flush();
- // fix for fastcgi close connection issue
+ // Fix for fastcgi close connection issue.
if (function_exists('fastcgi_finish_request')) {
@fastcgi_finish_request();
}
diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php
index f2c6ea2d9..0132c6b2e 100644
--- a/system/src/Grav/Common/Page/Page.php
+++ b/system/src/Grav/Common/Page/Page.php
@@ -41,6 +41,7 @@ class Page
protected $folder;
protected $path;
protected $extension;
+ protected $url_extension;
protected $id;
protected $parent;
@@ -128,6 +129,7 @@ class Page
$this->modularTwig($this->slug[0] == '_');
$this->setPublishState();
$this->published();
+ $this->urlExtension();
// some extension logic
if (empty($extension)) {
@@ -136,6 +138,7 @@ class Page
$this->extension($extension);
}
+
// extract page language from page extension
$language = trim(basename($this->extension(), 'md'), '.') ?: null;
$this->language($language);
@@ -349,7 +352,6 @@ class Page
if (isset($this->header->last_modified)) {
$this->last_modified = (bool) $this->header->last_modified;
}
-
}
return $this->header;
@@ -957,6 +959,17 @@ class Page
return $this->extension;
}
+ public function urlExtension()
+ {
+ // if not set in the page get the value from system config
+ if (empty($this->url_extension)) {
+ $this->url_extension = trim(isset($this->header->append_url_extension) ? $this->header->append_url_extension : self::getGrav()['config']->get('system.pages.append_url_extension', false));
+ }
+
+ return $this->url_extension;
+
+ }
+
/**
* Gets and sets the expires field. If not set will return the default
*
@@ -1262,7 +1275,7 @@ class Page
$rootUrl = $uri->rootUrl($include_host) . $pages->base();
- $url = $rootUrl.'/'.trim($route, '/');
+ $url = $rootUrl.'/'.trim($route, '/') . $this->urlExtension();
// trim trailing / if not root
if ($url !== '/') {
diff --git a/system/src/Grav/Common/Twig/TwigExtension.php b/system/src/Grav/Common/Twig/TwigExtension.php
index 2da411210..aa8c4a3ee 100644
--- a/system/src/Grav/Common/Twig/TwigExtension.php
+++ b/system/src/Grav/Common/Twig/TwigExtension.php
@@ -94,14 +94,17 @@ class TwigExtension extends \Twig_Extension
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('evaluate', [$this, 'evaluateFunc']),
new \Twig_SimpleFunction('gist', [$this, 'gistFunc']),
+ new \Twig_SimpleFunction('nonce_field', [$this, 'nonceFieldFunc']),
new \Twig_simpleFunction('random_string', [$this, 'randomStringFunc']),
new \Twig_SimpleFunction('repeat', [$this, 'repeatFunc']),
new \Twig_SimpleFunction('string', [$this, 'stringFunc']),
new \Twig_simpleFunction('t', [$this, 'translate']),
new \Twig_simpleFunction('ta', [$this, 'translateArray']),
new \Twig_SimpleFunction('url', [$this, 'urlFunc']),
- new \Twig_SimpleFunction('evaluate', [$this, 'evaluateFunc']),
+
+
];
}
@@ -595,4 +598,22 @@ class TwigExtension extends \Twig_Extension
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.
+ *
+ * @todo evaluate if adding referrer or not
+ *
+ * @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;
+ }
}
diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php
index 64a9c4ad4..98b45fcac 100644
--- a/system/src/Grav/Common/Uri.php
+++ b/system/src/Grav/Common/Uri.php
@@ -139,6 +139,7 @@ class Uri
$valid_page_types = implode('|', $config->get('system.pages.types'));
+ // Strip the file extension for valid page types
if (preg_match("/\.(".$valid_page_types.")$/", $parts['basename'])) {
$uri = rtrim(str_replace(DIRECTORY_SEPARATOR, DS, $parts['dirname']), DS). '/' .$parts['filename'];
}
@@ -571,4 +572,21 @@ class Uri
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')
+ {
+ $nonce = Utils::getNonce($action);
+ $nonce = str_replace('/', 'SLASH', $nonce);
+ $urlWithNonce = $url . '/' . $nonceParamName . Grav::instance()['config']->get('system.param_sep', ':') . $nonce;
+ return $urlWithNonce;
+ }
}
diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php
index 8c55669a2..4935ee53a 100644
--- a/system/src/Grav/Common/Utils.php
+++ b/system/src/Grav/Common/Utils.php
@@ -384,7 +384,109 @@ abstract class Utils
*
* @return boolean
*/
- public static function isPositive($value) {
+ 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()
+ *
+ * @param string $action
+ * @param bool $plusOneTick if true, generates the token for the next tick (the next 12 hours)
+ *
+ * @return string the nonce string
+ */
+ private static function generateNonceString($action, $plusOneTick = false)
+ {
+ if (isset(self::getGrav()['user'])) {
+ $user = self::getGrav()['user'];
+ $username = $user->username;
+ } else {
+ $username = false;
+ }
+
+ if (!$username) {
+ $username = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
+ }
+
+ $token = session_id();
+ $i = self::nonceTick();
+
+ if ($plusOneTick) {
+ $i++;
+ }
+
+ return ( $i . '|' . $action . '|' . $username . '|' . $token );
+ }
+
+ /**
+ * Get the time-dependent variable for nonce creation.
+ *
+ * @todo 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 ));
+ }
+
+ /**
+ * Get hash of given string
+ *
+ * @param string $data string to hash
+ *
+ * @return string hashed value of $data, cut to 10 characters
+ */
+ private static function hash($data)
+ {
+ $hash = password_hash($data, PASSWORD_DEFAULT);
+ return $hash;
+ }
+
+ /**
+ * 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 $plusOneTick if true, generates the token for the next tick (the next 12 hours)
+ *
+ * @return string the nonce
+ */
+ public static function getNonce($action, $plusOneTick = false)
+ {
+ $nonce = self::hash(self::generateNonceString($action, $plusOneTick));
+ return $nonce;
+ }
+
+ /**
+ * Verify the passed nonce for the give action
+ *
+ * @param 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)
+ {
+ $nonce = str_replace('SLASH', '/', $nonce);
+
+ //Nonce generated 0-12 hours ago
+ if (password_verify(self::generateNonceString($action), $nonce)) {
+ return true;
+ }
+
+ //Nonce generated 12-24 hours ago
+ $plusOneTick = true;
+ if (password_verify(self::generateNonceString($action, $plusOneTick), $nonce)) {
+ return true;
+ }
+
+ //Invalid nonce
+ return false;
+ }
}