diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e914ad1b..ea1f66a09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +# v0.9.28 +## 06/16/2015 + +1. [](#new) + * Added method to set raw markdown on a page + * Added ability to enabled system and page level `etag` and `last_modified` headers +2. [](#improved) + * Improved image path processing + * Improved query string handling + * Optimization to image handling supporting URL encoded filenames + * Use global `composer` when available rather than Grv provided one + * Use `PHP_BINARY` contant rather than `php` executable + * Updated Doctrine Cache library + * Updated Symfony libraries + * Moved `convertUrl()` method to Uri object +3. [](#bugfix) + * Fix incorrect slug causing problems with CLI `uninstall` + * Fix Twig runtime error with assets pipeline in sufolder installations + * Fix for `+` in image filenames + * Fix for dot files causing issues with page processing + * Fix for Uri path detection on Windows platform + * Fix for atlernative media resolutions + * Fix for modularTypes key properties + # v0.9.27 ## 05/09/2015 diff --git a/bin/gpm b/bin/gpm index a19fea5d1..5a039e597 100755 --- a/bin/gpm +++ b/bin/gpm @@ -6,10 +6,17 @@ if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) { exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req)); } +if (!file_exists(__DIR__ . '/../vendor')){ + require_once __DIR__ . '/../system/src/Grav/Common/Composer.php'; +} + +use Grav\Common\Composer; + if (!file_exists(__DIR__ . '/../vendor')){ // Before we can even start, we need to run composer first + $composer = Composer::getComposerExecutor(); echo "Preparing to install vendor dependencies...\n\n"; - echo system('php bin/composer.phar --working-dir="'.__DIR__.'/../" --no-interaction --no-dev --prefer-dist -o install'); + echo system($composer.' --working-dir="'.__DIR__.'/../" --no-interaction --no-dev --prefer-dist -o install'); echo "\n\n"; } diff --git a/bin/grav b/bin/grav index 1d31dd687..fb101b78f 100755 --- a/bin/grav +++ b/bin/grav @@ -6,10 +6,17 @@ if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) { exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req)); } +if (!file_exists(__DIR__ . '/../vendor')){ + require_once __DIR__ . '/../system/src/Grav/Common/Composer.php'; +} + +use Grav\Common\Composer; + if (!file_exists(__DIR__ . '/../vendor')){ // Before we can even start, we need to run composer first + $composer = Composer::getComposerExecutor(); echo "Preparing to install vendor dependencies...\n\n"; - echo system('php bin/composer.phar --working-dir="'.__DIR__.'/../" --no-interaction --no-dev --prefer-dist -o install'); + echo system($composer.' --working-dir="'.__DIR__.'/../" --no-interaction --no-dev --prefer-dist -o install'); echo "\n\n"; } diff --git a/composer.json b/composer.json index b4422074b..ba949a5df 100644 --- a/composer.json +++ b/composer.json @@ -9,10 +9,10 @@ "php": ">=5.4.0", "twig/twig": "~1.16", "erusev/parsedown-extra": "~0.7", - "symfony/yaml": "~2.6", - "symfony/console": "~2.6", - "symfony/event-dispatcher": "~2.6", - "doctrine/cache": "~1.3", + "symfony/yaml": "2.7.*", + "symfony/console": "2.7.*", + "symfony/event-dispatcher": "2.7.*", + "doctrine/cache": "~1.4", "maximebf/debugbar": "dev-master", "filp/whoops": "1.2.*@dev", "monolog/monolog": "~1.0", diff --git a/system/config/system.yaml b/system/config/system.yaml index 98e183b1a..e438e6b4a 100644 --- a/system/config/system.yaml +++ b/system/config/system.yaml @@ -32,6 +32,8 @@ pages: '<': 'lt' types: 'txt|xml|html|json|rss|atom' # Pipe separated list of valid page types expires: 0 # Page expires time in seconds (604800 seconds = 7 days) + last_modified: true # Set the last modified header + etag: true # Set the expires header tag cache: enabled: true # Set to true to enable caching diff --git a/system/defines.php b/system/defines.php index 71aa234fd..562e18707 100644 --- a/system/defines.php +++ b/system/defines.php @@ -2,7 +2,7 @@ // Some standard defines define('GRAV', true); -define('GRAV_VERSION', '0.9.27'); +define('GRAV_VERSION', '0.9.28'); define('DS', '/'); // Directories and Paths diff --git a/system/src/Grav/Common/Assets.php b/system/src/Grav/Common/Assets.php index 2d44912a8..3662f7d98 100644 --- a/system/src/Grav/Common/Assets.php +++ b/system/src/Grav/Common/Assets.php @@ -825,7 +825,8 @@ class Assets } else { // Fix to remove relative dir if grav is in one if (($this->base_url != '/') && (strpos($this->base_url, $link) == 0)) { - $relative_path = ltrim(preg_replace($this->base_url, '/', $link, 1), '/'); + $base_url = '#' . preg_quote($this->base_url, '#') . '#'; + $relative_path = ltrim(preg_replace($base_url, '/', $link, 1), '/'); } $relative_dir = dirname($relative_path); diff --git a/system/src/Grav/Common/Composer.php b/system/src/Grav/Common/Composer.php new file mode 100644 index 000000000..5efde8912 --- /dev/null +++ b/system/src/Grav/Common/Composer.php @@ -0,0 +1,55 @@ +modified()) . ' GMT'; - header('Last-Modified: ' . $last_modified_date); + if ($page->lastModified()) { + $last_modified_date = gmdate('D, d M Y H:i:s', $page->modified()) . ' GMT'; + header('Last-Modified: ' . $last_modified_date); + } // Calculate a Hash based on the raw file - header('ETag: ' . md5($page->raw().$page->modified())); + if ($page->eTag()) { + header('ETag: ' . md5($page->raw() . $page->modified())); + } // Set debugger data in headers if (!($extension === null || $extension == 'html')) { diff --git a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php index c265bf599..dcf571564 100644 --- a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php +++ b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php @@ -133,10 +133,9 @@ trait ParsedownGravTrait // get the local path to page media if possible if ($path_parts['dirname'] == $this->page->url()) { - $url['path'] = ltrim(str_replace($this->page->url(), '', $url['path']), '/'); + $url['path'] = $path_parts['basename']; // get the media objects for this page $media = $this->page->media(); - } else { // see if this is an external page to this one $page_route = str_replace($this->base_url, '', $path_parts['dirname']); @@ -205,81 +204,10 @@ trait ParsedownGravTrait // if there is no scheme, the file is local if (!isset($url['scheme']) && (count($url) > 0)) { // convert the URl is required - $excerpt['element']['attributes']['href'] = $this->convertUrl(Uri::buildUrl($url)); + $excerpt['element']['attributes']['href'] = Uri::convertUrl($this->page, Uri::buildUrl($url)); } } return $excerpt; } - - /** - * Converts links from absolute '/' or relative (../..) to a grav friendly format - * @param string $markdown_url the URL as it was written in the markdown - * @return string the more friendly formatted url - */ - protected function convertUrl($markdown_url) - { - // if absolute and starts with a base_url move on - if ($this->base_url != '' && Utils::startsWith($markdown_url, $this->base_url)) { - return $markdown_url; - // if contains only a fragment - } elseif (Utils::startsWith($markdown_url, '#')) { - return $markdown_url; - } else { - $target = null; - // see if page is relative to this or absolute - if (Utils::startsWith($markdown_url, '/')) { - $normalized_path = Utils::normalizePath($this->pages_dir . $markdown_url); - $normalized_url = Utils::normalizePath($this->base_url . $markdown_url); - } else { - $normalized_url = $this->base_url . Utils::normalizePath($this->page->route() . '/' . $markdown_url); - $normalized_path = Utils::normalizePath($this->page->path() . '/' . $markdown_url); - } - - // special check to see if path checking is required. - $just_path = str_replace($normalized_url, '', $normalized_path); - if ($just_path == $this->page->path()) { - return $normalized_url; - } - - // if this file exits, get the page and work with that - if ($normalized_path) { - $url_bits = parse_url($normalized_path); - $full_path = $url_bits['path']; - - if ($full_path && file_exists($full_path)) { - $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 - $filename_regex = "/([\w\d-_]+\.([a-zA-Z]{2,4}))$/"; - if (preg_match($filename_regex, $full_path, $matches)) { - if ($matches[2] != 'md') { - $filename = '/' . $matches[1]; - } - } else { - $page_path = $full_path; - } - } - - - - // get page instances and try to find one that fits - $instances = $this->pages->instances(); - if (isset($instances[$page_path])) { - $target = $instances[$page_path]; - $url_bits['path'] = $this->base_url . $target->route() . $filename; - return Uri::buildUrl($url_bits); - } - } - } - - return $normalized_url; - } - } } diff --git a/system/src/Grav/Common/Page/Media.php b/system/src/Grav/Common/Page/Media.php index d73064618..1b7ac2f0b 100644 --- a/system/src/Grav/Common/Page/Media.php +++ b/system/src/Grav/Common/Page/Media.php @@ -120,7 +120,7 @@ class Media extends Getters } foreach ($types['alternative'] as $ratio => $altMedium) { - $medium->addAlternative($ratio, $altMedium); + $medium->addAlternative($ratio, $altMedium['file']); } } diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php index 1d8a80241..c1dbd8e59 100644 --- a/system/src/Grav/Common/Page/Page.php +++ b/system/src/Grav/Common/Page/Page.php @@ -78,6 +78,8 @@ class Page protected $process; protected $summary_size; protected $markdown_extra; + protected $etag; + protected $last_modified; /** * @var Page Unmodified (original) version of the page. Used for copying and moving the page. @@ -277,6 +279,12 @@ class Page if (isset($this->header->expires)) { $this->expires = intval($this->header->expires); } + 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; + } } @@ -361,7 +369,6 @@ class Page $this->id(time().md5($this->filePath())); $this->content = null; } - // If no content, process it if ($this->content === null) { // Get media @@ -568,6 +575,15 @@ class Page return $default; } + public function rawMarkdown($var = null) + { + if ($var !== null) { + $this->raw_content = $var; + } + + return $this->raw_content; + } + /** * Get file object to the page. * @@ -1134,6 +1150,40 @@ class Page return $this->modified; } + /** + * Gets and sets the option to show the etag header for the page. + * + * @param boolean $var show etag header + * @return boolean show etag header + */ + public function eTag($var = null) + { + if ($var !== null) { + $this->etag = $var; + } + if (!isset($this->etag)) { + $this->etag = (bool) self::getGrav()['config']->get('system.pages.etag'); + } + return $this->etag; + } + + /** + * 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) + { + if ($var !== null) { + $this->last_modified = $var; + } + if (!isset($this->last_modified)) { + $this->last_modified = (bool) self::getGrav()['config']->get('system.pages.last_modified'); + } + return $this->last_modified; + } + /** * Gets and sets the path to the .md file for this Page object. * diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php index ac0e9e70f..826618b70 100644 --- a/system/src/Grav/Common/Page/Pages.php +++ b/system/src/Grav/Common/Page/Pages.php @@ -557,7 +557,7 @@ class Pages $last_modified = $modified; } - if (Utils::endsWith($name, CONTENT_EXT)) { + if (preg_match('/^[^.].*'.CONTENT_EXT.'$/', $name)) { $page->init($file); $content_exists = true; diff --git a/system/src/Grav/Common/Page/Types.php b/system/src/Grav/Common/Page/Types.php index 69f61e597..418faba9c 100644 --- a/system/src/Grav/Common/Page/Types.php +++ b/system/src/Grav/Common/Page/Types.php @@ -78,7 +78,7 @@ class Types implements \ArrayAccess, \Iterator, \Countable if (strpos($name, 'modular/') !== 0) { continue; } - $list[$name] = trim(ucfirst(strtr(basename($name), '_', ' '))); + $list[basename($name)] = trim(ucfirst(strtr(basename($name), '_', ' '))); } ksort($list); return $list; diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php index c4297bc20..4c159e5b5 100644 --- a/system/src/Grav/Common/Uri.php +++ b/system/src/Grav/Common/Uri.php @@ -74,15 +74,27 @@ class Uri { $config = Grav::instance()['config']; + // resets + $this->paths = []; + $this->params = []; + $this->query = []; + + // get any params and remove them $uri = str_replace($this->root, '', $this->url); - // reset params - $this->params = []; - // process params $uri = $this->processParams($uri, $config->get('system.param_sep')); + // split the URL and params + $bits = parse_url($uri); + + // process query string + if (isset($bits['query'])) { + parse_str($bits['query'], $this->query); + $uri = $bits['path']; + } + // remove the extension if there is one set $parts = pathinfo($uri); @@ -90,25 +102,13 @@ class Uri $this->basename = $parts['basename']; if (preg_match("/\.(".$config->get('system.pages.types').")$/", $parts['basename'])) { - $uri = rtrim($parts['dirname'], '/').'/'.$parts['filename']; + $uri = rtrim(str_replace(DIRECTORY_SEPARATOR, DS, $parts['dirname']), DS). '/' .$parts['filename']; $this->extension = $parts['extension']; } // set the new url $this->url = $this->root . $uri; - - // split into bits - $this->bits = parse_url($uri); - - $this->query = array(); - if (isset($this->bits['query'])) { - parse_str($this->bits['query'], $this->query); - } - - $path = $this->bits['path']; - - $this->paths = array(); - $this->path = $path; + $this->path = $uri; $this->content_path = trim(str_replace($this->base, '', $this->path), '/'); if ($this->content_path != '') { $this->paths = explode('/', $this->content_path); @@ -441,4 +441,90 @@ class Uri $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; return "$scheme$user$pass$host$port$path$query$fragment"; } + + /** + * Converts links from absolute '/' or relative (../..) to a grav friendly format + * + * @param $page the current page to use as reference + * @param string $markdown_url the URL as it was written in the markdown + * + * @return string the more friendly formatted url + */ + public static function convertUrl($page, $markdown_url) + { + $grav = Grav::instance(); + + $pages_dir = $grav['locator']->findResource('page://'); + $base_url = rtrim($grav['base_url'] . $grav['pages']->base(), '/'); + + // if absolute and starts with a base_url move on + if (pathinfo($markdown_url, PATHINFO_DIRNAME) == '.') { + if ($page->url() == '/') { + return '/' . $markdown_url; + } else { + return $page->url() . '/' . $markdown_url; + } + // no path to convert + } elseif ($base_url != '' && Utils::startsWith($markdown_url, $base_url)) { + return $markdown_url; + // if contains only a fragment + } elseif (Utils::startsWith($markdown_url, '#')) { + return $markdown_url; + } else { + $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 = str_replace($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(urldecode($full_path))) { + $full_path = urldecode($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])) { + $target = $instances[$page_path]; + $url_bits['path'] = $base_url . $target->route() . $filename; + return Uri::buildUrl($url_bits); + } + + return $normalized_url; + } + } } diff --git a/system/src/Grav/Console/ConsoleTrait.php b/system/src/Grav/Console/ConsoleTrait.php index a2e80b6e8..233a5da79 100644 --- a/system/src/Grav/Console/ConsoleTrait.php +++ b/system/src/Grav/Console/ConsoleTrait.php @@ -2,6 +2,7 @@ namespace Grav\Console; use Grav\Common\GravTrait; +use Grav\Common\Composer; use Grav\Console\Cli\ClearCacheCommand; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\ArrayInput; @@ -85,7 +86,9 @@ trait ConsoleTrait public function composerUpdate($path, $action = 'install') { - return system('php bin/composer.phar --working-dir="'.$path.'" --no-interaction --no-dev --prefer-dist -o '. $action); + $composer = Composer::getComposerExecutor(); + + return system($composer . ' --working-dir="'.$path.'" --no-interaction --no-dev --prefer-dist -o '. $action); } /** diff --git a/system/src/Grav/Console/Gpm/UninstallCommand.php b/system/src/Grav/Console/Gpm/UninstallCommand.php index 085612c4f..8d32d685f 100644 --- a/system/src/Grav/Console/Gpm/UninstallCommand.php +++ b/system/src/Grav/Console/Gpm/UninstallCommand.php @@ -110,14 +110,14 @@ class UninstallCommand extends Command $this->output->write(" |- Checking destination... "); - $checks = $this->checkDestination($package); + $checks = $this->checkDestination($slug, $package); if (!$checks) { $this->output->writeln(" '- Installation failed or aborted."); $this->output->writeln(''); } else { $this->output->write(" |- Uninstalling package... "); - $uninstall = $this->uninstallPackage($package); + $uninstall = $this->uninstallPackage($slug, $package); if (!$uninstall) { $this->output->writeln(" '- Uninstallation failed or aborted."); @@ -135,12 +135,16 @@ class UninstallCommand extends Command /** + * @param $slug * @param $package + * * @return bool */ - private function uninstallPackage($package) + private function uninstallPackage($slug, $package) { - $path = self::getGrav()['locator']->findResource($package->package_type . '://' . $package->slug); + $locator = self::getGrav()['locator']; + + $path = self::getGrav()['locator']->findResource($package->package_type . '://' .$slug); Installer::uninstall($path); $errorCode = Installer::lastErrorCode(); @@ -159,15 +163,17 @@ class UninstallCommand extends Command return true; } + /** + * @param $slug * @param $package * * @return bool */ - private function checkDestination($package) + private function checkDestination($slug, $package) { - $path = self::getGrav()['locator']->findResource($package->package_type . '://' . $package->slug); + $path = self::getGrav()['locator']->findResource($package->package_type . '://' . $slug); $questionHelper = $this->getHelper('question'); $skipPrompt = $this->input->getOption('all-yes');