diff --git a/CHANGELOG.md b/CHANGELOG.md index 67164b01e..a88529ade 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +# v0.9.17 +## 02/05/2015 + +1. [](#new) + * Added **full HHVM support!** Get your speed on with Facebook's crazy fast PHP JIT compiler +2. [](#improved) + * More flexible page summary control + * Support **CamelCase** plugin and theme class names. Replaces dashes and underscores + * Moved summary delimiter into `site.yaml` so it can be configurable + * Various PSR fixes +3. [](#bugfix) + * Fix for `mergeConfig()` not falling back to defaults + * Fix for `addInlineCss()` and `addInlineJs()` Assets not working between Twig tags + * Fix for Markdown adding HTML tags into inline CSS and JS + # v0.9.16 ## 01/30/2015 diff --git a/bin/composer.phar b/bin/composer.phar index e46677b91..227468449 100755 Binary files a/bin/composer.phar and b/bin/composer.phar differ diff --git a/index.php b/index.php index cf2644f6c..4d125f332 100644 --- a/index.php +++ b/index.php @@ -5,6 +5,7 @@ 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.', $ver, $req)); } +// Ensure vendor libraries exist $autoload = __DIR__ . '/vendor/autoload.php'; if (!is_file($autoload)) { exit('Please run: bin/grav install'); @@ -15,19 +16,19 @@ use Grav\Common\Grav; // Register the auto-loader. $loader = require_once $autoload; -if (!ini_get('date.timezone')) { - date_default_timezone_set('UTC'); -} +// Set timezone to default, falls back to system if php.ini not set +date_default_timezone_set(@date_default_timezone_get()); +// Get the Grav instance $grav = Grav::instance( array( 'loader' => $loader ) ); +// Process the page try { $grav->process(); - } catch (\Exception $e) { $grav->fireEvent('onFatalException'); throw $e; diff --git a/nginx.conf b/nginx.conf index 07295e4da..22f7acd56 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; - } + 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 /cache { + rewrite ^/cache/(.*) /error redirect; + } - location /bin { - rewrite ^/bin/(.*)$ /error redirect; - } + location /bin { + rewrite ^/bin/(.*)$ /error redirect; + } - location /system { - rewrite ^/system/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /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; - } + 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 @@ -60,7 +60,6 @@ http { include fastcgi_params; } - } } diff --git a/system/config/system.yaml b/system/config/system.yaml index a3ac7e7a4..9edc14046 100644 --- a/system/config/system.yaml +++ b/system/config/system.yaml @@ -1,4 +1,5 @@ absolute_urls: false # Absolute or relative URLs for `base_url` +timezone: '' # Valid values: http://php.net/manual/en/timezones.php home: alias: '/home' # Default path for home, ie / diff --git a/system/defines.php b/system/defines.php index e55db94b1..562e85140 100644 --- a/system/defines.php +++ b/system/defines.php @@ -2,7 +2,7 @@ // Some standard defines define('GRAV', true); -define('GRAV_VERSION', '0.9.16'); +define('GRAV_VERSION', '0.9.17'); define('DS', '/'); // Directories and Paths diff --git a/system/src/Grav/Common/Assets.php b/system/src/Grav/Common/Assets.php index b63de9c72..4e940d09e 100644 --- a/system/src/Grav/Common/Assets.php +++ b/system/src/Grav/Common/Assets.php @@ -163,8 +163,8 @@ class Assets public function init() { /** @var Config $config */ - $config = self::$grav['config']; - $base_url = self::$grav['base_url']; + $config = self::getGrav()['config']; + $base_url = self::getGrav()['base_url']; $asset_config = (array)$config->get('system.assets'); $this->config($asset_config); @@ -358,7 +358,7 @@ class Assets } // Sort array by priorities (larger priority first) - if (self::$grav) { + if (self::getGrav()) { usort($this->css, function ($a, $b) { if ($a['priority'] == $b['priority']) { return $b['order'] - $a['order']; @@ -471,7 +471,7 @@ class Assets protected function pipeline($css = true) { /** @var Cache $cache */ - $cache = self::$grav['cache']; + $cache = self::getGrav()['cache']; $key = '?' . $cache->getKey(); if ($css) { @@ -687,7 +687,7 @@ class Assets protected function buildLocalLink($asset) { try { - $asset = self::$grav['locator']->findResource($asset, false); + $asset = self::getGrav()['locator']->findResource($asset, false); } catch (\Exception $e) { } diff --git a/system/src/Grav/Common/Filesystem/Folder.php b/system/src/Grav/Common/Filesystem/Folder.php index 79eb5ada8..223b3dce8 100644 --- a/system/src/Grav/Common/Filesystem/Folder.php +++ b/system/src/Grav/Common/Filesystem/Folder.php @@ -19,12 +19,13 @@ abstract class Folder { $last_modified = 0; - $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS); - $iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST); + $dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS); + $filterItr = new GravRecursiveFolderFilterIterator($dirItr); + $itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST); /** @var \RecursiveDirectoryIterator $file */ - foreach ($iterator as $file) { - $dir_modified = $file->getMTime(); + foreach ($itr as $dir) { + $dir_modified = $dir->getMTime(); if ($dir_modified > $last_modified) { $last_modified = $dir_modified; } @@ -33,6 +34,34 @@ abstract class Folder return $last_modified; } + /** + * Recursively find the last modified time under given path by file. + * + * @param string $path + * @return int + */ + public static function lastModifiedFile($path) + { + $last_modified = 0; + + $dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS); + $filterItr = new GravRecursiveFileFilterIterator($dirItr); + $itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST); + + /** @var \RecursiveDirectoryIterator $file */ + foreach ($itr as $file) { + if ($file->isDir()) { + continue; + } + $file_modified = $file->getMTime(); + if ($file_modified > $last_modified) { + $last_modified = $file_modified; + } + } + + return $last_modified; + } + /** * Get relative path between target and base path. If path isn't relative, return full path. * @@ -68,30 +97,7 @@ abstract class Folder return $result ?: null; } - /** - * Recursively find the last modified time under given path by file. - * - * @param string $path - * @return int - */ - public static function lastModifiedFile($path) - { - $last_modified = 0; - $dirItr = new \RecursiveDirectoryIterator($path); - $filterItr = new GravRecursiveFilterIterator($dirItr); - $itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST); - - /** @var \RecursiveDirectoryIterator $file */ - foreach ($itr as $file) { - $file_modified = $file->getMTime(); - if ($file_modified > $last_modified) { - $last_modified = $file_modified; - } - } - - return $last_modified; - } /** * Return recursive list of all files and directories under given path. @@ -296,19 +302,22 @@ abstract class Folder } } -class GravRecursiveFilterIterator extends \RecursiveFilterIterator +class GravRecursiveFolderFilterIterator extends \RecursiveFilterIterator { - public static $FILTERS = array( - '..', '.DS_Store' - ); + public function accept() + { + // only accept directories + return $this->current()->isDir(); + } +} + +class GravRecursiveFileFilterIterator extends \RecursiveFilterIterator +{ + public static $FILTERS = ['.DS_Store']; public function accept() { - return !in_array( - $this->current()->getFilename(), - self::$FILTERS, - true - ); + // Ensure any filtered file names are skipped + return !in_array($this->current()->getFilename(), self::$FILTERS, true); } - } diff --git a/system/src/Grav/Common/GPM/GPM.php b/system/src/Grav/Common/GPM/GPM.php index fd82f3642..c33d8cbb5 100644 --- a/system/src/Grav/Common/GPM/GPM.php +++ b/system/src/Grav/Common/GPM/GPM.php @@ -1,7 +1,9 @@ 'user/plugins/%name%', 'themes' => 'user/themes/%name%', 'skeletons' => 'user/']; + /** * Creates a new GPM instance with Local and Remote packages available * @param boolean $refresh Applies to Remote Packages only and forces a refetch of data @@ -347,7 +351,20 @@ class GPM extends Iterator $packages = ['total' => 0, 'not_found' => []]; foreach ($searches as $search) { + $repository = ''; + // if this is an object, get the search data from the key + if (is_object($search)) { + $search = (array) $search; + $key = key($search); + $repository = $search[$key]; + $search = $key; + } + if ($found = $this->findPackage($search)) { + // set override respository if provided + if ($repository) { + $found->override_repository = $repository; + } if (!isset($packages[$found->package_type])) { $packages[$found->package_type] = []; } @@ -355,7 +372,20 @@ class GPM extends Iterator $packages[$found->package_type][$found->slug] = $found; $packages['total']++; } else { - $packages['not_found'][] = $search; + // make a best guess at the type based on the repo URL + if (Utils::contains($repository, '-theme')) { + $type = 'themes'; + } else { + $type = 'plugins'; + } + + $not_found = new \stdClass(); + $not_found->name = Inflector::camelize($search); + $not_found->slug = $search; + $not_found->package_type = $type; + $not_found->install_path = str_replace('%name%', $search, $this->install_paths[$type]); + $not_found->override_repository = $repository; + $packages['not_found'][$search] = $not_found; } } diff --git a/system/src/Grav/Common/GPM/Local/Plugins.php b/system/src/Grav/Common/GPM/Local/Plugins.php index b5193ad86..b52efea93 100644 --- a/system/src/Grav/Common/GPM/Local/Plugins.php +++ b/system/src/Grav/Common/GPM/Local/Plugins.php @@ -17,7 +17,7 @@ class Plugins extends Collection */ public function __construct() { - $grav = self::$grav; + $grav = self::getGrav(); foreach ($grav['plugins']->all() as $name => $data) { $this->items[$name] = new Package($data, $this->type); diff --git a/system/src/Grav/Common/GPM/Local/Themes.php b/system/src/Grav/Common/GPM/Local/Themes.php index 19d68c6a6..673490144 100644 --- a/system/src/Grav/Common/GPM/Local/Themes.php +++ b/system/src/Grav/Common/GPM/Local/Themes.php @@ -6,7 +6,7 @@ class Themes extends Collection private $type = 'themes'; public function __construct() { - $grav = self::$grav; + $grav = self::getGrav(); foreach ($grav['themes']->all() as $name => $data) { $this->items[$name] = new Package($data, $this->type); diff --git a/system/src/Grav/Common/GPM/Remote/Collection.php b/system/src/Grav/Common/GPM/Remote/Collection.php index 58e2e6942..87c97ffae 100644 --- a/system/src/Grav/Common/GPM/Remote/Collection.php +++ b/system/src/Grav/Common/GPM/Remote/Collection.php @@ -31,7 +31,7 @@ class Collection extends Iterator { throw new \RuntimeException("A repository is required for storing the cache"); } - $cache_dir = self::$grav['locator']->findResource('cache://gpm', true, true); + $cache_dir = self::getGrav()['locator']->findResource('cache://gpm', true, true); $this->cache = new FilesystemCache($cache_dir); $this->repository = $repository; diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php index ee40691d9..3c8cbacd1 100644 --- a/system/src/Grav/Common/Grav.php +++ b/system/src/Grav/Common/Grav.php @@ -172,7 +172,6 @@ class Grav extends Container ob_start('ob_gzhandler'); } - /** @var Debugger $debugger */ $debugger = $this['debugger']; @@ -184,6 +183,11 @@ class Grav extends Container $this['config']->debug(); $debugger->stopTimer('_config'); + // Initialize the timezone + if ($this['config']->get('system.timezone')) { + date_default_timezone_set($this['config']->get('system.timezone')); + } + $debugger->startTimer('streams', 'Streams'); $this['streams']; $debugger->stopTimer('streams'); diff --git a/system/src/Grav/Common/GravTrait.php b/system/src/Grav/Common/GravTrait.php index b18258323..171017363 100644 --- a/system/src/Grav/Common/GravTrait.php +++ b/system/src/Grav/Common/GravTrait.php @@ -11,8 +11,11 @@ trait GravTrait /** * @return Grav */ - public function getGrav() + public static function getGrav() { + if (!self::$grav) { + self::$grav = Grav::instance(); + } return self::$grav; } diff --git a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php index 03d3e4bf3..ef6c8b12c 100644 --- a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php +++ b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php @@ -29,11 +29,18 @@ trait ParsedownGravTrait protected function init($page) { $this->page = $page; - $this->pages = self::$grav['pages']; + $this->pages = self::getGrav()['pages']; $this->BlockTypes['{'] [] = "TwigTag"; - $this->base_url = rtrim(self::$grav['base_url'] . self::$grav['pages']->base(), '/'); - $this->pages_dir = self::$grav['locator']->findResource('page://'); + $this->base_url = rtrim(self::getGrav()['base_url'] . self::getGrav()['pages']->base(), '/'); + $this->pages_dir = self::getGrav()['locator']->findResource('page://'); $this->special_chars = array('>' => 'gt', '<' => 'lt', '"' => 'quot'); + + $defaults = self::getGrav()['config']->get('system.pages.markdown'); + + $this->setBreaksEnabled($defaults['auto_line_breaks']); + $this->setUrlsLinked($defaults['auto_url_links']); + $this->setMarkupEscaped($defaults['escape_markup']); + $this->setSpecialChars($defaults['special_chars']); } /** @@ -156,6 +163,7 @@ trait ParsedownGravTrait } $excerpt['element'] = $medium->parseDownElement($title, $alt, $class); + } else { // not a current page media file, see if it needs converting to relative $excerpt['element']['attributes']['src'] = Uri::build_url($url); diff --git a/system/src/Grav/Common/Page/Media.php b/system/src/Grav/Common/Page/Media.php index a43653808..8c2d38a53 100644 --- a/system/src/Grav/Common/Page/Media.php +++ b/system/src/Grav/Common/Page/Media.php @@ -77,7 +77,7 @@ class Media extends Getters } } else { $altMedium = $this->createMedium($info->getPathname()); - + if (!$altMedium) { continue; } @@ -207,7 +207,7 @@ class Media extends Getters * Create a Medium object from a file * * @param string $file - * + * * @return Medium|null */ protected function createMedium($file) @@ -223,7 +223,7 @@ class Media extends Getters $basename = implode('.', $parts); /** @var Config $config */ - $config = self::$grav['config']; + $config = self::getGrav()['config']; // Check if medium type has been configured. $params = $config->get("media.".strtolower($ext)); @@ -245,7 +245,7 @@ class Media extends Getters 'modified' => filemtime($file), ); - $locator = self::$grav['locator']; + $locator = self::getGrav()['locator']; $lookup = $locator->findResources('image://'); foreach ($lookup as $lookupPath) { @@ -278,7 +278,7 @@ class Media extends Getters $medium->set('debug', false); $file = $medium->resize($width, $height)->setPrettyName($basename)->url(); - $file = preg_replace('|'. preg_quote(self::$grav['base_url_relative']) .'$|', '', GRAV_ROOT) . $file; + $file = preg_replace('|'. preg_quote(self::getGrav()['base_url_relative']) .'$|', '', GRAV_ROOT) . $file; $medium->set('debug', $debug); diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php index 404ce7c42..4d6c6169c 100644 --- a/system/src/Grav/Common/Page/Page.php +++ b/system/src/Grav/Common/Page/Page.php @@ -96,7 +96,7 @@ class Page public function __construct($array = array()) { /** @var Config $config */ - $config = self::$grav['config']; + $config = self::getGrav()['config']; $this->routable = true; $this->taxonomy = array(); @@ -123,20 +123,20 @@ class Page $this->modularTwig($this->slug[0] == '_'); // Handle publishing dates if no explict published option set - if (self::$grav['config']->get('system.pages.publish_dates') && !isset($this->header->published)) { + if (self::getGrav()['config']->get('system.pages.publish_dates') && !isset($this->header->published)) { // unpublish if required, if not clear cache right before page should be unpublished if ($this->unpublishDate()) { if ($this->unpublishDate() < time()) { $this->published(false); } else { $this->published(); - self::$grav['cache']->setLifeTime($this->unpublishDate()); + self::getGrav()['cache']->setLifeTime($this->unpublishDate()); } } // publish if required, if not clear cache right before page is published if ($this->publishDate() != $this->modified() && $this->publishDate() > time()) { $this->published(false); - self::$grav['cache']->setLifeTime($this->publishDate()); + self::getGrav()['cache']->setLifeTime($this->publishDate()); } } $this->published(); @@ -300,7 +300,7 @@ class Page public function summary($size = null) { /** @var Config $config */ - $config = self::$grav['config']; + $config = self::getGrav()['config']; $content = $this->content(); // Return summary based on settings in site config file @@ -362,7 +362,7 @@ class Page // Load cached content /** @var Cache $cache */ - $cache = self::$grav['cache']; + $cache = self::getGrav()['cache']; $cache_id = md5('page'.$this->id()); $this->content = $cache->fetch($cache_id); @@ -375,7 +375,7 @@ class Page // if no cached-content run everything if ($this->content == false) { $this->content = $this->raw_content; - self::$grav->fireEvent('onPageContentRaw', new Event(['page' => $this])); + self::getGrav()->fireEvent('onPageContentRaw', new Event(['page' => $this])); if ($twig_first) { if ($process_twig) { @@ -412,7 +412,7 @@ class Page } // Handle summary divider - $delimiter = self::$grav['config']->get('site.summary.delimiter', '==='); + $delimiter = self::getGrav()['config']->get('site.summary.delimiter', '==='); $divider_pos = strpos($this->content, "

