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');