diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e86e28b88..65b15ca7b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# v0.9.7 beta +## 11/24/2014 + +1. [](#improved) + * Nginx configuration updated + * Added gitter.im badge to README + * Removed `set_time_limit()` and put checks around `ignore_user_abort` + * More PSR code fixes +2. [](#bugfix) + * Fix issue with non-valid asset path showing up when they shouldn't + * Fix for JS asset pipeline and scripts that don't end in `;` + * Fix for schema-based markdown URLs broken routes (eg `mailto:`) + # v0.9.6 beta ## 11/17/2014 diff --git a/README.md b/README.md index d9840d26db..676be93493 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # ![](https://avatars1.githubusercontent.com/u/8237355?v=2&s=50) Grav +[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/getgrav/grav?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Grav is a **Fast**, **Simple**, and **Flexible**, file-based Web-platform. There is **Zero** installation required. Just extract the ZIP archive, and you are already up and running. It follows similar principals to other flat-file CMS platforms, but has a different design philosophy than most. Grav comes with a powerful **Package Management System** to allow for simple installation and upgrading of plugins and themes, as well as simple updating of Grav itself. diff --git a/nginx.conf b/nginx.conf index e3e94f4c26..07295e4daa 100644 --- a/nginx.conf +++ b/nginx.conf @@ -27,26 +27,26 @@ http { } location /user { - rewrite ^/user/accounts/(.*)$ /error redirect; - rewrite ^/user/config/(.*)$ /error redirect; - rewrite ^/user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect; - } - - location /cache { - rewrite ^/cache/(.*) /error redirect; - } - - location /bin { - rewrite ^/bin/(.*)$ /error redirect; - } - - location /system { - rewrite ^/system/(.*)$ /error redirect; - } - - location /vendor { - rewrite ^/vendor/(.*)$ /error redirect; - } + rewrite ^/user/accounts/(.*)$ /error redirect; + rewrite ^/user/config/(.*)$ /error redirect; + rewrite ^/user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect; + } + + location /cache { + rewrite ^/cache/(.*) /error redirect; + } + + location /bin { + rewrite ^/bin/(.*)$ /error redirect; + } + + location /system { + rewrite ^/system/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect; + } + + location /vendor { + rewrite ^/vendor/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect; + } # Remember to change 127.0.0.1:9000 to the Ip/port # you configured php-cgi.exe to run from diff --git a/system/config/system.yaml b/system/config/system.yaml index 7cdc6703bb..88b9791991 100644 --- a/system/config/system.yaml +++ b/system/config/system.yaml @@ -1,4 +1,4 @@ -absolute_urls: false # Whether to use absolute URLs. +absolute_urls: false # Absolute or relative URLs for `base_url` home: alias: '/home' # Default path for home, ie / diff --git a/system/defines.php b/system/defines.php index 38b114b95f..7760c13620 100644 --- a/system/defines.php +++ b/system/defines.php @@ -2,7 +2,7 @@ // Some standard defines define('GRAV', true); -define('GRAV_VERSION', '0.9.6'); +define('GRAV_VERSION', '0.9.7'); define('DS', '/'); // Directories and Paths diff --git a/system/src/Grav/Common/Assets.php b/system/src/Grav/Common/Assets.php index b23f1465e6..2270016ce5 100644 --- a/system/src/Grav/Common/Assets.php +++ b/system/src/Grav/Common/Assets.php @@ -17,10 +17,9 @@ * * Based on stolz/assets (https://github.com/Stolz/Assets) package modified for use with Grav * - * @author RocketTheme + * @author RocketTheme * @license MIT */ - class Assets { use GravTrait; @@ -53,6 +52,7 @@ class Assets * * The closure will receive as the only parameter a string with the path/URL of the asset and * it should return the content of the asset file as a string. + * * @var Closure */ protected $fetch_command; @@ -86,22 +86,9 @@ class Assets public function __construct(array $options = array()) { // Forward config options - if($options) + if ($options) { $this->config((array)$options); - } - - /** - * Initialization called in the Grav lifecycle to initialize the Assets with appropriate configuration - */ - public function init() - { - /** @var Config $config */ - $config = self::$grav['config']; - $base_url = self::$grav['base_url']; - $asset_config = (array) $config->get('system.assets'); - - $this->config($asset_config); - $this->base_url = $base_url . '/'; + } } /** @@ -111,50 +98,58 @@ public function init() * Also, an extra option 'autoload' may be passed containing an array of * assets and/or collections that will be automatically added on startup. * - * @param array $options Configurable options. + * @param array $config Configurable options. + * * @return $this * @throws \Exception */ public function config(array $config) { // Set pipeline modes - if(isset($config['css_pipeline'])) + if (isset($config['css_pipeline'])) { $this->css_pipeline = $config['css_pipeline']; + } - if(isset($config['js_pipeline'])) + if (isset($config['js_pipeline'])) { $this->js_pipeline = $config['js_pipeline']; + } // Pipeline requires public dir - if(($this->js_pipeline || $this->css_pipeline) && ! is_dir(ASSETS_DIR)) + if (($this->js_pipeline || $this->css_pipeline) && !is_dir(ASSETS_DIR)) { throw new \Exception('Assets: Public dir not found'); + } // Set custom pipeline fetch command - if(isset($config['fetch_command']) and ($config['fetch_command'] instanceof Closure)) + if (isset($config['fetch_command']) and ($config['fetch_command'] instanceof Closure)) { $this->fetch_command = $config['fetch_command']; + } // Set CSS Minify state - if(isset($config['css_minify'])) + if (isset($config['css_minify'])) { $this->css_minify = $config['css_minify']; + } - if(isset($config['css_minify_windows'])) + if (isset($config['css_minify_windows'])) { $this->css_minify_windows = $config['css_minify_windows']; + } - if(isset($config['css_rewrite'])) + if (isset($config['css_rewrite'])) { $this->css_rewrite = $config['css_rewrite']; + } // Set JS Minify state - if(isset($config['js_minify'])) + if (isset($config['js_minify'])) { $this->js_minify = $config['js_minify']; + } // Set collections - if(isset($config['collections']) and is_array($config['collections'])) + if (isset($config['collections']) and is_array($config['collections'])) { $this->collections = $config['collections']; + } // Autoload assets - if(isset($config['autoload']) and is_array($config['autoload'])) - { - foreach($config['autoload'] as $asset) - { + if (isset($config['autoload']) and is_array($config['autoload'])) { + foreach ($config['autoload'] as $asset) { $this->add($asset); } } @@ -162,41 +157,51 @@ public function config(array $config) return $this; } + /** + * Initialization called in the Grav lifecycle to initialize the Assets with appropriate configuration + */ + public function init() + { + /** @var Config $config */ + $config = self::$grav['config']; + $base_url = self::$grav['base_url']; + $asset_config = (array)$config->get('system.assets'); + + $this->config($asset_config); + $this->base_url = $base_url . '/'; + } + /** * Add an asset or a collection of assets. * * It automatically detects the asset type (JavaScript, CSS or collection). * You may add more than one asset passing an array as argument. * - * @param mixed $asset - * @param int $priority the priority, bigger comes first - * @param bool $pipeline false if this should not be pipelined + * @param mixed $asset + * @param int $priority the priority, bigger comes first + * @param bool $pipeline false if this should not be pipelined + * * @return $this */ public function add($asset, $priority = 10, $pipeline = true) { // More than one asset - if(is_array($asset)) - { - foreach($asset as $a) + if (is_array($asset)) { + foreach ($asset as $a) { $this->add($a, $priority, $pipeline); - } - // Collection - elseif(isset($this->collections[$asset])) - { + } + } elseif (isset($this->collections[$asset])) { $this->add($this->collections[$asset], $priority, $pipeline); - } - else - { + } else { // JavaScript or CSS $info = pathinfo($asset); - if(isset($info['extension'])) - { + if (isset($info['extension'])) { $ext = strtolower($info['extension']); - if($ext === 'css') + if ($ext === 'css') { $this->addCss($asset, $priority, $pipeline); - elseif($ext === 'js') + } elseif ($ext === 'js') { $this->addJs($asset, $priority, $pipeline); + } } } @@ -204,103 +209,117 @@ public function add($asset, $priority = 10, $pipeline = true) } /** - * Add an inline CSS asset. + * Add a CSS asset. * * It checks for duplicates. - * For adding chunks of string-based inline CSS + * You may add more than one asset passing an array as argument. + * + * @param mixed $asset + * @param int $priority the priority, bigger comes first + * @param bool $pipeline false if this should not be pipelined * - * @param mixed $asset - * @param int $priority the priority, bigger comes first * @return $this */ - public function addInlineCss($asset, $priority = 10) { + public function addCss($asset, $priority = 10, $pipeline = true) + { + if (is_array($asset)) { + foreach ($asset as $a) { + $this->addCss($a, $priority, $pipeline); + } - if (is_string($asset) && !in_array($asset, $this->inline_css)) { - $this->inline_css[] = $asset; + return $this; + } + + if (!$this->isRemoteLink($asset)) { + $asset = $this->buildLocalLink($asset); + } + + if ($asset && !array_key_exists($asset, $this->css)) { + $this->css[$asset] = [ + 'asset' => $asset, + 'priority' => $priority, + 'order' => count($this->css), + 'pipeline' => $pipeline + ]; } return $this; } /** - * Add a CSS asset. + * Add a JavaScript asset. * * It checks for duplicates. * You may add more than one asset passing an array as argument. * - * @param mixed $asset - * @param int $priority the priority, bigger comes first - * @param bool $pipeline false if this should not be pipelined + * @param mixed $asset + * @param int $priority the priority, bigger comes first + * @param bool $pipeline false if this should not be pipelined + * * @return $this */ - public function addCss($asset, $priority = 10, $pipeline = true) + public function addJs($asset, $priority = 10, $pipeline = true) { - if(is_array($asset)) - { - foreach($asset as $a) - $this->addCss($a, $priority, $pipeline); + if (is_array($asset)) { + foreach ($asset as $a) { + $this->addJs($a, $priority, $pipeline); + } return $this; } - if( !$this->isRemoteLink($asset)) { + if (!$this->isRemoteLink($asset)) { $asset = $this->buildLocalLink($asset); } - if( !array_key_exists($asset, $this->css)) { - $this->css[$asset] = ['asset'=>$asset, 'priority'=>$priority, 'order' => count($this->css), 'pipeline'=>$pipeline]; + if ($asset && !array_key_exists($asset, $this->js)) { + $this->js[$asset] = [ + 'asset' => $asset, + 'priority' => $priority, + 'order' => count($this->js), + 'pipeline' => $pipeline + ]; } return $this; } /** - * Add an inline JS asset. + * Add an inline CSS asset. * * It checks for duplicates. - * For adding chunks of string-based inline JS + * For adding chunks of string-based inline CSS + * + * @param mixed $asset + * @param int $priority the priority, bigger comes first * - * @param mixed $asset - * @param int $priority the priority, bigger comes first * @return $this */ - public function addInlineJs($asset) { + public function addInlineCss($asset, $priority = 10) + { - if (is_string($asset) && !in_array($asset, $this->inline_js)) { - $this->inline_js[] = $asset; + if (is_string($asset) && !in_array($asset, $this->inline_css)) { + $this->inline_css[] = $asset; } return $this; } /** - * Add a JavaScript asset. + * Add an inline JS asset. * * It checks for duplicates. - * You may add more than one asset passing an array as argument. + * For adding chunks of string-based inline JS + * + * @param mixed $asset * - * @param mixed $asset - * @param int $priority the priority, bigger comes first - * @param bool $pipeline false if this should not be pipelined * @return $this */ - public function addJs($asset, $priority = 10, $pipeline = true) + public function addInlineJs($asset) { - if(is_array($asset)) - { - foreach($asset as $a) - $this->addJs($a, $priority, $pipeline); - return $this; - } - - if( !$this->isRemoteLink($asset)) { - $asset = $this->buildLocalLink($asset); - } - - if( !array_key_exists($asset, $this->js)) { - - $this->js[$asset] = ['asset' => $asset, 'priority' => $priority, 'order' => count($this->js), 'pipeline' => $pipeline]; + if (is_string($asset) && !in_array($asset, $this->inline_js)) { + $this->inline_js[] = $asset; } return $this; @@ -309,36 +328,38 @@ public function addJs($asset, $priority = 10, $pipeline = true) /** * Build the CSS link tags. * - * @param array $attributes + * @param array $attributes + * * @return string */ public function css($attributes = []) { - if( ! $this->css) + if (!$this->css) { return null; - - if (self::$grav) + } // Sort array by priorities (larger priority first) - usort($this->css, function ($a, $b) { - if ($a['priority'] == $b['priority']) { - return $b['order'] - $a['order']; - } - return $a['priority'] - $b['priority']; - }); + if (self::$grav) { + usort($this->css, function ($a, $b) { + if ($a['priority'] == $b['priority']) { + return $b['order'] - $a['order']; + } + return $a['priority'] - $b['priority']; + }); + } $this->css = array_reverse($this->css); - $attributes = $this->attributes(array_merge([ 'type' => 'text/css', 'rel' => 'stylesheet' ], $attributes)); + $attributes = $this->attributes(array_merge(['type' => 'text/css', 'rel' => 'stylesheet'], $attributes)); $output = ''; - if($this->css_pipeline) { - $output .= ''."\n"; + if ($this->css_pipeline) { + $output .= '' . "\n"; foreach ($this->css_no_pipeline as $file) { - $output .= ''."\n"; + $output .= '' . "\n"; } } else { - foreach($this->css as $file) { + foreach ($this->css as $file) { $output .= '' . "\n"; } } @@ -346,7 +367,7 @@ public function css($attributes = []) // Render Inline CSS if (count($this->inline_css) > 0) { $output .= "\n"; @@ -359,13 +380,15 @@ public function css($attributes = []) /** * Build the JavaScript script tags. * - * @param array $attributes + * @param array $attributes + * * @return string */ public function js($attributes = []) { - if( ! $this->js) + if (!$this->js) { return null; + } // Sort array by priorities (larger priority first) usort($this->js, function ($a, $b) { @@ -376,16 +399,16 @@ public function js($attributes = []) }); $this->js = array_reverse($this->js); - $attributes = $this->attributes(array_merge([ 'type' => 'text/javascript' ], $attributes)); + $attributes = $this->attributes(array_merge(['type' => 'text/javascript'], $attributes)); $output = ''; - if($this->js_pipeline) { - $output .= ''."\n"; + if ($this->js_pipeline) { + $output .= '' . "\n"; foreach ($this->js_no_pipeline as $file) { - $output .= ''."\n"; + $output .= '' . "\n"; } } else { - foreach($this->js as $file) { + foreach ($this->js as $file) { $output .= '' . "\n"; } } @@ -393,7 +416,7 @@ public function js($attributes = []) // Render Inline JS if (count($this->inline_js) > 0) { $output .= "\n"; @@ -402,77 +425,7 @@ public function js($attributes = []) return $output; } - /** - * Build an HTML attribute string from an array. - * - * @param array $attributes - * @return string - */ - protected function attributes(array $attributes){ - $html = ''; - - foreach ( $attributes as $key => $value) - { - // For numeric keys we will assume that the key and the value are the same - // as this will convert HTML attributes such as "required" to a correct - // form like required="required" instead of using incorrect numerics. - if (is_numeric($key)) $key = $value; - if (is_array($value)) $value = implode(' ', $value); - - $element = $key.'="'.htmlentities($value, ENT_QUOTES, 'UTF-8', false).'"'; - $html .= ' '.$element; - } - - return $html; - } - - /** - * Add/replace collection. - * - * @param string $collectionName - * @param array $assets - * @return $this - */ - public function registerCollection($collectionName, Array $assets) - { - $this->collections[$collectionName] = $assets; - - return $this; - } - - /** - * Reset all assets. - * - * @return $this - */ - public function reset() - { - return $this->resetCss()->resetJs(); - } - - /** - * Reset CSS assets. - * - * @return $this - */ - public function resetCss() - { - $this->css = array(); - return $this; - } - - /** - * Reset JavaScript assets. - * - * @return $this - */ - public function resetJs() - { - $this->js = array(); - - return $this; - } /** * Minifiy and concatenate CSS / JS files. @@ -483,7 +436,7 @@ protected function pipeline($css = true) { /** @var Cache $cache */ $cache = self::$grav['cache']; - $key = '?'.$cache->getKey(); + $key = '?' . $cache->getKey(); if ($css) { $file = md5(json_encode($this->css) . $this->js_minify . $this->css_minify . $this->css_rewrite) . '.css'; @@ -503,12 +456,13 @@ protected function pipeline($css = true) } } - $relative_path = "{$this->base_url}".basename(ASSETS_DIR)."/{$file}"; - $absolute_path = ASSETS_DIR.$file; + $relative_path = "{$this->base_url}" . basename(ASSETS_DIR) . "/{$file}"; + $absolute_path = ASSETS_DIR . $file; // If pipeline exist return it - if(file_exists($absolute_path)) + if (file_exists($absolute_path)) { return $relative_path . $key; + } $css_minify = $this->css_minify; @@ -540,145 +494,52 @@ protected function pipeline($css = true) } /** - * Download and concatenate the content of several links. + * Add/replace collection. * - * @param array $links - * @return string - */ - protected function gatherLinks(array $links, $css = true) - { - - - $buffer = ''; - $local = true; - - foreach($links as $asset) - { - $link = $asset['asset']; - $relative_path = $link; - - if($this->isRemoteLink($link)) { - $local = false; - if('//' === substr($link, 0, 2)) - $link = 'http:' . $link; - } else { - // Fix to remove relative dir if grav is in one - if (($this->base_url != '/') && (strpos($this->base_url, $link) == 0)) { - $relative_path = str_replace($this->base_url, '/', $link); - } - - $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); - - // If this is CSS + the file is local + rewrite enabled - if ($css && $local && $this->css_rewrite) { - $file = $this->cssRewrite($file, $relative_dir); - } - - $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 $collectionName + * @param array $assets * - * @param string $file the file containing the combined CSS files - * @return string the modified file with any @imports at the top of the file + * @return $this */ - protected function moveImports($file) + public function registerCollection($collectionName, Array $assets) { - $this->imports = array(); - - $file = preg_replace_callback(self::CSS_IMPORT_REGEX, - function($matches) { - $this->imports[] = $matches[0]; - return ''; - }, - $file - ); + $this->collections[$collectionName] = $assets; - return implode("\n", $this->imports) . "\n\n" . $file; + return $this; } /** - * Finds relative CSS urls() and rewrites the URL with an absolute one - * @param string $file the css source file - * @param string $relative_path relative path to the css file - * @return [type] [description] + * Reset all assets. + * + * @return $this */ - protected function cssRewrite($file, $relative_path) + public function reset() { - // Strip any sourcemap comments - $file = preg_replace(self::CSS_SOURCEMAP_REGEX, '', $file); - - // Find any css url() elements, grab the URLs and calculate an absolute path - // Then replace the old url with the new one - $file = preg_replace_callback(self::CSS_URL_REGEX, - function($matches) use ($relative_path) { - - $old_url = $matches[1]; - - // ensure this is not a data url - if (strpos($old_url, 'data:') === 0) return $matches[0]; - - $newpath = array(); - $paths = explode('/', $old_url); - - foreach ($paths as $path) { - if ($path == '..') { - $relative_path = dirname($relative_path); - } else { - $newpath[] = $path; - } - } - - $new_url = rtrim($this->base_url,'/') . $relative_path . '/' . implode('/', $newpath); - - return str_replace($old_url, $new_url, $matches[0]); - }, - $file - ); - - return $file; + return $this->resetCss()->resetJs(); } /** - * Build local links including grav asset shortcodes + * Reset JavaScript assets. * - * @param string $asset the asset string reference - * @return string the final link url to the asset + * @return $this */ - protected function buildLocalLink($asset) + public function resetJs() { - try { - $asset = self::$grav['locator']->findResource($asset, false); - } catch (\Exception $e) {} + $this->js = array(); - return $this->base_url . ltrim($asset, '/'); + return $this; } - /** - * Determine whether a link is local or remote. - * - * Undestands both "http://" and "https://" as well as protocol agnostic links "//" + * Reset CSS assets. * - * @param string $link - * @return bool + * @return $this */ - protected function isRemoteLink($link) + public function resetCss() { - return ('http://' === substr($link, 0, 7) or 'https://' === substr($link, 0, 8) or '//' === substr($link, 0, 2)); + $this->css = array(); + + return $this; } /** @@ -701,52 +562,64 @@ public function getJs() return $this->js; } + /** + * Add all CSS assets within $directory (relative to public dir). + * + * @param string $directory Relative to $this->public_dir + * + * @return $this + */ + public function addDirCss($directory) + { + return $this->addDir($directory, self::CSS_REGEX); + } + /** * Add all assets matching $pattern within $directory. * * @param string $directory Relative to $this->public_dir - * @param string $pattern (regex) + * @param string $pattern (regex) + * * @return $this * @throws Exception */ public function addDir($directory, $pattern = self::DEFAULT_REGEX) { // Check if public_dir exists - if( ! is_dir(ASSETS_DIR)) + if (!is_dir(ASSETS_DIR)) { throw new Exception('Assets: Public dir not found'); + } // Get files $files = $this->rglob(ASSETS_DIR . DIRECTORY_SEPARATOR . $directory, $pattern, ASSETS_DIR); // No luck? Nothing to do - if( ! $files) + if (!$files) { return $this; + } // Add CSS files - if($pattern === self::CSS_REGEX) - { + if ($pattern === self::CSS_REGEX) { $this->css = array_unique(array_merge($this->css, $files)); return $this; } // Add JavaScript files - if($pattern === self::JS_REGEX) - { + if ($pattern === self::JS_REGEX) { $this->js = array_unique(array_merge($this->js, $files)); return $this; } // Unknown pattern. We must poll to know the extension :( - foreach($files as $asset) - { + foreach ($files as $asset) { $info = pathinfo($asset); - if(isset($info['extension'])) - { + if (isset($info['extension'])) { $ext = strtolower($info['extension']); - if($ext === 'css' and ! in_array($asset, $this->css)) + if ($ext === 'css' and !in_array($asset, $this->css)) { $this->css[] = $asset; - elseif($ext === 'js' and ! in_array($asset, $this->js)) + } elseif ($ext === 'js' and !in_array($asset, $this->js)) { $this->js[] = $asset; + } } } @@ -754,25 +627,190 @@ public function addDir($directory, $pattern = self::DEFAULT_REGEX) } /** - * Add all CSS assets within $directory (relative to public dir). + * Determine whether a link is local or remote. * - * @param string $directory Relative to $this->public_dir - * @return $this + * Undestands both "http://" and "https://" as well as protocol agnostic links "//" + * + * @param string $link + * + * @return bool */ - public function addDirCss($directory) + protected function isRemoteLink($link) { - return $this->addDir($directory, self::CSS_REGEX); + return ('http://' === substr($link, 0, 7) or 'https://' === substr($link, 0, 8) + or '//' === substr($link, 0, 2)); } /** - * Add all JavaScript assets within $directory. + * Build local links including grav asset shortcodes * - * @param string $directory Relative to $this->public_dir - * @return $this + * @param string $asset the asset string reference + * + * @return string the final link url to the asset */ - public function addDirJs($directory) + protected function buildLocalLink($asset) { - return $this->addDir($directory, self::JS_REGEX); + try { + $asset = self::$grav['locator']->findResource($asset, false); + } catch (\Exception $e) { + } + + return $asset ? $this->base_url . ltrim($asset, '/') : false; + } + + /** + * Build an HTML attribute string from an array. + * + * @param array $attributes + * + * @return string + */ + protected function attributes(array $attributes) + { + $html = ''; + + foreach ($attributes as $key => $value) { + // For numeric keys we will assume that the key and the value are the same + // as this will convert HTML attributes such as "required" to a correct + // form like required="required" instead of using incorrect numerics. + if (is_numeric($key)) { + $key = $value; + } + if (is_array($value)) { + $value = implode(' ', $value); + } + + $element = $key . '="' . htmlentities($value, ENT_QUOTES, 'UTF-8', false) . '"'; + $html .= ' ' . $element; + } + + return $html; + } + + /** + * Download and concatenate the content of several links. + * + * @param array $links + * + * @return string + */ + protected function gatherLinks(array $links, $css = true) + { + + + $buffer = ''; + $local = true; + + foreach ($links as $asset) { + $link = $asset['asset']; + $relative_path = $link; + + if ($this->isRemoteLink($link)) { + $local = false; + if ('//' === substr($link, 0, 2)) { + $link = 'http:' . $link; + } + } else { + // Fix to remove relative dir if grav is in one + if (($this->base_url != '/') && (strpos($this->base_url, $link) == 0)) { + $relative_path = str_replace($this->base_url, '/', $link); + } + + $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); + + // Double check last character being + if (!$css) { + $file = rtrim($file, ' ;') . ';'; + } + + // If this is CSS + the file is local + rewrite enabled + if ($css && $local && $this->css_rewrite) { + $file = $this->cssRewrite($file, $relative_dir); + } + + $buffer .= $file; + } + + // Pull out @imports and move to top + if ($css) { + $buffer = $this->moveImports($buffer); + } + + return $buffer; + } + + /** + * Finds relative CSS urls() and rewrites the URL with an absolute one + * + * @param $file the css source file + * @param $relative_path relative path to the css file + * + * @return mixed + */ + protected function cssRewrite($file, $relative_path) + { + // Strip any sourcemap comments + $file = preg_replace(self::CSS_SOURCEMAP_REGEX, '', $file); + + // Find any css url() elements, grab the URLs and calculate an absolute path + // Then replace the old url with the new one + $file = preg_replace_callback( + self::CSS_URL_REGEX, + function ($matches) use ($relative_path) { + + $old_url = $matches[1]; + + // ensure this is not a data url + if (strpos($old_url, 'data:') === 0) { + return $matches[0]; + } + + $newpath = array(); + $paths = explode('/', $old_url); + + foreach ($paths as $path) { + if ($path == '..') { + $relative_path = dirname($relative_path); + } else { + $newpath[] = $path; + } + } + + $new_url = rtrim($this->base_url, '/') . $relative_path . '/' . implode('/', $newpath); + + return str_replace($old_url, $new_url, $matches[0]); + }, + $file + ); + + return $file; + } + + /** + * Moves @import statements to the top of the file per the CSS specification + * + * @param string $file the file containing the combined CSS files + * + * @return string the modified file with any @imports at the top of the file + */ + protected function moveImports($file) + { + $this->imports = array(); + + $file = preg_replace_callback( + self::CSS_IMPORT_REGEX, + function ($matches) { + $this->imports[] = $matches[0]; + return ''; + }, + $file + ); + + return implode("\n", $this->imports) . "\n\n" . $file; } /** @@ -780,24 +818,52 @@ public function addDirJs($directory) * * @param string $directory * @param string $pattern (regex) - * @param string $ltrim Will be trimed from the left of the file path + * @param string $ltrim Will be trimed 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); + $iterator = new RegexIterator( + new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( + $directory, + FilesystemIterator::SKIP_DOTS + ) + ), + $pattern + ); $offset = strlen($ltrim); $files = array(); - foreach($iterator as $file) + foreach ($iterator as $file) { $files[] = substr($file->getPathname(), $offset); + } return $files; } + /** + * Add all JavaScript assets within $directory. + * + * @param string $directory Relative to $this->public_dir + * + * @return $this + */ + public function addDirJs($directory) + { + return $this->addDir($directory, self::JS_REGEX); + } + + public function __toString() + { + return ''; + } + /** * @param $a * @param $b + * * @return mixed */ protected function priorityCompare($a, $b) @@ -805,8 +871,4 @@ protected function priorityCompare($a, $b) return $a ['priority'] - $b ['priority']; } - public function __toString() { - return ''; - } - } diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php index ad02ad8dd8..b052ff3a7c 100644 --- a/system/src/Grav/Common/Grav.php +++ b/system/src/Grav/Common/Grav.php @@ -305,8 +305,10 @@ public function fireEvent($eventName, Event $event = null) public function shutdown() { if ($this['config']->get('system.debugger.shutdown.close_connection')) { - set_time_limit(0); - ignore_user_abort(true); + + if (function_exists('ignore_user_abort')) { + @ignore_user_abort(true); + } if (isset($this['session'])) { $this['session']->close(); diff --git a/system/src/Grav/Common/Markdown/MarkdownGravLinkTrait.php b/system/src/Grav/Common/Markdown/MarkdownGravLinkTrait.php index a1ab41906d..80e051a366 100644 --- a/system/src/Grav/Common/Markdown/MarkdownGravLinkTrait.php +++ b/system/src/Grav/Common/Markdown/MarkdownGravLinkTrait.php @@ -29,8 +29,8 @@ protected function identifyLink($Excerpt) $url = parse_url(htmlspecialchars_decode($Excerpt['element']['attributes']['href'])); - // if there is no host set but there is a path, the file is local - if (!isset($url['host']) && isset($url['path'])) { + // if there is no scheme, the file is local + if (!isset($url['scheme'])) { // convert the URl is required $Excerpt['element']['attributes']['href'] = $this->convertUrl(Uri::build_url($url));