{$delimiter}

"); if ($divider_pos !== false) { $this->summary_size = $divider_pos; @@ -430,7 +430,7 @@ class Page protected function processMarkdown() { /** @var Config $config */ - $config = self::$grav['config']; + $config = self::getGrav()['config']; $defaults = (array) $config->get('system.pages.markdown'); if (isset($this->header()->markdown)) { @@ -449,11 +449,6 @@ class Page $parsedown = new Parsedown($this); } - $parsedown->setBreaksEnabled($defaults['auto_line_breaks']); - $parsedown->setUrlsLinked($defaults['auto_url_links']); - $parsedown->setMarkupEscaped($defaults['escape_markup']); - $parsedown->setSpecialChars($defaults['special_chars']); - $this->content = $parsedown->text($this->content); } @@ -463,7 +458,7 @@ class Page */ private function processTwig() { - $twig = self::$grav['twig']; + $twig = self::getGrav()['twig']; $this->content = $twig->processPage($this, $this->content); } @@ -472,10 +467,10 @@ class Page */ private function cachePageContent() { - $cache = self::$grav['cache']; + $cache = self::getGrav()['cache']; $cache_id = md5('page'.$this->id()); - self::$grav->fireEvent('onPageContentProcessed', new Event(['page' => $this])); + self::getGrav()->fireEvent('onPageContentProcessed', new Event(['page' => $this])); $cache->save($cache_id, $this->content); } @@ -650,7 +645,7 @@ class Page public function blueprints() { /** @var Pages $pages */ - $pages = self::$grav['pages']; + $pages = self::getGrav()['pages']; return $pages->blueprints($this->template()); } @@ -729,7 +724,7 @@ class Page public function media($var = null) { /** @var Cache $cache */ - $cache = self::$grav['cache']; + $cache = self::getGrav()['cache']; if ($var) { $this->media = $var; @@ -947,7 +942,6 @@ class Page // if not metadata yet, process it. if (null === $this->metadata) { - $header_tag_http_equivs = ['content-type', 'default-style', 'refresh']; $this->metadata = array(); $page_header = $this->header; @@ -958,7 +952,7 @@ class Page // Safety check to ensure we have a header if ($page_header) { // Merge any site.metadata settings in with page metadata - $defaults = (array) self::$grav['config']->get('site.metadata'); + $defaults = (array) self::getGrav()['config']->get('site.metadata'); if (isset($page_header->metadata)) { $page_header->metadata = array_merge($defaults, $page_header->metadata); @@ -1061,10 +1055,10 @@ class Page public function url($include_host = false) { /** @var Pages $pages */ - $pages = self::$grav['pages']; + $pages = self::getGrav()['pages']; /** @var Uri $uri */ - $uri = self::$grav['uri']; + $uri = self::getGrav()['uri']; $rootUrl = $uri->rootUrl($include_host) . $pages->base(); $url = $rootUrl.'/'.trim($this->route(), '/'); @@ -1136,7 +1130,7 @@ class Page // Path to the page. $this->path = dirname(dirname($var)); } - return $this->name ? $this->path . '/' . $this->folder . '/' . $this->name : null; + return $this->path . '/' . $this->folder . '/' . ($this->name ?: ''); } /** @@ -1263,7 +1257,7 @@ class Page } if (empty($this->max_count)) { /** @var Config $config */ - $config = self::$grav['config']; + $config = self::getGrav()['config']; $this->max_count = (int) $config->get('system.pages.list.count'); } return $this->max_count; @@ -1338,7 +1332,7 @@ class Page } /** @var Pages $pages */ - $pages = self::$grav['pages']; + $pages = self::getGrav()['pages']; return $pages->get($this->parent); } @@ -1351,7 +1345,7 @@ class Page public function children() { /** @var Pages $pages */ - $pages = self::$grav['pages']; + $pages = self::getGrav()['pages']; return $pages->children($this->path()); } @@ -1418,7 +1412,7 @@ class Page public function active() { /** @var Uri $uri */ - $uri = self::$grav['uri']; + $uri = self::getGrav()['uri']; if ($this->url() == $uri->url()) { return true; } @@ -1434,8 +1428,8 @@ class Page public function activeChild() { /** @var Uri $uri */ - $uri = self::$grav['uri']; - $config = self::$grav['config']; + $uri = self::getGrav()['uri']; + $config = self::getGrav()['config']; // Special check when item is home if ($this->home()) { @@ -1489,7 +1483,7 @@ class Page public function find($url, $all = false) { /** @var Pages $pages */ - $pages = self::$grav['pages']; + $pages = self::getGrav()['pages']; return $pages->dispatch($url, $all); } @@ -1521,9 +1515,9 @@ class Page // TODO: MOVE THIS INTO SOMEWHERE ELSE? /** @var Uri $uri */ - $uri = self::$grav['uri']; + $uri = self::getGrav()['uri']; /** @var Config $config */ - $config = self::$grav['config']; + $config = self::getGrav()['config']; foreach ((array) $config->get('site.taxonomies') as $taxonomy) { if ($uri->param($taxonomy)) { @@ -1559,7 +1553,7 @@ class Page } /** @var Grav $grav */ - $grav = self::$grav['grav']; + $grav = self::getGrav()['grav']; // New Custom event to handle things like pagination. $grav->fireEvent('onCollectionProcessed', new Event(['collection' => $collection])); @@ -1639,7 +1633,7 @@ class Page // @taxonomy: { category: [ blog, featured ], level: 1 } /** @var Taxonomy $taxonomy_map */ - $taxonomy_map = self::$grav['taxonomy']; + $taxonomy_map = self::getGrav()['taxonomy']; if (!empty($parts)) { $params = [implode('.', $parts) => $params]; @@ -1715,7 +1709,7 @@ class Page // Do reordering. if ($reorder && $this->order() != $this->_original->order()) { /** @var Pages $pages */ - $pages = self::$grav['pages']; + $pages = self::getGrav()['pages']; $parent = $this->parent(); diff --git a/system/src/Grav/Common/Plugins.php b/system/src/Grav/Common/Plugins.php index dd495963e..d0c30c3e3 100644 --- a/system/src/Grav/Common/Plugins.php +++ b/system/src/Grav/Common/Plugins.php @@ -47,7 +47,8 @@ class Plugins extends Iterator $filePath = $this->grav['locator']('plugins://' . $plugin . DS . $plugin . PLUGIN_EXT); if (!is_file($filePath)) { - throw new \RuntimeException(sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $plugin)); + $this->grav['log']->addWarning(sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $plugin)); + continue; } require_once $filePath; @@ -93,7 +94,8 @@ class Plugins extends Iterator public static function all() { $list = array(); - $iterator = new \DirectoryIterator('plugins://'); + $locator = Grav::instance()['locator']; + $iterator = new \DirectoryIterator($locator->findResource('plugins://', false)); /** @var \DirectoryIterator $directory */ foreach ($iterator as $directory) { diff --git a/system/src/Grav/Common/Themes.php b/system/src/Grav/Common/Themes.php index e44472835..4ec394ee9 100644 --- a/system/src/Grav/Common/Themes.php +++ b/system/src/Grav/Common/Themes.php @@ -59,7 +59,8 @@ class Themes extends Iterator public function all() { $list = array(); - $iterator = new \DirectoryIterator('themes://'); + $locator = Grav::instance()['locator']; + $iterator = new \DirectoryIterator($locator->findResource('themes://', false)); /** @var \DirectoryIterator $directory */ foreach ($iterator as $directory) { diff --git a/system/src/Grav/Common/Twig.php b/system/src/Grav/Common/Twig.php index 0fbdb81fc..e5a7fafbd 100644 --- a/system/src/Grav/Common/Twig.php +++ b/system/src/Grav/Common/Twig.php @@ -219,6 +219,34 @@ class Twig } /** + * Process a Twig template directly by using a template name + * and optional array of variables + * + * @param string $template template to render with + * @param array $vars Optional variables + * @return string + */ + public function processTemplate($template, $vars = array()) + { + // override the twig header vars for local resolution + $this->grav->fireEvent('onTwigTemplateVariables'); + $vars += $this->twig_vars; + + try { + $output = $this->twig->render($template, $vars); + } catch (\Twig_Error_Loader $e) { + throw new \RuntimeException($e->getRawMessage(), 404, $e); + } + + return $output; + + } + + + /** + * Process a Twig template directly by using a Twig string + * and optional array of variables + * * @param string $string string to render. * @param array $vars Optional variables * @return string diff --git a/system/src/Grav/Common/TwigExtension.php b/system/src/Grav/Common/TwigExtension.php index 65a546900..303c7b2b7 100644 --- a/system/src/Grav/Common/TwigExtension.php +++ b/system/src/Grav/Common/TwigExtension.php @@ -1,6 +1,8 @@ SendEmail * {{ 'CamelCased'|underscorize }} => camel_cased * {{ 'Something Text'|hyphenize }} => something-text - * {{ 'something text to read'|humanize }} => "Something text to read" - * {{ '181'|monthize}} => 6 + * {{ 'something_text_to_read'|humanize }} => "Something text to read" + * {{ '181'|monthize }} => 6 * {{ '10'|ordinalize }} => 10th * * @param string $action @@ -316,6 +320,31 @@ class TwigExtension extends \Twig_Extension return "$difference $periods[$j] {$tense}"; } + public function absoluteUrlFilter($string) + { + $url = $this->grav['uri']->base(); + $string = preg_replace('/((?:href|src) *= *[\'"](?!(http|ftp)))/i', "$1$url", $string); + return $string; + + } + + public function markdownFilter($string) + { + $page = $this->grav['page']; + $defaults = $this->grav['config']->get('system.pages.markdown'); + + // Initialize the preferred variant of Parsedown + if ($defaults['extra']) { + $parsedown = new ParsedownExtra($page); + } else { + $parsedown = new Parsedown($page); + } + + $string = $parsedown->text($string); + + return $string; + } + /** * Repeat given string x times. * diff --git a/system/src/Grav/Common/User/User.php b/system/src/Grav/Common/User/User.php index 8da0896fe..80c0f9a0f 100644 --- a/system/src/Grav/Common/User/User.php +++ b/system/src/Grav/Common/User/User.php @@ -26,7 +26,7 @@ class User extends Data */ public static function load($username) { - $locator = self::$grav['locator']; + $locator = self::getGrav()['locator']; // FIXME: validate directory name $blueprints = new Blueprints('blueprints://user'); diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index ac4f95f0f..9c973964b 100644 --- a/system/src/Grav/Common/Utils.php +++ b/system/src/Grav/Common/Utils.php @@ -28,6 +28,16 @@ abstract class Utils return $needle === '' || substr($haystack, -strlen($needle)) === $needle; } + /** + * @param string $haystack + * @param string $needle + * @return bool + */ + public static function contains($haystack, $needle) + { + return $needle === '' || strpos($haystack, $needle) !== false; + } + /** * Merge two objects into one. * @@ -41,7 +51,7 @@ abstract class Utils } /** - * Recurseive remove a directory - DANGEROUS! USE WITH CARE!!!! + * Recursive remove a directory - DANGEROUS! USE WITH CARE!!!! * * @param $dir * @return bool @@ -69,6 +79,46 @@ abstract class Utils return rmdir($dir); } + /** + * Recursive copy of one directory to another + * + * @param $src + * @param $dest + * + * @return bool + */ + public static function rcopy($src, $dest) + { + + // If the src is not a directory do a simple file copy + if (!is_dir($src)) { + copy($src, $dest); + return true; + } + + // If the destination directory does not exist create it + if (!is_dir($dest)) { + if (!mkdir($dest)) { + // If the destination directory could not be created stop processing + return false; + } + } + + // Open the source directory to read in files + $i = new \DirectoryIterator($src); + /** @var \DirectoryIterator $f */ + foreach ($i as $f) { + if ($f->isFile()) { + copy($f->getRealPath(), "$dest/" . $f->getFilename()); + } else { + if (!$f->isDot() && $f->isDir()) { + static::rcopy($f->getRealPath(), "$dest/$f"); + } + } + } + return true; + } + /** * Truncate HTML by text length. * diff --git a/system/src/Grav/Console/Cli/SandboxCommand.php b/system/src/Grav/Console/Cli/SandboxCommand.php index 125fe7d0a..4d984d219 100644 --- a/system/src/Grav/Console/Cli/SandboxCommand.php +++ b/system/src/Grav/Console/Cli/SandboxCommand.php @@ -2,6 +2,7 @@ namespace Grav\Console\Cli; use Grav\Common\Filesystem\Folder; +use Grav\Common\Utils; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\InputArgument; @@ -188,7 +189,7 @@ class SandboxCommand extends Command $to = $this->destination . $target; $this->output->writeln(' ' . $source . ' -> ' . $to); - $this->rcopy($from, $to); + Utils::rcopy($from, $to); } } @@ -268,7 +269,7 @@ class SandboxCommand extends Command if (count($pages_files) == 0) { $destination = $this->source . '/user/pages'; - $this->rcopy($destination, $pages_dir); + Utils::rcopy($destination, $pages_dir); $this->output->writeln(' ' . $destination . ' -> Created'); } @@ -326,42 +327,4 @@ class SandboxCommand extends Command exit; } } - - /** - * @param $src - * @param $dest - * - * @return bool - */ - private function rcopy($src, $dest) - { - - // If the src is not a directory do a simple file copy - if (!is_dir($src)) { - copy($src, $dest); - return true; - } - - // If the destination directory does not exist create it - if (!is_dir($dest)) { - if (!mkdir($dest)) { - // If the destination directory could not be created stop processing - return false; - } - } - - // Open the source directory to read in files - $i = new \DirectoryIterator($src); - /** @var \DirectoryIterator $f */ - foreach ($i as $f) { - if ($f->isFile()) { - copy($f->getRealPath(), "$dest/" . $f->getFilename()); - } else { - if (!$f->isDot() && $f->isDir()) { - $this->rcopy($f->getRealPath(), "$dest/$f"); - } - } - } - return true; - } } diff --git a/system/src/Grav/Console/ConsoleTrait.php b/system/src/Grav/Console/ConsoleTrait.php index 4ad07bc4b..42c65d495 100644 --- a/system/src/Grav/Console/ConsoleTrait.php +++ b/system/src/Grav/Console/ConsoleTrait.php @@ -35,8 +35,8 @@ trait ConsoleTrait */ public function setupConsole(InputInterface $input, OutputInterface $output) { - if (self::$grav) { - self::$grav['config']->set('system.cache.driver', 'default'); + if (self::getGrav()) { + self::getGrav()['config']->set('system.cache.driver', 'default'); } $this->argv = $_SERVER['argv'][0]; diff --git a/system/src/Grav/Console/Gpm/InstallCommand.php b/system/src/Grav/Console/Gpm/InstallCommand.php index 261a73f4a..c39087db4 100644 --- a/system/src/Grav/Console/Gpm/InstallCommand.php +++ b/system/src/Grav/Console/Gpm/InstallCommand.php @@ -5,6 +5,8 @@ use Grav\Common\Filesystem\Folder; use Grav\Common\GPM\GPM; use Grav\Common\GPM\Installer; use Grav\Common\GPM\Response; +use Grav\Common\Inflector; +use Grav\Common\Utils; use Grav\Console\ConsoleTrait; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -12,6 +14,10 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Yaml\Yaml; + +define('GIT_REGEX', '/http[s]?:\/\/(?:.*@)?(github|bitbucket)(?:.org|.com)\/.*\/(.*)/'); /** * Class InstallCommand @@ -42,6 +48,9 @@ class InstallCommand extends Command */ protected $tmp; + protected $local_config; + + /** * */ @@ -93,6 +102,11 @@ class InstallCommand extends Command $packages = array_map('strtolower', $this->input->getArgument('package')); $this->data = $this->gpm->findPackages($packages); + $local_config_file = exec('eval echo ~/.grav/config'); + if (file_exists($local_config_file)) { + $this->local_config = Yaml::parse($local_config_file); + } + if ( !Installer::isGravInstance($this->destination) || !Installer::isValidDestination($this->destination, [Installer::EXISTS, Installer::IS_LINK]) @@ -111,7 +125,7 @@ class InstallCommand extends Command if (count($this->data['not_found'])) { $this->output->writeln("These packages were not found on Grav: " . implode(', ', - $this->data['not_found']) . ""); + array_keys($this->data['not_found'])) . ""); } unset($this->data['not_found']); @@ -119,29 +133,29 @@ class InstallCommand extends Command foreach ($this->data as $data) { foreach ($data as $package) { - $version = isset($package->available) ? $package->available : $package->version; - $this->output->writeln("Preparing to install " . $package->name . " [v" . $version . "]"); - - $this->output->write(" |- Downloading package... 0%"); - $this->file = $this->downloadPackage($package); - - $this->output->write(" |- Checking destination... "); - $checks = $this->checkDestination($package); - - if (!$checks) { - $this->output->writeln(" '- Installation failed or aborted."); + //Check for dependencies + if (isset($package->dependencies)) { + $this->output->writeln("Package " . $package->name . " has ". count($package->dependencies) . " required dependencies that must be installed first..."); $this->output->writeln(''); - } else { - $this->output->write(" |- Installing package... "); - $installation = $this->installPackage($package); - if (!$installation) { - $this->output->writeln(" '- Installation failed or aborted."); + + $dependency_data = $this->gpm->findPackages($package->dependencies); + + if (!$dependency_data['total']) { + $this->output->writeln("No dependencies found..."); $this->output->writeln(''); } else { - $this->output->writeln(" '- Success! "); - $this->output->writeln(''); + unset($dependency_data['total']); + + foreach($dependency_data as $type => $dep_data) { + foreach($dep_data as $name => $dep_package) { + + $this->processPackage($dep_package); + } + } } } + + $this->processPackage($package); } } @@ -149,6 +163,255 @@ class InstallCommand extends Command $this->clearCache(); } + /** + * @param $package + */ + private function processPackage($package) + { + $install_options = ['GPM']; + + // if no name, not found in GPM + if (!isset($package->version)) { + unset($install_options[0]); + } + // if local config found symlink is a valid option + if (isset($this->local_config) && $this->getSymlinkSource($package)) { + $install_options[] = 'Symlink'; + } + // if override set, can install via git + if (isset($package->override_repository)) { + $install_options[] = 'Git'; + } + + // reindex list + $install_options = array_values($install_options); + + if (count($install_options) == 0) { + // no valid install options - error and return + $this->output->writeln("not valid installation methods found!"); + return; + } elseif (count($install_options) == 1) { + // only one option, use it... + $method = $install_options[0]; + } else { + $helper = $this->getHelper('question'); + $question = new ChoiceQuestion( + 'Please select installation method for ' . $package->name . ' ('.$install_options[0].' is default)', array_values($install_options), 0 + ); + $question->setErrorMessage('Method %s is invalid'); + $method = $helper->ask($this->input, $this->output, $question); + } + + $this->output->writeln(''); + + $method_name = 'process'.$method; + $this->$method_name($package); + + $this->installDemoContent($package); + } + + + /** + * @param $package + */ + private function installDemoContent($package) + { + $demo_dir = $this->destination . DS . $package->install_path . DS . '_demo'; + $dest_dir = $this->destination . DS . 'user'; + $pages_dir = $dest_dir . DS . 'pages'; + + if (file_exists($demo_dir)) { + // Demo content exists, prompt to install it. + $this->output->writeln("Attention: ".$package->name . " contains demo content"); + $helper = $this->getHelper('question'); + $question = new ConfirmationQuestion('Do you wish to install this demo content? [y|N] ', false); + + if (!$helper->ask($this->input, $this->output, $question)) { + $this->output->writeln(" '- Skipped! "); + $this->output->writeln(''); + return; + } + + // if pages folder exists in demo + if (file_exists($demo_dir . DS . 'pages')) { + $pages_backup = 'pages.' . date('m-d-Y-H-i-s'); + $question = new ConfirmationQuestion('This will backup your current `user/pages` folder to `user/'. $pages_backup. '`, continue? [y|N]', false); + + if (!$helper->ask($this->input, $this->output, $question)) { + $this->output->writeln(" '- Skipped! "); + $this->output->writeln(''); + return; + } + + // backup current pages folder + if (file_exists($dest_dir)) { + if (rename($pages_dir, $dest_dir . DS . $pages_backup)) { + $this->output->writeln(" |- Backing up pages... ok"); + } else { + $this->output->writeln(" |- Backing up pages... failed"); + } + } + } + + // Confirmation received, copy over the data + $this->output->writeln(" |- Installing demo content... ok "); + Utils::rcopy($demo_dir, $dest_dir); + $this->output->writeln(" '- Success! "); + $this->output->writeln(''); + } + } + + /** + * @param $package + * + * @return array + */ + private function getGitRegexMatches($package) + { + if (isset($package->override_repository)) { + $repository = $package->override_repository; + } elseif (isset($package->repository)) { + $repository = $package->repository; + } else { + return false; + } + + preg_match(GIT_REGEX, $repository, $matches); + + return $matches; + } + + /** + * @param $package + * + * @return bool|string + */ + private function getSymlinkSource($package) + { + $matches = $this->getGitRegexMatches($package); + + foreach ($this->local_config as $path) { + if (Utils::endsWith($matches[2], '.git')) { + $repo_dir = preg_replace('/\.git$/', '', $matches[2]); + } else { + $repo_dir = $matches[2]; + } + + $from = rtrim($path, '/') . '/' . $repo_dir; + + if (file_exists($from)) { + return $from; + } + } + return false; + } + + /** + * @param $package + */ + private function processSymlink($package) + { + + exec('cd ' . $this->destination); + + $to = $this->destination . DS . $package->install_path; + $from = $this->getSymlinkSource($package); + + $this->output->writeln("Preparing to Symlink " . $package->name . ""); + $this->output->write(" |- Checking source... "); + + if (file_exists($from)) { + $this->output->writeln("ok"); + + $this->output->write(" |- Checking destination... "); + $checks = $this->checkDestination($package); + + if (!$checks) { + $this->output->writeln(" '- Installation failed or aborted."); + $this->output->writeln(''); + } else { + if (file_exists($to)) { + $this->output->writeln(" '- Symlink cannot overwrite an existing package, please remove first"); + $this->output->writeln(''); + } else { + symlink($from, $to); + + // extra white spaces to clear out the buffer properly + $this->output->writeln(" |- Symlinking package... ok "); + + $this->output->writeln(" '- Success! "); + $this->output->writeln(''); + } + + + } + return; + } + + $this->output->writeln("not found!"); + $this->output->writeln(" '- Installation failed or aborted."); + } + + /** + * @param $package + */ + private function processGit($package) + { + $matches = $this->getGitRegexMatches($package); + + $to = $this->destination . DS . $package->install_path; + + $this->output->writeln("Preparing to Git clone " . $package->name . " from " . $matches[0]); + + $this->output->write(" |- Checking destination... "); + $checks = $this->checkDestination($package); + + if (!$checks) { + $this->output->writeln(" '- Installation failed or aborted."); + $this->output->writeln(''); + } else { + $cmd = 'cd ' . $this->destination . ' && git clone ' . $matches[0] . ' ' . $package->install_path; + exec($cmd); + + // extra white spaces to clear out the buffer properly + $this->output->writeln(" |- Cloning package... ok "); + + $this->output->writeln(" '- Success! "); + $this->output->writeln(''); + } + } + + /** + * @param $package + */ + private function processGPM($package) + { + $version = isset($package->available) ? $package->available : $package->version; + + $this->output->writeln("Preparing to install " . $package->name . " [v" . $version . "]"); + + $this->output->write(" |- Downloading package... 0%"); + $this->file = $this->downloadPackage($package); + + $this->output->write(" |- Checking destination... "); + $checks = $this->checkDestination($package); + + if (!$checks) { + $this->output->writeln(" '- Installation failed or aborted."); + $this->output->writeln(''); + } else { + $this->output->write(" |- Installing package... "); + $installation = $this->installPackage($package); + if (!$installation) { + $this->output->writeln(" '- Installation failed or aborted."); + $this->output->writeln(''); + } else { + $this->output->writeln(" '- Success! "); + $this->output->writeln(''); + } + } + } + /** * @param $package * @@ -218,6 +481,8 @@ class InstallCommand extends Command $this->output->writeln(" | '- You decided to not delete the symlink automatically."); return false; + } else { + unlink($this->destination . DS . $package->install_path); } } diff --git a/system/src/Grav/Console/Gpm/UninstallCommand.php b/system/src/Grav/Console/Gpm/UninstallCommand.php index fdd28a520..085612c4f 100644 --- a/system/src/Grav/Console/Gpm/UninstallCommand.php +++ b/system/src/Grav/Console/Gpm/UninstallCommand.php @@ -140,7 +140,7 @@ class UninstallCommand extends Command */ private function uninstallPackage($package) { - $path = self::$grav['locator']->findResource($package->package_type . '://' . $package->slug); + $path = self::getGrav()['locator']->findResource($package->package_type . '://' . $package->slug); Installer::uninstall($path); $errorCode = Installer::lastErrorCode(); @@ -167,7 +167,7 @@ class UninstallCommand extends Command private function checkDestination($package) { - $path = self::$grav['locator']->findResource($package->package_type . '://' . $package->slug); + $path = self::getGrav()['locator']->findResource($package->package_type . '://' . $package->slug); $questionHelper = $this->getHelper('question'); $skipPrompt = $this->input->getOption('all-yes');