diff --git a/.travis.yml b/.travis.yml index e36c4a617..06ce0cdb9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ env: # GH_API_USER [for curl] - secure: "AQGcX1B2NrI8ajflY4AimZDNcK2kBA3F6mbtEFQ78NkDoWhMipsQHayWXiSTzRc0YJKvQl2Y16MTwQF4VHzjTAiiZFATgA8J88vQUjIPabi/kKjqSmcLFoaAOAxStQbW6e0z2GiQ6KBMcNF1y5iUuI63xVrBvtKrYX/w5y+ako8=" # Latest Release version - - TRAVIS_TAG=$(curl --fail -s https://api.github.com/repos/getgrav/grav/releases/latest | grep tag_name | head -n 1 | cut -d '"' -f 4) + - TRAVIS_TAG=$(curl --fail --user "${GH_API_USER}" -s https://api.github.com/repos/getgrav/grav/releases/latest | grep tag_name | head -n 1 | cut -d '"' -f 4) before_install: - export TZ=Pacific/Honolulu diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fcc851b1..cd8a33cb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +# v1.1.4 +## 09/07/2016 + +1. [](#new) + * Added new `tmp` folder at root. Accessible via stream `tmp://`. Can be cleared with `bin/grav clear --tmp-only` as well as `--all`. + * Added support for RTL in `LanguageCodes` so you can determine if a language is RTL or not + * Ability to set `custom_base_url` in system configuration + * Added `override` and `force` options for Streams setup +1. [](#improved) + * Important vendor updates to provide PHP 7.1 beta support! + * Added a `Util::arrayFlatten()` static function + * Added support for 'external_url' page header to enable easier external URL based menu items + * Improved the UI for CLI GPM Index view to use a table + * Added `@page.modular` Collection type [#988](https://github.com/getgrav/grav/issues/988) + * Added support for `self@`, `page@`, `taxonomy@`, `root@` Collection syntax for cleaner YAML compatibility + * Improved GPM commands to allow for `-y` to automate **yes** responses and `-o` for **update** and **selfupgrade** to overwrite installations [#985](https://github.com/getgrav/grav/issues/985) + * Added randomization to `safe_email` Twig filter for greater security [#998](https://github.com/getgrav/grav/issues/998) + * Allow `Utils::setDotNotation` to merge data, rather than just set + * Moved default `Image::filter()` to the `save` action to ensure they are applied last [#984](https://github.com/getgrav/grav/issues/984) + * Improved the `Truncator` code to be more reliable [#1019](https://github.com/getgrav/grav/issues/1019) + * Moved media blueprints out of core (now in Admin plugin) +1. [](#bugfix) + * Removed 307 redirect code option as it is not well supported [#743](https://github.com/getgrav/grav-plugin-admin/issues/743) + * Fixed issue with folders with name `*.md` are not confused with pages [#995](https://github.com/getgrav/grav/issues/995) + * Fixed an issue when filtering collections causing null key + * Fix for invalid HTML when rendering GIF and Vector media [#1001](https://github.com/getgrav/grav/issues/1001) + * Use pages.markdown.extra in the user's system.yaml [#1007](https://github.com/getgrav/grav/issues/1007) + * Fix for `Memcached` connection [#1020](https://github.com/getgrav/grav/issues/1020) + # v1.1.3 ## 08/14/2016 diff --git a/bin/composer.phar b/bin/composer.phar index 10c245e28..fa2a122b2 100755 Binary files a/bin/composer.phar and b/bin/composer.phar differ diff --git a/bin/gpm b/bin/gpm index 7e45c86dd..fa5910192 100755 --- a/bin/gpm +++ b/bin/gpm @@ -7,6 +7,7 @@ if (!file_exists(__DIR__ . '/../vendor')){ } use Grav\Common\Composer; +use Grav\Common\Config\Setup; if (!file_exists(__DIR__ . '/../vendor')){ // Before we can even start, we need to run composer first @@ -37,9 +38,24 @@ if (!function_exists('curl_version')) { exit('FATAL: GPM requires PHP Curl module to be installed'); } +$climate = new League\CLImate\CLImate; +$climate->arguments->add([ + 'environment' => [ + 'prefix' => 'e', + 'longPrefix' => 'env', + 'description' => 'Configuration Environment', + 'defaultValue' => 'localhost' + ] +]); +$climate->arguments->parse(); +$environment = $climate->arguments->get('environment'); + +// Set up environment based on params. +Setup::$environment = $environment; + $grav = Grav::instance(array('loader' => $autoload)); -$grav['config']->init(); $grav['uri']->init(); +$grav['config']->init(); $grav['streams']; $app = new Application('Grav Package Manager', GRAV_VERSION); @@ -53,5 +69,4 @@ $app->addCommands(array( new \Grav\Console\Gpm\SelfupgradeCommand(), )); -$app->setDefaultCommand('index'); $app->run(); diff --git a/bin/plugin b/bin/plugin index 1741ec281..c740f818c 100755 --- a/bin/plugin +++ b/bin/plugin @@ -21,6 +21,7 @@ use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Grav\Common\Grav; +use Grav\Common\Config\Setup; use Grav\Common\Filesystem\Folder; $autoload = require_once(__DIR__ . '/../vendor/autoload.php'); @@ -37,12 +38,29 @@ if (!file_exists(ROOT_DIR . 'index.php')) { exit('FATAL: Must be run from ROOT directory of Grav!'); } +$climate = new League\CLImate\CLImate; +$climate->arguments->add([ + 'environment' => [ + 'prefix' => 'e', + 'longPrefix' => 'env', + 'description' => 'Configuration Environment', + 'defaultValue' => 'localhost' + ] +]); +$climate->arguments->parse(); +$environment = $climate->arguments->get('environment'); + +// Set up environment based on params. +Setup::$environment = $environment; + $grav = Grav::instance(array('loader' => $autoload)); +$grav['uri']->init(); $grav['config']->init(); $grav['streams']; $grav['plugins']->init(); $grav['themes']->init(); + $app = new Application('Grav Plugins Commands', GRAV_VERSION); $pattern = '([A-Z]\w+Command\.php)'; diff --git a/composer.json b/composer.json index 97bad168c..559480546 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,9 @@ "ext-mbstring": "*", "ext-openssl": "*", "ext-curl": "*", - "ext-zip": "*" + "ext-zip": "*", + "league/climate": "^3.2", + "antoligy/dom-string-iterators": "^1.0" }, "require-dev": { "codeception/codeception": "^2.1", diff --git a/composer.lock b/composer.lock index 48a5d5ea0..afc4d23c8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,9 +4,53 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "65cd6cc2a20addb345acc1f84d5ae3ab", - "content-hash": "0ddb599b8e9fb7f0fb76619343180ef9", + "hash": "7a8caecbaedbf785d96b7437f296ca66", + "content-hash": "2fec25b3b5d627c0896d5ee3030b6bed", "packages": [ + { + "name": "antoligy/dom-string-iterators", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/antoligy/dom-string-iterators.git", + "reference": "9a624b082493fee9b972840dbd677494edb94cf7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antoligy/dom-string-iterators/zipball/9a624b082493fee9b972840dbd677494edb94cf7", + "reference": "9a624b082493fee9b972840dbd677494edb94cf7", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Public Domain" + ], + "authors": [ + { + "name": "Alex Wilson", + "email": "a@ax.gy" + }, + { + "name": "Kornel Lesinski", + "email": "pornel@pornel.net" + }, + { + "name": "Patrick Galbraith", + "email": "patrick.j.galbraith@gmail.com" + } + ], + "description": "Composer package for DOMWordsIterator and DOMLettersIterator", + "time": "2015-11-04 17:33:14" + }, { "name": "doctrine/cache", "version": "v1.6.0", @@ -79,16 +123,16 @@ }, { "name": "donatj/phpuseragentparser", - "version": "v0.5.1", + "version": "v0.5.2", "source": { "type": "git", "url": "https://github.com/donatj/PhpUserAgent.git", - "reference": "b00392786281877db6a51d0e48dee7296c7ba4f8" + "reference": "d8e7b6a0c83a6e4aa9098360a99c6eeb763c004a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/donatj/PhpUserAgent/zipball/b00392786281877db6a51d0e48dee7296c7ba4f8", - "reference": "b00392786281877db6a51d0e48dee7296c7ba4f8", + "url": "https://api.github.com/repos/donatj/PhpUserAgent/zipball/d8e7b6a0c83a6e4aa9098360a99c6eeb763c004a", + "reference": "d8e7b6a0c83a6e4aa9098360a99c6eeb763c004a", "shasum": "" }, "require": { @@ -126,7 +170,7 @@ "user agent", "useragent" ], - "time": "2016-03-07 17:14:32" + "time": "2016-09-01 22:33:01" }, { "name": "erusev/parsedown", @@ -134,12 +178,12 @@ "source": { "type": "git", "url": "https://github.com/erusev/parsedown.git", - "reference": "490a8f35a4163f59230f53c34f1fbb22a864c01e" + "reference": "f671ae73647b6666c1442cbaac98e5d60208e409" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/490a8f35a4163f59230f53c34f1fbb22a864c01e", - "reference": "490a8f35a4163f59230f53c34f1fbb22a864c01e", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/f671ae73647b6666c1442cbaac98e5d60208e409", + "reference": "f671ae73647b6666c1442cbaac98e5d60208e409", "shasum": "" }, "require": { @@ -168,7 +212,7 @@ "markdown", "parser" ], - "time": "2016-03-09 17:02:39" + "time": "2016-07-27 08:05:24" }, { "name": "erusev/parsedown-extra", @@ -216,16 +260,16 @@ }, { "name": "filp/whoops", - "version": "2.1.2", + "version": "2.1.3", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "d13505b240a6f580bc75ba591da30299d6cb0eec" + "reference": "8828aaa2178e0a19325522e2a45282ff0a14649b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/d13505b240a6f580bc75ba591da30299d6cb0eec", - "reference": "d13505b240a6f580bc75ba591da30299d6cb0eec", + "url": "https://api.github.com/repos/filp/whoops/zipball/8828aaa2178e0a19325522e2a45282ff0a14649b", + "reference": "8828aaa2178e0a19325522e2a45282ff0a14649b", "shasum": "" }, "require": { @@ -272,7 +316,7 @@ "whoops", "zf2" ], - "time": "2016-04-07 06:16:25" + "time": "2016-05-06 18:25:35" }, { "name": "gregwar/cache", @@ -365,6 +409,57 @@ ], "time": "2015-05-30 19:24:37" }, + { + "name": "league/climate", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/climate.git", + "reference": "b103fc8faa3780c802cc507d5f0ff534ecc94fb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/climate/zipball/b103fc8faa3780c802cc507d5f0ff534ecc94fb5", + "reference": "b103fc8faa3780c802cc507d5f0ff534ecc94fb5", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "seld/cli-prompt": "~1.0" + }, + "require-dev": { + "mikey179/vfsstream": "~1.4", + "mockery/mockery": "~0.9", + "phpunit/phpunit": "~4.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\CLImate\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joe Tannenbaum", + "email": "hey@joe.codes", + "homepage": "http://joe.codes/", + "role": "Developer" + } + ], + "description": "PHP's best friend for the terminal. CLImate allows you to easily output colored text, special formats, and more.", + "keywords": [ + "cli", + "colors", + "command", + "php", + "terminal" + ], + "time": "2016-04-04 20:24:59" + }, { "name": "matthiasmullie/minify", "version": "1.3.35", @@ -533,16 +628,16 @@ }, { "name": "monolog/monolog", - "version": "1.20.0", + "version": "1.21.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "55841909e2bcde01b5318c35f2b74f8ecc86e037" + "reference": "f42fbdfd53e306bda545845e4dbfd3e72edb4952" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/55841909e2bcde01b5318c35f2b74f8ecc86e037", - "reference": "55841909e2bcde01b5318c35f2b74f8ecc86e037", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f42fbdfd53e306bda545845e4dbfd3e72edb4952", + "reference": "f42fbdfd53e306bda545845e4dbfd3e72edb4952", "shasum": "" }, "require": { @@ -607,7 +702,7 @@ "logging", "psr-3" ], - "time": "2016-07-02 14:02:10" + "time": "2016-07-29 03:23:52" }, { "name": "pimple/pimple", @@ -699,12 +794,12 @@ "source": { "type": "git", "url": "https://github.com/rockettheme/toolbox.git", - "reference": "12d9007979c816038ed7deacf67811c8b864c679" + "reference": "327db9748b112c2c2c843f931e1ad88659fc8482" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rockettheme/toolbox/zipball/12d9007979c816038ed7deacf67811c8b864c679", - "reference": "12d9007979c816038ed7deacf67811c8b864c679", + "url": "https://api.github.com/repos/rockettheme/toolbox/zipball/327db9748b112c2c2c843f931e1ad88659fc8482", + "reference": "327db9748b112c2c2c843f931e1ad88659fc8482", "shasum": "" }, "require": { @@ -739,20 +834,68 @@ "php", "rockettheme" ], - "time": "2016-05-24 18:58:03" + "time": "2016-08-25 23:28:38" + }, + { + "name": "seld/cli-prompt", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/cli-prompt.git", + "reference": "8cbe10923cae5bcd7c5a713f6703fc4727c8c1b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/cli-prompt/zipball/8cbe10923cae5bcd7c5a713f6703fc4727c8c1b4", + "reference": "8cbe10923cae5bcd7c5a713f6703fc4727c8c1b4", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\CliPrompt\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Allows you to prompt for user input on the command line, and optionally hide the characters they type", + "keywords": [ + "cli", + "console", + "hidden", + "input", + "prompt" + ], + "time": "2016-04-18 09:31:41" }, { "name": "symfony/console", - "version": "v2.8.8", + "version": "v2.8.10", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c392a6ec72f2122748032c2ad6870420561ffcfa" + "reference": "bac1eb5869c1848a173852aee61df960e3a49197" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c392a6ec72f2122748032c2ad6870420561ffcfa", - "reference": "c392a6ec72f2122748032c2ad6870420561ffcfa", + "url": "https://api.github.com/repos/symfony/console/zipball/bac1eb5869c1848a173852aee61df960e3a49197", + "reference": "bac1eb5869c1848a173852aee61df960e3a49197", "shasum": "" }, "require": { @@ -799,20 +942,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2016-06-29 07:02:14" + "time": "2016-08-19 06:48:01" }, { "name": "symfony/event-dispatcher", - "version": "v2.8.8", + "version": "v2.8.10", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "b180b70439dca70049b6b9b7e21d75e6e5d7aca9" + "reference": "889983a79a043dfda68f38c38b6dba092dd49cd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b180b70439dca70049b6b9b7e21d75e6e5d7aca9", - "reference": "b180b70439dca70049b6b9b7e21d75e6e5d7aca9", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/889983a79a043dfda68f38c38b6dba092dd49cd8", + "reference": "889983a79a043dfda68f38c38b6dba092dd49cd8", "shasum": "" }, "require": { @@ -859,7 +1002,7 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2016-06-29 05:29:29" + "time": "2016-07-28 16:56:28" }, { "name": "symfony/polyfill-iconv", @@ -981,16 +1124,16 @@ }, { "name": "symfony/var-dumper", - "version": "v2.8.8", + "version": "v2.8.10", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "eb21798f1cd2d3a1c1e1e4d0632b6d04a798f210" + "reference": "43040b1113171ea24a2ae1c4b3d01eda76360a61" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/eb21798f1cd2d3a1c1e1e4d0632b6d04a798f210", - "reference": "eb21798f1cd2d3a1c1e1e4d0632b6d04a798f210", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/43040b1113171ea24a2ae1c4b3d01eda76360a61", + "reference": "43040b1113171ea24a2ae1c4b3d01eda76360a61", "shasum": "" }, "require": { @@ -1040,20 +1183,20 @@ "debug", "dump" ], - "time": "2016-06-29 05:29:29" + "time": "2016-08-31 09:05:30" }, { "name": "symfony/yaml", - "version": "v2.8.8", + "version": "v2.8.10", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "dba4bb5846798cd12f32e2d8f3f35d77045773c8" + "reference": "e7540734bad981fe59f8ef14b6fc194ae9df8d9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/dba4bb5846798cd12f32e2d8f3f35d77045773c8", - "reference": "dba4bb5846798cd12f32e2d8f3f35d77045773c8", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e7540734bad981fe59f8ef14b6fc194ae9df8d9c", + "reference": "e7540734bad981fe59f8ef14b6fc194ae9df8d9c", "shasum": "" }, "require": { @@ -1089,20 +1232,20 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-06-29 05:29:29" + "time": "2016-09-02 01:57:56" }, { "name": "twig/twig", - "version": "v1.24.1", + "version": "v1.24.2", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "3566d311a92aae4deec6e48682dc5a4528c4a512" + "reference": "33093f6e310e6976baeac7b14f3a6ec02f2d79b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/3566d311a92aae4deec6e48682dc5a4528c4a512", - "reference": "3566d311a92aae4deec6e48682dc5a4528c4a512", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/33093f6e310e6976baeac7b14f3a6ec02f2d79b7", + "reference": "33093f6e310e6976baeac7b14f3a6ec02f2d79b7", "shasum": "" }, "require": { @@ -1150,30 +1293,31 @@ "keywords": [ "templating" ], - "time": "2016-05-30 09:11:59" + "time": "2016-09-01 17:50:53" } ], "packages-dev": [ { "name": "behat/gherkin", - "version": "v4.4.1", + "version": "v4.4.2", "source": { "type": "git", "url": "https://github.com/Behat/Gherkin.git", - "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911" + "reference": "d26757dc970e67b50ed0ee47b95273daeb84fea6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Gherkin/zipball/1576b485c0f92ef6d27da9c4bbfc57ee30cf6911", - "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/d26757dc970e67b50ed0ee47b95273daeb84fea6", + "reference": "d26757dc970e67b50ed0ee47b95273daeb84fea6", "shasum": "" }, "require": { "php": ">=5.3.1" }, "require-dev": { - "phpunit/phpunit": "~4.0", - "symfony/yaml": "~2.1" + "phpunit/phpunit": "~4.5|~5", + "symfony/phpunit-bridge": "~2.7|~3", + "symfony/yaml": "~2.3|~3" }, "suggest": { "symfony/yaml": "If you want to parse features, represented in YAML files" @@ -1210,20 +1354,20 @@ "gherkin", "parser" ], - "time": "2015-12-30 14:47:00" + "time": "2016-09-03 07:28:57" }, { "name": "codeception/codeception", - "version": "2.2.2", + "version": "2.2.4", "source": { "type": "git", "url": "https://github.com/Codeception/Codeception.git", - "reference": "8d80bb4ec7470e8df5de0e4c401785bc3fa1f4f6" + "reference": "ea617b8b55e6e33cdd47edeafde5d3f6466049e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/8d80bb4ec7470e8df5de0e4c401785bc3fa1f4f6", - "reference": "8d80bb4ec7470e8df5de0e4c401785bc3fa1f4f6", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/ea617b8b55e6e33cdd47edeafde5d3f6466049e2", + "reference": "ea617b8b55e6e33cdd47edeafde5d3f6466049e2", "shasum": "" }, "require": { @@ -1301,7 +1445,7 @@ "functional testing", "unit testing" ], - "time": "2016-06-29 00:59:28" + "time": "2016-08-14 12:28:58" }, { "name": "doctrine/instantiator", @@ -1359,16 +1503,16 @@ }, { "name": "facebook/webdriver", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/facebook/php-webdriver.git", - "reference": "0b889d7de7461439f8a3bbcca46e0f696cb27986" + "reference": "b7186fb1bcfda956d237f59face250d06ef47253" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/0b889d7de7461439f8a3bbcca46e0f696cb27986", - "reference": "0b889d7de7461439f8a3bbcca46e0f696cb27986", + "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/b7186fb1bcfda956d237f59face250d06ef47253", + "reference": "b7186fb1bcfda956d237f59face250d06ef47253", "shasum": "" }, "require": { @@ -1376,7 +1520,9 @@ "php": ">=5.3.19" }, "require-dev": { - "phpunit/phpunit": "4.6.*" + "friendsofphp/php-cs-fixer": "^1.11", + "phpunit/phpunit": "4.6.* || ~5.0", + "squizlabs/php_codesniffer": "^2.6" }, "suggest": { "phpdocumentor/phpdocumentor": "2.*" @@ -1399,7 +1545,7 @@ "selenium", "webdriver" ], - "time": "2016-06-04 00:02:34" + "time": "2016-08-10 00:44:08" }, { "name": "fzaninotto/faker", @@ -2073,16 +2219,16 @@ }, { "name": "phpunit/phpunit", - "version": "4.8.26", + "version": "4.8.27", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74" + "reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc1d8cd5b5de11625979125c5639347896ac2c74", - "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c062dddcb68e44b563f66ee319ddae2b5a322a90", + "reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90", "shasum": "" }, "require": { @@ -2141,7 +2287,7 @@ "testing", "xunit" ], - "time": "2016-05-17 03:09:28" + "time": "2016-07-21 06:48:14" }, { "name": "phpunit/phpunit-mock-objects", @@ -2201,16 +2347,16 @@ }, { "name": "psr/http-message", - "version": "1.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298" + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", - "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", "shasum": "" }, "require": { @@ -2238,6 +2384,7 @@ } ], "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", "keywords": [ "http", "http-message", @@ -2246,7 +2393,7 @@ "request", "response" ], - "time": "2015-05-04 20:22:00" + "time": "2016-08-06 14:39:51" }, { "name": "sebastian/comparator", @@ -2366,23 +2513,23 @@ }, { "name": "sebastian/environment", - "version": "1.3.7", + "version": "1.3.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716" + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4e8f0da10ac5802913afc151413bc8c53b6c2716", - "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^5.3.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^4.8 || ^5.0" }, "type": "library", "extra": { @@ -2412,7 +2559,7 @@ "environment", "hhvm" ], - "time": "2016-05-17 03:18:57" + "time": "2016-08-18 05:49:44" }, { "name": "sebastian/exporter", @@ -2622,16 +2769,16 @@ }, { "name": "symfony/browser-kit", - "version": "v3.1.2", + "version": "v3.1.4", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "dcf41ed026b0499254385b5c88f03247b2ba010b" + "reference": "d2a07cc11c5fa94820240b1e67592ffb18e347b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/dcf41ed026b0499254385b5c88f03247b2ba010b", - "reference": "dcf41ed026b0499254385b5c88f03247b2ba010b", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/d2a07cc11c5fa94820240b1e67592ffb18e347b9", + "reference": "d2a07cc11c5fa94820240b1e67592ffb18e347b9", "shasum": "" }, "require": { @@ -2675,11 +2822,11 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2016-06-29 05:41:56" + "time": "2016-07-26 08:04:17" }, { "name": "symfony/css-selector", - "version": "v3.1.2", + "version": "v3.1.4", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", @@ -2732,16 +2879,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v3.1.2", + "version": "v3.1.4", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "99ec4a23330fcd0c8667095f3ef7aa204ffd9dc0" + "reference": "bb7395e8b1db3654de82b9f35d019958276de4d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/99ec4a23330fcd0c8667095f3ef7aa204ffd9dc0", - "reference": "99ec4a23330fcd0c8667095f3ef7aa204ffd9dc0", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/bb7395e8b1db3654de82b9f35d019958276de4d7", + "reference": "bb7395e8b1db3654de82b9f35d019958276de4d7", "shasum": "" }, "require": { @@ -2784,20 +2931,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2016-06-29 05:41:56" + "time": "2016-08-05 08:37:39" }, { "name": "symfony/finder", - "version": "v3.1.2", + "version": "v3.1.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8201978de88a9fa0923e18601bb17f1df9c721e7" + "reference": "e568ef1784f447a0e54dcb6f6de30b9747b0f577" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8201978de88a9fa0923e18601bb17f1df9c721e7", - "reference": "8201978de88a9fa0923e18601bb17f1df9c721e7", + "url": "https://api.github.com/repos/symfony/finder/zipball/e568ef1784f447a0e54dcb6f6de30b9747b0f577", + "reference": "e568ef1784f447a0e54dcb6f6de30b9747b0f577", "shasum": "" }, "require": { @@ -2833,32 +2980,33 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2016-06-29 05:41:56" + "time": "2016-08-26 12:04:02" }, { "name": "webmozart/assert", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde" + "reference": "bb2d123231c095735130cc8f6d31385a44c7b308" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", - "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bb2d123231c095735130cc8f6d31385a44c7b308", + "reference": "bb2d123231c095735130cc8f6d31385a44c7b308", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^5.3.3|^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.6" + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.2-dev" } }, "autoload": { @@ -2882,7 +3030,7 @@ "check", "validate" ], - "time": "2015-08-24 13:29:44" + "time": "2016-08-09 15:02:57" } ], "aliases": [ diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml index 780aaeb88..8c5b93fdf 100644 --- a/system/blueprints/config/system.yaml +++ b/system/blueprints/config/system.yaml @@ -178,8 +178,9 @@ form: help: PLUGIN_ADMIN.REDIRECT_DEFAULT_CODE_HELP options: 301: 301 - Permanent + 302: 302 - Found 303: 303 - Other - 307: 307 - Temporary + 304: 304 - Not Modified pages.redirect_trailing_slash: type: toggle @@ -953,6 +954,12 @@ form: validate: type: bool + session.path: + type: text + size: small + label: PLUGIN_ADMIN.SESSION_PATH + help: PLUGIN_ADMIN.SESSION_PATH_HELP + advanced: type: section title: PLUGIN_ADMIN.ADVANCED @@ -1029,3 +1036,10 @@ form: 0: PLUGIN_ADMIN.NO validate: type: bool + + custom_base_url: + type: text + size: medium + placeholder: "e.g. http://localhost:8080" + label: PLUGIN_ADMIN.CUSTOM_BASE_URL + help: PLUGIN_ADMIN.CUSTOM_BASE_URL_HELP diff --git a/system/blueprints/media/meta.yaml b/system/blueprints/media/meta.yaml deleted file mode 100644 index 7d242e0b5..000000000 --- a/system/blueprints/media/meta.yaml +++ /dev/null @@ -1,7 +0,0 @@ -form: - validation: loose - fields: - - alt_text: - type: string - label: Alt Text diff --git a/system/blueprints/media/move.yaml b/system/blueprints/media/move.yaml deleted file mode 100644 index 06fc5bac8..000000000 --- a/system/blueprints/media/move.yaml +++ /dev/null @@ -1,8 +0,0 @@ -form: - validation: loose - fields: - route: - type: select - label: PLUGIN_ADMIN.PAGE - classes: fancy - data-options@: '\Grav\Common\Page\Pages::parents' diff --git a/system/blueprints/media/rename.yaml b/system/blueprints/media/rename.yaml deleted file mode 100644 index 529349cac..000000000 --- a/system/blueprints/media/rename.yaml +++ /dev/null @@ -1,8 +0,0 @@ -form: - validation: loose - fields: - new_file_name: - type: text - label: PLUGIN_ADMIN_PRO.NEW_FILE_NAME - validate: - required: true diff --git a/system/blueprints/pages/external.yaml b/system/blueprints/pages/external.yaml new file mode 100644 index 000000000..13085ed75 --- /dev/null +++ b/system/blueprints/pages/external.yaml @@ -0,0 +1,36 @@ +rules: + slug: + pattern: "[a-zа-я][a-zа-я0-9_\-]+" + min: 2 + max: 80 + +form: + validation: loose + + fields: + + tabs: + type: tabs + active: 1 + + fields: + content: + type: tab + title: PLUGIN_ADMIN.CONTENT + + fields: + + header.title: + type: text + autofocus: true + label: PLUGIN_ADMIN.TITLE + + header.external_url: + type: text + label: PLUGIN_ADMIN.EXTERNAL_URL + placeholder: https://getgrav.org + validate: + required: true + options: + type: tab + title: PLUGIN_ADMIN.OPTIONS diff --git a/system/config/system.yaml b/system/config/system.yaml index 3d35c0740..1d688bf7a 100644 --- a/system/config/system.yaml +++ b/system/config/system.yaml @@ -61,7 +61,7 @@ pages: url_taxonomy_filters: true # Enable auto-magic URL-based taxonomy filters for page collections frontmatter: process_twig: false # Should the frontmatter be processed to replace Twig variables? - ignore_fields: ['form'] # Fields that might contain Twig variables and should not be processed + ignore_fields: ['form','forms'] # Fields that might contain Twig variables and should not be processed cache: enabled: true # Set to true to enable caching @@ -123,7 +123,10 @@ session: name: grav-site # Name prefix of the session cookie. Use alphanumeric, dashes or underscores only. Do not use dots in the session name secure: false # Set session secure. If true, indicates that communication for this cookie must be over an encrypted transmission. Enable this only on sites that run exclusively on HTTPS httponly: true # Set session HTTP only. If true, indicates that cookies should be used only over HTTP, and JavaScript modification is not allowed. + path: gpm: releases: stable # Set to either 'stable' or 'testing' proxy_url: # Configure a manual proxy URL for GPM (eg 127.0.0.1:3128) + +custom_base_url: '' diff --git a/system/defines.php b/system/defines.php index 1fbffa94f..6c9dfbb4d 100644 --- a/system/defines.php +++ b/system/defines.php @@ -8,7 +8,7 @@ // Some standard defines define('GRAV', true); -define('GRAV_VERSION', '1.1.3'); +define('GRAV_VERSION', '1.1.4'); define('GRAV_TESTING', false); define('DS', '/'); define('GRAV_PHP_MIN', '5.5.9'); diff --git a/system/src/Grav/Common/Assets.php b/system/src/Grav/Common/Assets.php index c324c4df7..014112531 100644 --- a/system/src/Grav/Common/Assets.php +++ b/system/src/Grav/Common/Assets.php @@ -207,7 +207,7 @@ public function init() $this->assets_url = $locator->findResource('asset://', false); $this->config($asset_config); - $this->base_url = $base_url . '/'; + $this->base_url = ($config->get('system.absolute_urls') ? '' : '/') . ltrim(ltrim($base_url, '/') . '/', '/'); // Register any preconfigured collections foreach ($config->get('system.assets.collections', []) as $name => $collection) { @@ -1349,7 +1349,7 @@ public function setTimestamp($value) public function getTimestamp($asset = null) { if (is_array($asset)) { - if ($asset['remote'] == false) { + if ($asset['remote'] === false) { if (Utils::contains($asset['asset'], '?')) { return str_replace('?', '&', $this->timestamp); } else { diff --git a/system/src/Grav/Common/Backup/ZipBackup.php b/system/src/Grav/Common/Backup/ZipBackup.php index c7d452b61..4ccea9a31 100644 --- a/system/src/Grav/Common/Backup/ZipBackup.php +++ b/system/src/Grav/Common/Backup/ZipBackup.php @@ -17,7 +17,8 @@ class ZipBackup 'backup', 'cache', 'images', - 'logs' + 'logs', + 'tmp' ]; protected static $ignoreFolders = [ diff --git a/system/src/Grav/Common/Cache.php b/system/src/Grav/Common/Cache.php index 207159ab1..49329448c 100644 --- a/system/src/Grav/Common/Cache.php +++ b/system/src/Grav/Common/Cache.php @@ -65,7 +65,8 @@ class Cache extends Getters protected static $all_remove = [ 'cache://', 'cache://images', - 'asset://' + 'asset://', + 'tmp://' ]; protected static $assets_remove = [ @@ -80,6 +81,10 @@ class Cache extends Getters 'cache://' ]; + protected static $tmp_remove = [ + 'tmp://' + ]; + /** * Constructor * @@ -183,7 +188,7 @@ public function getCacheDriver() case 'memcached': $memcached = new \Memcached(); - $memcached->connect($this->config->get('system.cache.memcached.server', 'localhost'), + $memcached->addServer($this->config->get('system.cache.memcached.server', 'localhost'), $this->config->get('system.cache.memcached.port', 11211)); $driver = new DoctrineCache\MemcachedCache(); $driver->setMemcached($memcached); @@ -309,6 +314,9 @@ public static function clearCache($remove = 'standard') case 'cache-only': $remove_paths = self::$cache_remove; break; + case 'tmp-only': + $remove_paths = self::$tmp_remove; + break; default: $remove_paths = self::$standard_remove; } diff --git a/system/src/Grav/Common/Config/Setup.php b/system/src/Grav/Common/Config/Setup.php index 27c7c3668..c613b9f7b 100644 --- a/system/src/Grav/Common/Config/Setup.php +++ b/system/src/Grav/Common/Config/Setup.php @@ -17,6 +17,8 @@ class Setup extends Data { + public static $environment; + protected $streams = [ 'system' => [ 'type' => 'ReadOnlyStream', @@ -26,6 +28,7 @@ class Setup extends Data ], 'user' => [ 'type' => 'ReadOnlyStream', + 'force' => true, 'prefixes' => [ '' => ['user'], ] @@ -78,6 +81,7 @@ class Setup extends Data ], 'cache' => [ 'type' => 'Stream', + 'force' => true, 'prefixes' => [ '' => ['cache'], 'images' => ['images'] @@ -85,16 +89,25 @@ class Setup extends Data ], 'log' => [ 'type' => 'Stream', + 'force' => true, 'prefixes' => [ '' => ['logs'] ] ], 'backup' => [ 'type' => 'Stream', + 'force' => true, 'prefixes' => [ '' => ['backup'] ] ], + 'tmp' => [ + 'type' => 'Stream', + 'force' => true, + 'prefixes' => [ + '' => ['tmp'] + ] + ], 'image' => [ 'type' => 'ReadOnlyStream', 'prefixes' => [ @@ -120,7 +133,7 @@ class Setup extends Data */ public function __construct($container) { - $environment = $container['uri']->environment() ?: 'localhost'; + $environment = static::$environment ?: ($container['uri']->environment() ?: 'localhost'); // Pre-load setup.php which contains our initial configuration. // Configuration may contain dynamic parts, which is why we need to always load it. @@ -194,9 +207,13 @@ public function initializeLocator(UniformResourceLocator $locator) if (isset($config['paths'])) { $locator->addPath($scheme, '', $config['paths']); } + + $override = isset($config['override']) ? $config['override'] : false; + $force = isset($config['force']) ? $config['force'] : false; + if (isset($config['prefixes'])) { foreach ($config['prefixes'] as $prefix => $paths) { - $locator->addPath($scheme, $prefix, $paths); + $locator->addPath($scheme, $prefix, $paths, $override, $force); } } } diff --git a/system/src/Grav/Common/Data/Blueprint.php b/system/src/Grav/Common/Data/Blueprint.php index d4e535146..a79881210 100644 --- a/system/src/Grav/Common/Data/Blueprint.php +++ b/system/src/Grav/Common/Data/Blueprint.php @@ -110,6 +110,18 @@ public function filter(array $data) return $this->blueprintSchema->filter($data); } + /** + * Return blueprint data schema. + * + * @return BlueprintSchema + */ + public function schema() + { + $this->initInternals(); + + return $this->blueprintSchema; + } + /** * Initialize validator. */ diff --git a/system/src/Grav/Common/Data/Validation.php b/system/src/Grav/Common/Data/Validation.php index 984afb612..22f6d8454 100644 --- a/system/src/Grav/Common/Data/Validation.php +++ b/system/src/Grav/Common/Data/Validation.php @@ -37,12 +37,6 @@ public static function validate($value, array $field) $field['type'] = 'text'; } - // Special case for files, value is never empty and errors with code 4 instead. - if (empty($validate['required']) && $field['type'] == 'file' && isset($value['error']) - && ($value['error'] == UPLOAD_ERR_NO_FILE || in_array(UPLOAD_ERR_NO_FILE, $value['error']))) { - return $messages; - } - // Get language class. $language = Grav::instance()['language']; @@ -101,12 +95,6 @@ public static function filter($value, array $field) $field['type'] = 'text'; } - // Special case for files, value is never empty and errors with code 4 instead. - if (empty($validate['required']) && $field['type'] == 'file' && isset($value['error']) - && ($value['error'] == UPLOAD_ERR_NO_FILE || in_array(UPLOAD_ERR_NO_FILE, $value['error']))) { - return null; - } - // If this is a YAML field, simply parse it and return the value. if (isset($field['yaml']) && $field['yaml'] === true) { try { diff --git a/system/src/Grav/Common/GPM/GPM.php b/system/src/Grav/Common/GPM/GPM.php index 6d310bc5d..ef518cd5b 100644 --- a/system/src/Grav/Common/GPM/GPM.php +++ b/system/src/Grav/Common/GPM/GPM.php @@ -61,14 +61,36 @@ public function __construct($refresh = false, $callback = null) } /** - * Returns the Locally installed packages - * @return Iterator The installed packages + * Return the locally installed packages + * + * @return Local\Packages */ public function getInstalled() { return $this->installed; } + /** + * Returns the Locally installable packages + * + * @param array $list_type_installed + * @return Iterator The installed packages + */ + public function getInstallable($list_type_installed = ['plugins' => true, 'themes' => true]) + { + $items = ['total' => 0]; + foreach ($list_type_installed as $type => $type_installed) { + if ($type_installed === false) { + continue; + } + $methodInstallableType = 'getInstalled' . ucfirst($type); + $to_install = $this->$methodInstallableType(); + $items[$type] = $to_install; + $items['total'] += count($to_install); + } + return $items; + } + /** * Returns the amount of locally installed packages * @return integer Amount of installed packages diff --git a/system/src/Grav/Common/GPM/Installer.php b/system/src/Grav/Common/GPM/Installer.php index 2aeeaad1f..b038cd5c0 100644 --- a/system/src/Grav/Common/GPM/Installer.php +++ b/system/src/Grav/Common/GPM/Installer.php @@ -88,8 +88,8 @@ public static function install($package, $destination, $options = []) $zip = new \ZipArchive(); $archive = $zip->open($package); - $cache_dir = Grav::instance()['locator']->findResource('cache://', true); - $tmp = $cache_dir . DS . 'tmp/Grav-' . uniqid(); + $tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true); + $tmp = $tmp_dir . '/Grav-' . uniqid(); if ($archive !== true) { self::$error = self::ZIP_OPEN_ERROR; diff --git a/system/src/Grav/Common/Helpers/Truncator.php b/system/src/Grav/Common/Helpers/Truncator.php index 7c8ab2f0b..4f67a56c1 100644 --- a/system/src/Grav/Common/Helpers/Truncator.php +++ b/system/src/Grav/Common/Helpers/Truncator.php @@ -8,12 +8,16 @@ namespace Grav\Common\Helpers; +use DOMText; use DOMDocument; +use DOMWordsIterator; +use DOMLettersIterator; /** - * This file is part of urodoz/truncateHTML. + * This file is part of https://github.com/Bluetel-Solutions/twig-truncate-extension * - * (c) Albert Lacarta + * Copyright (c) 2015 Bluetel Solutions developers@bluetel.co.uk + * Copyright (c) 2015 Alex Wilson ajw@bluetel.co.uk * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -21,181 +25,188 @@ class Truncator { - public static $default_options = array( - 'ellipsis' => '…', - 'break' => ' ', - 'length_in_chars' => false, - 'word_safe' => false, - ); - - // These tags are allowed to have an ellipsis inside - public static $ellipsable_tags = array( - 'p', 'ol', 'ul', 'li', - 'div', 'header', 'article', 'nav', - 'section', 'footer', 'aside', - 'dd', 'dt', 'dl', - ); - - public static $self_closing_tags = array( - 'br', 'hr', 'img', - ); - /** - * Truncate given HTML string to specified length. - * If length_in_chars is false it's trimmed by number - * of words, otherwise by number of characters. - * - * @param string $html - * @param integer $length - * @param string|array $opts - * @return string + * Safely truncates HTML by a given number of words. + * @param string $html Input HTML. + * @param integer $limit Limit to how many words we preserve. + * @param string $ellipsis String to use as ellipsis (if any). + * @return string Safe truncated HTML. */ - public static function truncate($html, $length, $opts=array()) + public static function truncateWords($html, $limit = 0, $ellipsis = "") { - if (is_string($opts)) $opts = array('ellipsis' => $opts); - $opts = array_merge(static::$default_options, $opts); - // wrap the html in case it consists of adjacent nodes like

