diff --git a/.gitignore b/.gitignore index 755814004..197774ee6 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,8 @@ logs/* !logs/.* images/* !images/.* +user/accounts/* +!user/accounts/.* user/data/* !user/data/.* user/plugins/* diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fc9ca4b7..e13155533 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +# v1.0.0 +## 12/11/2015 + +1. [](#new) + * Add new link attributes via markdown media + * Added setters to set state of CSS/JS pipelining + * Added `user/accounts` to `.gitignore` + * Added configurable permissions option for Image cache +1. [](#improved) + * Hungarian translation updated + * Refactored Theme initialization for improved flexibility + * Wrapped security section of account blueprints in an 'super user' authorize check + * Minor performance optimizations + * Updated core page blueprints with markdown preview option + * Added useful cache info output to Debugbar + * Added `iconv` polyfill library used by Symfony 2.8 + * Force lowercase of username in a few places for case sensitive filesystems +1. [](#bugfix) + * Fix for GPM problems "Call to a member function set() on null" + * Fix for individual asset pipeline values not functioning + * Fix `Page::copy()` and `Page::move()` to support multiple moves at once + * Fixed page moving of a page with no content + * Fix for wrong ordering when moving many pages + * Escape root path in page medium files to work with special characters + * Add missing parent constructor to Themes class + * Fix missing file error in `bin/grav sandbox` command + * Fixed changelog differ when upgrading Grav + * Fixed a logic error in `Validation->validate()` + * Make `$container` available in `setup.php` to fix multi-site + # v1.0.0-rc.6 ## 12/01/2015 @@ -78,7 +108,7 @@ 1. [](#new) * New Page collection options! `@self.parent, @self.siblings, @self.descendants` + more - * Whitelist of file types for fallback route functionality (images by default) + * White list of file types for fallback route functionality (images by default) 1. [](#improved) * Assets switched from defines to streams 1. [](#bugfix) diff --git a/README.md b/README.md index 8a405e3dc..18c060dbf 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ What you mainly want to know is that: # License -See [LICENSE](LICENSE) +See [LICENSE](LICENSE.txt) [gitflow-model]: http://nvie.com/posts/a-successful-git-branching-model/ diff --git a/bin/gpm b/bin/gpm index 5a039e597..f9f763624 100755 --- a/bin/gpm +++ b/bin/gpm @@ -40,8 +40,6 @@ if (!function_exists('curl_version')) { $grav = Grav::instance(array('loader' => $autoload)); $grav['config']->init(); $grav['streams']; -$grav['plugins']->init(); -$grav['themes']->init(); $app = new Application('Grav Package Manager', GRAV_VERSION); $app->addCommands(array( diff --git a/composer.json b/composer.json index 86116ed1f..302cabfdc 100644 --- a/composer.json +++ b/composer.json @@ -7,14 +7,15 @@ "license": "MIT", "require": { "php": ">=5.4.0", - "twig/twig": "~1.16", + "twig/twig": "~1.23", "erusev/parsedown-extra": "~0.7", - "symfony/yaml": "~2.7", - "symfony/console": "~2.7", - "symfony/event-dispatcher": "~2.7", - "symfony/var-dumper": "~2.7", - "doctrine/cache": "~1.4", - "filp/whoops": "1.2.*@dev", + "symfony/yaml": "~2.8", + "symfony/console": "~2.8", + "symfony/event-dispatcher": "~2.8", + "symfony/var-dumper": "~2.8", + "symfony/polyfill-iconv": "~1.0", + "doctrine/cache": "~1.5", + "filp/whoops": "1.1.10", "monolog/monolog": "~1.0", "gregwar/image": "~2.0", "ircmaxell/password-compat": "1.0.*", diff --git a/composer.lock b/composer.lock index d19e9a3f8..3483fa781 100644 --- a/composer.lock +++ b/composer.lock @@ -1,23 +1,24 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "b1323e540382de7390663756b3a87de7", + "hash": "09fcc6b4528be7d9c8af68a66e85f0b2", + "content-hash": "69bee250cbc5160401d50cc47c8d6aba", "packages": [ { "name": "doctrine/cache", - "version": "v1.5.1", + "version": "v1.5.2", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "2b9cec5a5e722010cbebc91713d4c11eaa064d5e" + "reference": "47c7128262da274f590ae6f86eb137a7a64e82af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/2b9cec5a5e722010cbebc91713d4c11eaa064d5e", - "reference": "2b9cec5a5e722010cbebc91713d4c11eaa064d5e", + "url": "https://api.github.com/repos/doctrine/cache/zipball/47c7128262da274f590ae6f86eb137a7a64e82af", + "reference": "47c7128262da274f590ae6f86eb137a7a64e82af", "shasum": "" }, "require": { @@ -74,7 +75,7 @@ "cache", "caching" ], - "time": "2015-11-02 18:35:48" + "time": "2015-12-03 10:50:37" }, { "name": "donatj/phpuseragentparser", @@ -212,20 +213,20 @@ }, { "name": "filp/whoops", - "version": "dev-master", + "version": "1.1.10", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "9a393ceb80f7497b6513feb574638e87048fed55" + "reference": "72538eeb70bbfb11964412a3d098d109efd012f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/9a393ceb80f7497b6513feb574638e87048fed55", - "reference": "9a393ceb80f7497b6513feb574638e87048fed55", + "url": "https://api.github.com/repos/filp/whoops/zipball/72538eeb70bbfb11964412a3d098d109efd012f7", + "reference": "72538eeb70bbfb11964412a3d098d109efd012f7", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=5.3.0" }, "require-dev": { "mockery/mockery": "0.9.*" @@ -266,7 +267,7 @@ "whoops", "zf2" ], - "time": "2015-09-27 09:47:06" + "time": "2015-06-29 05:42:04" }, { "name": "gregwar/cache", @@ -403,25 +404,25 @@ }, { "name": "maximebf/debugbar", - "version": "v1.10.5", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "30e53e8a28284b69dd223c9f5ee8957befd72636" + "reference": "07741d84d39d10f00551c94284cdefcc69703e77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/30e53e8a28284b69dd223c9f5ee8957befd72636", - "reference": "30e53e8a28284b69dd223c9f5ee8957befd72636", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/07741d84d39d10f00551c94284cdefcc69703e77", + "reference": "07741d84d39d10f00551c94284cdefcc69703e77", "shasum": "" }, "require": { "php": ">=5.3.0", - "psr/log": "~1.0", - "symfony/var-dumper": "~2.6" + "psr/log": "^1.0", + "symfony/var-dumper": "^2.6|^3.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "^4.0|^5.0" }, "suggest": { "kriswallsmith/assetic": "The best way to manage assets", @@ -431,12 +432,12 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.10-dev" + "dev-master": "1.11-dev" } }, "autoload": { - "psr-0": { - "DebugBar": "src/" + "psr-4": { + "DebugBar\\": "src/DebugBar/" } }, "notification-url": "https://packagist.org/downloads/", @@ -448,14 +449,19 @@ "name": "Maxime Bouroumeau-Fuseau", "email": "maxime.bouroumeau@gmail.com", "homepage": "http://maximebf.com" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" } ], "description": "Debug bar in the browser for php application", "homepage": "https://github.com/maximebf/php-debugbar", "keywords": [ - "debug" + "debug", + "debugbar" ], - "time": "2015-10-19 20:35:12" + "time": "2015-12-10 09:50:24" }, { "name": "monolog/monolog", @@ -714,25 +720,26 @@ }, { "name": "symfony/console", - "version": "v2.7.7", + "version": "v2.8.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "16bb1cb86df43c90931df65f529e7ebd79636750" + "reference": "d232bfc100dfd32b18ccbcab4bcc8f28697b7e41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/16bb1cb86df43c90931df65f529e7ebd79636750", - "reference": "16bb1cb86df43c90931df65f529e7ebd79636750", + "url": "https://api.github.com/repos/symfony/console/zipball/d232bfc100dfd32b18ccbcab4bcc8f28697b7e41", + "reference": "d232bfc100dfd32b18ccbcab4bcc8f28697b7e41", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.3.9", + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { "psr/log": "~1.0", - "symfony/event-dispatcher": "~2.1", - "symfony/process": "~2.1" + "symfony/event-dispatcher": "~2.1|~3.0.0", + "symfony/process": "~2.1|~3.0.0" }, "suggest": { "psr/log": "For using the console logger", @@ -742,7 +749,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } }, "autoload": { @@ -769,20 +776,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2015-11-18 09:54:26" + "time": "2015-11-30 12:35:10" }, { "name": "symfony/event-dispatcher", - "version": "v2.7.7", + "version": "v2.8.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "7e2f9c31645680026c2372edf66f863fc7757af5" + "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7e2f9c31645680026c2372edf66f863fc7757af5", - "reference": "7e2f9c31645680026c2372edf66f863fc7757af5", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a5eb815363c0388e83247e7e9853e5dbc14999cc", + "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc", "shasum": "" }, "require": { @@ -790,10 +797,10 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~2.0,>=2.0.5", - "symfony/dependency-injection": "~2.6", - "symfony/expression-language": "~2.6", - "symfony/stopwatch": "~2.3" + "symfony/config": "~2.0,>=2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" }, "suggest": { "symfony/dependency-injection": "", @@ -802,7 +809,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } }, "autoload": { @@ -829,24 +836,140 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2015-10-30 20:10:21" + "time": "2015-10-30 20:15:42" }, { - "name": "symfony/var-dumper", - "version": "v2.7.7", + "name": "symfony/polyfill-iconv", + "version": "v1.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "72bcb27411780eaee9469729aace73c0d46fb2b8" + "url": "https://github.com/symfony/polyfill-iconv.git", + "reference": "21a18998764e569c1675efc7191887130b319605" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/72bcb27411780eaee9469729aace73c0d46fb2b8", - "reference": "72bcb27411780eaee9469729aace73c0d46fb2b8", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/21a18998764e569c1675efc7191887130b319605", + "reference": "21a18998764e569c1675efc7191887130b319605", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Iconv\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Iconv extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "iconv", + "polyfill", + "portable", + "shim" + ], + "time": "2015-11-04 20:28:58" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0b6a8940385311a24e060ec1fe35680e17c74497" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0b6a8940385311a24e060ec1fe35680e17c74497", + "reference": "0b6a8940385311a24e060ec1fe35680e17c74497", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2015-11-04 20:28:58" + }, + { + "name": "symfony/var-dumper", + "version": "v2.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "e6f3855005f2bfad7d7e72431d374a6478893fe3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e6f3855005f2bfad7d7e72431d374a6478893fe3", + "reference": "e6f3855005f2bfad7d7e72431d374a6478893fe3", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "twig/twig": "~1.20|~2.0" }, "suggest": { "ext-symfony_debug": "" @@ -854,7 +977,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } }, "autoload": { @@ -888,20 +1011,20 @@ "debug", "dump" ], - "time": "2015-11-18 13:41:01" + "time": "2015-11-18 13:45:00" }, { "name": "symfony/yaml", - "version": "v2.7.7", + "version": "v2.8.0", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "4cfcd7a9fceba662b3c036b7d9a91f6197af046c" + "reference": "f79824187de95064a2f5038904c4d7f0227fedb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/4cfcd7a9fceba662b3c036b7d9a91f6197af046c", - "reference": "4cfcd7a9fceba662b3c036b7d9a91f6197af046c", + "url": "https://api.github.com/repos/symfony/yaml/zipball/f79824187de95064a2f5038904c4d7f0227fedb5", + "reference": "f79824187de95064a2f5038904c4d7f0227fedb5", "shasum": "" }, "require": { @@ -910,7 +1033,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } }, "autoload": { @@ -937,7 +1060,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2015-11-18 13:41:01" + "time": "2015-11-30 12:35:10" }, { "name": "twig/twig", @@ -1004,9 +1127,7 @@ "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "filp/whoops": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml index 78ac4d5ca..63b13ac92 100644 --- a/system/blueprints/config/system.yaml +++ b/system/blueprints/config/system.yaml @@ -516,6 +516,17 @@ form: validate: type: bool + twig.umask_fix: + type: toggle + label: PLUGIN_ADMIN.TWIG_UMASK_FIX + help: PLUGIN_ADMIN.TWIG_UMASK_FIX_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + assets: type: section title: PLUGIN_ADMIN.ASSETS @@ -688,6 +699,17 @@ form: validate: type: bool + images.cache_perms: + type: select + size: small + label: PLUGIN_ADMIN.CACHE_PERMS + help: PLUGIN_ADMIN.CACHE_PERMS_HELP + highlight: '0755' + options: + 1: '0755' + 0: '0775' + + images.debug: type: toggle label: PLUGIN_ADMIN.IMAGES_DEBUG @@ -745,13 +767,14 @@ form: fields: session.enabled: - type: toggle + type: hidden label: PLUGIN_ADMIN.ENABLED help: PLUGIN_ADMIN.SESSION_ENABLED_HELP highlight: 1 options: 1: PLUGIN_ADMIN.YES 0: PLUGIN_ADMIN.NO + default: true validate: type: bool diff --git a/system/blueprints/pages/default.yaml b/system/blueprints/pages/default.yaml index 7be8a6fdd..d421bbd62 100644 --- a/system/blueprints/pages/default.yaml +++ b/system/blueprints/pages/default.yaml @@ -29,6 +29,7 @@ form: content: type: markdown + showPreview: true validate: type: textarea diff --git a/system/blueprints/pages/modular_raw.yaml b/system/blueprints/pages/modular_raw.yaml index 430881712..393cde5fd 100644 --- a/system/blueprints/pages/modular_raw.yaml +++ b/system/blueprints/pages/modular_raw.yaml @@ -25,6 +25,7 @@ form: content: type: markdown + showPreview: true uploads: type: pagemedia diff --git a/system/blueprints/pages/raw.yaml b/system/blueprints/pages/raw.yaml index d26f05063..bed82d593 100644 --- a/system/blueprints/pages/raw.yaml +++ b/system/blueprints/pages/raw.yaml @@ -25,6 +25,7 @@ form: content: type: markdown + showPreview: true uploads: type: pagemedia diff --git a/system/blueprints/user/account.yaml b/system/blueprints/user/account.yaml index 83674c721..81a659d0c 100644 --- a/system/blueprints/user/account.yaml +++ b/system/blueprints/user/account.yaml @@ -54,26 +54,32 @@ form: default: 'en' help: PLUGIN_ADMIN.LANGUAGE_HELP - groups: - type: selectize - size: large - label: PLUGIN_ADMIN.GROUPS - '@data-options': '\Grav\User\Groups::groups' - classes: fancy - help: PLUGIN_ADMIN.GROUPS_HELP - validate: - type: commalist + security: + title: Security + type: section + security: admin.super - access.admin: - type: array - label: PLUGIN_ADMIN.ADMIN_ACCESS - multiple: false - validate: - type: array + fields: + groups: + type: selectize + size: large + label: PLUGIN_ADMIN.GROUPS + '@data-options': '\Grav\User\Groups::groups' + classes: fancy + help: PLUGIN_ADMIN.GROUPS_HELP + validate: + type: commalist - access.site: - type: array - label: PLUGIN_ADMIN.SITE_ACCESS - multiple: false - validate: - type: array \ No newline at end of file + access.admin: + type: array + label: PLUGIN_ADMIN.ADMIN_ACCESS + multiple: false + validate: + type: array + + access.site: + type: array + label: PLUGIN_ADMIN.SITE_ACCESS + multiple: false + validate: + type: array \ No newline at end of file diff --git a/system/config/system.yaml b/system/config/system.yaml index f64620cbf..652524902 100644 --- a/system/config/system.yaml +++ b/system/config/system.yaml @@ -97,6 +97,7 @@ debugger: images: default_image_quality: 85 # Default image quality to use when resampling images (85%) cache_all: false # Cache all image by default + cache_perms: 0755 # Default cache folder perms. Usually 0755 or 0775 depending on setup debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example media: diff --git a/system/defines.php b/system/defines.php index 2e0e01442..df48dff23 100644 --- a/system/defines.php +++ b/system/defines.php @@ -2,7 +2,7 @@ // Some standard defines define('GRAV', true); -define('GRAV_VERSION', '1.0.0-rc.6'); +define('GRAV_VERSION', '1.0.0'); define('DS', '/'); // Directories and Paths diff --git a/system/languages/hu.yaml b/system/languages/hu.yaml index 71c0ee91e..e807896eb 100644 --- a/system/languages/hu.yaml +++ b/system/languages/hu.yaml @@ -50,3 +50,4 @@ NICETIME: FORM: VALIDATION_FAIL: A validáció hibát talált: INVALID_INPUT: Az itt megadott érték érvénytelen: + MISSING_REQUIRED_FIELD: Ez a kötelező mező nincs kitöltve: diff --git a/system/src/Grav/Common/Assets.php b/system/src/Grav/Common/Assets.php index 16822b990..be6b81f7a 100644 --- a/system/src/Grav/Common/Assets.php +++ b/system/src/Grav/Common/Assets.php @@ -203,7 +203,7 @@ class Assets * * @return $this */ - public function add($asset, $priority = null, $pipeline = null) + public function add($asset, $priority = null, $pipeline = true) { // More than one asset if (is_array($asset)) { @@ -243,7 +243,7 @@ class Assets * * @return $this */ - public function addCss($asset, $priority = null, $pipeline = null, $group = null) + public function addCss($asset, $priority = null, $pipeline = true, $group = null) { if (is_array($asset)) { foreach ($asset as $a) { @@ -259,16 +259,20 @@ class Assets $asset = $this->buildLocalLink($asset); } + // Check for existence + if ($asset === false) { + return $this; + } + $data = [ 'asset' => $asset, 'priority' => intval($priority ?: 10), 'order' => count($this->css), - 'pipeline' => $pipeline ?: true, + 'pipeline' => (bool) $pipeline, 'group' => $group ?: 'head' ]; // check for dynamic array and merge with defaults - $count_args = func_num_args(); if (func_num_args() == 2) { $dynamic_arg = func_get_arg(1); if (is_array($dynamic_arg)) { @@ -297,7 +301,7 @@ class Assets * @param string $group name of the group * @return $this */ - public function addJs($asset, $priority = null, $pipeline = null, $loading = null, $group = null) + public function addJs($asset, $priority = null, $pipeline = true, $loading = null, $group = null) { if (is_array($asset)) { foreach ($asset as $a) { @@ -313,17 +317,21 @@ class Assets $asset = $this->buildLocalLink($asset); } + // Check for existence + if ($asset === false) { + return $this; + } + $data = [ 'asset' => $asset, 'priority' => intval($priority ?: 10), 'order' => count($this->js), - 'pipeline' => $pipeline ?: true, + 'pipeline' => (bool) $pipeline, 'loading' => $loading ?: '', 'group' => $group ?: 'head' ]; // check for dynamic array and merge with defaults - $count_args = func_num_args(); if (func_num_args() == 2) { $dynamic_arg = func_get_arg(1); if (is_array($dynamic_arg)) { @@ -351,7 +359,7 @@ class Assets * * @return \Grav\Common\Assets */ - public function addAsyncJs($asset, $priority = null, $pipeline = null, $group = null) + public function addAsyncJs($asset, $priority = null, $pipeline = true, $group = null) { return $this->addJs($asset, $priority, $pipeline, 'async', $group); } @@ -368,7 +376,7 @@ class Assets * * @return \Grav\Common\Assets */ - public function addDeferJs($asset, $priority = null, $pipeline = null, $group = null) + public function addDeferJs($asset, $priority = null, $pipeline = true, $group = null) { return $this->addJs($asset, $priority, $pipeline, 'defer', $group); } @@ -1124,6 +1132,36 @@ class Assets return $this->addDir($directory, self::JS_REGEX); } + /** + * Sets the state of CSS Pipeline + * + * @param boolean $value + */ + public function setCssPipeline($value) + { + $this->css_pipeline = (bool) $value; + } + + /** + * Sets the state of JS Pipeline + * + * @param boolean $value + */ + public function setJsPipeline($value) + { + $this->js_pipeline = (bool) $value; + } + + /** + * Explicitly set's a timestamp for assets + * + * @param $value + */ + public function setTimestamp($value) + { + $this->timestamp = '?'.$value; + } + public function __toString() { return ''; diff --git a/system/src/Grav/Common/Cache.php b/system/src/Grav/Common/Cache.php index 6ba75f4a3..da0825ed9 100644 --- a/system/src/Grav/Common/Cache.php +++ b/system/src/Grav/Common/Cache.php @@ -38,6 +38,8 @@ class Cache extends Getters */ protected $driver; + protected $driver_name; + /** * @var bool */ @@ -110,6 +112,10 @@ class Cache extends Getters // Set the cache namespace to our unique key $this->driver->setNamespace($this->key); + + // Dump Cache state + $grav['debugger']->addMessage('Cache: [' . ($this->enabled ? 'true' : 'false') . '] Driver: [' . $this->driver_name . ']'); + } /** @@ -136,6 +142,8 @@ class Cache extends Getters $driver_name = $setting; } + $this->driver_name = $driver_name; + switch ($driver_name) { case 'apc': $driver = new \Doctrine\Common\Cache\ApcCache(); diff --git a/system/src/Grav/Common/Config/Setup.php b/system/src/Grav/Common/Config/Setup.php index 90178d6b2..b345426c1 100644 --- a/system/src/Grav/Common/Config/Setup.php +++ b/system/src/Grav/Common/Config/Setup.php @@ -113,8 +113,13 @@ class Setup extends Data ], ]; - public function __construct($environment = 'localhost') + public function __construct($container) { + $environment = $container['uri']->environment(); + if (!$environment) { + $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. $file = GRAV_ROOT . '/setup.php'; diff --git a/system/src/Grav/Common/Data/Validation.php b/system/src/Grav/Common/Data/Validation.php index 9cebd0d76..9e62802b1 100644 --- a/system/src/Grav/Common/Data/Validation.php +++ b/system/src/Grav/Common/Data/Validation.php @@ -32,7 +32,7 @@ class Validation } // 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']))) { + 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; } @@ -43,7 +43,7 @@ class Validation $type = (string) isset($field['validate']['type']) ? $field['validate']['type'] : $field['type']; $method = 'type'.strtr($type, '-', '_'); $name = ucfirst(isset($field['label']) ? $field['label'] : $field['name']); - $message = (string) isset($field['validate']['message']) ? $field['validate']['message'] : $language->translate('FORM.INVALID_INPUT', null, true) . ' "' . $language->translate($name) . '"'; + $message = (string) isset($field['validate']['message']) ? $language->translate($field['validate']['message']) : $language->translate('FORM.INVALID_INPUT', null, true) . ' "' . $language->translate($name) . '"'; if (method_exists(__CLASS__, $method)) { $success = self::$method($value, $validate, $field); @@ -84,7 +84,7 @@ class Validation } // 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']))) { + 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; } @@ -620,7 +620,7 @@ class Validation if (is_string($value)) { $value = trim($value); } - + return (bool) $params !== true || !empty($value); } diff --git a/system/src/Grav/Common/Filesystem/Folder.php b/system/src/Grav/Common/Filesystem/Folder.php index 62d610b65..81d610898 100644 --- a/system/src/Grav/Common/Filesystem/Folder.php +++ b/system/src/Grav/Common/Filesystem/Folder.php @@ -274,6 +274,11 @@ abstract class Folder throw new \RuntimeException('Cannot move non-existing folder.'); } + // Don't do anything if the source is the same as the new target + if ($source == $target) { + return; + } + // Make sure that path to the target exists before moving. self::create(dirname($target)); diff --git a/system/src/Grav/Common/GPM/Remote/Grav.php b/system/src/Grav/Common/GPM/Remote/Grav.php index f8148a526..152b8eda7 100644 --- a/system/src/Grav/Common/GPM/Remote/Grav.php +++ b/system/src/Grav/Common/GPM/Remote/Grav.php @@ -55,7 +55,7 @@ class Grav extends AbstractPackageCollection $diffLog = []; foreach ($this->data['changelog'] as $version => $changelog) { - preg_match("/[\d\.]+/", $version, $cleanVersion); + preg_match("/[\w-\.]+/", $version, $cleanVersion); if (!$cleanVersion || version_compare($diff, $cleanVersion[0], ">=")) { continue; } diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php index eefef19a1..bb987f751 100644 --- a/system/src/Grav/Common/Grav.php +++ b/system/src/Grav/Common/Grav.php @@ -230,7 +230,6 @@ class Grav extends Container $debugger->startTimer('themes', 'Themes'); $this['themes']->init(); - $this->fireEvent('onThemeInitialized'); $debugger->stopTimer('themes'); $task = $this['task']; diff --git a/system/src/Grav/Common/Iterator.php b/system/src/Grav/Common/Iterator.php index d23f42910..287b925ae 100644 --- a/system/src/Grav/Common/Iterator.php +++ b/system/src/Grav/Common/Iterator.php @@ -215,4 +215,26 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable return $this; } + + + /** + * Sorts elements from the list and returns a copy of the list in the proper order + * + * @param callable|null $callback + * + * @param bool $desc + * + * @return $this|array + * @internal param bool $asc + * + */ + public function sort(callable $callback = null, $desc = false) + { + if (!$callback || !is_callable($callback)) { return $this; } + + $items = $this->items; + uasort($items, $callback); + + return !$desc ? $items : array_reverse($items, true); + } } diff --git a/system/src/Grav/Common/Language/Language.php b/system/src/Grav/Common/Language/Language.php index 5dfe343f2..6a9e3f18c 100644 --- a/system/src/Grav/Common/Language/Language.php +++ b/system/src/Grav/Common/Language/Language.php @@ -352,7 +352,7 @@ class Language if ($this->config->get('system.languages.translations_fallback', true)) { $languages = $this->getFallbackLanguages(); } else { - $languages = (array)$this->getDefault(); + $languages = (array)$this->getLanguage(); } } } else { diff --git a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php index fd6dd623f..5c075dcb5 100644 --- a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php +++ b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php @@ -204,6 +204,48 @@ trait ParsedownGravTrait if (isset($excerpt['element']['attributes']['href'])) { $url = parse_url(htmlspecialchars_decode($excerpt['element']['attributes']['href'])); + // if there is a query, then parse it and build action calls + if (isset($url['query'])) { + $actions = array_reduce(explode('&', $url['query']), function ($carry, $item) { + $parts = explode('=', $item, 2); + $value = isset($parts[1]) ? $parts[1] : null; + $carry[$parts[0]] = $value; + + return $carry; + }, []); + + // valid attributes supported + $valid_attributes = ['rel', 'target', 'id', 'class', 'classes']; + + // Unless told to not process, go through actions + if (array_key_exists('noprocess', $actions)) { + unset($actions['noprocess']); + } else { + // loop through actions for the image and call them + foreach ($actions as $attrib => $value) { + $key = $attrib; + + if (in_array($attrib, $valid_attributes)) { + // support both class and classes + if ($attrib == 'classes') { + $attrib = 'class'; + } + $excerpt['element']['attributes'][$attrib] = $value; + unset($actions[$key]); + } + } + } + + + $url['query']= http_build_query($actions, null, '&', PHP_QUERY_RFC3986); + } + + // if no query elements left, unset query + if (empty($url['query'])) { + unset ($url['query']); + } + + // if there is no scheme, the file is local if (!isset($url['scheme']) && (count($url) > 0)) { // convert the URl is required diff --git a/system/src/Grav/Common/Page/Medium/ImageFile.php b/system/src/Grav/Common/Page/Medium/ImageFile.php index 8e59f6d7b..c02922b30 100644 --- a/system/src/Grav/Common/Page/Medium/ImageFile.php +++ b/system/src/Grav/Common/Page/Medium/ImageFile.php @@ -55,6 +55,7 @@ class ImageFile extends Image $cacheFile .= $this->prettyName; } + $cacheFile .= '.'.$type; // If the files does not exists, save it @@ -79,7 +80,8 @@ class ImageFile extends Image // Asking the cache for the cacheFile try { - $file = $this->cache->getOrCreateFile($cacheFile, $conditions, $generate, $actual); + $perms = octdec(self::getGrav()['config']->get('system.images.cache_perms', '0755')); + $file = $this->cache->setDirectoryMode($perms)->getOrCreateFile($cacheFile, $conditions, $generate, $actual); } catch (GenerationError $e) { $file = $e->getNewFile(); } diff --git a/system/src/Grav/Common/Page/Medium/ImageMedium.php b/system/src/Grav/Common/Page/Medium/ImageMedium.php index 85e3d63b6..3db039643 100644 --- a/system/src/Grav/Common/Page/Medium/ImageMedium.php +++ b/system/src/Grav/Common/Page/Medium/ImageMedium.php @@ -135,7 +135,7 @@ class ImageMedium extends Medium */ public function url($reset = true) { - $output = preg_replace('|^' . GRAV_ROOT . '|', '', $this->saveImage()); + $output = preg_replace('|^' . preg_quote(GRAV_ROOT) . '|', '', $this->saveImage()); if ($reset) { $this->reset(); diff --git a/system/src/Grav/Common/Page/Medium/Medium.php b/system/src/Grav/Common/Page/Medium/Medium.php index a5e4001a7..714eeb5e8 100644 --- a/system/src/Grav/Common/Page/Medium/Medium.php +++ b/system/src/Grav/Common/Page/Medium/Medium.php @@ -137,7 +137,7 @@ class Medium extends Data implements RenderableInterface */ public function url($reset = true) { - $output = preg_replace('|^' . GRAV_ROOT . '|', '', $this->get('filepath')); + $output = preg_replace('|^' . preg_quote(GRAV_ROOT) . '|', '', $this->get('filepath')); if ($reset) { $this->reset(); diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php index 50b3fa83f..a6e4282e9 100644 --- a/system/src/Grav/Common/Page/Page.php +++ b/system/src/Grav/Common/Page/Page.php @@ -5,6 +5,7 @@ use Exception; use Grav\Common\Filesystem\Folder; use Grav\Common\Config\Config; use Grav\Common\GravTrait; +use Grav\Common\Language\Language; use Grav\Common\Utils; use Grav\Common\Cache; use Grav\Common\Twig; @@ -381,7 +382,7 @@ class Page */ public function modifyHeader($key, $value) { - $this->header->$key = $value; + $this->header->{$key} = $value; } /** @@ -392,8 +393,7 @@ class Page */ public function summary($size = null) { - /** @var Config $config */ - $config = self::getGrav()['config']->get('site.summary'); + $config = (array) self::getGrav()['config']->get('site.summary'); if (isset($this->header->summary)) { $config = array_merge($config, $this->header->summary); } @@ -728,27 +728,30 @@ class Page * You need to call $this->save() in order to perform the move. * * @param Page $parent New parent page. - * @return Page + * @return $this */ public function move(Page $parent) { - $clone = clone $this; - $clone->_action = 'move'; - $clone->_original = $this; - $clone->parent($parent); - $clone->id(time().md5($clone->filePath())); + if (!$this->_original) { + $clone = clone $this; + $this->_original = $clone; + } + + $this->_action = 'move'; + $this->parent($parent); + $this->id(time().md5($this->filePath())); if ($parent->path()) { - $clone->path($parent->path() . '/' . $clone->folder()); + $this->path($parent->path() . '/' . $this->folder()); } if ($parent->route()) { - $clone->route($parent->route() . '/'. $clone->slug()); + $this->route($parent->route() . '/'. $this->slug()); } else { - $clone->route(self::getGrav()['pages']->root()->route() . '/'. $clone->slug()); + $this->route(self::getGrav()['pages']->root()->route() . '/'. $this->slug()); } - return $clone; + return $this; } /** @@ -758,14 +761,14 @@ class Page * You need to call $this->save() in order to perform the move. * * @param Page $parent New parent page. - * @return Page + * @return $this */ public function copy($parent) { - $clone = $this->move($parent); - $clone->_action = 'copy'; + $this->move($parent); + $this->_action = 'copy'; - return $clone; + return $this; } /** @@ -826,7 +829,7 @@ class Page $blueprints = $this->blueprints(); $values = $blueprints->filter($this->toArray()); if ($values && isset($values['header'])) { - $this->header($values['header']); + $this->header($values['header']); } } @@ -1805,11 +1808,13 @@ class Page if (isset($routes[$uri_path])) { $child_page = $pages->dispatch($uri->route())->parent(); - if ($child_page) while (!$child_page->root()) { - if ($this->path() == $child_page->path()) { - return true; + if ($child_page) { + while (!$child_page->root()) { + if ($this->path() == $child_page->path()) { + return true; + } + $child_page = $child_page->parent(); } - $child_page = $child_page->parent(); } } @@ -1843,7 +1848,7 @@ class Page /** * Helper method to return a page. * - * @param string $url the url of the page + * @param string $url the url of the page * @param bool $all * * @return \Grav\Common\Page\Page page you were looking for if it exists @@ -1951,7 +1956,7 @@ class Page * @return mixed * @internal */ - protected function evaluate($value) + public function evaluate($value) { // Parse command. if (is_string($value)) { @@ -2141,7 +2146,7 @@ class Page */ protected function doRelocation($reorder) { - if (empty($this->_original) ) { + if (!$this->_original) { return; } @@ -2182,7 +2187,7 @@ class Page // Handle all the other pages. $page = $pages->get($path); - if ($page && $page->exists() && $page->order() != $order+1) { + if ($page && $page->exists() && !$page->_action && $page->order() != $order+1) { $page = $page->move($parent); $page->order($order+1); $page->save(false); @@ -2190,11 +2195,12 @@ class Page } } } - if ($this->_action == 'move' && $this->_original->exists()) { - Folder::move($this->_original->path(), $this->path()); - } - if ($this->_action == 'copy' && $this->_original->exists()) { - Folder::copy($this->_original->path(), $this->path()); + if (is_dir($this->_original->path())) { + if ($this->_action == 'move') { + Folder::move($this->_original->path(), $this->path()); + } elseif ($this->_action == 'copy') { + Folder::copy($this->_original->path(), $this->path()); + } } if ($this->name() != $this->_original->name()) { @@ -2204,7 +2210,6 @@ class Page } } - $this->_action = null; $this->_original = null; } diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php index efe7ef1c0..7193db791 100644 --- a/system/src/Grav/Common/Page/Pages.php +++ b/system/src/Grav/Common/Page/Pages.php @@ -237,7 +237,7 @@ class Pages /** * Get a page instance. * - * @param string $path + * @param string $path The filesystem full path of the page * @return Page * @throws \Exception */ @@ -264,7 +264,7 @@ class Pages /** * Dispatch URI to a page. * - * @param $url + * @param string $url The relative URL of the page * @param bool $all * @return Page|null */ diff --git a/system/src/Grav/Common/Plugins.php b/system/src/Grav/Common/Plugins.php index 84844804c..de479e138 100644 --- a/system/src/Grav/Common/Plugins.php +++ b/system/src/Grav/Common/Plugins.php @@ -104,8 +104,12 @@ class Plugins extends Iterator continue; } - $type = $directory->getBasename(); - $list[$type] = self::get($type); + $plugin = $directory->getBasename(); + $result = self::get($plugin); + + if ($result) { + $list[$plugin] = $result; + } } } ksort($list); @@ -120,9 +124,9 @@ class Plugins extends Iterator $blueprint->name = $name; // Load default configuration. - $file = CompiledYamlFile::instance("plugins://{$name}/{$name}.yaml"); + $file = CompiledYamlFile::instance("plugins://{$name}/{$name}" . YAML_EXT); - // ensure the plugin exists physically + // ensure this is a valid plugin if (!$file->exists()) { return null; } diff --git a/system/src/Grav/Common/Service/ConfigServiceProvider.php b/system/src/Grav/Common/Service/ConfigServiceProvider.php index febdcd6a7..9efbd7465 100644 --- a/system/src/Grav/Common/Service/ConfigServiceProvider.php +++ b/system/src/Grav/Common/Service/ConfigServiceProvider.php @@ -40,7 +40,7 @@ class ConfigServiceProvider implements ServiceProviderInterface public static function setup(Container $container) { - return new Setup($container['uri']->environment()); + return new Setup($container); } public static function blueprints(Container $container) diff --git a/system/src/Grav/Common/Themes.php b/system/src/Grav/Common/Themes.php index 4c5748be6..2f020f7a4 100644 --- a/system/src/Grav/Common/Themes.php +++ b/system/src/Grav/Common/Themes.php @@ -25,6 +25,8 @@ class Themes extends Iterator public function __construct(Grav $grav) { + parent::__construct(); + $this->grav = $grav; $this->config = $grav['config']; @@ -34,13 +36,18 @@ class Themes extends Iterator public function init() { - /** @var EventDispatcher $events */ - $events = $this->grav['events']; - /** @var Themes $themes */ $themes = $this->grav['themes']; $themes->configure(); + $this->initTheme(); + } + + public function initTheme() + { + /** @var Themes $themes */ + $themes = $this->grav['themes']; + try { $instance = $themes->load(); } catch (\InvalidArgumentException $e) { @@ -48,10 +55,15 @@ class Themes extends Iterator } if ($instance instanceof EventSubscriberInterface) { + /** @var EventDispatcher $events */ + $events = $this->grav['events']; + $events->addSubscriber($instance); } $this->grav['theme'] = $instance; + + $this->grav->fireEvent('onThemeInitialized'); } /** @@ -74,8 +86,12 @@ class Themes extends Iterator continue; } - $type = $directory->getBasename(); - $list[$type] = self::get($type); + $theme = $directory->getBasename(); + $result = self::get($theme); + + if ($result) { + $list[$theme] = $result; + } } } ksort($list); @@ -100,14 +116,20 @@ class Themes extends Iterator $blueprint = $blueprints->get("{$name}/blueprints"); $blueprint->name = $name; + // Load default configuration. + $file = CompiledYamlFile::instance("themes://{$name}/{$name}" . YAML_EXT); + + // ensure this is a valid theme + if (!$file->exists()) { + return null; + } + // Find thumbnail. $thumb = "themes://{$name}/thumbnail.jpg"; if ($path = $this->grav['locator']->findResource($thumb, false)) { $blueprint->set('thumbnail', $this->grav['base_url'] . '/' . $path); } - // Load default configuration. - $file = CompiledYamlFile::instance("themes://{$name}/{$name}" . YAML_EXT); $obj = new Data($file->content(), $blueprint); // Override with user configuration. diff --git a/system/src/Grav/Common/User/User.php b/system/src/Grav/Common/User/User.php index 9caad3741..4ba6a20c4 100644 --- a/system/src/Grav/Common/User/User.php +++ b/system/src/Grav/Common/User/User.php @@ -32,6 +32,9 @@ class User extends Data { $locator = self::getGrav()['locator']; + // force lowercase of username + $username = strtolower($username); + $blueprints = new Blueprints('blueprints://'); $blueprint = $blueprints->get('user/account'); $file_path = $locator->findResource('account://' . $username . YAML_EXT); diff --git a/system/src/Grav/Console/Cli/SandboxCommand.php b/system/src/Grav/Console/Cli/SandboxCommand.php index a77bc9720..3b1142646 100644 --- a/system/src/Grav/Console/Cli/SandboxCommand.php +++ b/system/src/Grav/Console/Cli/SandboxCommand.php @@ -48,7 +48,7 @@ class SandboxCommand extends ConsoleCommand '/.editorconfig' => '/.editorconfig', '/.gitignore' => '/.gitignore', '/CHANGELOG.md' => '/CHANGELOG.md', - '/LICENSE' => '/LICENSE', + '/LICENSE.txt' => '/LICENSE.txt', '/README.md' => '/README.md', '/index.php' => '/index.php', '/composer.json' => '/composer.json', diff --git a/system/src/Grav/Console/Gpm/IndexCommand.php b/system/src/Grav/Console/Gpm/IndexCommand.php index b75c55d59..70a262953 100644 --- a/system/src/Grav/Console/Gpm/IndexCommand.php +++ b/system/src/Grav/Console/Gpm/IndexCommand.php @@ -21,6 +21,16 @@ class IndexCommand extends ConsoleCommand */ protected $gpm; + /** + * @var + */ + protected $options; + + /** + * @var array + */ + protected $sortKeys = ['name', 'slug', 'author', 'date']; + /** * */ @@ -34,6 +44,43 @@ class IndexCommand extends ConsoleCommand InputOption::VALUE_NONE, 'Force re-fetching the data from remote' ) + ->addOption( + 'filter', + 'F', + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'Allows to limit the results based on one or multiple filters input. This can be either portion of a name/slug or a regex' + ) + ->addOption( + 'themes-only', + 'T', + InputOption::VALUE_NONE, + 'Filters the results to only Themes' + ) + ->addOption( + 'plugins-only', + 'P', + InputOption::VALUE_NONE, + 'Filters the results to only Plugins' + ) + ->addOption( + 'updates-only', + 'U', + InputOption::VALUE_NONE, + 'Filters the results to Updatable Themes and Plugins only' + ) + ->addOption( + 'sort', + 's', + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'Allows to sort (ASC) the results based on one or multiple keys. SORT can be either "name", "slug", "author", "date"', + ['date'] + ) + ->addOption( + 'desc', + 'D', + InputOption::VALUE_NONE, + 'Reverses the order of the output.' + ) ->setDescription("Lists the plugins and themes available for installation") ->setHelp('The index command lists the plugins and themes available for installation') ; @@ -44,16 +91,21 @@ class IndexCommand extends ConsoleCommand */ protected function serve() { - $this->gpm = new GPM($this->input->getOption('force')); + $this->options = $this->input->getOptions(); + + $this->gpm = new GPM($this->options['force']); $this->data = $this->gpm->getRepository(); $this->output->writeln(''); - foreach ($this->data as $type => $packages) { + $data = $this->filter($this->data); + + foreach ($data as $type => $packages) { $this->output->writeln("" . ucfirst($type) . " [ " . count($packages) . " ]"); - $index = 0; + $index = 0; + $packages = $this->sort($packages); foreach ($packages as $slug => $package) { $this->output->writeln( // index @@ -108,4 +160,65 @@ class IndexCommand extends ConsoleCommand return ''; } + + /** + * @param $data + * + * @return mixed + */ + public function filter($data) + { + // filtering and sorting + if ($this->options['plugins-only']) { + unset($data['themes']); + } + if ($this->options['themes-only']) { + unset($data['plugins']); + } + + if ($this->options['filter'] || $this->options['updates-only'] || $this->options['desc']) { + foreach ($data as $type => $packages) { + foreach ($packages as $slug => $package) { + $filter = true; + + // Filtering by string + if ($this->options['filter']) { + $filter = preg_grep('/(' . (implode('|', $this->options['filter'])) . ')/i', [$slug, $package->name]); + } + + // Filtering updatables only + if ($this->options['updates-only'] && $filter) { + $method = ucfirst(preg_replace("/s$/", '', $package->package_type)); + $filter = $this->gpm->{'is' . $method . 'Updatable'}($package->slug); + } + + if (!$filter) { + unset($data[$type][$slug]); + } + } + } + } + + return $data; + } + + /** + * @param $packages + */ + public function sort($packages) + { + foreach ($this->options['sort'] as $key) { + $packages = $packages->sort(function ($a, $b) use ($key) { + switch ($key) { + case 'author': + return strcmp($a->{$key}['name'], $b->{$key}['name']); + break; + default: + return strcmp($a->$key, $b->$key); + } + }, $this->options['desc'] ? true : false); + } + + return $packages; + } }