foo

bar

- $html = mb_convert_encoding("
".$html."
", 'HTML-ENTITIES', 'UTF-8'); - - $root_node = null; - // Parse using HTML5Lib if it's available. - if (class_exists('HTML5Lib\\Parser')) { - try { - $doc = \HTML5Lib\Parser::parse($html); - $root_node = $doc->documentElement->lastChild->lastChild; - } - catch (\Exception $e) { - ; - } + if ($limit <= 0) { + return $html; } - if ($root_node === null) { - // HTML5Lib not available so we'll have to use DOMDocument - // We'll only be able to parse HTML5 if it's valid XML - $doc = new DOMDocument('4.01', 'utf-8'); - $doc->formatOutput = false; - $doc->preserveWhiteSpace = true; - // loadHTML will fail with HTML5 tags (article, nav, etc) - // so we need to suppress errors and if it fails to parse we - // retry with the XML parser instead - $prev_use_errors = libxml_use_internal_errors(true); - if ($doc->loadHTML($html)) { - $root_node = $doc->documentElement->lastChild->lastChild; - } - else if ($doc->loadXML($html)) { - $root_node = $doc->documentElement; - } - else { - libxml_use_internal_errors($prev_use_errors); - throw new \RuntimeException; + + $dom = self::htmlToDomDocument($html); + + // Grab the body of our DOM. + $body = $dom->getElementsByTagName("body")->item(0); + + // Iterate over words. + $words = new DOMWordsIterator($body); + foreach ($words as $word) { + + // If we have exceeded the limit, we delete the remainder of the content. + if ($words->key() >= $limit) { + + // Grab current position. + $currentWordPosition = $words->currentWordPosition(); + $curNode = $currentWordPosition[0]; + $offset = $currentWordPosition[1]; + $words = $currentWordPosition[2]; + + $curNode->nodeValue = substr( + $curNode->nodeValue, + 0, + $words[$offset][1] + strlen($words[$offset][0]) + ); + + self::removeProceedingNodes($curNode, $body); + + if (!empty($ellipsis)) { + self::insertEllipsis($curNode, $ellipsis); + } + + break; } - libxml_use_internal_errors($prev_use_errors); - } - list($text, $_, $opts) = static::truncateNode($doc, $root_node, $length, $opts); - $text = mb_substr(mb_substr($text, 0, -6), 5); + } - return $text; + return self::innerHTML($body); } - protected static function truncateNode($doc, $node, $length, $opts) + /** + * Safely truncates HTML by a given number of letters. + * @param string $html Input HTML. + * @param integer $limit Limit to how many letters we preserve. + * @param string $ellipsis String to use as ellipsis (if any). + * @return string Safe truncated HTML. + */ + public static function truncateLetters($html, $limit = 0, $ellipsis = "") { - if ($length === 0 && !static::ellipsable($node)) { - return array('', 1, $opts); - } - list($inner, $remaining, $opts) = static::innerTruncate($doc, $node, $length, $opts); - if (0 === mb_strlen($inner)) { - return array(in_array(mb_strtolower($node->nodeName), static::$self_closing_tags) ? $doc->saveXML($node) : "", $length - $remaining, $opts); - } - while($node->firstChild) { - $node->removeChild($node->firstChild); + if ($limit <= 0) { + return $html; } - $newNode = $doc->createDocumentFragment(); - // handle the ampersand - $newNode->appendXml(static::xmlEscape($inner)); - $node->appendChild($newNode); - return array($doc->saveXML($node), $length - $remaining, $opts); - } - protected static function innerTruncate($doc, $node, $length, $opts) - { - $inner = ''; - $remaining = $length; - foreach($node->childNodes as $childNode) { - if ($childNode->nodeType === XML_ELEMENT_NODE) { - list($txt, $nb, $opts) = static::truncateNode($doc, $childNode, $remaining, $opts); - } - else if ($childNode->nodeType === XML_TEXT_NODE) { - list($txt, $nb, $opts) = static::truncateText($childNode, $remaining, $opts); - } else { - $txt = ''; - $nb = 0; - } + $dom = self::htmlToDomDocument($html); + + // Grab the body of our DOM. + $body = $dom->getElementsByTagName("body")->item(0); + + // Iterate over letters. + $letters = new DOMLettersIterator($body); + foreach ($letters as $letter) { - // unhandle the ampersand - $txt = static::xmlUnescape($txt); + // If we have exceeded the limit, we want to delete the remainder of this document. + if ($letters->key() >= $limit) { - $remaining -= $nb; - $inner .= $txt; - if ($remaining < 0) { - if (static::ellipsable($node)) { - $inner = preg_replace('/(?:[\s\pP]+|(?:&(?:[a-z]+|#[0-9]+);?))*$/u', '', $inner).$opts['ellipsis']; - $opts['ellipsis'] = ''; - $opts['was_truncated'] = true; + $currentText = $letters->currentTextPosition(); + $currentText[0]->nodeValue = substr($currentText[0]->nodeValue, 0, $currentText[1] + 1); + self::removeProceedingNodes($currentText[0], $body); + + if (!empty($ellipsis)) { + self::insertEllipsis($currentText[0], $ellipsis); } + break; } } - return array($inner, $remaining, $opts); + + return self::innerHTML($body); } - protected static function truncateText($node, $length, $opts) + /** + * Builds a DOMDocument object from a string containing HTML. + * @param string HTML to load + * @returns DOMDocument Returns a DOMDocument object. + */ + public static function htmlToDomDocument($html) { - $string = $node->textContent; + // Transform multibyte entities which otherwise display incorrectly. + $html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'); - if ($opts['length_in_chars']) { - $count = mb_strlen($string); - if ($count <= $length && $length > 0) { - return array($string, $count, $opts); - } - if ($opts['word_safe']) { - if (false !== ($breakpoint = mb_strpos($string, $opts['break'], $length))) { - if ($breakpoint < mb_strlen($string) - 1) { - $string = mb_substr($string, 0, $breakpoint) . $opts['break']; - } + // Internal errors enabled as HTML5 not fully supported. + libxml_use_internal_errors(true); + + // Instantiate new DOMDocument object, and then load in UTF-8 HTML. + $dom = new DOMDocument(); + $dom->encoding = 'UTF-8'; + $dom->loadHTML($html); + + return $dom; + } + + /** + * Removes all nodes after the current node. + * @param DOMNode|DOMElement $domNode + * @param DOMNode|DOMElement $topNode + * @return void + */ + private static function removeProceedingNodes($domNode, $topNode) + { + $nextNode = $domNode->nextSibling; + + if ($nextNode !== null) { + self::removeProceedingNodes($nextNode, $topNode); + $domNode->parentNode->removeChild($nextNode); + } else { + //scan upwards till we find a sibling + $curNode = $domNode->parentNode; + while ($curNode !== $topNode) { + if ($curNode->nextSibling !== null) { + $curNode = $curNode->nextSibling; + self::removeProceedingNodes($curNode, $topNode); + $curNode->parentNode->removeChild($curNode); + break; } - return array($string, $count, $opts); - } - return array(mb_substr($node->textContent, 0, $length), $count, $opts); - } - else { - preg_match_all('/\s*\S+/', $string, $words); - $words = $words[0]; - $count = count($words); - if ($count <= $length && $length > 0) { - return array($string, $count, $opts); + $curNode = $curNode->parentNode; } - return array(implode('', array_slice($words, 0, $length)), $count, $opts); } } - protected static function ellipsable($node) + /** + * Inserts an ellipsis + * @param DOMNode|DOMElement $domNode Element to insert after. + * @param string $ellipsis Text used to suffix our document. + * @return void + */ + private static function insertEllipsis($domNode, $ellipsis) { - return ($node instanceof DOMDocument) - || in_array(mb_strtolower($node->nodeName), static::$ellipsable_tags) - ; - } + $avoid = array('a', 'strong', 'em', 'h1', 'h2', 'h3', 'h4', 'h5'); //html tags to avoid appending the ellipsis to - protected static function xmlEscape($string) - { - $string = str_replace('&', '&', $string); - $string = str_replace('parentNode->nodeName, $avoid) && $domNode->parentNode->parentNode !== null) { + // Append as text node to parent instead + $textNode = new DOMText($ellipsis); + + if ($domNode->parentNode->parentNode->nextSibling) { + $domNode->parentNode->parentNode->insertBefore($textNode, $domNode->parentNode->parentNode->nextSibling); + } else { + $domNode->parentNode->parentNode->appendChild($textNode); + } + + } else { + // Append to current node + $domNode->nodeValue = rtrim($domNode->nodeValue) . $ellipsis; + } } - protected static function xmlUnescape($string) - { - $string = str_replace('&', '&', $string); - $string = str_replace('<?', 'childNodes; + foreach ($children as $child) + { + $tmp_dom = new DOMDocument(); + $tmp_dom->appendChild($tmp_dom->importNode($child, true)); + $innerHTML.=trim($tmp_dom->saveHTML()); + } + return $innerHTML; } + } diff --git a/system/src/Grav/Common/Language/LanguageCodes.php b/system/src/Grav/Common/Language/LanguageCodes.php index ad278c24b..85b3236e3 100644 --- a/system/src/Grav/Common/Language/LanguageCodes.php +++ b/system/src/Grav/Common/Language/LanguageCodes.php @@ -14,7 +14,7 @@ class LanguageCodes 'af' => [ 'name' => 'Afrikaans', 'nativeName' => 'Afrikaans' ], 'ak' => [ 'name' => 'Akan', 'nativeName' => 'Akan' ], // unverified native name 'ast' => [ 'name' => 'Asturian', 'nativeName' => 'Asturianu' ], - 'ar' => [ 'name' => 'Arabic', 'nativeName' => 'عربي' ], + 'ar' => [ 'name' => 'Arabic', 'nativeName' => 'عربي', 'orientation' => 'rtl'], 'as' => [ 'name' => 'Assamese', 'nativeName' => 'অসমীয়া' ], 'be' => [ 'name' => 'Belarusian', 'nativeName' => 'Беларуская' ], 'bg' => [ 'name' => 'Bulgarian', 'nativeName' => 'Български' ], @@ -48,7 +48,7 @@ class LanguageCodes 'es-MX' => [ 'name' => 'Spanish (Mexico)', 'nativeName' => 'Español (de México)' ], 'et' => [ 'name' => 'Estonian', 'nativeName' => 'Eesti keel' ], 'eu' => [ 'name' => 'Basque', 'nativeName' => 'Euskara' ], - 'fa' => [ 'name' => 'Persian', 'nativeName' => 'فارسی' ], + 'fa' => [ 'name' => 'Persian', 'nativeName' => 'فارسی' , 'orientation' => 'rtl' ], 'fi' => [ 'name' => 'Finnish', 'nativeName' => 'Suomi' ], 'fj-FJ' => [ 'name' => 'Fijian', 'nativeName' => 'Vosa vaka-Viti' ], 'fr' => [ 'name' => 'French', 'nativeName' => 'Français' ], @@ -62,7 +62,7 @@ class LanguageCodes 'gl' => [ 'name' => 'Galician', 'nativeName' => 'Galego' ], 'gu' => [ 'name' => 'Gujarati', 'nativeName' => 'ગુજરાતી' ], 'gu-IN' => [ 'name' => 'Gujarati', 'nativeName' => 'ગુજરાતી' ], - 'he' => [ 'name' => 'Hebrew', 'nativeName' => 'עברית' ], + 'he' => [ 'name' => 'Hebrew', 'nativeName' => 'עברית', 'orientation' => 'rtl' ], 'hi' => [ 'name' => 'Hindi', 'nativeName' => 'हिन्दी' ], 'hi-IN' => [ 'name' => 'Hindi (India)', 'nativeName' => 'हिन्दी (भारत)' ], 'hr' => [ 'name' => 'Croatian', 'nativeName' => 'Hrvatski' ], @@ -135,7 +135,7 @@ class LanguageCodes 'tt' => [ 'name' => 'Tatar', 'nativeName' => 'Tatarça' ], 'tt-RU' => [ 'name' => 'Tatar', 'nativeName' => 'Tatarça' ], 'uk' => [ 'name' => 'Ukrainian', 'nativeName' => 'Українська' ], - 'ur' => [ 'name' => 'Urdu', 'nativeName' => 'اُردو' ], + 'ur' => [ 'name' => 'Urdu', 'nativeName' => 'اُردو', 'orientation' => 'rtl' ], 've' => [ 'name' => 'Venda', 'nativeName' => 'Tshivenḓa' ], 'vi' => [ 'name' => 'Vietnamese', 'nativeName' => 'Tiếng Việt' ], 'wo' => [ 'name' => 'Wolof', 'nativeName' => 'Wolof' ], @@ -165,6 +165,24 @@ public static function getNativeName($code) } } + public static function getOrientation($code) + { + if (isset(static::$codes[$code])) { + if (isset(static::$codes[$code]['orientation'])) { + return static::get($code, 'orientation'); + } + } + return 'ltr'; + } + + public static function isRtl($code) + { + if (static::getOrientation($code) == 'rtl') { + return true; + } + return false; + } + public static function getNames(array $keys) { $results = []; diff --git a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php index 9b491c22c..19b710386 100644 --- a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php +++ b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php @@ -151,7 +151,7 @@ public function elementToHtml(array $Element) * * @return $this */ - function setSpecialChars($special_chars) + public function setSpecialChars($special_chars) { $this->special_chars = $special_chars; diff --git a/system/src/Grav/Common/Page/Medium/ImageMedium.php b/system/src/Grav/Common/Page/Medium/ImageMedium.php index 57d949382..521c41c0a 100644 --- a/system/src/Grav/Common/Page/Medium/ImageMedium.php +++ b/system/src/Grav/Common/Page/Medium/ImageMedium.php @@ -501,8 +501,6 @@ protected function image() ->setActualCacheDir($cacheDir) ->setPrettyName(basename($this->get('basename'))); - $this->filter(); - return $this; } @@ -517,6 +515,8 @@ protected function saveImage() return parent::path(false); } + $this->filter(); + if (isset($this->result)) { return $this->result; } diff --git a/system/src/Grav/Common/Page/Medium/Medium.php b/system/src/Grav/Common/Page/Medium/Medium.php index 20f0d297c..b13306f52 100644 --- a/system/src/Grav/Common/Page/Medium/Medium.php +++ b/system/src/Grav/Common/Page/Medium/Medium.php @@ -426,7 +426,6 @@ public function classes() */ public function id($id) { - xdebug_break(); if (is_string($id)) { $this->attributes['id'] = trim($id); } diff --git a/system/src/Grav/Common/Page/Medium/StaticImageMedium.php b/system/src/Grav/Common/Page/Medium/StaticImageMedium.php index 8e540b053..eae05d478 100644 --- a/system/src/Grav/Common/Page/Medium/StaticImageMedium.php +++ b/system/src/Grav/Common/Page/Medium/StaticImageMedium.php @@ -23,6 +23,6 @@ protected function sourceParsedownElement(array $attributes, $reset = true) { empty($attributes['src']) && $attributes['src'] = $this->url($reset); - return [ 'name' => 'image', 'attributes' => $attributes ]; + return [ 'name' => 'img', 'attributes' => $attributes ]; } } diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php index abb421078..0062d609a 100644 --- a/system/src/Grav/Common/Page/Page.php +++ b/system/src/Grav/Common/Page/Page.php @@ -54,6 +54,7 @@ class Page protected $routable; protected $modified; protected $redirect; + protected $external_url; protected $items; protected $header; protected $frontmatter; @@ -364,6 +365,9 @@ public function header($var = null) if (isset($this->header->redirect)) { $this->redirect = trim($this->header->redirect); } + if (isset($this->header->external_url)) { + $this->external_url = trim($this->header->external_url); + } if (isset($this->header->order_dir)) { $this->order_dir = trim($this->header->order_dir); } @@ -498,7 +502,8 @@ public function summary($size = null) $size = 300; } - return html_entity_decode(Utils::truncateHTML($content, $size)); + $summary = Utils::truncateHTML($content, $size); + return html_entity_decode($summary); } /** @@ -1470,6 +1475,11 @@ public function url($include_host = false, $canonical = false, $include_lang = t /** @var Uri $uri */ $uri = $grav['uri']; + // Override any URL when external_url is set + if (isset($this->external_url)) { + return $this->external_url; + } + // get pre-route if ($include_lang && $language->enabled()) { $pre_route = $language->getLanguageURLPrefix(); @@ -2144,7 +2154,9 @@ public function activeChild() */ public function home() { - return $this->find('/') == $this; + $home = Grav::instance()['config']->get('system.home.alias'); + $is_home = ($this->route() == $home || $this->rawRoute() == $home); + return $is_home; } /** @@ -2226,7 +2238,7 @@ public function collection($params = 'content', $pagination = true) if (empty($page->taxonomy[$taxonomy]) || !in_array(htmlspecialchars_decode($item, ENT_QUOTES), $page->taxonomy[$taxonomy]) ) { - $collection->remove(); + $collection->remove($page->path()); } } } @@ -2300,11 +2312,6 @@ public function evaluate($value) return new Collection($result); } - // We only evaluate commands which start with @ - if (empty($cmd) || $cmd[0] != '@') { - return $value; - } - /** @var Pages $pages */ $pages = Grav::instance()['pages']; @@ -2315,6 +2322,7 @@ public function evaluate($value) $results = new Collection(); switch ($current) { + case 'self@': case '@self': if (!empty($parts)) { switch ($parts[0]) { @@ -2351,6 +2359,7 @@ public function evaluate($value) $results = $results->published(); break; + case 'page@': case '@page': $page = null; @@ -2366,27 +2375,33 @@ public function evaluate($value) // Handle a @page.descendants if (!empty($parts)) { switch ($parts[0]) { + case 'modular': + $results = new Collection(); + $results = $results->addPage($page)->Modular(); + break; + case 'page': case 'self': $results = new Collection(); - $results = $results->addPage($page); + $results = $results->addPage($page)->nonModular(); break; case 'descendants': - $results = $pages->all($page)->remove($page->path()); + $results = $pages->all($page)->remove($page->path())->nonModular(); break; case 'children': - $results = $page->children(); + $results = $page->children()->nonModular(); break; } } else { - $results = $page->children(); + $results = $page->children()->nonModular(); } - $results = $results->nonModular()->published(); + $results = $results->published(); break; + case 'root@': case '@root': if (!empty($parts) && $parts[0] == 'descendants') { $results = $pages->all($pages->root())->nonModular()->published(); @@ -2395,7 +2410,7 @@ public function evaluate($value) } break; - + case 'taxonomy@': case '@taxonomy': // Gets a collection of pages by using one of the following formats: // @taxonomy.category: blog diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php index 26a338e6a..1fab5e597 100644 --- a/system/src/Grav/Common/Page/Pages.php +++ b/system/src/Grav/Common/Page/Pages.php @@ -846,14 +846,22 @@ protected function recurse($directory, Page &$parent = null) } $content_exists = false; - $pages_found = glob($directory . '/*' . CONTENT_EXT); + $pages_found = new \GlobIterator($directory . '/*' . CONTENT_EXT); + $page_found = null; + $page_extension = ''; - if ($pages_found) { + if ($pages_found && count($pages_found) > 0) { + $page_extensions = $language->getFallbackPageExtensions(); + foreach ($page_extensions as $extension) { foreach ($pages_found as $found) { - if (preg_match('/^.*\/[0-9A-Za-z\-\_]+(' . $extension . ')$/', $found)) { + if ($found->isDir()) { + continue; + } + $regex = '/^[^\.]*' . preg_quote($extension) . '$/'; + if (preg_match($regex, $found->getFilename())) { $page_found = $found; $page_extension = $extension; break 2; @@ -863,8 +871,7 @@ protected function recurse($directory, Page &$parent = null) } if ($parent && !empty($page_found)) { - $file = new \SplFileInfo($page_found); - $page->init($file, $page_extension); + $page->init($page_found, $page_extension); $content_exists = true; @@ -1065,12 +1072,12 @@ protected function buildSort($path, array $pages, $order_by = 'default', $manual } else { // else just sort the list according to specified key if (extension_loaded('intl')) { - $locale = setlocale(LC_COLLATE, 0); //`setlocale` with a 0 param returns the current locale set - $col = \Collator::create($locale); + $locale = setlocale(LC_COLLATE, 0); //`setlocale` with a 0 param returns the current locale set + $col = \Collator::create($locale); if ($col) { - $col->asort($list, $sort_flags); + $col->asort($list, $sort_flags); } else { - asort($list, $sort_flags); + asort($list, $sort_flags); } } else { asort($list, $sort_flags); diff --git a/system/src/Grav/Common/Page/Types.php b/system/src/Grav/Common/Page/Types.php index e915c07b6..04a6ad3d0 100644 --- a/system/src/Grav/Common/Page/Types.php +++ b/system/src/Grav/Common/Page/Types.php @@ -52,6 +52,8 @@ public function scanBlueprints($uri) // Register default by default. $this->register('default'); + + $this->register('external'); } foreach ($this->findBlueprints($uri) as $type => $blueprint) { diff --git a/system/src/Grav/Common/Service/PageServiceProvider.php b/system/src/Grav/Common/Service/PageServiceProvider.php index 5042afe9b..a8b005678 100644 --- a/system/src/Grav/Common/Service/PageServiceProvider.php +++ b/system/src/Grav/Common/Service/PageServiceProvider.php @@ -37,7 +37,7 @@ public function register(Container $container) { if (!isset($_SERVER['HTTPS']) || $_SERVER["HTTPS"] != "on") { $url = "https://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"]; $c->redirect($url); - } + } } $url = $page->route(); diff --git a/system/src/Grav/Common/Session.php b/system/src/Grav/Common/Session.php index b7594842d..6f541e022 100644 --- a/system/src/Grav/Common/Session.php +++ b/system/src/Grav/Common/Session.php @@ -38,7 +38,10 @@ public function init() $base_url = $uri->rootUrl(false); $session_timeout = $config->get('system.session.timeout', 1800); - $session_path = $config->get('system.session.path', '/' . ltrim($base_url, '/')); + $session_path = $config->get('system.session.path'); + if (!$session_path) { + $session_path = '/' . ltrim($base_url, '/'); + } // Activate admin if we're inside the admin path. if ($config->get('plugins.admin.enabled')) { @@ -56,13 +59,14 @@ public function init() } if ($config->get('system.session.enabled') || $is_admin) { - // Define session service. - parent::__construct($session_timeout, $session_path); - $domain = $uri->host(); if ($domain === 'localhost') { $domain = ''; } + + // Define session service. + parent::__construct($session_timeout, $session_path, $domain); + $secure = $config->get('system.session.secure', false); $httponly = $config->get('system.session.httponly', true); diff --git a/system/src/Grav/Common/Twig/TwigExtension.php b/system/src/Grav/Common/Twig/TwigExtension.php index 311162dd0..f67e441f2 100644 --- a/system/src/Grav/Common/Twig/TwigExtension.php +++ b/system/src/Grav/Common/Twig/TwigExtension.php @@ -142,12 +142,16 @@ public function fieldNameFilter($str) public function safeEmailFilter($str) { $email = ''; - $str_len = strlen($str); - for ($i = 0; $i < $str_len; $i++) { - $email .= "&#" . ord($str[$i]) . ";"; + for ( $i = 0, $len = strlen( $str ); $i < $len; $i++ ) { + $j = rand( 0, 1); + if ( $j == 0 ) { + $email .= '&#' . ord( $str[$i] ) . ';'; + } elseif ( $j == 1 ) { + $email .= $str[$i]; + } } - return $email; + return str_replace( '@', '@', $email ); } /** diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php index 0e22e9774..b6f86d541 100644 --- a/system/src/Grav/Common/Uri.php +++ b/system/src/Grav/Common/Uri.php @@ -8,6 +8,7 @@ namespace Grav\Common; +use Grav\Common\Language\Language; use Grav\Common\Page\Page; class Uri @@ -207,6 +208,7 @@ public function initializeWithUrl($url = '') $grav = Grav::instance(); + /** @var Language $language */ $language = $grav['language']; $uri_bits = Uri::parseUrl($url); @@ -275,15 +277,12 @@ public function init() } // Set some defaults - $this->root = $this->base . $this->root_path; + $this->root = $grav['config']->get('system.custom_base_url') ?: $this->base . $this->root_path; $this->url = $this->base . $this->uri; // get any params and remove them $uri = str_replace($this->root, '', $this->url); - // remove double slashes - $uri = preg_replace('#/{2,}#', '/', $uri); - // remove the setup.php based base if set: $setup_base = $grav['pages']->base(); if ($setup_base) { @@ -292,7 +291,7 @@ public function init() // If configured to, redirect trailing slash URI's with a 301 redirect if ($config->get('system.pages.redirect_trailing_slash', false) && $uri != '/' && Utils::endsWith($uri, '/')) { - $grav->redirect(rtrim($uri, '/'), 301); + $grav->redirect(str_replace($this->root, '', rtrim($uri, '/')), 301); } // process params @@ -344,7 +343,7 @@ public function init() } // Set some Grav stuff - $grav['base_url_absolute'] = $this->rootUrl(true); + $grav['base_url_absolute'] = $grav['config']->get('system.custom_base_url') ?: $this->rootUrl(true); $grav['base_url_relative'] = $this->rootUrl(false); $grav['base_url'] = $grav['config']->get('system.absolute_urls') ? $grav['base_url_absolute'] : $grav['base_url_relative']; } diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index 9e07d1524..672ff0e08 100644 --- a/system/src/Grav/Common/Utils.php +++ b/system/src/Grav/Common/Utils.php @@ -183,26 +183,28 @@ public static function safeTruncate($string, $limit = 150) * Truncate HTML by number of characters. not "word-safe"! * * @param string $text - * @param int $length + * @param int $length in characters + * @param string $ellipsis * * @return string */ - public static function truncateHtml($text, $length = 100) + public static function truncateHtml($text, $length = 100, $ellipsis = '...') { - return Truncator::truncate($text, $length, ['length_in_chars' => true]); + return Truncator::truncateLetters($text, $length, $ellipsis); } /** * Truncate HTML by number of characters in a "word-safe" manor. * * @param string $text - * @param int $length + * @param int $length in words + * @param string $ellipsis * * @return string */ - public static function safeTruncateHtml($text, $length = 100) + public static function safeTruncateHtml($text, $length = 25, $ellipsis = '...') { - return Truncator::truncate($text, $length, ['length_in_chars' => true, 'word_safe' => true]); + return Truncator::truncateWords($text, $length, $ellipsis); } /** @@ -432,6 +434,27 @@ public static function arrayFilterRecursive(Array $source, $fn) return $result; } + /** + * Flatten an array + * + * @param $array + * @return array + */ + public static function arrayFlatten($array) + { + $flatten = array(); + foreach ($array as $key => $inner){ + if (is_array($inner)) { + foreach ($inner as $inner_key => $value) { + $flatten[$inner_key] = $value; + } + } else { + $flatten[$key] = $inner; + } + } + return $flatten; + } + /** * Checks if the passed path contains the language code prefix * @@ -690,12 +713,14 @@ public static function getDotNotation($array, $key, $default = null) * Set portion of array (passed by reference) for a dot-notation key * and set the value * - * @param $array - * @param $key - * @param $value + * @param $array + * @param $key + * @param $value + * @param bool $merge + * * @return mixed */ - public static function setDotNotation(&$array, $key, $value) + public static function setDotNotation(&$array, $key, $value, $merge = false) { if (is_null($key)) return $array = $value; @@ -713,7 +738,14 @@ public static function setDotNotation(&$array, $key, $value) $array =& $array[$key]; } - $array[array_shift($keys)] = $value; + $key = array_shift($keys); + + if (!$merge || !isset($array[$key])) { + $array[$key] = $value; + } else { + $array[$key] = array_merge($array[$key], $value); + } + return $array; } diff --git a/system/src/Grav/Console/Cli/CleanCommand.php b/system/src/Grav/Console/Cli/CleanCommand.php index 69d05758b..9fea3fd7b 100644 --- a/system/src/Grav/Console/Cli/CleanCommand.php +++ b/system/src/Grav/Console/Cli/CleanCommand.php @@ -94,6 +94,7 @@ class CleanCommand extends Command 'vendor/ircmaxell/password-compat/version-test.php', 'vendor/ircmaxell/password-compat/.travis.yml', 'vendor/ircmaxell/password-compat/test', + 'vendor/league/climate/composer.json', 'vendor/matthiasmullie/minify/bin', 'vendor/matthiasmullie/minify/composer.json', 'vendor/matthiasmullie/minify/CONTRIBUTING.md', @@ -124,6 +125,7 @@ class CleanCommand extends Command 'vendor/rockettheme/toolbox/.travis.yml', 'vendor/rockettheme/toolbox/composer.json', 'vendor/rockettheme/toolbox/phpunit.xml', + 'vendor/seld/cli-prompt/composer.json', 'vendor/symfony/console/composer.json', 'vendor/symfony/console/phpunit.xml.dist', 'vendor/symfony/console/.gitignore', diff --git a/system/src/Grav/Console/Cli/ClearCacheCommand.php b/system/src/Grav/Console/Cli/ClearCacheCommand.php index a5d8f214b..591b0f917 100644 --- a/system/src/Grav/Console/Cli/ClearCacheCommand.php +++ b/system/src/Grav/Console/Cli/ClearCacheCommand.php @@ -27,6 +27,7 @@ protected function configure() ->addOption('assets-only', null, InputOption::VALUE_NONE, 'If set will remove only assets/*') ->addOption('images-only', null, InputOption::VALUE_NONE, 'If set will remove only images/*') ->addOption('cache-only', null, InputOption::VALUE_NONE, 'If set will remove only cache/*') + ->addOption('tmp-only', null, InputOption::VALUE_NONE, 'If set will remove only tmp/*') ->setHelp('The clear-cache deletes all cache files'); } @@ -55,6 +56,8 @@ private function cleanPaths() $remove = 'images-only'; } elseif ($this->input->getOption('cache-only')) { $remove = 'cache-only'; + } elseif ($this->input->getOption('tmp-only')) { + $remove = 'tmp-only'; } else { $remove = 'standard'; } diff --git a/system/src/Grav/Console/Cli/SandboxCommand.php b/system/src/Grav/Console/Cli/SandboxCommand.php index a593165a7..769bf6a33 100644 --- a/system/src/Grav/Console/Cli/SandboxCommand.php +++ b/system/src/Grav/Console/Cli/SandboxCommand.php @@ -19,15 +19,16 @@ class SandboxCommand extends ConsoleCommand * @var array */ protected $directories = [ + '/assets', '/backup', '/cache', - '/logs', '/images', - '/assets', + '/logs', + '/tmp', '/user/accounts', '/user/config', - '/user/pages', '/user/data', + '/user/pages', '/user/plugins', '/user/themes', ]; diff --git a/system/src/Grav/Console/Gpm/IndexCommand.php b/system/src/Grav/Console/Gpm/IndexCommand.php index 2e3edb229..80c535d70 100644 --- a/system/src/Grav/Console/Gpm/IndexCommand.php +++ b/system/src/Grav/Console/Gpm/IndexCommand.php @@ -9,7 +9,9 @@ namespace Grav\Console\Gpm; use Grav\Common\GPM\GPM; +use Grav\Common\Utils; use Grav\Console\ConsoleCommand; +use League\CLImate\CLImate; use Symfony\Component\Console\Input\InputOption; class IndexCommand extends ConsoleCommand @@ -100,31 +102,36 @@ protected function configure() protected function serve() { $this->options = $this->input->getOptions(); - $this->gpm = new GPM($this->options['force']); - $this->displayGPMRelease(); - $this->data = $this->gpm->getRepository(); $data = $this->filter($this->data); - foreach ($data as $type => $packages) { - $this->output->writeln("" . ucfirst($type) . " [ " . count($packages) . " ]"); + $climate = new CLImate; + $climate->extend('Grav\Console\TerminalObjects\Table'); - $index = 0; + foreach ($data as $type => $packages) { + $this->output->writeln("" . strtoupper($type) . " [ " . count($packages) . " ]"); $packages = $this->sort($packages); - foreach ($packages as $slug => $package) { - $this->output->writeln( - // index - str_pad($index++ + 1, 2, '0', STR_PAD_LEFT) . ". " . - // package name - "" . str_pad($package->name, 20) . " " . - // slug - "[" . str_pad($slug, 20, ' ', STR_PAD_BOTH) . "] " . - // version details - $this->versionDetails($package) - ); + + if (!empty($packages)) { + + $table = []; + $index = 0; + + foreach ($packages as $slug => $package) { + $row = [ + 'Count' => $index++ + 1, + 'Name' => "" . Utils::truncate($package->name, 20, false, ' ', '...') . " ", + 'Slug' => $slug, + 'Version'=> $this->version($package), + 'Installed' => $this->installed($package) + ]; + $table[] = $row; + } + + $climate->table($table); } $this->output->writeln(''); @@ -143,7 +150,7 @@ protected function serve() * * @return string */ - private function versionDetails($package) + private function version($package) { $list = $this->gpm->{'getUpdatable' . ucfirst($package->package_type)}(); $package = isset($list[$package->slug]) ? $list[$package->slug] : $package; @@ -154,21 +161,30 @@ private function versionDetails($package) if (!$installed || !$updatable) { $version = $installed ? $local->version : $package->version; - $installed = !$installed ? ' (not installed)' : ' (installed)'; - - return str_pad(" [v" . $version . "]", 35) . $installed; + return "v" . $version . ""; } if ($updatable) { - $installed = !$installed ? ' (not installed)' : ' (installed)'; - - return str_pad(" [v" . $package->version . " v" . $package->available . "]", - 61) . $installed; + return "v" . $package->version . " -> v" . $package->available . ""; } return ''; } + /** + * @param $package + * + * @return string + */ + private function installed($package) + { + $package = isset($list[$package->slug]) ? $list[$package->slug] : $package; + $type = ucfirst(preg_replace("/s$/", '', $package->package_type)); + $installed = $this->gpm->{'is' . $type . 'Installed'}($package->slug); + + return !$installed ? 'not installed' : 'installed'; + } + /** * @param $data * diff --git a/system/src/Grav/Console/Gpm/InfoCommand.php b/system/src/Grav/Console/Gpm/InfoCommand.php index 1036c597c..dad532764 100644 --- a/system/src/Grav/Console/Gpm/InfoCommand.php +++ b/system/src/Grav/Console/Gpm/InfoCommand.php @@ -25,6 +25,8 @@ class InfoCommand extends ConsoleCommand */ protected $gpm; + protected $all_yes; + /** * */ @@ -60,6 +62,8 @@ protected function serve() { $this->gpm = new GPM($this->input->getOption('force')); + $this->all_yes = $this->input->getOption('all-yes'); + $this->displayGPMRelease(); $foundPackage = $this->gpm->findPackage($this->input->getArgument('package')); @@ -133,38 +137,34 @@ protected function serve() // display changelog information $questionHelper = $this->getHelper('question'); - $skipPrompt = $this->input->getOption('all-yes'); - - if (!$skipPrompt) { - $question = new ConfirmationQuestion("Would you like to read the changelog? [y|N] ", - false); - $answer = $questionHelper->ask($this->input, $this->output, $question); - - if ($answer) { - $changelog = $foundPackage->changelog; - + $question = new ConfirmationQuestion("Would you like to read the changelog? [y|N] ", + false); + $answer = $this->all_yes ? true : $questionHelper->ask($this->input, $this->output, $question); + + if ($answer) { + $changelog = $foundPackage->changelog; + + $this->output->writeln(""); + foreach ($changelog as $version => $log) { + $title = $version . ' [' . $log['date'] . ']'; + $content = preg_replace_callback("/\d\.\s\[\]\(#(.*)\)/", function ($match) { + return "\n" . ucfirst($match[1]) . ":"; + }, $log['content']); + + $this->output->writeln(''.$title.''); + $this->output->writeln(str_repeat('-', strlen($title))); + $this->output->writeln($content); $this->output->writeln(""); - foreach ($changelog as $version => $log) { - $title = $version . ' [' . $log['date'] . ']'; - $content = preg_replace_callback("/\d\.\s\[\]\(#(.*)\)/", function ($match) { - return "\n" . ucfirst($match[1]) . ":"; - }, $log['content']); - - $this->output->writeln(''.$title.''); - $this->output->writeln(str_repeat('-', strlen($title))); - $this->output->writeln($content); - $this->output->writeln(""); - - $question = new ConfirmationQuestion("Press [ENTER] to continue or [q] to quit ", true); - if (!$questionHelper->ask($this->input, $this->output, $question)) { - break; - } - $this->output->writeln(""); + + $question = new ConfirmationQuestion("Press [ENTER] to continue or [q] to quit ", true); + $answer = $this->all_yes ? false : $questionHelper->ask($this->input, $this->output, $question); + if (!$answer) { + break; } + $this->output->writeln(""); } } - $this->output->writeln(''); if ($installed && $updatable) { diff --git a/system/src/Grav/Console/Gpm/InstallCommand.php b/system/src/Grav/Console/Gpm/InstallCommand.php index 47eeeadb4..fee65129e 100644 --- a/system/src/Grav/Console/Gpm/InstallCommand.php +++ b/system/src/Grav/Console/Gpm/InstallCommand.php @@ -49,6 +49,8 @@ class InstallCommand extends ConsoleCommand /** @var array */ protected $demo_processing = []; + protected $all_yes; + /** * */ @@ -101,6 +103,8 @@ protected function serve() { $this->gpm = new GPM($this->input->getOption('force')); + $this->all_yes = $this->input->getOption('all-yes'); + $this->displayGPMRelease(); $this->destination = realpath($this->input->getOption('destination')); @@ -139,16 +143,20 @@ protected function serve() unset($this->data['not_found']); unset($this->data['total']); + if (isset($this->local_config)) { // Symlinks available, ask if Grav should use them - $this->use_symlinks = false; $helper = $this->getHelper('question'); $question = new ConfirmationQuestion('Should Grav use the symlinks if available? [y|N] ', false); - if ($helper->ask($this->input, $this->output, $question)) { + $answer = $this->all_yes ? false : $helper->ask($this->input, $this->output, $question); + + if ($answer) { $this->use_symlinks = true; } + + } $this->output->writeln(''); @@ -199,8 +207,9 @@ protected function serve() $helper = $this->getHelper('question'); $question = new ConfirmationQuestion("The package $package_name is already installed, overwrite? [y|N] ", false); + $answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question); - if ($helper->ask($this->input, $this->output, $question)) { + if ($answer) { $is_update = true; $this->processPackage($package, true, $is_update); } else { @@ -208,8 +217,8 @@ protected function serve() } } else { if (Installer::lastErrorCode() == Installer::IS_LINK) { - $this->output->writeln("Cannot overwrite existing symlink"); - return false; + $this->output->writeln("Cannot overwrite existing symlink for $package_name"); + $this->output->writeln(""); } } } @@ -244,6 +253,11 @@ public function askConfirmationIfMajorVersionUpdated($package) $major_version_changed = explode('.', $new_version)[0] !== explode('.', $old_version)[0]; if ($major_version_changed) { + if ($this->all_yes) { + $this->output->writeln("The package $package_name will be updated to a new major version $new_version, from $old_version"); + return; + } + $question = new ConfirmationQuestion("The package $package_name will be updated to a new major version $new_version, from $old_version. Be sure to read what changed with the new major release. Continue? [y|N] ", false); if (!$helper->ask($this->input, $this->output, $question)) { @@ -297,9 +311,10 @@ public function installDependencies($dependencies, $type, $message, $required = $questionNoun = 'packages'; } - $question = new ConfirmationQuestion("$questionAction $questionArticle $questionNoun? [y|N] ", false); + $question = new ConfirmationQuestion("$questionAction $questionArticle $questionNoun? [Y|n] ", true); + $answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question); - if ($helper->ask($this->input, $this->output, $question)) { + if ($answer) { foreach ($packages as $dependencyName => $dependencyVersion) { $package = $this->gpm->findPackage($dependencyName); $this->processPackage($package, true, ($type == 'update') ? true : false); @@ -315,10 +330,9 @@ public function installDependencies($dependencies, $type, $message, $required = /** * @param $package - * @param bool $skip_prompt - * @param bool $update True if the package is an update + * @param bool $is_update True if the package is an update */ - private function processPackage($package, $skip_prompt = false, $is_update = false) + private function processPackage($package, $is_update = false) { if (!$package) { $this->output->writeln("Package not found on the GPM! "); @@ -333,7 +347,7 @@ private function processPackage($package, $skip_prompt = false, $is_update = fal } } - $symlink ? $this->processSymlink($package, $skip_prompt) : $this->processGpm($package, $skip_prompt, $is_update); + $symlink ? $this->processSymlink($package) : $this->processGpm($package, $is_update); $this->processDemo($package); } @@ -369,7 +383,9 @@ private function installDemoContent($package) $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)) { + $answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question); + + if (!$answer) { $this->output->writeln(" '- Skipped! "); $this->output->writeln(''); @@ -380,8 +396,9 @@ private function installDemoContent($package) 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); + $answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question); - if (!$helper->ask($this->input, $this->output, $question)) { + if (!$answer) { $this->output->writeln(" '- Skipped! "); $this->output->writeln(''); @@ -452,9 +469,8 @@ private function getSymlinkSource($package) /** * @param $package - * @param bool $skip_prompt */ - private function processSymlink($package, $skip_prompt = false) + private function processSymlink($package) { exec('cd ' . $this->destination); @@ -469,7 +485,7 @@ private function processSymlink($package, $skip_prompt = false) $this->output->writeln("ok"); $this->output->write(" |- Checking destination... "); - $checks = $this->checkDestination($package, $skip_prompt); + $checks = $this->checkDestination($package); if (!$checks) { $this->output->writeln(" '- Installation failed or aborted."); @@ -497,9 +513,8 @@ private function processSymlink($package, $skip_prompt = false) /** * @param $package - * @param bool $skip_prompt */ - private function processGpm($package, $skip_prompt = false, $is_update = false) + private function processGpm($package, $is_update = false) { $version = isset($package->available) ? $package->available : $package->version; @@ -509,7 +524,7 @@ private function processGpm($package, $skip_prompt = false, $is_update = false) $this->file = $this->downloadPackage($package); $this->output->write(" |- Checking destination... "); - $checks = $this->checkDestination($package, $skip_prompt); + $checks = $this->checkDestination($package); if (!$checks) { $this->output->writeln(" '- Installation failed or aborted."); @@ -534,8 +549,8 @@ private function processGpm($package, $skip_prompt = false, $is_update = false) */ private function downloadPackage($package) { - $cache_dir = Grav::instance()['locator']->findResource('cache://', true); - $this->tmp = $cache_dir . DS . 'tmp/Grav-' . uniqid(); + $tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true); + $this->tmp = $tmp_dir . '/Grav-' . uniqid(); $filename = $package->slug . basename($package->zipball_url); $output = Response::get($package->zipball_url, [], [$this, 'progress']); @@ -553,42 +568,19 @@ private function downloadPackage($package) /** * @param $package * - * @param bool $skip_prompt - * * @return bool */ - private function checkDestination($package, $skip_prompt = false) + private function checkDestination($package) { $question_helper = $this->getHelper('question'); - if (!$skip_prompt) { - $skip_prompt = $this->input->getOption('all-yes'); - } - Installer::isValidDestination($this->destination . DS . $package->install_path); - if (Installer::lastErrorCode() == Installer::EXISTS) { - if (!$skip_prompt) { - $this->output->write("\x0D"); - $this->output->writeln(" |- Checking destination... exists"); - - $question = new ConfirmationQuestion(" | '- The package is already installed, do you want to overwrite it? [y|N] ", - false); - $answer = $question_helper->ask($this->input, $this->output, $question); - - if (!$answer) { - $this->output->writeln(" | '- You decided to not overwrite the already installed package."); - - return false; - } - } - } - if (Installer::lastErrorCode() == Installer::IS_LINK) { $this->output->write("\x0D"); $this->output->writeln(" |- Checking destination... symbolic link"); - if ($skip_prompt) { + if ($this->all_yes) { $this->output->writeln(" | '- Skipped automatically."); return false; diff --git a/system/src/Grav/Console/Gpm/SelfupgradeCommand.php b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php index b943789db..9179ae7c7 100644 --- a/system/src/Grav/Console/Gpm/SelfupgradeCommand.php +++ b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php @@ -49,6 +49,9 @@ class SelfupgradeCommand extends ConsoleCommand */ private $upgrader; + protected $all_yes; + protected $overwrite; + /** * */ @@ -69,6 +72,12 @@ protected function configure() InputOption::VALUE_NONE, 'Assumes yes (or best approach) instead of prompting' ) + ->addOption( + 'overwrite', + 'o', + InputOption::VALUE_NONE, + 'Option to overwrite packages if they already exist' + ) ->setDescription("Detects and performs an update of Grav itself when available") ->setHelp('The update command updates Grav itself when a new version is available'); } @@ -79,6 +88,8 @@ protected function configure() protected function serve() { $this->upgrader = new Upgrader($this->input->getOption('force')); + $this->all_yes = $this->input->getOption('all-yes'); + $this->overwrite = $this->input->getOption('overwrite'); $this->displayGPMRelease(); @@ -89,7 +100,6 @@ protected function serve() $release = strftime('%c', strtotime($this->upgrader->getReleaseDate())); if (!$this->upgrader->meetsRequirements()) { - $this->output->writeln(""); $this->output->writeln("ATTENTION:"); $this->output->writeln(" Grav has increased the minimum PHP requirement."); $this->output->writeln(" You are currently running PHP " . PHP_VERSION . ", but PHP " . GRAV_PHP_MIN . " is required."); @@ -100,21 +110,29 @@ protected function serve() exit; } - if (!$this->upgrader->isUpgradable()) { + if (!$this->overwrite && !$this->upgrader->isUpgradable()) { $this->output->writeln("You are already running the latest version of Grav (v" . $local . ") released on " . $release); exit; } + Installer::isValidDestination(GRAV_ROOT . '/system'); + if (Installer::IS_LINK === Installer::lastErrorCode()) { + $this->output->writeln("ATTENTION: Grav is symlinked, cannot upgrade, aborting..."); + $this->output->writeln(''); + $this->output->writeln("You are currently running a symbolically linked Grav v" . $local . ". Latest available is v". $remote . "."); + exit; + } + // not used but preloaded just in case! new ArrayInput([]); $questionHelper = $this->getHelper('question'); - $skipPrompt = $this->input->getOption('all-yes'); + $this->output->writeln("Grav v$remote is now available [release date: $release]."); $this->output->writeln("You are currently using v" . GRAV_VERSION . "."); - if (!$skipPrompt) { + if (!$this->all_yes) { $question = new ConfirmationQuestion("Would you like to read the changelog before proceeding? [y|N] ", false); $answer = $questionHelper->ask($this->input, $this->output, $question); @@ -177,8 +195,8 @@ protected function serve() */ private function download($package) { - $cache_dir = Grav::instance()['locator']->findResource('cache://', true); - $this->tmp = $cache_dir . DS . 'tmp/Grav-' . uniqid(); + $tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true); + $this->tmp = $tmp_dir . '/Grav-' . uniqid(); $output = Response::get($package['download'], [], [$this, 'progress']); Folder::mkdir($this->tmp); diff --git a/system/src/Grav/Console/Gpm/UninstallCommand.php b/system/src/Grav/Console/Gpm/UninstallCommand.php index 3fabe29ab..d9bc2f541 100644 --- a/system/src/Grav/Console/Gpm/UninstallCommand.php +++ b/system/src/Grav/Console/Gpm/UninstallCommand.php @@ -39,6 +39,10 @@ class UninstallCommand extends ConsoleCommand */ protected $tmp; + protected $dependencies= []; + + protected $all_yes; + /** * */ @@ -68,6 +72,8 @@ protected function serve() { $this->gpm = new GPM(); + $this->all_yes = $this->input->getOption('all-yes'); + $packages = array_map('strtolower', $this->input->getArgument('package')); $this->data = ['total' => 0, 'not_found' => []]; @@ -108,15 +114,12 @@ protected function serve() $this->output->writeln(" '- Installation failed or aborted."); $this->output->writeln(''); } else { - $this->output->write(" |- Uninstalling package... "); $uninstall = $this->uninstallPackage($slug, $package); if (!$uninstall) { $this->output->writeln(" '- Uninstallation failed or aborted."); - $this->output->writeln(''); } else { $this->output->writeln(" '- Success! "); - $this->output->writeln(''); } } @@ -156,36 +159,29 @@ private function uninstallPackage($slug, $package, $is_dependency = false) return false; } - $locator = Grav::instance()['locator']; - $path = $locator->findResource($package->package_type . '://' . $slug); - Installer::uninstall($path); - $errorCode = Installer::lastErrorCode(); - - if ($errorCode && $errorCode !== Installer::IS_LINK && $errorCode !== Installer::EXISTS) { - $this->output->write("\x0D"); - // extra white spaces to clear out the buffer properly - $this->output->writeln(" |- Uninstalling package... error "); - $this->output->writeln(" | '- " . Installer::lastErrorMsg()); + if (isset($package->dependencies)) { - return false; - } + $dependencies = $package->dependencies; - $message = Installer::getMessage(); - if ($message) { - $this->output->write("\x0D"); - // extra white spaces to clear out the buffer properly - $this->output->writeln(" |- " . $message); - } + if ($is_dependency) { + foreach ($dependencies as $key => $dependency) { + if (in_array($dependency['name'], $this->dependencies)) { + unset($dependencies[$key]); + } + } + } else { + if (count($dependencies) > 0) { + $this->output->writeln(' `- Dependencies found...'); + $this->output->writeln(''); + } + } - $this->output->write("\x0D"); - // extra white spaces to clear out the buffer properly - $this->output->writeln(" |- Uninstalling package... ok "); + $questionHelper = $this->getHelper('question'); + foreach ($dependencies as $dependency) { - if (isset($package->dependencies)) { - $questionHelper = $this->getHelper('question'); + $this->dependencies[] = $dependency['name']; - foreach($package->dependencies as $dependency) { if (is_array($dependency)) { $dependency = $dependency['name']; } @@ -194,25 +190,59 @@ private function uninstallPackage($slug, $package, $is_dependency = false) } $dependencyPackage = $this->gpm->findPackage($dependency); - $question = new ConfirmationQuestion(" | '- Delete dependency " . $dependency . " too? [y|N] ", false); - $answer = $questionHelper->ask($this->input, $this->output, $question); - if ($answer) { - $this->output->writeln(" | '- You decided to delete " . $dependency . "."); + $dependency_exists = $this->packageExists($dependency, $dependencyPackage); + + if ($dependency_exists == Installer::EXISTS) { + $this->output->writeln("A dependency on " . $dependencyPackage->name . " [v" . $dependencyPackage->version . "] was found"); + + $question = new ConfirmationQuestion(" |- Uninstall " . $dependencyPackage->name . "? [y|N] ", false); + $answer = $this->all_yes ? true : $questionHelper->ask($this->input, $this->output, $question); + + if ($answer) { + $uninstall = $this->uninstallPackage($dependency, $dependencyPackage, true); - $uninstall = $this->uninstallPackage($dependency, $dependencyPackage, true); + if (!$uninstall) { + $this->output->writeln(" '- Uninstallation failed or aborted."); + } else { + $this->output->writeln(" '- Success! "); - if (!$uninstall) { - $this->output->writeln(" '- Uninstallation failed or aborted."); + } $this->output->writeln(''); } else { - $this->output->writeln(" '- Success! "); + $this->output->writeln(" '- You decided not to uninstall " . $dependencyPackage->name . "."); $this->output->writeln(''); } } + } } + + $locator = Grav::instance()['locator']; + $path = $locator->findResource($package->package_type . '://' . $slug); + Installer::uninstall($path); + $errorCode = Installer::lastErrorCode(); + + if ($errorCode && $errorCode !== Installer::IS_LINK && $errorCode !== Installer::EXISTS) { + $this->output->writeln(" |- Uninstalling " . $package->name . " package... error "); + $this->output->writeln(" | '- " . Installer::lastErrorMsg().""); + + return false; + } + + $message = Installer::getMessage(); + if ($message) { + $this->output->writeln(" |- " . $message); + } + + if (!$is_dependency && $this->dependencies) { + $this->output->writeln("Finishing up uninstalling " . $package->name . ""); + } + $this->output->writeln(" |- Uninstalling " . $package->name . " package... ok "); + + + return true; } @@ -225,17 +255,15 @@ private function uninstallPackage($slug, $package, $is_dependency = false) private function checkDestination($slug, $package) { - $path = Grav::instance()['locator']->findResource($package->package_type . '://' . $slug); $questionHelper = $this->getHelper('question'); - $skipPrompt = $this->input->getOption('all-yes'); - Installer::isValidDestination($path); + $exists = $this->packageExists($slug, $package); - if (Installer::lastErrorCode() == Installer::IS_LINK) { + if ($exists == Installer::IS_LINK) { $this->output->write("\x0D"); $this->output->writeln(" |- Checking destination... symbolic link"); - if ($skipPrompt) { + if ($this->all_yes) { $this->output->writeln(" | '- Skipped automatically."); return false; @@ -243,10 +271,10 @@ private function checkDestination($slug, $package) $question = new ConfirmationQuestion(" | '- Destination has been detected as symlink, delete symbolic link first? [y|N] ", false); - $answer = $questionHelper->ask($this->input, $this->output, $question); + $answer = $this->all_yes ? true : $questionHelper->ask($this->input, $this->output, $question); if (!$answer) { - $this->output->writeln(" | '- You decided to not delete the symlink automatically."); + $this->output->writeln(" | '- You decided not to delete the symlink automatically."); return false; } @@ -257,4 +285,18 @@ private function checkDestination($slug, $package) return true; } + + /** + * Check if package exists + * + * @param $slug + * @param $package + * @return int + */ + private function packageExists($slug, $package) + { + $path = Grav::instance()['locator']->findResource($package->package_type . '://' . $slug); + Installer::isValidDestination($path); + return Installer::lastErrorCode(); + } } diff --git a/system/src/Grav/Console/Gpm/UpdateCommand.php b/system/src/Grav/Console/Gpm/UpdateCommand.php index cc9fd7143..6428e90a0 100644 --- a/system/src/Grav/Console/Gpm/UpdateCommand.php +++ b/system/src/Grav/Console/Gpm/UpdateCommand.php @@ -47,6 +47,10 @@ class UpdateCommand extends ConsoleCommand */ protected $gpm; + protected $all_yes; + + protected $overwrite; + /** * */ @@ -73,6 +77,12 @@ protected function configure() InputOption::VALUE_NONE, 'Assumes yes (or best approach) instead of prompting' ) + ->addOption( + 'overwrite', + 'o', + InputOption::VALUE_NONE, + 'Option to overwrite packages if they already exist' + ) ->addOption( 'plugins', 'p', @@ -101,31 +111,40 @@ protected function serve() { $this->gpm = new GPM($this->input->getOption('force')); + $this->all_yes = $this->input->getOption('all-yes'); + $this->overwrite = $this->input->getOption('overwrite'); + $this->displayGPMRelease(); $this->destination = realpath($this->input->getOption('destination')); - $skip_prompt = $this->input->getOption('all-yes'); if (!Installer::isGravInstance($this->destination)) { $this->output->writeln("ERROR: " . Installer::lastErrorMsg()); exit; } - if ($this->input->getOption('plugins') === false and $this->input->getOption('themes') === false) { - $list_type_update = ['plugins' => true, 'themes' => true]; + if ($this->input->getOption('plugins') === false && $this->input->getOption('themes') === false) { + $list_type = ['plugins' => true, 'themes' => true]; } else { - $list_type_update['plugins'] = $this->input->getOption('plugins'); - $list_type_update['themes'] = $this->input->getOption('themes'); + $list_type['plugins'] = $this->input->getOption('plugins'); + $list_type['themes'] = $this->input->getOption('themes'); + } + + if ($this->overwrite) { + $this->data = $this->gpm->getInstallable($list_type); + $description = " can be overwritten"; + } else { + $this->data = $this->gpm->getUpdatable($list_type); + $description = " need updating"; } - $this->data = $this->gpm->getUpdatable($list_type_update); $only_packages = array_map('strtolower', $this->input->getArgument('package')); - if (!$this->data['total']) { + if (!$this->overwrite && !$this->data['total']) { $this->output->writeln("Nothing to update."); exit; } - $this->output->write("Found " . $this->gpm->countInstalled() . " extensions installed of which " . $this->data['total'] . " need updating"); + $this->output->write("Found " . $this->gpm->countInstalled() . " packages installed of which " . $this->data['total'] . "" . $description); $limit_to = $this->userInputPackages($only_packages); @@ -145,19 +164,23 @@ protected function serve() continue; } + if (!$package->available) { + $package->available = $package->version; + } + $this->output->writeln( // index str_pad($index++ + 1, 2, '0', STR_PAD_LEFT) . ". " . // name "" . str_pad($package->name, 15) . " " . // version - "[v" . $package->version . " ➜ v" . $package->available . "]" + "[v" . $package->version . " -> v" . $package->available . "]" ); $slugs[] = $slug; } } - if (!$skip_prompt) { + if (!$this->all_yes) { // prompt to continue $this->output->writeln(""); $questionHelper = $this->getHelper('question'); @@ -165,7 +188,7 @@ protected function serve() $answer = $questionHelper->ask($this->input, $this->output, $question); if (!$answer) { - $this->output->writeln("Update aborted. Exiting..."); + $this->output->writeln("Update aborted. Exiting..."); exit; } } @@ -183,7 +206,7 @@ protected function serve() $command_exec = $install_command->run($args, $this->output); if ($command_exec != 0) { - $this->output->writeln("Error: An error occurred while trying to install the extensions"); + $this->output->writeln("Error: An error occurred while trying to install the packages"); exit; } } @@ -204,7 +227,7 @@ private function userInputPackages($only_packages) foreach ($only_packages as $only_package) { $find = $this->gpm->findPackage($only_package); - if (!$find || !$this->gpm->isUpdatable($find->slug)) { + if (!$find || (!$this->overwrite && !$this->gpm->isUpdatable($find->slug))) { $name = isset($find->slug) ? $find->slug : $only_package; $ignore[$name] = $name; } else { diff --git a/system/src/Grav/Console/TerminalObjects/Table.php b/system/src/Grav/Console/TerminalObjects/Table.php new file mode 100644 index 000000000..7c5dc328b --- /dev/null +++ b/system/src/Grav/Console/TerminalObjects/Table.php @@ -0,0 +1,29 @@ +column_widths = $this->getColumnWidths(); + $this->table_width = $this->getWidth(); + $this->border = $this->getBorder(); + + $this->buildHeaderRow(); + + foreach ($this->data as $key => $columns) { + $this->rows[] = $this->buildRow($columns); + } + + $this->rows[] = $this->border; + + return $this->rows; + } +} diff --git a/tests/unit/Grav/Common/Language/LanguageCodesTest.php b/tests/unit/Grav/Common/Language/LanguageCodesTest.php new file mode 100644 index 000000000..ef6cabc6d --- /dev/null +++ b/tests/unit/Grav/Common/Language/LanguageCodesTest.php @@ -0,0 +1,24 @@ +assertSame('ltr', + LanguageCodes::getOrientation('en')); + $this->assertSame('rtl', + LanguageCodes::getOrientation('ar')); + $this->assertSame('rtl', + LanguageCodes::getOrientation('he')); + $this->assertTrue(LanguageCodes::isRtl('ar')); + $this->assertFalse(LanguageCodes::isRtl('fr')); + + } + +} diff --git a/tests/unit/Grav/Common/Twig/TwigExtensionTest.php b/tests/unit/Grav/Common/Twig/TwigExtensionTest.php index 50b84f553..4bb5a0d65 100644 --- a/tests/unit/Grav/Common/Twig/TwigExtensionTest.php +++ b/tests/unit/Grav/Common/Twig/TwigExtensionTest.php @@ -30,7 +30,7 @@ public function testInflectorFilter() $this->assertSame('camel_cased', $this->twig_ext->inflectorFilter('underscor', 'CamelCased')); $this->assertSame('something-text', $this->twig_ext->inflectorFilter('hyphen', 'Something Text')); $this->assertSame('Something text to read', $this->twig_ext->inflectorFilter('human', 'something_text_to_read')); - $this->assertSame(5, $this->twig_ext->inflectorFilter('month', 181)); + $this->assertSame(6, $this->twig_ext->inflectorFilter('month', 181)); $this->assertSame('10th', $this->twig_ext->inflectorFilter('ordinal', 10)); } @@ -70,12 +70,6 @@ public function testNicetimeFilter() } } - public function testSafeEmailFilter() - { - $this->assertSame('devs@getgrav.org', $this->twig_ext->safeEmailFilter('devs@getgrav.org')); - $this->assertSame('someone@example.com', $this->twig_ext->safeEmailFilter('someone@example.com')); - } - public function testRandomizeFilter() { $array = [1,2,3,4,5]; diff --git a/tests/unit/Grav/Common/UtilsTest.php b/tests/unit/Grav/Common/UtilsTest.php index 5021e690e..4293bd113 100644 --- a/tests/unit/Grav/Common/UtilsTest.php +++ b/tests/unit/Grav/Common/UtilsTest.php @@ -123,16 +123,22 @@ public function testSafeTruncate() public function testTruncateHtml() { - $this->assertEquals('

T…

', Utils::truncateHtml('

This is a string to truncate

', 1)); - $this->assertEquals('

This…

', Utils::truncateHtml('

This is a string to truncate

', 4)); - $this->assertEquals('', Utils::truncateHtml('', 6, true)); - + $this->assertEquals('

T...

', Utils::truncateHtml('

This is a string to truncate

', 1)); + $this->assertEquals('

This...

', Utils::truncateHtml('

This is a string to truncate

', 4)); + $this->assertEquals('

This is a...

', Utils::truncateHtml('

This is a string to truncate

', 10)); + $this->assertEquals('

This is a string to truncate

', Utils::truncateHtml('

This is a string to truncate

', 100)); + $this->assertEquals('', Utils::truncateHtml('', 6)); + $this->assertEquals('
  1. item 1 so...
', Utils::truncateHtml('
  1. item 1 something
  2. item 2 bold
', 10)); } public function testSafeTruncateHtml() { - $this->assertEquals('

This…

', Utils::safeTruncateHtml('

This is a string to truncate

', 1)); - $this->assertEquals('

This…

', Utils::safeTruncateHtml('

This is a string to truncate

', 4)); + $this->assertEquals('

This...

', Utils::safeTruncateHtml('

This is a string to truncate

', 1)); + $this->assertEquals('

This is...

', Utils::safeTruncateHtml('

This is a string to truncate

', 2)); + $this->assertEquals('

This is a string to...

', Utils::safeTruncateHtml('

This is a string to truncate

', 5)); + $this->assertEquals('

This is a string to truncate

', Utils::safeTruncateHtml('

This is a string to truncate

', 20)); + $this->assertEquals('', Utils::safeTruncateHtml('', 6)); + $this->assertEquals('
  1. item 1 something
  2. item 2...
', Utils::safeTruncateHtml('
  1. item 1 something
  2. item 2 bold
', 5)); } public function testGenerateRandomString() diff --git a/tmp/.gitkeep b/tmp/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/user/config/system.yaml b/user/config/system.yaml index 21dd89bf1..bbe2a3a3f 100644 --- a/user/config/system.yaml +++ b/user/config/system.yaml @@ -5,7 +5,8 @@ home: pages: theme: antimatter - markdown_extra: false + markdown: + extra: false process: markdown: true twig: false